下面我们以Release版本为例,解剖程序。
将exe反汇编得到关键代码如下:
函数ShowComputerName:
主函数main:
我们知道函数调用是采用堆栈方式,那么CPU是怎么工作的呢?
在WindowsXP/2000操作系统下,通过大量的汇编代码我们可以看到,函数调用的参数传递是采用堆栈进行。
何故?
原因是我们使用的个人计算机一般都是采用x86系列CPU,由于可用通用寄存器非常少的缘故,只能采用堆栈进行。而作者工作使用的PowerPC系列CPU,由于有32个通用寄存器,所以函数调用的参数都采用寄存器R3、R4……来完成。据说苹果机采用PowerPC应该就属于按照寄存器传递的方式,但作者没有苹果机,有大家可以自己看看。
下面还是回归正题,这个代码怎么跑呢?
假定代码运行到00401080处,ESP指针为00130000
那么堆栈将如何发展呢?
| ………… | 低地址
+------------------------------+
| |
+------------------------------+ 高地址 ESP 00130000
完成push 405030h后,405030h被压栈,ESP指针上移。
| ………… |
+------------------------------+ ESP 0012FFFC
| 00405030h |
+------------------------------+
完成call 00401030后,也就是开始调用函数ShowComputerName。
函数调用后,返回main主函数的 EIP地址0040108A被压栈,同时ESP指针上移。这个压栈数值就是我们要利用的溢出攻击目的。
| ………… |
+------------------------------+ ESP 0012FFF8
| 0040108Ah |
+------------------------------+
| 00405030h |
+------------------------------+
下面我们就来到溢出函数部分,我们可以知道现在ESP是0012FFF8,所以mov ecx,dword ptr [esp+4]就是将压栈的数组首地址取出来放置到寄存器ecx。
接下来sub esp,0Ch,是预留局部变量pucComputerName的空间,刚好12个字节。ESP上移12个字节,就是pucComputerName首地址。
| ………… |
+------------------------------+ ESP 0012FFEC
| pucComputerName |
| 的空间 |
| |
+------------------------------+
| 0040108Ah |
+------------------------------+
| 00405030h |
+------------------------------+
再后lea eax,[esp],将esp的值赋值给eax,现在eax指向pucComputerName的首地址,exc指向全局变量g_aucName的首地址
0040103B: 50 push eax
0040103C: 51 push ecx
eax和ecx被压栈给函数GetName调用时候使用,这样栈空间图演变为:
| ………… |
+------------------------------+ ESP 0012FFE4
| 00405030h |
+------------------------------+
| 0012FFECh |
+------------------------------+
| pucComputerName |
| 的空间 |
| |
+------------------------------+
| 0040108Ah |
+------------------------------+
| 00405030h |
+------------------------------+
接下来调用函数GetName,返回函数ShowComputerName的EIP指针00401042已经被压栈,ESP再次上移,栈空间图演变为:
| ………… |
+------------------------------+ ESP 0012FFE0
| 00401042h |
+------------------------------+
| 00405030h |
+------------------------------+
| 0012FFECh |
+------------------------------+
| pucComputerName |
| 的空间 |
| |
+------------------------------+
| 0040108Ah |
+------------------------------+
| 00405030h |
+------------------------------+
接下来在函数GetName就是复制拷贝了,也就是将g_aucName的内容如实全部拷贝到局部变量pucComputerName所在地址,由于局部变量使用的是栈空间,这样我们main函数返回的EIP就被我们无情改写得面目全非了,欺骗了CPU,目的达到。
拷贝完成暂未返回函数ShowComputerName的栈空间状态。
| ………… |
+------------------------------+ ESP 0012FFE0
| 00401042h |
+------------------------------+
| 00405030h |
+------------------------------+
| 0012FFECh |
+------------------------------+
| 'H','e','l','l' |
| 'o','W','o','r' |
| 'l','d','!','/0' |
+------------------------------+
| 00401072h |
+------------------------------+
| 0x6A,0x00,0x68,0x30, |
+------------------------------+
| 0x50,0x40,0x00,0x68, |
+------------------------------+
| 0x30,0x50,0x40,0x00, |
+------------------------------+
| 0x6A,0x00,0xFF,0x15, |
+------------------------------+
| 0x90,0x40,0x40,0x00, |
+------------------------------+
| 0x6A,0x00,0xFF,0x15, |
+------------------------------+
| 0x48,0x40,0x40,0x00, |
+------------------------------+
由于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 所在地址就是我们溢出攻击指令。下面发生了什么相信大家都明白了,那就是我们攻击自己溢出漏洞的程序部分被激活。
由于时间太晚了,就写到这里了。