Who?
- Frederic Perriot
- Solution received: Jul 12, 2017 at 3:40 PM
How?
I solved challenge-0 manually, then automated some parts to solve challenge-1, and adapted the automation for challenge-2/3/4.
Steps:- Find protected function code in IDA, locate call to dynamic code
- Dump jitted code in gdb
- Find address of bytecode in jitted code with IDA
- Extract bytecode
- Decompile jitted code with Hex-Rays
- Rename local variables vip/vsp/expand (virtual instruction pointer, virtual stack pointer, input expansion array)
- Apply Perl script to decompiled output to recognize opcodes
- Produce VM opcode-to-instruction map (manual step, massaging decompiler output)
- Apply generic disassembler to bytecode (taking opcode map an input)
- Feed bytecode into OCaml for simplification and conversion to C
Tools used
- gdb
- IDA/Hex-Rays
- Perl
- OCaml
# reco.pl # read in the whole input undef $/; $_ = <>; # convert hex-rays pseudo-code to opcodes s/\Q*(_QWORD *)vsp = **(_QWORD **)vsp\E/load/g; s/\Q++vip;\E\s*\Qvip += *(signed int *)vip\E/jmp/g; s/\Q++vip;\E \s* \Qif ( *(_DWORD *)vsp )\E \s* \Qvip += *(signed int *)vip;\E \s* \Qelse\E \s* \Qvip += 4;\E \s* \Qvsp -= 8\E/jnz/gx; s/\Q*((_QWORD *)vsp - 1) ^= *(_QWORD *)vsp;\E \s* \Qvsp -= 8\E/xor/gx; s/\Q*((_QWORD *)vsp - 1) *= *(_QWORD *)vsp;\E \s* \Qvsp -= 8\E/mul/gx; s/\Q*((_DWORD *)vsp - 2) = *(_QWORD *)vsp <= *((_QWORD *)vsp - 1);\E \s* \Qvsp -= 8\E/lte/gx; s/\Q*((_QWORD *)vsp - 1) = *(_QWORD *)vsp - *((_QWORD *)vsp - 1)\E/sub/g; s/\Q*((_QWORD *)vsp - 1) -= *(_QWORD *)vsp;\E \s* \Qvsp -= 8\E/rsub/gx; s/\Q*(_QWORD *)vsp = *(_QWORD *)vsp\E/nop/g; s/\Q*((_QWORD *)vsp + 1) = *(_QWORD *)++vip;\E \s* \Qvsp += 8\E/push/gx; s/\Q*((_QWORD *)vsp - 1) >>= *(_QWORD *)vsp;\E \s* \Qvsp -= 8\E/rshr/gx; s/\Q*((_QWORD *)vsp - 1) = *(_QWORD *)vsp + *((signed int *)vsp - 2);\E \s* \Qvsp -= 8\E/add_i32/gx; s/\Q*((_QWORD *)vsp - 1) += *(signed int *)vsp;\E \s* \Qvsp -= 8\E/radd_i32/gx; s/\Q*((_QWORD *)vsp - 1) &= *(_QWORD *)vsp;\E \s* \Qvsp -= 8\E/and/gx; s/\Q*((_QWORD *)vsp - 1) |= *(_QWORD *)vsp;\E \s* \Qvsp -= 8\E/or/gx; s/\Q*(_QWORD *)vsp = *(signed int *)vsp\E/conv_i32_u64/g; s/\Q*((_QWORD *)vsp - 1) += *(_QWORD *)vsp;\E \s* \Qvsp -= 8\E/add/gx; s/\Q*((_DWORD *)vsp + 2) = *(_DWORD *)++vip;\E \s* \Qvsp += 8\E/push_u32/gx; s/\Q**(_BYTE **)vsp = *(vsp - 8);\E \s* \Qvsp -= 16\E/store_8/gx; s/\Q**((_BYTE **)vsp - 1) = *vsp;\E \s* \Qvsp -= 16\E/rstore_8/gx; s/\Q**(_QWORD **)vsp = *((_QWORD *)vsp - 1);\E \s* \Qvsp -= 16\E/store/gx; s/\Q**((_QWORD **)vsp - 1) = *(_QWORD *)vsp;\E \s* \Qvsp -= 16\E/rstore/gx; s/\Q*(_DWORD *)vsp = **(_DWORD **)vsp\E/load/g; s/\Q*vsp = **(_BYTE **)vsp\E/load_8/g; s/\Q**(_DWORD **)vsp = *((_DWORD *)vsp - 2);\E \s* \Qvsp -= 16\E/store_32/gx; s/\Q**((_DWORD **)vsp - 1) = *(_DWORD *)vsp;\E \s* \Qvsp -= 16\E/rstore_32/gx; s/\Q*((_QWORD *)vsp - 1) = *(_QWORD *)vsp << *((_QWORD *)vsp - 1);\E \s* \Qvsp -= 8\E/shl/gx; s/\Q*((_QWORD *)vsp - 1) <<= *(_QWORD *)vsp;\E \s* \Qvsp -= 8\E/rshl/gx; s/\Q*((_QWORD *)vsp + 1) = &expand[*(signed int *)++vip];\E \s* \Qvsp += 8\E/expand/gx; s/\Q*((_QWORD *)vsp - 1) \/= *(_QWORD *)vsp\E/rdiv/g; s/\Q**(_WORD **)vsp = *((_WORD *)vsp - 4);\E \s* \Qvsp -= 16\E/store_16/gx; s/\Q*(_WORD *)vsp = **(_WORD **)vsp\E/load_16/g; s/\Q*((_DWORD *)vsp - 2) = *(_QWORD *)vsp == *((_QWORD *)vsp - 1)\E/eq/g; s/\Q*((_DWORD *)vsp - 2) = *(_QWORD *)vsp > *((_QWORD *)vsp - 1)\E/gt/g; print;
// disas-gen.c #include#include unsigned char bytecode[4096]; enum { NONE, SDW, DW, QW, OFS }; struct insn_t { char *mnemonic; int args; }; struct insn_t load = { "Load", NONE }; struct insn_t jmp = { "Jmp", OFS }; struct insn_t jnz = { "Jnz", OFS }; struct insn_t xor = { "Xor", NONE }; struct insn_t mul = { "Mul", NONE }; struct insn_t div = { "Div", NONE }; struct insn_t eq = { "Eq", NONE }; struct insn_t gt = { "Gt", NONE }; struct insn_t lte = { "Lte", NONE }; struct insn_t sub = { "Sub", NONE }; struct insn_t rsub = { "Rsub", NONE }; struct insn_t nop = { "Nop", NONE }; struct insn_t stop = { "Stop", NONE }; struct insn_t push = { "Push", QW }; struct insn_t io = { "Io", DW }; struct insn_t rshr = { "Rshr", NONE }; struct insn_t add_i32 = { "Addi32", NONE }; struct insn_t radd_i32 = { "Raddi32", NONE }; struct insn_t and = { "And", NONE }; struct insn_t or = { "Or", NONE }; struct insn_t conv_i32_u64 = { "Convi32u64", NONE }; struct insn_t add = { "Add", NONE }; struct insn_t push_i32 = { "Pushi32", SDW }; struct insn_t push_u32 = { "Pushu32", DW }; struct insn_t store_8 = { "Store8", NONE }; struct insn_t rstore_8 = { "Rstore8", NONE }; struct insn_t rstore = { "Rstore", NONE }; struct insn_t store = { "Store", NONE }; struct insn_t load_8 = { "Load8", NONE }; struct insn_t load_16 = { "Load16", NONE }; struct insn_t store_16 = { "Store16", NONE }; struct insn_t store_32 = { "Store32", NONE }; struct insn_t rstore_32 = { "Rstore32", NONE }; struct insn_t shl = { "Shl", NONE }; struct insn_t rshl = { "Rshl", NONE }; struct insn_t expand = { "Expand", SDW }; struct insn_t invalid = { "???", NONE }; struct handler_t { int opcode; struct insn_t * insn; } handlers[] = #include "vmtbl-4.inc" ; struct insn_t *lookup[256]; int main(int argc, char *argv[]) { FILE *f; int n; int i; unsigned char opc, o2; int32_t sdw; uint32_t dw; uint64_t qw; struct insn_t * insn; if (argc < 2) { printf("Usage: disas-gen \n"); return 0; } f = fopen(argv[1], "rb"); if (! f) { printf("Can't open bytecode file\n"); return 1; } n = fread(bytecode, 1, 4096, f); fclose(f); // fill up lookup table indexed by opcode value for (i = 0; i < 256; i++) { lookup[i] = &invalid; } for (i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) { lookup[handlers[i].opcode] = handlers[i].insn; } // disas bytecode for (i = 0; i < n;) { opc = bytecode[i]; printf("%03x %02x ", i, opc); i++; insn = lookup[opc]; printf("%s", insn->mnemonic); switch (insn->args) { case NONE: break; case SDW: sdw = *(int32_t *)&bytecode[i]; i += 4; printf(" 0x%x", sdw); break; case OFS: sdw = *(int32_t *)&bytecode[i]; printf(" 0x%03x", i + sdw); i += 4; break; case DW: dw = *(uint32_t *)&bytecode[i]; i += 4; printf(" 0x%xl", dw); break; case QW: qw = *(uint64_t *)&bytecode[i]; i += 8; printf(" 0x%I64xL", qw); break; default: printf("Unsupported argument type\n"); exit(2); } printf(";\n"); } return 0; }
Time spent
- Challenge-0 took me 2 days
- Challenge-1 one day
- Challenge-2/3/4 about one hour each.