下面我们以Release版本为例,解剖程序。 将exe反汇编得到关键代码如下: 函数ShowComputerName: 00401030: 8B 4C 24 04 mov ecx,dword ptr [esp+4]
00401034: 83 EC 0C sub esp,0Ch 00401037: 8D 44 24 00 lea eax,[esp] 0040103B: 50 push eax 0040103C: 51 push ecx 0040103D: E8 BE FF FF FF call 00401000 00401042: 83 C4 14 add esp,14h 00401045: C3 ret 主函数main: 00401080: 68 30 50 40 00 push 405030h
00401085: E8 A6 FF FF FF call 00401030 0040108A: 83 C4 04 add esp,4
我们知道函数调用是采用堆栈方式,那么CPU是怎么工作的呢? 在WindowsXP/2000操作系统下,通过大量的汇编代码我们可以看到,函数调用的参数传递是采用堆栈进行。 何故? 原因是我们使用的个人计算机一般都是采用x86系列CPU,由于可用通用寄存器非常少的缘故,只能采用堆栈进行。而作者工作使用的PowerPC系列CPU,由于有32个通用寄存器,所以函数调用的参数都采用寄存器R3、R4……来完成。据说苹果机采用PowerPC应该就属于按照寄存器传递的方式,但作者没有苹果机,有大家可以自己看看。 下面还是回归正题,这个代码怎么跑呢? 假定代码运行到00401080处,ESP指针为00130000 那么堆栈将如何发展呢? | ………… | 低地址 完成push 405030h后,405030h被压栈,ESP指针上移。 | ………… | 完成call 00401030后,也就是开始调用函数ShowComputerName。 函数调用后,返回main主函数的 EIP地址0040108A被压栈,同时ESP指针上移。这个压栈数值就是我们要利用的溢出攻击目的。 | ………… | 下面我们就来到溢出函数部分,我们可以知道现在ESP是0012FFF8,所以mov ecx,dword ptr [esp+4]就是将压栈的数组首地址取出来放置到寄存器ecx。 接下来sub esp,0Ch,是预留局部变量pucComputerName的空间,刚好12个字节。ESP上移12个字节,就是pucComputerName首地址。 | ………… | 再后lea eax,[esp],将esp的值赋值给eax,现在eax指向pucComputerName的首地址,exc指向全局变量g_aucName的首地址 0040103B: 50 push eax eax和ecx被压栈给函数GetName调用时候使用,这样栈空间图演变为: | ………… | 接下来调用函数GetName,返回函数ShowComputerName的EIP指针00401042已经被压栈,ESP再次上移,栈空间图演变为: | ………… | 接下来在函数GetName就是复制拷贝了,也就是将g_aucName的内容如实全部拷贝到局部变量pucComputerName所在地址,由于局部变量使用的是栈空间,这样我们main函数返回的EIP就被我们无情改写得面目全非了,欺骗了CPU,目的达到。 拷贝完成暂未返回函数ShowComputerName的栈空间状态。 | ………… | 由于GetName调用关系的保留的EIP指针和ESP现在指针都未被破坏,因此ret能正常返回,执行过后。 EIP被置为00401042h,ESP下移为0012FFE4 接下来我们要执行00401042所在指令了,也就是add esp,14h,为什么是14h呢,原因是我们不是push了2个函数参数么?此处也需要“弹出”,此处非pop弹出,是编译器优化结果,直接该esp最快、最直接。14h=0Ch+08h 这样我们的ESP指针就下移到0012FFF8,此时你看看此处返回地址是什么?已经被我们改写为jmp esp指令所在地址,这样执行ret后,EIP被置为00401072h,ESP下移为0012FFFC。 EIP即将执行的指令是jmp esp,而ESP 所在地址就是我们溢出攻击指令。下面发生了什么相信大家都明白了,那就是我们攻击自己溢出漏洞的程序部分被激活。 由于时间太晚了,就写到这里了。 编写该攻击程序心得体会,请看下篇。^_^ |
|