4.2.4 跳转到ShellCode
“ 第三步,跳转到ShellCode 。”
“这里是关键!和堆栈一样,涉及到系统内部的处理机制了。”
“ buf1 = HeapAlloc(hHeap, 0, 200)
,程序动态分配200字节的buf1,堆结构如图4-10。”
“先是8字节‘buf1’的管理结构,该结构对用户是不可见的;然后是系统返回给我们能实际操作的200字节的空间,‘buf1’就是从这儿开始的;接着是8字节的下一空闲堆的管理结构;最后是两个双链表指针,各4个字节共8个字节。”
老师歇了一口气,说:“最后的两个双链表指针才是真正的关键。我们用超长字符串覆盖‘buf1’的时候,其溢出后的结构如图4-11。”
“特别的,当用其他字符为A、第212字节为B覆盖时,其结构如图4-12。”
“200+8+4=212,这就是我们计算得到溢出点为212字节的原因。我们把结构最后的双链表指针覆盖了,那系统要使用它们时当然会不正确,会出错――这就是要出现报错对话框的原因。”
“哦!212的位置是这样得到的啊!”大家恍然大悟。
“报的是write错误,莫非是要往后一个地址写东西?”宇强不解地说。
“对!当程序分配‘buf2’的时候,就要使用那两个双链表指针了,且eax为前一指针的值,而ecx为后一指针的值,并作如下操作: mov?[ecx],eax; mov [eax+4], ecx ,该操作的目的是在分配内存时改变链表的指向。”
“对刚才的程序来讲,在我们在最后一次覆盖时,其结构和指针被覆盖为图4-13的的样子。”
“当系统重新分配‘buf2’,执行到 mov?[ecx],eax 时,就等于执行 mov [BBBB],AAAA 。即 mov[0x42424242],0x41414141 ,就是把0x41414141写入到0x42424242地址的内存中, 而地址0x42424242一般是不能写的,所以就会报0x42424242不能写的错误。”
“哦!原来是这样。”
“出错时程序就会终止,如果还要执行下一操作 mov [eax+4], ecx ,就等于 mov [AAAA+4],BBBB ,即 mov [0x41414145],0x42424242 了,就是把0x42424242写入到0x41414145的地址中,通常也是会出错的。”
“把这个过程抽象出来,系统中有what→where的操作,而我们可以把what和where覆盖成任意的值。当where像上面一样是个随意的值时,会出现写错误。那我们怎样精心构造where和what,使系统能跳转到我们想要的地方——ShellCode呢?”
老师端起杯子,悠闲的说:“大家先自己想想,讨论一下各自的想法,就我一人讲,可能大家都会听困的。”
“就是有what→where的操作,what和where该改写成什么呢?”老师再提示道。
古风想了想,说:“可以像堆栈溢出利用一样,覆盖函数的返回点。因为有what→where的操作可把what的值构造成ShellCode的地址,把where的值构造成某个函数返回点的地址,这样执行what→where时,就可以用ShellCode的地址覆盖函数返回点的值,函数返回的时候我们的ShellCode就可以执行了。如图4-14。”
老师说:“不错,是个很好的方法,还有想法吗?”
宇强灵机一动,悄声的对小倩说:“既然可以覆盖函数的返回点,那好像也可以覆盖函数入口点的地方哦?”
小倩想了想:“嗯,是啊,应该可以!”
于是宇强大声说道:“我们也可以把ecx的值构造成某个函数入口点的地址,这样,执行那个函数时,就执行我们的ShellCode了。当系统后面要调用那个函数时,其实就执行了我们的ShellCode。如图4-15。”
老师非常满意的说:“Very Good!堆溢出向来以通用性困难著称。各个能利用的漏洞都有独特的利用方法,上述两种方法都曾在实际中使用过。”
“在这里我再介绍一种比较基础的方法,覆盖默认异常处理的地址。”