跳转至

把代码洞穴写入内存

有了合适的 shellcode,你可以使用 VirtualAllocEx() 和 WriteProcessMemory() 把它放入目标进程。清单 7-4 展示了这样做的一种方式。

int stringlen = strlen(string) + 1; // +1 包含 null 终

止符

int cavelen = sizeof(codeCave);
 int fulllen = stringlen + cavelen;
auto remoteString = // 分配具有 EXECUTE 权限的内存
    VirtualAllocEx(process, 0, fulllen, 
auto remoteCave = // 记下代码洞穴将要去的位置
    (LPVOID)((DWORD)remoteString + stringlen);
// 先写字符串
 WriteProcessMemory(process, remoteString, 
string, stringlen, NULL);
// 再写代码洞穴
 WriteProcessMemory(process, remoteCave, 
codeCave, cavelen, NULL);

清单 7-4:把最终 shellcode 写入代码洞穴内存

首先,这段代码确定把字符串参数和代码洞穴写入游戏内存需要多少字节,并把该值存储在 fulllen①中。然后,它调用 API 函数 VirtualAllocEx() 在 process 内分配 fulllen 字节,使用 PAGE_EXECUTE 保护(第二和第四个参数你可以总是分别用 0 和 MEM_COMMIT),并把内存地址存储在 remoteString②中。它还把 remoteString 递增 stringlen 字节,把结果存储在 remoteCave③中,因为 shellcode 应该直接写入字符串参数之后的内存。最后,它用 WriteProcessMemory() 用 string④和存储在 codeCave 中的汇编字节⑤填充分配的缓冲区。

表 7-1 展示了代码洞穴的内存转储可能的样子,假设它分配在 0x030000,someFunction 在 0xDEADBEEF,times 为 5,string 指向 injected! 文本。

地址代码表示原始数据数据含义地址代码表示原始数据数据含义

「地址」列显示洞穴的每块在内存中的位置;「代码表示」列告诉你 remoteString 和 remoteCave 的哪些索引对应「原始数据」列中的字节;「数据含义」列以人类可读格式显示字节代表什么。你可以在 0x030000 看到 injected! 字符串,在 0x03000E 看到 times 的值,在 0x030014 看到 someFunction 的地址。