试验工具:ollydbg
实验环境:windows2000虚拟机
1.目标
(1)了解SEH攻击及虚函数攻击的基本原理。
(2)通过调试SEH攻击代码,理解Windows异常处理机制,掌握针对SEH的攻击方式,并利用OllyDbg跟踪异常状态。
(3)调试虚函数攻击代码,理解虚函数工作机制与内存分布方式,掌握基本的虚函数攻击与计算方式,并可以用OllyDbg追踪。
2.测试步骤与结果
第一部分:SEH攻击
对代码进行一下简要的解释。函数test中存在典型的栈溢出漏洞,__try{}会在test的函数栈帧中安装一个S.E.H结构,__try中的除0操作会产生一个异常。当strcpy操作没有溢出时,除0操作的异常将最终被MyExceptionHandler函数处理;当strcpy操作产生溢出时,并精确地将栈帧中的S.E.H异常处理句柄修改为shellcode的入口地址时,操作系统将会错误地使用shellcode去处理除0异常,代码植入成功。此外,异常处理机制会检测进程是否处于调试状态。如果直接用调试器加载程序,异常处理会进入调试状态下的处理流程。因此在代码中加入断点__asm int 3让进程中断,然后用调试器attach的方法进行调试。
现在直接打开程序就会附加到ollydbg并且停在int 3处。
在00401158处的strcpy函数上面加断点,调试得到shellcode的地址、偏移量等信息。
可以看到shellcode的起始地址是0012FE48,再看一下SEH的情况。点击查看->SEH链。
在这里我们主要针对第一个地址。看一下那个地址的记录是一个指向下一个SEH的指针,接着是异常处理程序。只需要把0012FF1C这个地址的内容改成shellcode起始地址就可以了。
已知shellcode的起始地址为 0x0012FE48,第一个SEH地址为0x0012FF18(指向下一个SEH的指针)和0x0012FF1C(异常处理地址),shellcode应该有0x0012FF1C-0x0012FE 48=212的字节进行填充。使用buptbupt作为我们shellcode的核心内容,剩下的空间用0x90来补齐至212字节,然后在213-216字节使用0x0012FE48填充(不要忘了内存的大端小端的问题)。在main函数中加入LoadLibrary(“user32.dll”);并注释掉int 3,直接启动程序。
果然成功弹出了对话框。
弹窗无法取消,因为shellcode已经被当作系统异常处理了。
第二部分:虚函数攻击
int 3触发后我们可以在strcpy函数上看到shellcode的起始地址0x0042E27C。shellCode长度为 216 Bytes,换算成十六进制为D8,故shellcode的末尾后四个字节地址是0x0042E27C+0xD8–0x4=0x0042E350。
修改Vtable的指针值为0x0042E350。
将shellcode最后的地址赋值为shellcode的起始地址。
现在注释掉int 3就可以直接运行程序了。果然,shellcode被执行了。
实验的原理如下图所示。
3.思考题
首先双击这个程序尝试运行,发现闪退了。在命令行运行结果如下。
看来这个程序运行貌似是需要输入两个参数,接着在后面跟两个参数试试看。
确认这个程序无壳之后,接下来我们用IDA Pro分析一下这个程序。
可以看到main函数调用了两次strcpy,分别在0041193E和00411952。接下来00411974处调用了call dword ptr [edx],不同于常见的类似于call sub_xxxxxx的调用方式,这种看不见地址的调用是使用虚函数的标志。mov ecx,dword_42EB08访问指向这个对象开头的指针,而mov edx,[ecx]访问这个对象开头的前4个字节,最后call dword ptr [edx]调用虚函数。整个过程如下图所示。
用ollydbg打开这个程序,在两个strcpy处下断点。点击调试->参数,为程序输入命令行参数。
第一个断点之后得到dest1的地址。
还记得前面IDA Pro中0041195A处有这么一句汇编代码mov dword_42EB08,offset unk_42EB58么?前面我们分析dword_42EB08是对象开头的指针,那么0042EB58处就是对象开头的位置了。这里0042EB58刚好在dest地址0042EB5C之前也进一步验证了我们的推断。所以虚表指针地址为0042801C。仍然使用前面实验的shellcode和计算地址的办法,把这个地方覆盖为shellcode尾部的地址0x0042EB5C+0xD8-0x4=0x0042EC30,shellcode尾部再填上伪造的虚函数入口地址即可。问题在于dest1的地址刚好在虚表指针地址之后,所以无论如何都覆盖不了。不着急,我们继续运行程序命中第二个断点之后得到dest2的地址0042EB14。这次的地址在虚表指针地址之前,所以刚好可以覆盖。
用于第一次复制的shellcode1就是下面这样的。
用于第二次复制的shellcode2就是下面这样的。
按理来说shellcode中不能有空字节,但是我们在ollydbg的数据窗口也看到了,本来那一片就都是0,所以这个问题也就不成问题了,在shellcode中我们就把最后的\x00省略即可。最后写个程序传入这两个shellcode作为参数来执行我们的程序。
运行,果然弹出了shellcode。