菜鸟版exploit编写指南[转]


从早期的idq/ida到webdavx,一直以来都是用大虾们写的溢出利用程序来玩,在看过了很多溢出入门的文章后,我忍不住也试着学习了一下。这些文 字也算是一些心得体会,拿出来献丑的原因,一是和同样身为菜鸟的朋友们交流一下,二来还留下了一些问题,希望高手们能够在看到后抽出百忙的时间,指点一下 迷津。
一般的溢出入门文章,都是自己写一个有弱点的程序然后来hack,感觉上总是有点不过瘾,所以我想试试看弄一个实战的演习,偶然间就选定了CMail Server 4.0。漏洞报告称这个Mail服务软件的3.3版本有漏洞,不过我在看的时候发现4.0版存在相同的漏洞,也就拿来做了实验(附带一句,这个邮件服务软 件现在的最高版本是5.2,已经是安全的了)。其实就算你没有看过溢出的文章,我想只要有一点汇编的基础,我们来一起走上这么一次,以后看见漏洞公告,一 般的溢出程序还是可以应付一下。这个软件应该在杂志附带的光盘里面附送,在一起的还有一个调试工具Ollydbg,再打开一个VC的窗口,OK, let's go~

安装好了CMail软件以后,我们先试试看触发这个溢出,在cmd窗口下面telnet localhost 110,然后输入USER 和一长串的A,接着回车。咣……弹出了一个出错的窗口,上面写着“0x61616161指令应用的0x61616161内存。该内存不能为read”(图 一)。呵呵,如果你看过一些入门的文章,应该知道这就是一个典型的栈溢出,不过不清楚也没有关系,我们一起做下去,到后面会发现,写溢出程序,不知道这些 东西好像也没有什么关系的哦。
点下确定以后,我们先写一个C++的小程序,把我们刚才做的那些事情让计算机去做,附带的第一个小程序1.cpp就是那个架子,不要小看这个程序,这可是 exploit的最初雏形,我们可以从这里一步一步地修改出最终的梦幻版本!重新打开桌面上的CMailServer,执行我们的1.cpp,看看,刚才 的错误窗口又弹出来了。

好,到了这里,必须要解释一些东西:首先,0x61616161是多少?我们不用去看这个十六进制数对应的数字,在cmd.exe窗口中按住ALT键再按 小键盘上的97(=0x61),然后一起放开,看到的是不是一个小写的“a”?这和我们刚才输入的大写“A”之间的关系决不是巧合,这里留一个伏笔,我们 到后面看是怎么一回事情。还有,怎么会出现这样子的错误呢?答案是执行到内存中的0x61616161去了,这个地址和我们刚才的输入有关系,换句话说, 就是我们可以通过输入来改变程序的执行流程,现在我们就会想,既然能够控制程序的流程,那么能不能让程序做点什么呢?

把问题放在这里,我们先去解决另外一个令人困惑的问题,我们输入了这么多的“A”,到底是哪几个成为了上面那个错误对话框中的0x61616161?或者,对于看过溢出入门文章的朋友们来说,怎么去快速定位溢出点呢?

再次回到VC下面,修改刚才的程序。在main函数中有这么一句

memset(szUser+5, 'A', 800);

这是在“USER”后面加上了800个“A”,如果加的是其他字符呢?修改一下,把这句话替换掉:

for(int i = 0; i < 800; i++)
memset(szUser + 5 + i, (char)(i % 100 + 100), 1);

再执行一下,这次出现的错误框成了“0x7f7e7d7c指令应用的0x7f7e7d7c内存。该内存不能为read”(图二)。好,我们记录下这个数 字,看来这次程序执行到内存中的0x7f7e7d7c地址去了。再次修改刚才的程序,把(char)(i % 100 + 100)中的取余数改成整除:

for(int i = 0; i < 800; i++)
memset(szUser + 5 + i, (char)(i / 100 + 100), 1);

再次启动CMailServer,执行我们的小程序(附带的2.cpp)。这次出现的错误框成了“0x69696969指令应用的0x69696969内存。该内存不能为read”(图二)。

现在可以定位了。为什么呢?分析一下上两次我们做的事情,第一次是在“USER”后面不停的加上了ASCII码为100到199之间的数字,第二次是以 100为一段,数字分别是100、101、102……第一次溢出的时候,最小的一个数是0x7c,也就是十进制的124,整个字符串中只有在i为24、 124、224……的时候才有这个字符,所以大胆的确定尾数是24。第二次溢出的时候全部是0x69,也就是十进制的105,也就是在字符串的第5个段, 所以可以大胆的确定第524个字符开始,就是让程序执行的到异常内存导致出错的关键!

再改一下程序,指定从i等于524开始的四个字符的地址。对了,还有一句,因为计算机的设计原因,我们输入的是“7c7d7e7f”,但是换成了地址就成 了倒过来的“0x7f7e7d7c”,学过汇编应该明白,不过不理解也没有关系,记住就可以了——计算机中很多东西是很难理解的,我的方法就是记住,: -)

for(int i = 0; i < 800; i++)
memset(szUser + 5 + i, (char)(i / 100 + 100), 1);
*(int *)(szUser + 5 + 524) = 0x12347678;

改成这个以后,猜想一下应该执行到内存的0x12347678地址去了吧?试试看,执行一下这个程序(3.cpp),果然,弹出的错误对话框成了 “0x12347678指令应用的0x12347678内存。该内存不能为read”。在修改成其他的比如0xaaaaaaaa或者其他的,是不是都一 样。这样子我们就已经定位了溢出点,可以随意控制程序的流向了。

那么,让程序流到什么地方呢?最好是我们自己提交的字符也是一段程序代码,让CMailServer来执行我们的代码,不就可以获得梦寐以求的shell 了么?好像现在所有的exploit都是这么做的,我们也来试试看。下面要用到Ollydbg了,也开始有一点麻烦。

先把挂掉的CMailServer打开,然后打开一个Ollydbg,选择“文件”->“附加”,在弹出的窗口中选择CMailServer,确定 后在“调试”菜单下面选择“运行”或者直接按F9,这样子我们就可以通过Ollydbg来调试CMailServer。这个过程要做熟,因为以后会做很多 次。
回来打开我们的VC,运行刚才的程序,这下子没有了对话框,而是Ollydbg把程序的错误给拦截了下来。仔细看Ollydbg的左下角状态栏,“访问违反:正在执行[12347678]……”,程序确实执行到0x12347678去了(图五)。
保持这个状态不要动,在地址的子窗口里面点右键,选择前往,然后在对话框内输入esp,看看到了什么地方(图六),这就是我们输入的字符串,而在前面一 点,就是我们输入的“0x12347678”,由于高位取反的缘故,变成了“78 76 34 12”,不过我们还是认得出来的。
这里就是溢出的最关键地方,理论上我们可以直接定位,跳到0x02fd9014也就是esp的值上面去,但是这个地址只是对我的机器而且是这一次的运行有 效,我想你的机器上应该不是这个地址吧?不管怎么样,esp指向的内容是一定的,所以聪明的Hacker们想了一个办法,在内存中寻找一个固定的地址A, 这个地址A处对应的指令是call esp,然后我们指定程序跳转的地址是A,在溢出的时候,先到了A处,然后一个跳转就可以准确跳到我们发送的字符串上面了。如果我们的字符串刚好是一个可 以执行的代码,那计算机也会老老实实去执行。
顺带解释一下为什么可以找到一个相对固定的call esp地址。Windows中会加载很多动态链接库,这些动态链接库的加载地址有固定的,而在很多版本中,这些动态链接库几乎没有改动,所以找上个把个的 jmp esp地址还是很容易。这个地址,可以用现成的,也可以自己找,打个比方,lion原来给过一个中文版下2000/XP/2003都通用的 pop/pop/ret地址0x7ffa1571,我们猜想这附近的数据在这几个版本的windows下都是相同的,所以我们可以在这附近找找看。 call esp和jmp esp还有push esp/ret的指令效果相同,前面两个在XP/2003下面会失败,所以找找最后的一个指令串看看,我先给一个0x7FFA9C1B看看,后面来说怎么 找。

再次回到VC下面,把跳转的地址改成0x7FFA9C1B,用Ollydbg调试CMailServer(还记得么,过程在上面),运行一下。看看 Ollydbg,已经运行到esp的地址上面去了,虽然出错了——0x69696969是一长串非法的指令,Ollydbg因此而停了下来(图七)。把这 一串串的69换成shellcode就可以了么?理论上是这样子的。我们先找一个别人写好的shellcode,把这里的69696969改成 shellcode(3a.cpp)。Shellcode虽然很重要,但是要写一个溢出程序不一定要自己写shellcode,外面有很多,随便拿一个来 用就可以,比如这里,我就用的eyas的connect back的shellcode,MShell函数也是固定的格式。关于shellcode的制作,可以参看其他的文章。
打住一下,事情并不是这么简单的。这里我们拖动一下左下地址栏窗口的滚动条,是不是附近一个大写字母都没有?我们提交的数据已经全部被修改过了,所以直接 给shellcode还不行——就算给了shellcode,确实会执行上去,但是shellcode已经和我们提交的那个不一样,硬上的话只是会发生异 常(图八)。一般的溢出文章大约到此给个shellcode就可以了,我想这里就是一点不同的地方,有了些许的挑战性,而实际情况中,这种事情应该还是很 常见的,好像Serv-U的最近连续两个溢出都是同样的情况。

来实际情况分析一下。在左下地址栏窗口中,滚动条的拖动范围稍微大一点,往下面看,到了大约0x2FDBXXX的地方的时候,又发现了我们提交的数据的另 外一个拷贝,仔细观察一下,好像就是原始的数据。如果能够到这里来执行的话,提交的是什么就执行的是什么了!可以考虑在获得控制权的时候先搜索一下内存, 找到这一片原始的数据,然后跳过来执行。至于控制权,呵呵,我们在前面的一步不是已经得到了吗?

好了,既然我们提交什么就能执行什么,那我们就提交一个搜索shellcode的代码,在内存中找到原始的shellcode,然后跳过去执行就能满足要 求。设想是这样:esp指向是当前被修改过的字符串,从这里开始往下找,找到原始的字符串后跳转过去执行。关于判断是否是原始字符串的问题,通过观察看 到,明明提交的是大写的字母,却变成了小写,可以设想CMailServer把提交的字符串中大写的变成了小写字母,我们只要设定一个大写字母构成的标 志,在搜索的时候发现了大写的标志,就认为找到了原始的字符串。先写一个框架。

mov eax, 'NEWS'    ;设定标记
mov ebx, esp    ;从esp开始找
a: inc ebx        ;向下找
cmp [ebx], eax    ;比较是否找到了
jnz a        ;没有的话继续向下
add ebx, 3        ;找到了的话,跳过这几个字符
jmp ebx        ;再跳转过去

VC中是可以嵌入汇编的,而且可以方便地看汇编后的代码,嵌入汇编以后,按F10调试,然后按Ctrl+F11就可以看到(如果没有看到,可以试试看右键菜单中的byte code),后面是编译后的代码,前面就是机器码(图九)。
这里还有一个问题,不知道大家有没有想到。这些代码是要执行的,而且是在被修改了以后的字符串,所以还不能有大写的字母,即ASCII码在0x41到 0x5A之间的字符。要是有的话,被CMailServer一转换,全成了小写字母,到时候都不能正常执行,这个道理和不能直接加shellcode是一 样的。那么,把上面的东西改一下,看看能不能避免ASCII码在0x41到0x5A之间的字符,如果可以,不管CMailServer怎么转换,都没有关 系。
出问题的主要在mov eax, 'NEWS'和inc ebx。第一个,咱们退而求其次,用一个加减法,后面的inc ebx,也用一个加减法:

mov eax, 'NEWS' => mov eax, 'news'
sub eax, 0x20202020
inc ebx => add ebx, 1 或者 sub ebx, -1

在编译一下看看机器码,全部没有了ASCII码在0x41到0x5A之间的字符,也算是符合要求。我们把这一段汇编编译后的机器码收集起来,加在前面的跳 转地址后面,也就是esp指向的地方,在溢出的时候,他们就可以被执行到。接下来设定大写的标记,然后加上shellcode,全部组成一个程序,就成了 程序4.cpp。

整个看一下这个好长的字符串,前面是一大堆的垃圾字符,然后在szUser + 5 + 524的地方是0x7ffa1571,接下来是我们花了不少力气写的寻找没有被修改过的字符串的search code,然后是一个标记,最后是shellcode。用L表示垃圾字符,R表示0x7FFA9C1B这个push esp/ret的地址,S表示search code,F表示标记“NEWS”,C表示shellcode的话,是下面一张表:

L' R' S' F' C'
... ...
L R S F C

这也是内存中的表示,加了一个'的是被修改后的字符串,实际上我们获得控制权是通过溢出,我们不管怎么获得的,反正是就是执行到了R'给定的地方(0x7FFA9C1B),然后跳转到了esp指向的地方即S',S'向下搜索到F,最后一个跳转,跳到了C。
这其中,L和L'是否一致无所谓,R和R',S和S'一定要一样,也就是不能含有大写的字母,不然执行的时候就变了。F和F'一定要不一样——如果一样的 话就没有办法区分原始的数据与被CMailServer修改过的数据,最后C'和C是不一样的,要是一样的话我们就不用花这么大的力气去写一个 search code。当然改shellcode也是可以的,要改到没有ASCII码在0x41到0x5A之间的字符太困难,所以还是算了。
试试看执行的结果,在本地监听一个端口1111(cmd.exe下面nc -l -vv -p 1111),是不是出来了shell?呵呵,一个exploit就这么写好了,好像不用去看反汇编后的CMailServer,也不用对溢出有多么深刻的 理解,我们只要知道溢出后能够执行到我们自己提交的字符串上面去,然后想办法让他执行正确的shellcode就成。上面的过程同样适用于最近的一个 Serv-U的mdtm溢出,过程麻烦一点,但道理都是一样。

说说那个寻找push esp/ret的方法。首先我们要确定一下这个的机器码是多少,在VC中嵌入汇编,按照前面的方法看到编译后的机器码,是54 C3。在Ollydbg左下角的地址框中间,先到一个地方0x7ffa1571——lion在这里找到了通用的另外一个代码,我们的猜想是在附近能够找到 我们想要的代码。右键选择搜索,HEX栏填入54 C3,然后就可以找到匹配的,按Ctrl+L键看看,能够找到很多呢。

最后还有个小问题:前面的ret地址的定位,如果是固定长度的话,确实可以通过两次溢出定位,但是实际上这个是和安装的路径有关的,还有没有好一点的方法来定位呢?不知道有没有哪位大虾可以为在下解惑啊。

posted on 2007-08-24 16:21  dhb133  阅读(779)  评论(1编辑  收藏  举报

导航