musl libc利用
musl libc利用
本文适合
已经理解 ELF、PLT、GOT与libc、ROP基础、堆基础 的 Pwn 学习者。学完你能:识别 Alpine/musl 环境,放弃不适用的 glibc 模板,并选择 ret2libc、syscall ROP、ORW 或简单堆原语路线
一句话判断
看到 ld-musl、/lib/ld-musl-x86_64.so.1、Alpine 容器,或者本地 glibc exp 远程完全不对,就要先确认题目是不是 musl libc。
musl 题最重要的第一反应是:不要直接套 glibc 的 tcache、unsorted bin、one_gadget、FSOP 模板。
题目中常见信号
动态链接器路径:
/lib/ld-musl-x86_64.so.1
/lib/ld-musl-i386.so.1Dockerfile 信号:
FROM alpine
RUN apk add ...命令信号:
ldd ./vuln
strings libc.so | rg -i "musl|alpine"
file libc.so典型输出:
/lib/ld-musl-x86_64.so.1 (0x7f...)
musl libc (x86_64)运行表现信号:
- glibc 本地 exp 能泄露地址,但远程偏移不对。
- one_gadget 找不到可用 gadget,或条件永远不满足。
_IO_FILE、__malloc_hook、__free_hook路线不可用。- 堆题结构不像 fastbin/tcache/unsorted bin。
核心概念
musl 是另一套 C 标准库实现,不是 glibc 的轻量版皮肤。
对 CTF Pwn 的影响主要在四处:
- 动态链接器不同。
- libc 符号和偏移不同。
- malloc 实现不同。
- stdio 结构不同。
所以 glibc 中常见的这些路线要谨慎:
__malloc_hook / __free_hook
unsorted bin leak
tcache poisoning 模板
one_gadget
glibc _IO_FILE FSOP但基础能力仍然通用:
- 泄露地址。
- 计算基址。
- 控制返回地址或函数指针。
- ROP 调用函数。
- syscall 读写文件。
最小分析流程
1. 确认运行时
file ./vuln
ldd ./vuln
checksec --file=./vuln如果 ldd 不可用或输出异常,用:
readelf -l ./vuln | rg "interpreter|Requesting"看到:
[Requesting program interpreter: /lib/ld-musl-x86_64.so.1]就按 musl 处理。
2. 固定本地环境
如果题目给 Dockerfile,优先在同容器里跑:
docker build -t musl-pwn .
docker run --rm -it -v "$PWD:/work" musl-pwn sh没有 Dockerfile 时,至少保存远程 libc、ld 和二进制:
vuln
libc.so
ld-musl-x86_64.so.13. 检查保护和原语
checksec --file=./vuln记录:
- Canary。
- NX。
- PIE。
- RELRO。
- 是否 seccomp。
然后判断漏洞给你的原语:
- 栈溢出:能不能覆盖返回地址。
- 格式化字符串:能不能泄露和写。
- UAF/堆溢出:能不能得到任意写或函数指针覆盖。
- 任意读写:能不能直接改返回地址、GOT、全局回调。
4. 优先选择稳路线
入门 musl 题优先考虑:
泄露 libc -> ret2libc(system("/bin/sh"))
泄露地址 -> syscall ROP -> ORW
任意写 -> 覆盖返回地址 / 函数指针 / GOT不要一开始就找 glibc hook。
最小验证示例
验证是否能 ret2libc
假设泄露了 puts 地址:
puts_leak = 0x7f1234567890
libc_base = puts_leak - libc.symbols["puts"]
system = libc_base + libc.symbols["system"]
binsh = libc_base + next(libc.search(b"/bin/sh\x00"))验证点:
log.info(hex(libc_base))
log.info(hex(system))
log.info(hex(binsh))如果 /bin/sh 字符串不存在,不要卡住。musl 环境可以考虑:
- 自己写
/bin/sh\x00到.bss。 - 用
execvesyscall。 - 走 ORW 读取 flag。
验证 syscall ROP
如果能控制寄存器,优先看 syscall gadget:
ROPgadget --binary ./vuln | rg "syscall|pop rdi|pop rsi|pop rdx|pop rax"ORW 需要:
open("flag", 0)
read(fd, buf, 0x100)
write(1, buf, n)如果题目有 seccomp,先看允许哪些 syscall:
seccomp-tools dump ./vuln常见利用 / 解题路线
路线总览:
路线一:栈溢出 + ret2libc
适合:
- NX 开。
- 有 libc 泄露。
- 能控制返回地址。
步骤:
- 第一次 ROP 泄露 libc 函数地址。
- 返回 main 或重新触发漏洞。
- 第二次 ROP 调
system("/bin/sh")或execve。
路线二:栈溢出 + ORW
适合:
- seccomp 禁止
execve。 - 没有
/bin/sh。 - 只需要读 flag。
步骤:
- 写入
flag\x00到.bss。 - ROP 调
open。 - ROP 调
read。 - ROP 调
write。
路线三:任意写覆盖函数指针
适合:
- Full RELRO 让 GOT 不可写。
- 程序有全局函数指针、回调表、退出处理逻辑。
步骤:
- 用泄露确定程序或 libc 基址。
- 找可触发的函数指针。
- 写入
system或 ROP pivot 地址。 - 触发调用。
路线四:堆漏洞转任意写
musl 堆题不要套 glibc tcache 模板。先看:
- chunk 元数据位置。
- free list 结构。
- UAF 后能不能改 next 指针。
- 后续 malloc 是否能返回目标地址附近。
目标仍然是得到稳定读写原语,而不是背某个固定模板。
常见失败原因
- 把 musl 当 glibc:偏移、hook、FILE 结构都错。
- one_gadget 依赖症:musl 通常不走这条路。
- 本地环境不一致:Alpine 版本、ld-musl 文件不同,偏移会变。
- 忽略栈对齐:ret2libc 调用崩溃时先检查对齐和参数。
- 只找
/bin/sh:musl libc 或题目环境里可能没有可用字符串。 - 忽略 seccomp:远程起 shell 失败时先确认是不是 syscall 被禁。
迷你案例
题目给:
vuln
DockerfileDockerfile:
FROM alpine:3.18
COPY vuln /vuln第一步确认:
readelf -l vuln | rg interpreter输出:
[Requesting program interpreter: /lib/ld-musl-x86_64.so.1]第二步检查保护:
checksec --file=vuln发现 NX 开、Canary 关、PIE 关。
第三步,程序有 puts(name) 格式化字符串,可以泄露 libc 地址;另一个输入点有栈溢出。利用链:
格式化字符串泄露 puts 地址
-> 计算 musl libc base
-> 栈溢出 ROP
-> 写 "flag\x00" 到 .bss
-> open/read/write 读取 flagWP 里要说明为什么不使用 glibc 模板:
- 动态链接器是
ld-musl。 - 没有 glibc hook。
- seccomp 禁止 execve,所以采用 ORW。