windows花指令和堆栈溢出原理
1、今天拿到一个exe,用IDA打开后在main的代码如下,想直接F5,报错如下:
细看代码,有两个比较有意思的地方:
- 第一个红框,JZ和JNZ跳转到同一个地址:0x4010A1+1=0x4010A2
- 第二个红框,也就是0x4010A1的开始地址是一个call指令,5字节长度;再下一个指令就从0x4010A6开始了;
- 0x4010A1处指令call的跳转地址是0x52285E33h,这个明显比用户空间大很多:用户空间一般从0x00400000,很难直接跳转到0x52285E33h;事实上,从IDA看,.text段在0x00413FE0就已经结束了,所以这里的跳转地址肯定有问题(IDA也标红处理)
综合,0x4010A2就不是指令的入口地址,这里遇到花指令了;
2、选中call指令,右键“D”转成db数据,如下:
既然jz和jnz的目的地都是0x4010A2,那就从0x4010A2开始选中剩余db代码,右键“Analyze selected area”,如下:
终于看到了“庐山真面目”:0x4010A2原来是个lea指令,前面哪个0xe8彻底没用了;
直接把原0xe8打patch,改成0x90,也就是NOP,避免影响其他指令的执行,如下:
随后同样选中0x90,右键analyze seleced area,转成nop指令;最后快捷键P还原成完整的函数;
往上,有个函数执行了system("cmd")代码,这里可以获取shell,先把函数改名cmd_shell;
4、先在重点分析main函数,从第1行代码开始:
(1)这个简单,就是保存原ebp,然后开辟58h的栈空间,最后赋初事值0xcc
.text:00401070 push ebp .text:00401071 mov ebp, esp .text:00401073 sub esp, 58h .text:00401076 push ebx .text:00401077 push esi .text:00401078 push edi .text:00401079 lea edi, [ebp-58h] .text:0040107C mov ecx, 16h .text:00401081 mov eax, 0CCCCCCCCh .text:00401086 rep stosd
(2)紧接着从ebp-0x14到ebp-0x4赋值0;
text:00401088 mov dword ptr [ebp-14h], 0 .text:0040108F xor eax, eax .text:00401091 mov [ebp-10h], eax .text:00401094 mov [ebp-0Ch], eax .text:00401097 mov [ebp-8], eax .text:0040109A mov [ebp-4], eax
(3)花指令的这几行代码直接略过,到了最关键的代码之一:scanf 这里直接把用户输入的数字保存在ebp-0x18单元中
.text:004010A2 lea ecx, [ebp-18h] ; 用户通过scanf输入的数字保存在ebp-0x18这里,比如输入6 .text:004010A5 push ecx .text:004010A6 push offset aD ; "%d" .text:004010AB call _scanf
.text:004010B0 add esp, 8
紧接着把用户输入的数字*4后减去0x14h作为偏移,ebp作为基址,写入cmd_shell函数的入口地址;
.text:004010B3 mov edx, [ebp-18h] ; edx=6 .text:004010B6 mov dword ptr [ebp+edx*4-14h], offset cmd_shell
这个程序中最大的漏洞就在这里了:稍微有点windows32位逆向经验的同学都知道,整个栈都是用ebp加减常数来寻址的,比如ebp+4是函数调用前上个帧的ebp地址;ebp+8是函数调用完的返回地址,ebp+12开始是函数参数;ebp自身开始时函数的局部变量,空间是esp减去常数分配的;具体到这个程序,如果用户输入6,也就是edx=6,那么ebp+edx*4-14h=ebp+4,刚好是main函数的返回地址。如果这里用cmd_shell的地址覆盖,一旦main执行完,就会跳转到cmd_shell执行,这就是传说中的栈溢出(如果这是个网络服务,黑客完全可以精心构造一段字符串发送过来得到反弹shell);完全绕开了原来的跳转流程;前面所有代码执行完后的堆栈示意图如下:
代码执行的效果:输入6以后,马上得到当前目录的shell;通过whoami查询发现是administrator权限,也能查看当前目录的文件;
5、程序完整的C代码如下:
#include "stdafx.h" #include "stdlib.h" void shell(){ system("cmd"); } int main(int argc, char* argv[]) { int a[5]={0}; int x; _asm{ jz label; jnz label; _emit 0xE8; label: } scanf("%d",&x); //a[x] = (int)&shell; a[x] = (int)shell; printf("Hello World!\n"); return 0; }
参考:
1、https://www.bilibili.com/video/BV1mK411A75G?from=search&seid=10691647714478259112 花指令patch与原理分析