把汇编翻译成 Shellcode¶
由于代码洞穴需要写入另一个进程的内存,它们不能直接用汇编编写。相反,你需要逐字节编写。没有标准方法确定哪些字节代表哪些汇编代码,但有一些黑客式的方法。我个人最喜欢的是用函数中的汇编代码编译一个空的 C++ 应用程序,然后用 OllyDbg 检查该函数。或者,你可以打开 OllyDbg 附加到任意进程,扫描反汇编,直到找到需要的所有操作的字节。这种方法实际上很好,因为你的代码洞穴应尽可能简单编写,意味着所有操作都应该非常常见。你也可以在网上找到汇编操作码图表,但我发现它们都很难读;我刚才描述的方法总体上更容易。
当你知道字节应该是什么时,你可以用 C++ 轻松生成合适的 shellcode。清单 7-2 显示了清单 7-1 中汇编的完整 shellcode 骨架。
BYTE codeCave[20] = {
0xFF, 0x74, 0x24, 0x04, // PUSH DWORD
PTR:[ESP+0x4]
0x68, 0x00, 0x00, 0x00, 0x00, // PUSH 0
0xB8, 0x00, 0x00, 0x00, 0x00, // MOV EAX, 0x0
0xFF, 0xD0, // CALL EAX
0xC3 // RETN
};
清单 7-2:Shellcode 骨架
这个示例创建一个包含所需 shellcode 字节的 BYTE 数组。但 times 参数需要是动态的,而且编译时不可能知道 someFunction 的地址,所以这个 shellcode 写成骨架。两组连续的 0x00 字节(每组4 个)是 times 和 someFunction 地址的占位符,你可以在运行时通过调用 memcpy() 把真实值插入洞穴,如清单 7-3 中的片段。
清单 7-3:把 times 和 someFunction 的位置插入代码洞穴
times 和 someFunction 的地址各占 4 字节(回想 times 是 int,地址是 32 位值),它们分别位于 codeCave[5-8] 和 codeCave[10-13]。两次 memcpy() 调用把这些信息作参数传递,填充 codeCave 数组中的空白。