### 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:
1. Find protected function code in IDA, locate call to dynamic code
2. Dump jitted code in gdb
3. Find address of bytecode in jitted code with IDA
4. Extract bytecode
5. Decompile jitted code with Hex-Rays
6. Rename local variables vip/vsp/expand (virtual instruction pointer, virtual stack pointer, input expansion array)
7. Apply Perl script to decompiled output to recognize opcodes
8. Produce VM opcode-to-instruction map (manual step, massaging decompiler output)
9. Apply generic disassembler to bytecode (taking opcode map an input)
10. 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++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*

s/\Q*((_QWORD *)vsp - 1) += *(signed int *)vsp;\E \s*

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*

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 - 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*((_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 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 and       = { "And", NONE };
struct insn_t or        = { "Or", NONE };
struct insn_t conv_i32_u64  = { "Convi32u64", 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 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.