shellcode loader杂谈
载荷编码与加密
常见情况下会使用组合使用多层编码与加密的形式对载荷本身进行混淆。
常见编码
- Base85
- HEX
常见加密
- XOR
- RC4
- AES-GCM
- chacha20
分离载荷
- 本地载荷分离:从本地读取其他文件
- 远程载荷分离:远程拉文件读取进内存中
注入技术
这里以AniYa免杀框架[https://github.com/piiperxyz/AniYa]的注入模块来举例。
CreateThread
技术: 本地线程注入。这是最简单的注入方式,在自身进程空间内执行 shellcode。
- 使用 VirtualAlloc 在当前进程中分配内存。
- 将 shellcode 复制到该内存区域。
- 使用 VirtualProtect 将内存属性修改为可执行。
- 调用 CreateThread 创建一个新线程来执行 shellcode。
CreateRemoteThread / RtlCreateUserThread
技术:远程进程注入技术。
- 通过 OpenProcess 打开一个目标进程的句柄,获取完全控制权限。
- 使用 VirtualAllocEx 在目标进程中分配一块内存。
- 使用 WriteProcessMemory 将 shellcode 写入这块分配的内存中。
- 调用 CreateRemoteThreadEx 或 RtlCreateUserThread 在目标进程中创建一个新线程,该线程的起始地址就是我们写入的 shellcode 地址,从而执行 shellcode。
区别:
- CreateRemoteThread 是调用 CreateRemoteThreadEx API。
- RtlCreateUserThread 使用了 ntdll.dll 中的底层原生API RtlCreateUserThread,这可以帮助绕过一些在 kernel32.dll 层做的API钩子检测。
CreateProcess
技术:进程镂空(Process Hollowing)技术。
- 以挂起模式 (CREATE_SUSPENDED) 创建一个新进程(如 notepad.exe)。
- 在挂起的进程中分配内存 (VirtualAllocEx) 并写入 shellcode (WriteProcessMemory)。
- 解析目标进程的PEB(进程环境块)和PE头,找到其程序入口点(EntryPoint)。
- 修改入口点处的代码,替换为一个跳转指令,使其跳转到我们写入的 shellcode 地址。
- 调用 ResumeThread 恢复进程主线程的执行。此时,进程不会执行原始的 notepad.exe 代码,而是直接跳转执行我们的 shellcode。
EarlyBird
技术:异步过程调用(APC)注入。
- 同样以挂起模式创建一个新进程。
- 在目标进程中分配内存并写入 shellcode。
- 不直接创建线程,而是使用 QueueUserAPC 将一个APC对象放入目标进程主线程的APC队列中。这个APC指向我们的 shellcode。
- 恢复主线程后,线程在执行其正常代码之前,会先处理APC队列中的任务,从而执行我们的 shellcode。这种方法比 CreateRemoteThread 更隐蔽。
CreateFiber
技术:纤程(Fiber)注入。纤程是一种比线程更轻量级的执行单位,使用它来执行代码可以绕过一些基于线程创建的监控。
- 在当前进程中分配可执行内存并写入 shellcode。
- 调用 ConvertThreadToFiber 将当前线程转换为主纤程。
- 使用 CreateFiber 基于我们的 shellcode 地址创建一个新的纤程。
- 调用 SwitchToFiber 切换到新的纤程上,执行 shellcode。
EtwpCreateEtwThread
技术:利用 ntdll.dll 中一个未公开的函数 EtwpCreateEtwThread 来创建线程。EtwpCreateEtwThread 通常用于Windows事件跟踪(ETW),因此直接调用它可以有效规避对 CreateThread 等常见API的监控。
- 在本地进程分配可执行内存并写入 shellcode。
- 直接调用这个函数,并将 shellcode 地址作为参数,函数会创建一个新线程来执行它。
NtQueueApcThreadEx
技术:本地APC注入。
- 在当前进程中分配可执行内存并写入 shellcode。
- 获取当前线程的句柄。
- 调用 NtQueueApcThreadEx 将一个指向 shellcode 的APC注入到当前线程的APC队列中。当线程进入可警报状态(Alertable State)时,shellcode 就会被执行。
HeapAlloc
技术:堆分配与执行。
- 使用 RtlCreateHeap 创建一个具有可执行属性 (HEAP_CREATE_ENABLE_EXECUTE) 的堆。
- 从这个堆中分配一块内存 (RtlAllocateHeap)。
- 手动将 shellcode 逐字节复制到这块内存中。
- 通过类型转换和系统调用 (syscall.Syscall) 直接跳转到这块内存地址执行代码。
UuidFromString
技术:uuid隐藏shellcode与回调函数执行。
- 首先,将 shellcode 每16个字节转换成一个UUID字符串的格式。
- 在内存中分配一块空间。
- 遍历这些UUID字符串,并调用 UuidFromStringA 函数。这个函数会将UUID字符串转换回其16字节的二进制形式,并写回我们分配的内存中。通过这个过程,shellcode被完整地重构在内存里。
- 最后,调用 EnumSystemLocalesA 函数,并将 shellcode 的内存地址作为其第一个参数(回调函数指针)。EnumSystemLocalesA 会尝试调用这个“回调函数”,从而触发 shellcode 的执行。