Unfolding obfuscated code with Reven (part 2)
by tdta
Categories: Technical -
Tags: Reverse Engineering - Deobfuscation - ctf - REVEN -
Last time, by abstracting the runtime effect of the first virtual machine, we have reduced the challenge to a simpler but semantically equivalent program. Its control flow graph has a unique entry point as the basic block starting at 0x402048
, whereas ones at 0x4023d4
and at 0x40266e
are exit points corresponding to the cases where the program prints Yes!
and Nop!
.
Loops analysis
It is quite direct to identify natural loops in this graph. Indeed, the entry point block is also the root of the dominator tree; there are back-edges, e.g. from the block starting at 0x402460
to the entry point, or from one at 0x402513
to the entry point, etc. These back edges form natural loops which have a common header (that is the entry point), then can be combined into a single natural loop. There are also some nested loops, e.g. one having the basic block 0x4044d6
as its header and 0x404331 -> 0x4044d6
as its back-edge.
The program terminates by calling ExitProcess
either at the block 0x4023d4
or 0x40266e
(respectively for Nop!
or Yes!
), moreover the exit block (i.e. one at 0x40235
) post-dominates these terminating blocks. For comprehension purpose, we can “add” pseudo back-edges 0x4023d4 -> 0x402048
and 0x40266e -> 0x402048
without changing the semantics of the program. Consequently, it can be interpreted as a single “high-level” while (true)
loop, with several loops nested within.
The program is “just” a loop: would life be easier now? Not this time yet, unfortunately :-). Welcome to the world of bit-level and multi-process virtual machines.
Reversing the second virtual machine
We can quickly recognize a “pattern” in the loop. There are “top” blocks, i.e. ones starting at 0x402048
, 0x404563
, 0x402058
, 0x4046a8
, 0x40207f
, 0x402081
and 0x402096
, which seem to be used to extract some value into ebx
. Next, there are tests on ebx
with some constants (e.g. at 0x4020d3
, 0x4020c7
, etc.), and depending on the results of tests, there are corresponding “bottom” blocks, (e.g. at 0x4022f8
, 0x4023de
, 0x40217b
, 0x402486
, .etc) which seem to do the real things.
This pattern suggests us to think of a virtual machine with switch-based dispatcher again: the higher blocks might correspond with the dispatcher whereas the lower ones might correspond with opcodes.
Dispatcher
Let’s consider the higher basic blocks and their control flow, consisting in the following control flow graph. They form a region whose header is the block at 0x402048
. There is even a unique exit block at 0x402096
. This is a useful property since we can safely isolate the data-flow analysis on these blocks from other parts of the program.
Remark:
for the sake of readability, we have omitted nop
(s) from basic blocks; the instruction test ebx, ...
is split from the exit block, so it is not included in the region. We have added also a “pseudo” back-edge from the lowest block to the entry point to imply that the dispatcher is executed through a loop.
Indeed, we observe that this region accesses 4
different memory addresses: 0x403041
(byte access), 0x403ca7
(byte access), 0x403042
(word access), 0x40268b
(double word access). Moreover, a simple liveness analysis shows that all accessed registers are dead before entering the header block; except ebx
, they are also dead when going out the exit block. Consequently, the region is completely “parameterized” by values at these memory addresses.
Bit-level access
To recover the semantics of the region, we notice an interesting pattern in the exit block (which occurs also at lower basic blocks, e.g. at 0x4023de
, 0x4022f8
, etc.). That is the following sequence of instructions (as shown in Reven-Axion):
which can be interpreted as:
mov ebx, dword ptr [eax+0x40268b] ; ebx = address of a byte array
mov ax, word ptr [0x403042] ; eax = a bit-level offset
xor edx, edx
mov ecx, 0x8
div ecx ; eax = eax / 8 (byte-level offset)
add ebx, eax ; ebx = address of the element at this offset in the array
mov ebx, dword ptr [ebx] ; get the dword at this address
bswap ebx ; most significant byte of ebx becomes the byte at the address
mov cl, dl ; note: edx = eax % 8 (bit-level remainder)
shl ebx, cl ; remove remainder bits and round ebx
As explained in the comments above, given some offset i
in bits, the sequence extracts a dword
in a byte array from the bit-offset i
. The extracted value is rounded to 2^(i % 8)
.
This bit-level data extracting pattern is repeated at other blocks. The control flow is diverted by test ebx, ...
instructions depending on the extracted value. More concretely, for each “kind” of extracted data, there is a unique corresponding operator consisting in a single block (e.g. operators at 0x402250
, 0x40263c
, etc.), or in several blocks (e.g. operator consisting in blocks at 0x404d6
, 0x402572
, 0x402573
, 0x404331
, etc.). That is a “strong” indication of a virtual machine (well, “strong” but it is just a guesswork, actually).
Opcode tables
We now examine the array where bit-level data is extracted (i.e. the opcode table). First, we notice that the bit-offset is typed as word
value at 0x403042
. Moreover, the address of the opcode table is indexed by eax
in a dword
array at 0x40268b
:
0x4020a0 mov ebx, dword ptr [eax+0x40268b]
whereas eax
is calculated by:
0x402096 movzx eax, byte ptr [0x403ca7]
0x40209d shl eax, 0x2
In Reven-Axion, using the memory history widget to analyze accesses at 0x403ca7
, we observe that the byte
value stored at this address is periodically increased from 0
to 6
(we call it opcode table ID
):
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3,...
and when examining corresponding dword
(s) starting at 0x40268b
, we receive the following values:
0x403c32, 0x40365b, 0x403056, 0x403598, 0x403121, 0x403d88, 0x403000
Each value is the base address of an opcode table, so we get 7
different tables!!! Ok, a virtual machine with multiple opcode tables, that’s nice :-)
The periodic increment from 0
to 6
of opcode table id
is revealed also in the following static slice of the dispatcher, with respect to the point of interest at 0x402096
and the value of eax
.
Remark:
if we examine dword
values at 0x40268b
using Reven-Axion’s memory history widget, we will see that they are not constant :-), i.e. they are not always 0x403c32
, 0x40365b
, etc. We should remember that the binary is split into multiple gadgets which are repeatedly encrypted/decrypted: there is only one gadget in cleartext at a given time. But these values are always 0x403c32
, 0x40365b
, etc. when they are read to get the opcode table.
Instruction pointers
As examined above, in extracting data at each opcode table, the bit-level offset is read as a word
at 0x403042
:
0x4020a6 mov ax, word ptr [0x403042]
moreover, we observe that this value is indexed also by the ID of the opcode table in a word
array at 0x403048
:
0x402081 mov byte ptr [0x403ca7], al ; table ID
0x402086 shl eax, 0x1
0x402088 mov bx, word ptr [eax+0x403048] ; bit-level offset
0x40208f mov word ptr [0x403042], bx
Also, this is nothing surprising (the dispatcher is not obfuscated :-)) that this offset is updated back to the array by:
0x40205f movzx eax, byte ptr [0x403ca7] ; table ID
0x402066 mov bx, word ptr [0x403042] ; bit-level offset
0x40206d movzx ecx, al
0x402070 shl cl, 0x1
0x402072 mov word ptr [ecx+0x403048], bx ; update
So for each opcode table ID
, we have a corresponding pair of (opcode table, bit-level offset)
. Well, noticing now that each offset can be interpreted as the instruction pointer
(abbr. VmIP
) of a virtual machine, that means… not one, but… there are indeed 7
concurrent virtual machines corresponding with opcode table ID
(s) from 0
to 6
, each has its own code and instruction pointer, and they share the same dispatcher and opcode handlers, WtF %$*#~@!!!.
Multitasking
These concurrent VM(s) can be seen as concurrent (virtual) processes, we then call the opcode table ID
VpID
hereafter. Observing that if the value of al
in the instruction at 0x044568
is not 5
then VpID
is kept, and so does the opcode table address; the bit-level offset (i.e. the instruction pointer) is not extracted (resp. updated) from (resp. to) the instruction pointer table (i.e. word
array at 0x403048
), it is simply increased when data is extracted from the corresponding opcode table. Otherwise, the VpID
is periodically increased, and the corresponding opcode table as well as instruction pointer
will be used. In other words, if al
is not 5
then the same virtual process is executed.
We notice that the value of al
is extracted as the byte
value at 0x403041
. Slicing the dispatcher with respect to this byte
, we get the control flow graph below. It shows that each virtual process will execute exactly 5
opcodes, then switch to the next one. This is nothing but a preemptive multitasking execution model of 7
processes, each having a time-slice of 5
instructions.
Searching for entry points
Searching for the address 0x402048
in Reven-Axion, we obtain all the occurences in the execution trace and jump to the first one.
The instruction pointer of the virtual process VpID
is accessed/retrieved as word ptr Ox403048[VpID]
. Except for process 0
whose VpID
is retrieved at the first time from word ptr [0x403042]
(since the time-slice is initialized with 0
), others has their VpID
are retrieved at the first time from word ptr Ox403048[VpID]
. In any case, the instruction pointer of each process is initialized with 0
, or the entry point of each process is 0
.
Summary
We have reversed completely the dispatcher of the second virtual machine. This “non-obfuscated” devil consists of 7
“virtual” processes, identified by a VpID
ranged in [0, 6]
, each
- has the address of opcode table as
dword ptr 0x40268b[VpID]
, - executes in a time-slice of
5
instructions before switching cyclically to the next process, - has a bit-level instruction pointer, and its entry point is
0
.
Instruction set
We have reversed the instruction (i.e. opcode) tables of all processes. Our purpose is to completely decompile them, then the natural next step is to reverse the instruction set of the virtual machine.
As previously noticed, the instruction handlers are located at “bottom” basic blocks. We observe also that the “bit extraction” pattern appears all over these blocks, this is no surprise: the VM uses the pattern to extract instructions (in the instruction tables), each consists in several consecutive bits.
Following the instruction format, the instruction set can be divided into 4
classes; we illustrate each class by a color in the following control flow graph: the basic blocks handling the instructions of the same class are assigned the same color.
Below, let vI
denote a value v
of I
bits, each instruction is denoted by its syntax (e.g. 00|v16|v3
consists of instructions having first 2
bits 0
, then some value encoded in 16
bits, and some value encoded in 3
bits). pID
denotes the byte
value stored in [0x403ca7]
and 0x403ca8[i][j]
is a dword
element of the array 7x7
starting from 0x403ca8
. The semantics of instructions in each class is revealed in the following pseudo code C.
00|v16|v3
:00|a16|000
:if (0x403654[pID] == 0x1) then goto a16
00|a16|001
:if (0x403654[pID] != 0x1) then goto a16
00|a16|010
:goto a16
00|o16|011
:if (0x403732[o16] != 0xff) then goto +0 else 0x403732[o16] = pID
00|o16|100
:if (0x403732[o16] != pID) then goto +0 else 0x403732[o16] = 0xff
00|v16|(101,110,111)
:check_password()
01|a3|b3|v2
:01|a3|b3|00
:dw 0x403ca8[pID][a3] = dw 0x403ca8[pID][b3]
,01|a3|b3|01
:0x403ca8[pID][a3] = v32
(v32
is calculated asx16|y16
, where thex16
andy16
are extracted consecutively as16
bit values, next to the current instruction)01|a3|b3|10
:if (0x403732[v16] == pID) then password[v16] = 0x403ca8[pID][b3] else goto +0
(wherev16
is extracted as16
bit value, next to the current instruction)01|a3|b3|11
:if (0x403732[o16] == pID) then 0x403ca8[pID][a3] = password[v16] else goto +0
(the valuev16
is extracted similarly as above)
10|v3|v1
:10|v3|0
:0x403ca8[pID][v3] = bit_reverse(0x403ca8[pID][v3])
(the functionbit_reverse
reverses bits of a32
bit value, i.e. bit0
becomes31
, bit1
become30
, etc)10|v3|1
:tmp_ecx = ++0x403832[pID][0]; 0x403832[pID][tmp_ecx] = 0x403ca8[pID][v3]
11|a3|b3|v3
:11|a3|b3|000
:0x403ca8[pID][a3] += 0x403ca8[pID][b3]
11|a3|b3|001
:0x403ca8[pID][a3] -= 0x403ca8[pID][b3]
11|a3|b3|010
:0x403ca8[pID][a3] = rol(0x403ca8[pID][a3], 0x403ca8[pID][b3])
11|a3|b3|011
:0x403ca8[pID][a3] = ror(0x403ca8[pID][a3], 0x403ca8[pID][b3])
11|a3|b3|100
:0x403ca8[pID][a3] = 0x403ca8[pID][b3] ^ 0x403ca8[pID][a3]
11|a3|b3|101
:0x403654[pID] = (0x403ca8[pID][a3] == 0x403ca8[pID][b3])
11|a3|b3|(110,111)
:if (b3 < 0x403832[a3][0]) then 0x403ca8[pID][a3][b3 + 1] else goto +0
where password
is nothing but the buffer at 0x403198
containing the input password.
Remark:
without an explicit type annotation (e.g. w
for word
, dw
for double word
, etc.), array access operator []
is typed to return byte
, also types are omitted when they are clear from the context (i.e. can be referenced).
Disasembling, decompiling and semantics
Both syntax and semantics of the instruction set, the opcode table and entry point of each virtual process have now been revealed.
Disassembling
We then implement a recursive traversal disassembler which gives the following result (the numbers on the left are bit-offsets of instructions):
Process 0
:
0x000: 0x403ca8[0][2] = 0x550342b8;
0x02a: if (0x403732[0] != 0xff) then goto 0x02a else 0x403732[0] = 0x00;
0x03f: if (0x403732[0] == 0x00) then 0x403ca8[0][1] = password[0] else goto 0x03f;
0x059: 0x403ca8[0][1] += 0x403ca8[0][2];
0x064: if (0x403732[0] == 0x00) then password[0] = 0x403ca8[0][1] else goto 0x064;
0x07e: if (0x403732[0] != 0x00) then goto 0x07e else 0x403732[0] = 0xff;
0x093: 0x403ca8[0][2] = 0xe3348f8b;
0x0bd: if (0x403732[1] != 0xff) then goto 0x0bd else 0x403732[1] = 0x00;
0x0d2: if (0x403732[1] == 0x00) then 0x403ca8[0][1] = password[1] else goto 0x0d2;
0x0ec: 0x403ca8[0][1] += 0x403ca8[0][2];
0x0f7: if (0x403732[1] == 0x00) then password[1] = 0x403ca8[0][1] else goto 0x0f7;
0x111: if (0x403732[1] != 0x00) then goto 0x111 else 0x403732[1] = 0xff;
0x126: 0x403ca8[0][2] = 0x58c85bdd;
0x150: if (0x403732[2] != 0xff) then goto 0x150 else 0x403732[2] = 0x00;
0x165: if (0x403732[2] == 0x00) then 0x403ca8[0][1] = password[2] else goto 0x165;
0x17f: 0x403ca8[0][1] += 0x403ca8[0][2];
0x18a: if (0x403732[2] == 0x00) then password[2] = 0x403ca8[0][1] else goto 0x18a;
0x1a4: if (0x403732[2] != 0x00) then goto 0x1a4 else 0x403732[2] = 0xff;
0x1b9: 0x403ca8[0][2] = 0x7406e41c;
0x1e3: if (0x403732[3] != 0xff) then goto 0x1e3 else 0x403732[3] = 0x00;
0x1f8: if (0x403732[3] == 0x00) then 0x403ca8[0][1] = password[3] else goto 0x1f8;
0x212: 0x403ca8[0][1] += 0x403ca8[0][2];
0x21d: if (0x403732[3] == 0x00) then password[3] = 0x403ca8[0][1] else goto 0x21d;
0x237: if (0x403732[3] != 0x00) then goto 0x237 else 0x403732[3] = 0xff;
0x24c: 0x403ca8[0][2] = 0x72ece789;
0x276: if (0x403732[4] != 0xff) then goto 0x276 else 0x403732[4] = 0x00;
0x28b: if (0x403732[4] == 0x00) then 0x403ca8[0][1] = password[4] else goto 0x28b;
0x2a5: 0x403ca8[0][1] += 0x403ca8[0][2];
0x2b0: if (0x403732[4] == 0x00) then password[4] = 0x403ca8[0][1] else goto 0x2b0;
0x2ca: if (0x403732[4] != 0x00) then goto 0x2ca else 0x403732[4] = 0xff;
0x2df: 0x403ca8[0][2] = 0xefa2f7ec;
0x309: if (0x403732[5] != 0xff) then goto 0x309 else 0x403732[5] = 0x00;
0x31e: if (0x403732[5] == 0x00) then 0x403ca8[0][1] = password[5] else goto 0x31e;
0x338: 0x403ca8[0][1] += 0x403ca8[0][2];
0x343: if (0x403732[5] == 0x00) then password[5] = 0x403ca8[0][1] else goto 0x343;
0x35d: if (0x403732[5] != 0x00) then goto 0x35d else 0x403732[5] = 0xff;
0x372: goto 0x000;
Process 1
:
0x000: 0x403ca8[1][5] = 0x00000001;
0x02a: 0x403ca8[1][2] = 0x6dc555e2;
0x054: 0x403ca8[1][3] = 0x0000001f;
0x07e: 0x403ca8[1][4] = 0x0000036d;
0x0a8: if (0x403732[0] != 0xff) then goto 0x0a8 else 0x403732[0] = 0x01;
0x0bd: if (0x403732[0] == 0x01) then 0x403ca8[1][1] = password[0] else goto 0x0bd;
0x0d7: 0x403ca8[1][6] = 0x403ca8[1][6] ^ 0x403ca8[1][6];
0x0e2: 0x403ca8[1][1] = 0x403ca8[1][2] ^ 0x403ca8[1][1];
0x0ed: 0x403ca8[1][1] = rol(0x403ca8[1][1], 0x403ca8[1][3]);
0x0f8: 0x403ca8[1][6] += 0x403ca8[1][5];
0x103: 0x403654[1] = (0x403ca8[1][6] == 0x403ca8[1][4]);
0x10e: if (0x403654[1] != 0x01) then goto 0x0e2;
0x123: if (0x403732[0] != 0x01) then goto 0x123 else 0x403732[0] = 0xff;
0x138: tmp_ecx = ++0x403832[1][0]; 0x403832[1][tmp_ecx] = 0x403ca8[1][1];
0x13e: 0x403ca8[1][2] = 0x668471f5;
0x168: 0x403ca8[1][3] = 0x00000013;
0x192: 0x403ca8[1][4] = 0x00000305;
0x1bc: if (0x403732[1] != 0xff) then goto 0x1bc else 0x403732[1] = 0x01;
0x1d1: if (0x403732[1] == 0x01) then 0x403ca8[1][1] = password[1] else goto 0x1d1;
0x1eb: 0x403ca8[1][6] = 0x403ca8[1][6] ^ 0x403ca8[1][6];
0x1f6: 0x403ca8[1][1] = 0x403ca8[1][2] ^ 0x403ca8[1][1];
0x201: 0x403ca8[1][1] = rol(0x403ca8[1][1], 0x403ca8[1][3]);
0x20c: 0x403ca8[1][6] += 0x403ca8[1][5];
0x217: 0x403654[1] = (0x403ca8[1][6] == 0x403ca8[1][4]);
0x222: if (0x403654[1] != 0x01) then goto 0x1f6;
0x237: if (0x403732[1] != 0x01) then goto 0x237 else 0x403732[1] = 0xff;
0x24c: tmp_ecx = ++0x403832[1][0]; 0x403832[1][tmp_ecx] = 0x403ca8[1][1];
0x252: 0x403ca8[1][2] = 0x21f8a104;
0x27c: 0x403ca8[1][3] = 0x00000010;
0x2a6: 0x403ca8[1][4] = 0x000001e6;
0x2d0: if (0x403732[2] != 0xff) then goto 0x2d0 else 0x403732[2] = 0x01;
0x2e5: if (0x403732[2] == 0x01) then 0x403ca8[1][1] = password[2] else goto 0x2e5;
0x2ff: 0x403ca8[1][6] = 0x403ca8[1][6] ^ 0x403ca8[1][6];
0x30a: 0x403ca8[1][1] = 0x403ca8[1][2] ^ 0x403ca8[1][1];
0x315: 0x403ca8[1][1] = rol(0x403ca8[1][1], 0x403ca8[1][3]);
0x320: 0x403ca8[1][6] += 0x403ca8[1][5];
0x32b: 0x403654[1] = (0x403ca8[1][6] == 0x403ca8[1][4]);
0x336: if (0x403654[1] != 0x01) then goto 0x30a;
0x34b: if (0x403732[2] != 0x01) then goto 0x34b else 0x403732[2] = 0xff;
0x360: tmp_ecx = ++0x403832[1][0]; 0x403832[1][tmp_ecx] = 0x403ca8[1][1];
0x366: 0x403ca8[1][2] = 0x0d665e60;
0x390: 0x403ca8[1][3] = 0x0000001a;
0x3ba: 0x403ca8[1][4] = 0x000001d5;
0x3e4: if (0x403732[3] != 0xff) then goto 0x3e4 else 0x403732[3] = 0x01;
0x3f9: if (0x403732[3] == 0x01) then 0x403ca8[1][1] = password[3] else goto 0x3f9;
0x413: 0x403ca8[1][6] = 0x403ca8[1][6] ^ 0x403ca8[1][6];
0x41e: 0x403ca8[1][1] = 0x403ca8[1][2] ^ 0x403ca8[1][1];
0x429: 0x403ca8[1][1] = rol(0x403ca8[1][1], 0x403ca8[1][3]);
0x434: 0x403ca8[1][6] += 0x403ca8[1][5];
0x43f: 0x403654[1] = (0x403ca8[1][6] == 0x403ca8[1][4]);
0x44a: if (0x403654[1] != 0x01) then goto 0x41e;
0x45f: if (0x403732[3] != 0x01) then goto 0x45f else 0x403732[3] = 0xff;
0x474: tmp_ecx = ++0x403832[1][0]; 0x403832[1][tmp_ecx] = 0x403ca8[1][1];
0x47a: 0x403ca8[1][2] = 0x69fd3480;
0x4a4: 0x403ca8[1][3] = 0x00000007;
0x4ce: 0x403ca8[1][4] = 0x000000cc;
0x4f8: if (0x403732[4] != 0xff) then goto 0x4f8 else 0x403732[4] = 0x01;
0x50d: if (0x403732[4] == 0x01) then 0x403ca8[1][1] = password[4] else goto 0x50d;
0x527: 0x403ca8[1][6] = 0x403ca8[1][6] ^ 0x403ca8[1][6];
0x532: 0x403ca8[1][1] = 0x403ca8[1][2] ^ 0x403ca8[1][1];
0x53d: 0x403ca8[1][1] = rol(0x403ca8[1][1], 0x403ca8[1][3]);
0x548: 0x403ca8[1][6] += 0x403ca8[1][5];
0x553: 0x403654[1] = (0x403ca8[1][6] == 0x403ca8[1][4]);
0x55e: if (0x403654[1] != 0x01) then goto 0x532;
0x573: if (0x403732[4] != 0x01) then goto 0x573 else 0x403732[4] = 0xff;
0x588: tmp_ecx = ++0x403832[1][0]; 0x403832[1][tmp_ecx] = 0x403ca8[1][1];
0x58e: 0x403ca8[1][2] = 0xe4b8c392;
0x5b8: 0x403ca8[1][3] = 0x00000012;
0x5e2: 0x403ca8[1][4] = 0x0000025d;
0x60c: if (0x403732[5] != 0xff) then goto 0x60c else 0x403732[5] = 0x01;
0x621: if (0x403732[5] == 0x01) then 0x403ca8[1][1] = password[5] else goto 0x621;
0x63b: 0x403ca8[1][6] = 0x403ca8[1][6] ^ 0x403ca8[1][6];
0x646: 0x403ca8[1][1] = 0x403ca8[1][2] ^ 0x403ca8[1][1];
0x651: 0x403ca8[1][1] = rol(0x403ca8[1][1], 0x403ca8[1][3]);
0x65c: 0x403ca8[1][6] += 0x403ca8[1][5];
0x667: 0x403654[1] = (0x403ca8[1][6] == 0x403ca8[1][4]);
0x672: if (0x403654[1] != 0x01) then goto 0x646;
0x687: if (0x403732[5] != 0x01) then goto 0x687 else 0x403732[5] = 0xff;
0x69c: tmp_ecx = ++0x403832[1][0]; 0x403832[1][tmp_ecx] = 0x403ca8[1][1];
0x6a2: goto 0x6a2;
Process 2
:
0x000: 0x403ca8[2][5] = 0x00000001;
0x02a: 0x403ca8[2][2] = 0xecf6d571;
0x054: 0x403ca8[2][3] = 0x0000000e;
0x07e: 0x403ca8[2][4] = 0x0000006e;
0x0a8: if (0x403732[0] != 0xff) then goto 0x0a8 else 0x403732[0] = 0x02;
0x0bd: if (0x403732[0] == 0x02) then 0x403ca8[2][1] = password[0] else goto 0x0bd;
0x0d7: 0x403ca8[2][6] = 0x403ca8[2][6] ^ 0x403ca8[2][6];
0x0e2: 0x403ca8[2][1] -= 0x403ca8[2][2];
0x0ed: 0x403ca8[2][1] = ror(0x403ca8[2][1], 0x403ca8[2][3]);
0x0f8: 0x403ca8[2][6] += 0x403ca8[2][5];
0x103: 0x403654[2] = (0x403ca8[2][6] == 0x403ca8[2][4]);
0x10e: if (0x403654[2] != 0x01) then goto 0x0e2;
0x123: if (0x403732[0] != 0x02) then goto 0x123 else 0x403732[0] = 0xff;
0x138: tmp_ecx = ++0x403832[2][0]; 0x403832[2][tmp_ecx] = 0x403ca8[2][1];
0x13e: 0x403ca8[2][2] = 0xec829194;
0x168: 0x403ca8[2][3] = 0x00000010;
0x192: 0x403ca8[2][4] = 0x0000020d;
0x1bc: if (0x403732[1] != 0xff) then goto 0x1bc else 0x403732[1] = 0x02;
0x1d1: if (0x403732[1] == 0x02) then 0x403ca8[2][1] = password[1] else goto 0x1d1;
0x1eb: 0x403ca8[2][6] = 0x403ca8[2][6] ^ 0x403ca8[2][6];
0x1f6: 0x403ca8[2][1] -= 0x403ca8[2][2];
0x201: 0x403ca8[2][1] = ror(0x403ca8[2][1], 0x403ca8[2][3]);
0x20c: 0x403ca8[2][6] += 0x403ca8[2][5];
0x217: 0x403654[2] = (0x403ca8[2][6] == 0x403ca8[2][4]);
0x222: if (0x403654[2] != 0x01) then goto 0x1f6;
0x237: if (0x403732[1] != 0x02) then goto 0x237 else 0x403732[1] = 0xff;
0x24c: tmp_ecx = ++0x403832[2][0]; 0x403832[2][tmp_ecx] = 0x403ca8[2][1];
0x252: 0x403ca8[2][2] = 0x5c167e65;
0x27c: 0x403ca8[2][3] = 0x0000000d;
0x2a6: 0x403ca8[2][4] = 0x000001d7;
0x2d0: if (0x403732[2] != 0xff) then goto 0x2d0 else 0x403732[2] = 0x02;
0x2e5: if (0x403732[2] == 0x02) then 0x403ca8[2][1] = password[2] else goto 0x2e5;
0x2ff: 0x403ca8[2][6] = 0x403ca8[2][6] ^ 0x403ca8[2][6];
0x30a: 0x403ca8[2][1] -= 0x403ca8[2][2];
0x315: 0x403ca8[2][1] = ror(0x403ca8[2][1], 0x403ca8[2][3]);
0x320: 0x403ca8[2][6] += 0x403ca8[2][5];
0x32b: 0x403654[2] = (0x403ca8[2][6] == 0x403ca8[2][4]);
0x336: if (0x403654[2] != 0x01) then goto 0x30a;
0x34b: if (0x403732[2] != 0x02) then goto 0x34b else 0x403732[2] = 0xff;
0x360: tmp_ecx = ++0x403832[2][0]; 0x403832[2][tmp_ecx] = 0x403ca8[2][1];
0x366: 0x403ca8[2][2] = 0x3950cc83;
0x390: 0x403ca8[2][3] = 0x0000001e;
0x3ba: 0x403ca8[2][4] = 0x00000318;
0x3e4: if (0x403732[3] != 0xff) then goto 0x3e4 else 0x403732[3] = 0x02;
0x3f9: if (0x403732[3] == 0x02) then 0x403ca8[2][1] = password[3] else goto 0x3f9;
0x413: 0x403ca8[2][6] = 0x403ca8[2][6] ^ 0x403ca8[2][6];
0x41e: 0x403ca8[2][1] -= 0x403ca8[2][2];
0x429: 0x403ca8[2][1] = ror(0x403ca8[2][1], 0x403ca8[2][3]);
0x434: 0x403ca8[2][6] += 0x403ca8[2][5];
0x43f: 0x403654[2] = (0x403ca8[2][6] == 0x403ca8[2][4]);
0x44a: if (0x403654[2] != 0x01) then goto 0x41e;
0x45f: if (0x403732[3] != 0x02) then goto 0x45f else 0x403732[3] = 0xff;
0x474: tmp_ecx = ++0x403832[2][0]; 0x403832[2][tmp_ecx] = 0x403ca8[2][1];
0x47a: 0x403ca8[2][2] = 0x604dc3f2;
0x4a4: 0x403ca8[2][3] = 0x00000001;
0x4ce: 0x403ca8[2][4] = 0x0000002b;
0x4f8: if (0x403732[4] != 0xff) then goto 0x4f8 else 0x403732[4] = 0x02;
0x50d: if (0x403732[4] == 0x02) then 0x403ca8[2][1] = password[4] else goto 0x50d;
0x527: 0x403ca8[2][6] = 0x403ca8[2][6] ^ 0x403ca8[2][6];
0x532: 0x403ca8[2][1] -= 0x403ca8[2][2];
0x53d: 0x403ca8[2][1] = ror(0x403ca8[2][1], 0x403ca8[2][3]);
0x548: 0x403ca8[2][6] += 0x403ca8[2][5];
0x553: 0x403654[2] = (0x403ca8[2][6] == 0x403ca8[2][4]);
0x55e: if (0x403654[2] != 0x01) then goto 0x532;
0x573: if (0x403732[4] != 0x02) then goto 0x573 else 0x403732[4] = 0xff;
0x588: tmp_ecx = ++0x403832[2][0]; 0x403832[2][tmp_ecx] = 0x403ca8[2][1];
0x58e: 0x403ca8[2][2] = 0x0b3799a2;
0x5b8: 0x403ca8[2][3] = 0x00000019;
0x5e2: 0x403ca8[2][4] = 0x00000234;
0x60c: if (0x403732[5] != 0xff) then goto 0x60c else 0x403732[5] = 0x02;
0x621: if (0x403732[5] == 0x02) then 0x403ca8[2][1] = password[5] else goto 0x621;
0x63b: 0x403ca8[2][6] = 0x403ca8[2][6] ^ 0x403ca8[2][6];
0x646: 0x403ca8[2][1] -= 0x403ca8[2][2];
0x651: 0x403ca8[2][1] = ror(0x403ca8[2][1], 0x403ca8[2][3]);
0x65c: 0x403ca8[2][6] += 0x403ca8[2][5];
0x667: 0x403654[2] = (0x403ca8[2][6] == 0x403ca8[2][4]);
0x672: if (0x403654[2] != 0x01) then goto 0x646;
0x687: if (0x403732[5] != 0x02) then goto 0x687 else 0x403732[5] = 0xff;
0x69c: tmp_ecx = ++0x403832[2][0]; 0x403832[2][tmp_ecx] = 0x403ca8[2][1];
0x6a2: goto 0x6a2;
Process 3
:
0x000: 0x403ca8[3][5] = 0x00000001;
0x02a: 0x403ca8[3][2] = 0x8fd5c5bd;
0x054: 0x403ca8[3][4] = 0x00000028;
0x07e: if (0x403732[0] != 0xff) then goto 0x07e else 0x403732[0] = 0x03;
0x093: if (0x403732[0] == 0x03) then 0x403ca8[3][1] = password[0] else goto 0x093;
0x0ad: 0x403ca8[3][6] = 0x403ca8[3][6] ^ 0x403ca8[3][6];
0x0b8: 0x403ca8[3][1] = bit_reverse(0x403ca8[3][1]);
0x0be: 0x403ca8[3][1] = 0x403ca8[3][2] ^ 0x403ca8[3][1];
0x0c9: 0x403ca8[3][1] += 0x403ca8[3][2];
0x0d4: 0x403ca8[3][6] += 0x403ca8[3][5];
0x0df: 0x403654[3] = (0x403ca8[3][6] == 0x403ca8[3][4]);
0x0ea: if (0x403654[3] != 0x01) then goto 0x0b8;
0x0ff: if (0x403732[0] != 0x03) then goto 0x0ff else 0x403732[0] = 0xff;
0x114: tmp_ecx = ++0x403832[3][0]; 0x403832[3][tmp_ecx] = 0x403ca8[3][1];
0x11a: 0x403ca8[3][2] = 0x1f817abb;
0x144: 0x403ca8[3][4] = 0x0000014b;
0x16e: if (0x403732[1] != 0xff) then goto 0x16e else 0x403732[1] = 0x03;
0x183: if (0x403732[1] == 0x03) then 0x403ca8[3][1] = password[1] else goto 0x183;
0x19d: 0x403ca8[3][6] = 0x403ca8[3][6] ^ 0x403ca8[3][6];
0x1a8: 0x403ca8[3][1] = bit_reverse(0x403ca8[3][1]);
0x1ae: 0x403ca8[3][1] = 0x403ca8[3][2] ^ 0x403ca8[3][1];
0x1b9: 0x403ca8[3][1] += 0x403ca8[3][2];
0x1c4: 0x403ca8[3][6] += 0x403ca8[3][5];
0x1cf: 0x403654[3] = (0x403ca8[3][6] == 0x403ca8[3][4]);
0x1da: if (0x403654[3] != 0x01) then goto 0x1a8;
0x1ef: if (0x403732[1] != 0x03) then goto 0x1ef else 0x403732[1] = 0xff;
0x204: tmp_ecx = ++0x403832[3][0]; 0x403832[3][tmp_ecx] = 0x403ca8[3][1];
0x20a: 0x403ca8[3][2] = 0xe8504430;
0x234: 0x403ca8[3][4] = 0x000000cd;
0x25e: if (0x403732[2] != 0xff) then goto 0x25e else 0x403732[2] = 0x03;
0x273: if (0x403732[2] == 0x03) then 0x403ca8[3][1] = password[2] else goto 0x273;
0x28d: 0x403ca8[3][6] = 0x403ca8[3][6] ^ 0x403ca8[3][6];
0x298: 0x403ca8[3][1] = bit_reverse(0x403ca8[3][1]);
0x29e: 0x403ca8[3][1] = 0x403ca8[3][2] ^ 0x403ca8[3][1];
0x2a9: 0x403ca8[3][1] += 0x403ca8[3][2];
0x2b4: 0x403ca8[3][6] += 0x403ca8[3][5];
0x2bf: 0x403654[3] = (0x403ca8[3][6] == 0x403ca8[3][4]);
0x2ca: if (0x403654[3] != 0x01) then goto 0x298;
0x2df: if (0x403732[2] != 0x03) then goto 0x2df else 0x403732[2] = 0xff;
0x2f4: tmp_ecx = ++0x403832[3][0]; 0x403832[3][tmp_ecx] = 0x403ca8[3][1];
0x2fa: 0x403ca8[3][2] = 0xa6258a12;
0x324: 0x403ca8[3][4] = 0x0000003a;
0x34e: if (0x403732[3] != 0xff) then goto 0x34e else 0x403732[3] = 0x03;
0x363: if (0x403732[3] == 0x03) then 0x403ca8[3][1] = password[3] else goto 0x363;
0x37d: 0x403ca8[3][6] = 0x403ca8[3][6] ^ 0x403ca8[3][6];
0x388: 0x403ca8[3][1] = bit_reverse(0x403ca8[3][1]);
0x38e: 0x403ca8[3][1] = 0x403ca8[3][2] ^ 0x403ca8[3][1];
0x399: 0x403ca8[3][1] += 0x403ca8[3][2];
0x3a4: 0x403ca8[3][6] += 0x403ca8[3][5];
0x3af: 0x403654[3] = (0x403ca8[3][6] == 0x403ca8[3][4]);
0x3ba: if (0x403654[3] != 0x01) then goto 0x388;
0x3cf: if (0x403732[3] != 0x03) then goto 0x3cf else 0x403732[3] = 0xff;
0x3e4: tmp_ecx = ++0x403832[3][0]; 0x403832[3][tmp_ecx] = 0x403ca8[3][1];
0x3ea: 0x403ca8[3][2] = 0x90bf3d8b;
0x414: 0x403ca8[3][4] = 0x00000178;
0x43e: if (0x403732[4] != 0xff) then goto 0x43e else 0x403732[4] = 0x03;
0x453: if (0x403732[4] == 0x03) then 0x403ca8[3][1] = password[4] else goto 0x453;
0x46d: 0x403ca8[3][6] = 0x403ca8[3][6] ^ 0x403ca8[3][6];
0x478: 0x403ca8[3][1] = bit_reverse(0x403ca8[3][1]);
0x47e: 0x403ca8[3][1] = 0x403ca8[3][2] ^ 0x403ca8[3][1];
0x489: 0x403ca8[3][1] += 0x403ca8[3][2];
0x494: 0x403ca8[3][6] += 0x403ca8[3][5];
0x49f: 0x403654[3] = (0x403ca8[3][6] == 0x403ca8[3][4]);
0x4aa: if (0x403654[3] != 0x01) then goto 0x478;
0x4bf: if (0x403732[4] != 0x03) then goto 0x4bf else 0x403732[4] = 0xff;
0x4d4: tmp_ecx = ++0x403832[3][0]; 0x403832[3][tmp_ecx] = 0x403ca8[3][1];
0x4da: 0x403ca8[3][2] = 0xc350be97;
0x504: 0x403ca8[3][4] = 0x0000035e;
0x52e: if (0x403732[5] != 0xff) then goto 0x52e else 0x403732[5] = 0x03;
0x543: if (0x403732[5] == 0x03) then 0x403ca8[3][1] = password[5] else goto 0x543;
0x55d: 0x403ca8[3][6] = 0x403ca8[3][6] ^ 0x403ca8[3][6];
0x568: 0x403ca8[3][1] = bit_reverse(0x403ca8[3][1]);
0x56e: 0x403ca8[3][1] = 0x403ca8[3][2] ^ 0x403ca8[3][1];
0x579: 0x403ca8[3][1] += 0x403ca8[3][2];
0x584: 0x403ca8[3][6] += 0x403ca8[3][5];
0x58f: 0x403654[3] = (0x403ca8[3][6] == 0x403ca8[3][4]);
0x59a: if (0x403654[3] != 0x01) then goto 0x568;
0x5af: if (0x403732[5] != 0x03) then goto 0x5af else 0x403732[5] = 0xff;
0x5c4: tmp_ecx = ++0x403832[3][0]; 0x403832[3][tmp_ecx] = 0x403ca8[3][1];
0x5ca: goto 0x5ca;
Process 4
:
0x000: 0x403ca8[4][3] = 0x00000000;
0x02a: if (0 < 0x403832[1][0]) then 0x403ca8[4][0] = 0x403832[1][1] else goto 0x02a;
0x035: 0x403ca8[4][1] = 0x403ca8[4][0];
0x03f: if (0 < 0x403832[2][0]) then 0x403ca8[4][0] = 0x403832[2][1] else goto 0x03f;
0x04a: 0x403ca8[4][2] = 0x403ca8[4][0];
0x054: 0x403ca8[4][1] = rol(0x403ca8[4][1], 0x403ca8[4][3]);
0x05f: 0x403ca8[4][1] = 0x403ca8[4][2] ^ 0x403ca8[4][1];
0x06a: tmp_ecx = ++0x403832[4][0]; 0x403832[4][tmp_ecx] = 0x403ca8[4][1];
0x070: 0x403ca8[4][3] = 0x0000001a;
0x09a: if (1 < 0x403832[1][0]) then 0x403ca8[4][0] = 0x403832[1][2] else goto 0x09a;
0x0a5: 0x403ca8[4][1] = 0x403ca8[4][0];
0x0af: if (1 < 0x403832[2][0]) then 0x403ca8[4][0] = 0x403832[2][2] else goto 0x0af;
0x0ba: 0x403ca8[4][2] = 0x403ca8[4][0];
0x0c4: 0x403ca8[4][1] = rol(0x403ca8[4][1], 0x403ca8[4][3]);
0x0cf: 0x403ca8[4][1] = 0x403ca8[4][2] ^ 0x403ca8[4][1];
0x0da: tmp_ecx = ++0x403832[4][0]; 0x403832[4][tmp_ecx] = 0x403ca8[4][1];
0x0e0: 0x403ca8[4][3] = 0x00000008;
0x10a: if (2 < 0x403832[1][0]) then 0x403ca8[4][0] = 0x403832[1][3] else goto 0x10a;
0x115: 0x403ca8[4][1] = 0x403ca8[4][0];
0x11f: if (2 < 0x403832[2][0]) then 0x403ca8[4][0] = 0x403832[2][3] else goto 0x11f;
0x12a: 0x403ca8[4][2] = 0x403ca8[4][0];
0x134: 0x403ca8[4][1] = rol(0x403ca8[4][1], 0x403ca8[4][3]);
0x13f: 0x403ca8[4][1] = 0x403ca8[4][2] ^ 0x403ca8[4][1];
0x14a: tmp_ecx = ++0x403832[4][0]; 0x403832[4][tmp_ecx] = 0x403ca8[4][1];
0x150: 0x403ca8[4][3] = 0x0000000d;
0x17a: if (3 < 0x403832[1][0]) then 0x403ca8[4][0] = 0x403832[1][4] else goto 0x17a;
0x185: 0x403ca8[4][1] = 0x403ca8[4][0];
0x18f: if (3 < 0x403832[2][0]) then 0x403ca8[4][0] = 0x403832[2][4] else goto 0x18f;
0x19a: 0x403ca8[4][2] = 0x403ca8[4][0];
0x1a4: 0x403ca8[4][1] = rol(0x403ca8[4][1], 0x403ca8[4][3]);
0x1af: 0x403ca8[4][1] = 0x403ca8[4][2] ^ 0x403ca8[4][1];
0x1ba: tmp_ecx = ++0x403832[4][0]; 0x403832[4][tmp_ecx] = 0x403ca8[4][1];
0x1c0: 0x403ca8[4][3] = 0x00000010;
0x1ea: if (4 < 0x403832[1][0]) then 0x403ca8[4][0] = 0x403832[1][5] else goto 0x1ea;
0x1f5: 0x403ca8[4][1] = 0x403ca8[4][0];
0x1ff: if (4 < 0x403832[2][0]) then 0x403ca8[4][0] = 0x403832[2][5] else goto 0x1ff;
0x20a: 0x403ca8[4][2] = 0x403ca8[4][0];
0x214: 0x403ca8[4][1] = rol(0x403ca8[4][1], 0x403ca8[4][3]);
0x21f: 0x403ca8[4][1] = 0x403ca8[4][2] ^ 0x403ca8[4][1];
0x22a: tmp_ecx = ++0x403832[4][0]; 0x403832[4][tmp_ecx] = 0x403ca8[4][1];
0x230: 0x403ca8[4][3] = 0x0000001e;
0x25a: if (5 < 0x403832[1][0]) then 0x403ca8[4][0] = 0x403832[1][6] else goto 0x25a;
0x265: 0x403ca8[4][1] = 0x403ca8[4][0];
0x26f: if (5 < 0x403832[2][0]) then 0x403ca8[4][0] = 0x403832[2][6] else goto 0x26f;
0x27a: 0x403ca8[4][2] = 0x403ca8[4][0];
0x284: 0x403ca8[4][1] = rol(0x403ca8[4][1], 0x403ca8[4][3]);
0x28f: 0x403ca8[4][1] = 0x403ca8[4][2] ^ 0x403ca8[4][1];
0x29a: tmp_ecx = ++0x403832[4][0]; 0x403832[4][tmp_ecx] = 0x403ca8[4][1];
0x2a0: goto 0x2a0;
Process 5
:
0x000: if (0 < 0x403832[2][0]) then 0x403ca8[5][0] = 0x403832[2][1] else goto 0x000;
0x00b: 0x403ca8[5][1] = 0x403ca8[5][0];
0x015: if (0 < 0x403832[3][0]) then 0x403ca8[5][0] = 0x403832[3][1] else goto 0x015;
0x020: 0x403ca8[5][2] = 0x403ca8[5][0];
0x02a: 0x403ca8[5][2] = bit_reverse(0x403ca8[5][2]);
0x030: 0x403ca8[5][1] = 0x403ca8[5][2] ^ 0x403ca8[5][1];
0x03b: tmp_ecx = ++0x403832[5][0]; 0x403832[5][tmp_ecx] = 0x403ca8[5][1];
0x041: if (1 < 0x403832[2][0]) then 0x403ca8[5][0] = 0x403832[2][2] else goto 0x041;
0x04c: 0x403ca8[5][1] = 0x403ca8[5][0];
0x056: if (1 < 0x403832[3][0]) then 0x403ca8[5][0] = 0x403832[3][2] else goto 0x056;
0x061: 0x403ca8[5][2] = 0x403ca8[5][0];
0x06b: 0x403ca8[5][2] = bit_reverse(0x403ca8[5][2]);
0x071: 0x403ca8[5][1] = 0x403ca8[5][2] ^ 0x403ca8[5][1];
0x07c: tmp_ecx = ++0x403832[5][0]; 0x403832[5][tmp_ecx] = 0x403ca8[5][1];
0x082: if (2 < 0x403832[2][0]) then 0x403ca8[5][0] = 0x403832[2][3] else goto 0x082;
0x08d: 0x403ca8[5][1] = 0x403ca8[5][0];
0x097: if (2 < 0x403832[3][0]) then 0x403ca8[5][0] = 0x403832[3][3] else goto 0x097;
0x0a2: 0x403ca8[5][2] = 0x403ca8[5][0];
0x0ac: 0x403ca8[5][2] = bit_reverse(0x403ca8[5][2]);
0x0b2: 0x403ca8[5][1] = 0x403ca8[5][2] ^ 0x403ca8[5][1];
0x0bd: tmp_ecx = ++0x403832[5][0]; 0x403832[5][tmp_ecx] = 0x403ca8[5][1];
0x0c3: if (3 < 0x403832[2][0]) then 0x403ca8[5][0] = 0x403832[2][4] else goto 0x0c3;
0x0ce: 0x403ca8[5][1] = 0x403ca8[5][0];
0x0d8: if (3 < 0x403832[3][0]) then 0x403ca8[5][0] = 0x403832[3][4] else goto 0x0d8;
0x0e3: 0x403ca8[5][2] = 0x403ca8[5][0];
0x0ed: 0x403ca8[5][2] = bit_reverse(0x403ca8[5][2]);
0x0f3: 0x403ca8[5][1] = 0x403ca8[5][2] ^ 0x403ca8[5][1];
0x0fe: tmp_ecx = ++0x403832[5][0]; 0x403832[5][tmp_ecx] = 0x403ca8[5][1];
0x104: if (4 < 0x403832[2][0]) then 0x403ca8[5][0] = 0x403832[2][5] else goto 0x104;
0x10f: 0x403ca8[5][1] = 0x403ca8[5][0];
0x119: if (4 < 0x403832[3][0]) then 0x403ca8[5][0] = 0x403832[3][5] else goto 0x119;
0x124: 0x403ca8[5][2] = 0x403ca8[5][0];
0x12e: 0x403ca8[5][2] = bit_reverse(0x403ca8[5][2]);
0x134: 0x403ca8[5][1] = 0x403ca8[5][2] ^ 0x403ca8[5][1];
0x13f: tmp_ecx = ++0x403832[5][0]; 0x403832[5][tmp_ecx] = 0x403ca8[5][1];
0x145: if (5 < 0x403832[2][0]) then 0x403ca8[5][0] = 0x403832[2][6] else goto 0x145;
0x150: 0x403ca8[5][1] = 0x403ca8[5][0];
0x15a: if (5 < 0x403832[3][0]) then 0x403ca8[5][0] = 0x403832[3][6] else goto 0x15a;
0x165: 0x403ca8[5][2] = 0x403ca8[5][0];
0x16f: 0x403ca8[5][2] = bit_reverse(0x403ca8[5][2]);
0x175: 0x403ca8[5][1] = 0x403ca8[5][2] ^ 0x403ca8[5][1];
0x180: tmp_ecx = ++0x403832[5][0]; 0x403832[5][tmp_ecx] = 0x403ca8[5][1];
0x186: goto 0x186;
Process 6
:
0x000: 0x403ca8[6][3] = 0x00000017;
0x02a: if (0 < 0x403832[4][0]) then 0x403ca8[6][0] = 0x403832[4][1] else goto 0x02a;
0x035: 0x403ca8[6][1] = 0x403ca8[6][0];
0x03f: if (0 < 0x403832[5][0]) then 0x403ca8[6][0] = 0x403832[5][1] else goto 0x03f;
0x04a: 0x403ca8[6][2] = 0x403ca8[6][0];
0x054: 0x403ca8[6][1] = rol(0x403ca8[6][1], 0x403ca8[6][3]);
0x05f: 0x403ca8[6][1] = 0x403ca8[6][2] ^ 0x403ca8[6][1];
0x06a: 0x403ca8[6][1] = bit_reverse(0x403ca8[6][1]);
0x070: tmp_ecx = ++0x403832[6][0]; 0x403832[6][tmp_ecx] = 0x403ca8[6][1];
0x076: if (1 < 0x403832[4][0]) then 0x403ca8[6][0] = 0x403832[4][2] else goto 0x076;
0x081: 0x403ca8[6][1] = 0x403ca8[6][0];
0x08b: if (1 < 0x403832[5][0]) then 0x403ca8[6][0] = 0x403832[5][2] else goto 0x08b;
0x096: 0x403ca8[6][2] = 0x403ca8[6][0];
0x0a0: 0x403ca8[6][1] = rol(0x403ca8[6][1], 0x403ca8[6][3]);
0x0ab: 0x403ca8[6][1] = 0x403ca8[6][2] ^ 0x403ca8[6][1];
0x0b6: 0x403ca8[6][1] = bit_reverse(0x403ca8[6][1]);
0x0bc: tmp_ecx = ++0x403832[6][0]; 0x403832[6][tmp_ecx] = 0x403ca8[6][1];
0x0c2: if (2 < 0x403832[4][0]) then 0x403ca8[6][0] = 0x403832[4][3] else goto 0x0c2;
0x0cd: 0x403ca8[6][1] = 0x403ca8[6][0];
0x0d7: if (2 < 0x403832[5][0]) then 0x403ca8[6][0] = 0x403832[5][3] else goto 0x0d7;
0x0e2: 0x403ca8[6][2] = 0x403ca8[6][0];
0x0ec: 0x403ca8[6][1] = rol(0x403ca8[6][1], 0x403ca8[6][3]);
0x0f7: 0x403ca8[6][1] = 0x403ca8[6][2] ^ 0x403ca8[6][1];
0x102: 0x403ca8[6][1] = bit_reverse(0x403ca8[6][1]);
0x108: tmp_ecx = ++0x403832[6][0]; 0x403832[6][tmp_ecx] = 0x403ca8[6][1];
0x10e: if (3 < 0x403832[4][0]) then 0x403ca8[6][0] = 0x403832[4][4] else goto 0x10e;
0x119: 0x403ca8[6][1] = 0x403ca8[6][0];
0x123: if (3 < 0x403832[5][0]) then 0x403ca8[6][0] = 0x403832[5][4] else goto 0x123;
0x12e: 0x403ca8[6][2] = 0x403ca8[6][0];
0x138: 0x403ca8[6][1] = rol(0x403ca8[6][1], 0x403ca8[6][3]);
0x143: 0x403ca8[6][1] = 0x403ca8[6][2] ^ 0x403ca8[6][1];
0x14e: 0x403ca8[6][1] = bit_reverse(0x403ca8[6][1]);
0x154: tmp_ecx = ++0x403832[6][0]; 0x403832[6][tmp_ecx] = 0x403ca8[6][1];
0x15a: if (4 < 0x403832[4][0]) then 0x403ca8[6][0] = 0x403832[4][5] else goto 0x15a;
0x165: 0x403ca8[6][1] = 0x403ca8[6][0];
0x16f: if (4 < 0x403832[5][0]) then 0x403ca8[6][0] = 0x403832[5][5] else goto 0x16f;
0x17a: 0x403ca8[6][2] = 0x403ca8[6][0];
0x184: 0x403ca8[6][1] = rol(0x403ca8[6][1], 0x403ca8[6][3]);
0x18f: 0x403ca8[6][1] = 0x403ca8[6][2] ^ 0x403ca8[6][1];
0x19a: 0x403ca8[6][1] = bit_reverse(0x403ca8[6][1]);
0x1a0: tmp_ecx = ++0x403832[6][0]; 0x403832[6][tmp_ecx] = 0x403ca8[6][1];
0x1a6: if (5 < 0x403832[4][0]) then 0x403ca8[6][0] = 0x403832[4][6] else goto 0x1a6;
0x1b1: 0x403ca8[6][1] = 0x403ca8[6][0];
0x1bb: if (5 < 0x403832[5][0]) then 0x403ca8[6][0] = 0x403832[5][6] else goto 0x1bb;
0x1c6: 0x403ca8[6][2] = 0x403ca8[6][0];
0x1d0: 0x403ca8[6][1] = rol(0x403ca8[6][1], 0x403ca8[6][3]);
0x1db: 0x403ca8[6][1] = 0x403ca8[6][2] ^ 0x403ca8[6][1];
0x1e6: 0x403ca8[6][1] = bit_reverse(0x403ca8[6][1]);
0x1ec: tmp_ecx = ++0x403832[6][0]; 0x403832[6][tmp_ecx] = 0x403ca8[6][1];
0x1f2: check_password(0x403832);
Remark:
in the previous part of this article, we have claimed that the input related instructions are spread over the execution trace. We now can examine, for example, the “virtual instruction” of the process 1
:
0x0bd: if (0x403732[0] == 0x01) then 0x403ca8[1][1] = password[0] else goto 0x0bd;
which corresponds with the basic block 0x40227f
in the control flow graph. Searching for this address in the execution trace in Reven-Axion, we observe that this virtual instruction is executed 12
times, scattered over a trace of more than two billions instructions.
Decompilation
We doubt that anyone bothers to read this decompilation crap, just too boring :-); fortunately making it more comprehensible is not hard. First, each process i
has some “local” variables as dword
elements of the array 0x403ca8[i][...]
, so we can apply the constant propagation and liveness analysis on these variables. There are also high-level loops, for example, the following instructions in the process 1
:
0x000: 0x403ca8[1][5] = 0x00000001;
...
0x07e: 0x403ca8[1][4] = 0x0000036d;
...
0x0e2: 0x403ca8[1][1] = 0x403ca8[1][2] ^ 0x403ca8[1][1];
0x0ed: 0x403ca8[1][1] = rol(0x403ca8[1][1], 0x403ca8[1][3]);
0x0f8: 0x403ca8[1][6] += 0x403ca8[1][5];
0x103: 0x403654[1] = (0x403ca8[1][6] == 0x403ca8[1][4]);
0x10e: if (0x403654[1] != 0x01) then goto 0x0e2;
...
can be interpreted as
...
for (i = 0; i < 0x36d; ++i) {
0x403ca8[1][1] = 0x403ca8[1][2] ^ 0x403ca8[1][1];
0x403ca8[1][1] = rol(0x403ca8[1][1], 0x403ca8[1][3]);
}
...
Next, these processes have some “shared” variables as dword
elements of the array 0x403832[..][..]
. Finally, the shared dword
elements 0x403732[j]
for j = 0..5
can be interpreted as mutex keeping the atomicity of some operations. We then can decompile the first 4
processes as:
Process 0
:
P(mutex0);
password[0] += 0x550342b8;
V(mutex0);
P(mutex1);
password[1] += 0xe3348f8b;
V(mutex1);
P(mutex2);
password[2] += 0x58c85bdd;
V(mutex2);
P(mutex3);
password[3] += 0x7406e41c;
V(mutex3);
P(mutex4);
password[4] += 0x72ece789;
V(mutex4);
P(mutex5);
password[5] += 0xefa2f7ec;
V(mutex5);
goto entry_point;
Process 1
:
P(mutex0);
for (v = password[0], i = 0; i < 0x36d; ++i) {
v ^= 0x6dc555e2; v = rol(v, 0x1f);
}
V(mutex0);
tmp = ++vshared[1][0]; vshared[1][tmp] = v;
P(mutex1);
for (v = password[1], i = 0; i < 0x305; ++i) {
v ^= 0x668471f5; v = rol(v, 0x13);
}
V(mutex1);
tmp = ++vshared[1][0]; vshared[1][tmp] = v;
P(mutex2);
for (v = password[2], i = 0; i < 0x1e6; ++i) {
v ^= 0x21f8a104; v1 = rol(v, 0x10);
}
V(mutex2);
tmp = ++vshared[1][0]; vshared[1][tmp] = v;
P(mutex3);
for (v = password[3], i = 0; i < 0x1d5; ++i) {
v ^= 0x0d665e60; v = rol(v1, 0x1a);
}
V(mutex3);
tmp = ++vshared[1][0]; vshared[1][tmp] = v1;
P(mutex4);
for (v = password[4], i = 0; i < 0xcc; ++i) {
v ^= 0x69fd3480; v = rol(v, 0x7);
}
V(mutex4);
tmp = ++vshared[1][0]; vshared[1][tmp] = v1;
P(mutex5);
for (v = password[5], i = 0; i < 0x25d; ++i) {
v ^= 0xe4b8c392; v1 = rol(v, 0x12);
}
V(mutex5);
tmp = ++vshared[1][0]; vshared[1][tmp] = v;
while (true) {};
Process 2
:
P(mutex0);
for (v = password[0], i = 0; i < 0x6e; ++i) {
v -= 0xecf6d571; v = ror(v, 0xe);
}
tmp = ++vshared[2][0]; vshared[2][tmp] = v;
V(mutex0);
P(mutex1);
for (v = password[1], i = 0; i < 0x20d; ++i) {
v -= 0xec829194; v = ror(v, 0x10);
}
tmp = ++vshared[2][0]; vshared[2][tmp] = v;
V(mutex1);
P(mutex2);
for (v = password[2], i = 0; i < 0x1d7; ++i) {
v -= 0x5c167e65; v = ror(v, 0xd);
}
tmp = ++vshared[2][0]; vshared[2][tmp] = v;
V(mutex2);
P(mutex3);
for (v = password[3], i = 0; i < 0x318; ++i) {
v -= 0x3950cc83; v = ror(v, 0x1e);
}
tmp = ++vshared[2][0]; vshared[2][tmp] = v;
V(mutex3);
P(mutex4);
for (v = password[4]; i = 0; i < 0x2b; ++i) {
v -= 0x604dc3f2; v =ror(v, 0x1);
}
tmp = ++vshared[2][0]; vshared[2][tmp] = v;
V(mutex4);
P(mutex5);
for (v = password[5]; i = 0; i < 0x234; ++i) {
v -= 0x0b3799a2; v =ror(v, 0x19);
}
tmp = ++vshared[2][0]; vshared[2][tmp] = v;
V(mutex5);
while (true) {};
Process 3
:
P(mutex0);
for (v = password[0], i = 0; i < 0x28; ++i) {
v = 0x8fd5c5bd ^ bit_reverse(v); v += 0x8fd5c5bd;
}
V(mutex0);
tmp = ++vshared[3][0]; vshared[3][tmp] = v;
P(mutex1);
for (v = password[1], i = 0; i < 0x14b; ++i) {
v = 0x1f817abb ^ bit_reverse(v); v += 0x1f817abb;
}
V(mutex1);
tmp = ++vshared[3][0]; vshared[3][tmp] = v;
P(mutex2);
for (v = password[2], i = 0; i < 0xcd; ++i) {
v = 0xe8504430 ^ bit_reverse(v); v += 0xe8504430;
}
V(mutex2);
tmp = ++vshared[3][0]; vshared[3][tmp] = v;
P(mutex3);
for (v = password[3], i = 0; i < 0x3a; ++i) {
v = 0xa6258a12 ^ bit_reverse(v); v += 0xa6258a12;
}
V(mutex3);
tmp = ++vshared[3][0]; vshared[3][tmp] = v;
P(mutex4);
for (v = password[4], i = 0; i < 0x178; ++i) {
v = 0x90bf3d8b ^ bit_reverse(v); v += 0x90bf3d8b;
}
V(mutex4);
tmp = ++vshared[3][0]; vshared[3][tmp] = v;
P(mutex5);
for (v = password[5], i = 0; i < 0x35e; ++i) {
v = 0xc350be97 ^ bit_reverse(v); v += 0xc350be97;
}
V(mutex5);
tmp = ++vshared[3][0]; vshared[3][tmp] = v;
while (true) {};
Remark:
the dword
values of the vshared
array (starting at 0x403832
) are initialized with 0
, for example the following figure shows the value of vshared[1][0]
just before it is increased at the first time:
Semantics of concurrent processes
We might remember that there is a scheduler in the concurrency model of these processes, this one cyclically switches the execution from process 0
to process 6
. The processes and the scheduler can be presented in CCS as:
P0 | P1 | P2 | ... | P6 | S
where S
is a scheduler (or semaphore) which forbids direct interactions between Pi
(s), that trivializes also the semantics of these processes.
Indeed, the modification taken on password[j]
in each process is protected by a separated mutexj
. But when a mutex is released by some process Pi
, because of the semaphore S
, only Pi+1
can gain the mutex. This property is independent from the input password (in other words, this concurrency model is deterministic). Its interleaving semantics is a single trace, for example the trace of the first 4
processes is basically:
password[0] += 0x550342b8; // process 0
for (v = password[0], i = 0; i < 0x36d; ++i) { // process 1
v ^= 0x6dc555e2; v = rol(v, 0x1f);
}
++vshared[1][0]; ++vshared[1][1] = v;
for (v = password[0], i = 0; i < 0x6e; ++i) { // process 2
v -= 0xecf6d571; v = ror(v, 0xe);
}
++vshared[2][0]; vshared[2][1] = v;
for (v = password[0], i = 0; i < 0x28; ++i) { // process 3
v = 0x8fd5c5bd ^ bit_reverse(v); v += 0x8fd5c5bd;
}
++vshared[3][0]; vshared[3][1] = v;
...
password[1] += 0xe3348f8b; // process 0
...
Similarly, we can see that the process 4
is just a consumer of processes 1
and 2
with mutexes vshared[1][0]
and vshared[2][0]
, respectively. It consumes vshared[1][..]
and vshared[2][..]
as
v = vshared[2][1] ^ rol(vshared[1][1], 0x0);
tmp = ++vshared[4][0]; vshared[4][tmp] = v;
v = vshared[2][2] ^ rol(vshared[1][2], 0x1a);
tmp = ++vshared[4][0]; vshared[4][tmp] = v;
v = vshared[2][3] ^ rol(vshared[1][3], 0x8);
tmp = ++vshared[4][0]; vshared[4][tmp] = v;
v = vshared[2][4] ^ rol(vshared[1][4], 0xd);
tmp = ++vshared[4][0]; vshared[4][tmp] = v;
v = vshared[2][5] ^ rol(vshared[1][5], 0x10);
tmp = ++vshared[4][0]; vshared[4][tmp] = v;
v = vshared[2][6] ^ rol(vshared[1][6], 0x1e);
tmp = ++vshared[4][0]; vshared[4][tmp] = v;
And process 5
is a consumer of processes 2
and 3
:
v = bit_reverse(vshared[3][1]) ^ vshared[2][1];
tmp = ++vshared[5][0]; vshared[5][tmp] = v;
...
v = bit_reverse(vshared[3][6]) ^ vshared[2][6];
tmp = ++vshared[5][0]; vshared[5][tmp] = v;
Finally, the process 6
consumes results of processes 4
and 5
as:
v = bit_reverse(vshared[5][1] ^ rol(vshared[4][1], 0x17));
tmp = ++vshared[6][0]; vshared[6][tmp] = v;
...
v = bit_reverse(vshared[5][6] ^ rol(vshared[4][6], 0x17));
tmp = ++vshared[6][0]; vshared[6][tmp] = v;
and it checks the input password as:
if (vshared[6][1] == 0x73ae5f50 && vshared[6][2] == 0xbd2b6a91 && vshared[6][3] == 0x3e4e9687 &&
vshared[6][4] == 0xbcfaadcc && vshared[6][5] == 0xcd2ca810 && vshared[6][6] == 0x9d26237e) {
printf("Yes!");
}
else {
printf("Nop!")
}
which corresponds to the following sequence of the execution trace in Reven-Axion:
Summary
We have completely reversed the second virtual machine: it is interpreted as a model of 7
concurrent processes and a cyclic dispatcher (which is also a semaphore). By reasoning the synchronization between them, we have discovered that this model is deterministic, which allows us to obtain a comprehensive semantics.
Finding the password
The lengthy analysis we have presented so far would be useless if it cannot help finding the good password (i.e. one making the program print Yes!
). Fortunately, this is mostly immediate once the semantics of the program is reversed.
Password checking scheme
The final checking procedure just compares each vshared[6][i+1]
(for i = 0..5
) with a specific constant. We can observe that vshared[6][i+1]
is indeed calculated from password[i]
under the following scheme:
In each process j
, noises are added separately into password[i]
to transform it to a new value vshared[j][i+1]
. All details have been already revealed in the previous section, for example, when i = 0
we have:
process 0:
password[0] += 0x550342b8;
process 1:
for (v = password[0], i = 0; i < 0x36d; ++i) {
v ^= 0x6dc555e2; v = rol(v, 0x1f);
}
vshared[1][1] = v;
process 2:
for (v = password[0], i = 0; i < 0x6e; ++i) {
v -= 0xecf6d571; v = ror(v, 0xe);
}
vshared[2][1] = v
process 3:
for (v = password[0], i = 0; i < 0x28; ++i) {
v = 0x8fd5c5bd ^ bit_reverse(v); v += 0x8fd5c5bd;
}
vshared[3][1] = v;
process 4:
v = vshared[2][1] ^ rol(vshared[1][1], 0x0);
vshared[4][1] = v;
...
This kind of transformation is well known as mixed boolean arithmetic which is introduced to protect softwares.
Remark:
the password manipulation scheme proceeds on password[i]
for i = 0..5
, namely on 6 * 4 = 24
bytes of password’s buffer, regardless the real length of input. We can also observe that the program reads maximum 24
bytes, and the password’s buffer is zero initialized.
SMT solver
We avoid (aka cannot find :-)) any trick and proceed with a direct approach. Indeed, the constraint of each password[i]
can be represented by a SMT formula in quantifier-free bit-vector (QF_BV
) theory. Building these formulae is mostly direct (e.g. this file contains constraints for passwords[0]
in smt2
format), a SMT solver will take care of the rest.
./boolector --lingeling passwords0_constraints.smt2
sat
((passwords0 #b01001101010111110011000001010011))
boolector found the 32
bit-vector value 01001101010111110011000001010011
for password[0]
that satisfies the password checking constraint. Since the left-to-right position of bits is from 31
to 0
, this value represents nothing but the string “S0_M
” in ASCII. Similarly, we get “4nY_
”, “ThR3
”, “ad_1
”, “n_D4
” and “t_VM
” for other password[i]
(s), that leads to the string S0_M4nY_ThR3ad_1n_D4t_VM
which satisfies all constraints of the program:
./F4b_XOR_W4kfu.exe
Welcome!
Password? S0_M4nY_ThR3ad_1n_D4t_VM
Yes!
this is also the good password, as expected :-).
Remark:
we provide smt2
files for other password[i]
(s): i=1, i=2, i=3, i=4 and i=5.
The end
We hope you have enjoyed reading this two-stage write-up. Your comments on it and any questions regarding the capabilities of Reven-Axion are welcome.