Tags: vm rev mips 

Rating:

```
$ file malware_engine
malware_engine: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld.so.1, for GNU/Linux 3.2.0, BuildID[sha1]=b8c9b386769a8957f142eda942b08bc8c4a8d082, stripped
```

It should be possible to run it using `qemu-mips`, but I didn't try that. Instead, I opened it in Ghidra, which worked surprisingly well as a decompiler.

In the `main` function we can see an endless cycle, where the binary asks you for a key, checks it in `check_key` function (let's call it that) and exits if the key is valid.

In the `check_key` function, a lot of stuff happens. We can observe it for a bit and see that there are 10 different checks, apart from the size check — the key shoukd be 38 characters long. Each should pass for key to be considered valid. A lot of functions is called but most of them are actually from C++ STL, such as `std::string` constructors, `std::vector` methods and so on. In the end, we can observe, that those 10 checks can be split in two categories — 4 of them use the same function, and all the other ones look pretty similar.

Starting with the second group of checks, there are 6 of them. They all can be described as "let's use some simple checks on key characters". For example, check at 0x401A48:
``` cpp
undefined4 check4(basic_string<char,std--char_traits<char>,std--allocator<char>> *param_1)

{
char c17;
char c18;
char c19;
int iVar1;
char *_;
undefined4 uVar2;

iVar1 = __stack_chk_guard;
_ = (char *)operator[](param_1,0x11);
c17 = *_;
_ = (char *)operator[](param_1,0x12);
c18 = *_;
_ = (char *)operator[](param_1,0x13);
c19 = *_;
_ = (char *)operator[](param_1,0x14);
if ((int)*_ + (int)c19 == 0x6a) {
if ((int)c17 - (int)c19 == 1) {
if (c18 == 'f') {
if ((int)c17 == 0x36) {
uVar2 = 1;
}
else {
uVar2 = 0;
}
}
else {
uVar2 = 0;
}
}
else {
uVar2 = 0;
}
}
else {
uVar2 = 0;
}
if (iVar1 != __stack_chk_guard) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar2;
}

```

From this check we can recover characters at positions 17-20. When we combine all 6 checks, we get the following known characters: `.....3135...6....6f55292d.......d8aa8}`

The other group of checks is more interesting. It uses some object, which consists of `std::string` and `std::vector<uint32_t>`. The string is initialized with key copy in the beginning of `check_key` and is not modified anywhere after. One of four global arrays is loaded into vector before each check. Let's look at this check function:
``` cpp
uint vm(Struc1 *param_1)

{
bool bVar1;
uint *puVar2;
byte O;
char S;
char *pcVar3;
int *piVar4;
undefined4 *puVar5;
uint uVar6;
int iVar7;
undefined4 uStack64;
int iStack60;
int iStack56;
int i;
int j;
int iStack44;
undefined4 uStack40;
uint uStack36;
Vector VStack32;
int iStack20;

iStack20 = __stack_chk_guard;
iStack60 = 0x80;
uStack40 = 0;
iStack56 = 0;
Vector__ctr(&VStack32);
i = 0;
while (i < 0x100) {
uStack64 = 0;
/* try { // try from 0040117c to 004013af has its CatchHandler @ 004015b0 */
FUN_004033ac((void **)&VStack32,&uStack64);
i = i + 1;
}
do {
puVar2 = (uint *)Vector_at((int *)&param_1->vec,iStack60);
uStack36 = *puVar2;
O = (byte)(uStack36 >> 0x18);
S = (char)(uStack36 >> 0x10);
if (O == 0x16) {
puVar2 = (uint *)Vector_at((int *)&param_1->vec,uStack36 & 0xffff);
*puVar2 = *puVar2 ^ (int)S;
}
else {
if (O < 0x17) {
if (O == 0xe) {
if (uStack36 == 0xe0e0e0e) {
j = 0;
while (j < 8) {
puVar2 = (uint *)Vector_at((int *)&param_1->vec,j);
*puVar2 = *puVar2 ^ 0x13;
j = j + 1;
}
}
}
else {
if (O < 0xf) {
if (O == 10) {
puVar2 = (uint *)Vector_at((int *)&param_1->vec,uStack36 & 0xffff);
*puVar2 = *puVar2 ^ 0x17;
}
}
else {
if (O == 0x13) {
uVar6 = uStack36 >> 0x10;
puVar2 = (uint *)Vector_at((int *)&param_1->vec,uStack36 & 0xffff);
*puVar2 = uVar6 & 0xff;
}
else {
if (O == 0x14) {
piVar4 = (int *)Vector_at((int *)&param_1->vec,uStack36 & 0xffff);
iVar7 = *piVar4;
pcVar3 = (char *)operator[]((
basic_string<char,std--char_traits<char>,std--allocator<char>>
*)param_1,(int)S);
if (iVar7 == (int)*pcVar3) {
iStack56 = iStack56 + 1;
}
}
}
}
}
}
else {
if (O == 0x18) {
piVar4 = (int *)Vector_at((int *)&param_1->vec,uStack36 & 0xffff);
*piVar4 = *piVar4 + (int)S;
}
else {
if (O < 0x18) {
piVar4 = (int *)Vector_at((int *)&param_1->vec,uStack36 & 0xffff);
*piVar4 = *piVar4 - (int)S;
}
else {
if (O == 0xde) {
bVar1 = iStack56 == (int)S;
Vector_dtr(&VStack32);
if (iStack20 != __stack_chk_guard) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return (uint)bVar1;
}
if (O == 0xfe) {
iStack44 = 0;
while (iStack44 < 8) {
puVar5 = (undefined4 *)Vector_at((int *)&param_1->vec,iStack44);
*puVar5 = 0xfe;
iStack44 = iStack44 + 1;
}
}
}
}
}
}
iStack60 = iStack60 + 1;
} while( true );
}
```

I called it a `vm` because it is a VM. The vector part is holding a program, which is interpreted by this function. We can also notice that some useles vector is loaded with 256 zero values in the beginning but is not used anywhere, guess it is the remnant of some previous versions.

This VM is pretty simple, it contains only a few instructions, there are even no jumps or calls, so the execution is linear. The virtual program first 128 ints can be interpreted as "register bank", especially first 8 of them, since there are several instruction that explicitly modify these 8 instructions, while there are also instructions that can modify any int in the program. That means, there is a possibility to create self-modifying code, however, that was not the case in this task.

If we read the code more closely, we can notice that it can't load values from key and modify them, it can only compare computed value with key characters. In other words, it is computing clear-text characters of the key. That means, we can write an emulator, run those virtual programs and dump all compared characters. An implementation of this idea in Python follows (the fourth program contains invalid opcodes which were meant to be skipped):
``` python
#!/usr/bin/env python3
import sys

OPNAMES = {
0x0A: 'xor_17',
0x0E: 'xor_13',
0x13: 'loadim',
0x14: 'check_',
0x16: 'xorimm',
0x17: 'subtra',
0x18: 'additi',
0xDE: 'return',
0xFE: 'mov_FE'
}

def run(prog):
pc = 0x80
cnt = 0
flag = ['.'] * 38
while True:
op = prog[pc]
O, S, L = op >> 24, (op >> 16) & 0xFF, op & 0xFFFF
try:
print(' ', pc, f'{OPNAMES[O]} {S:02X} {L:04X}', '[' + ','.join(f'{x:X}' for x in prog[:32]) + ']', ''.join(flag), file=sys.stderr)
except Exception:
print(f'Unknown opname for opcode {O:02X}', file=sys.stderr)
pc += 1
continue
if O == 0x0A:
prog[L] ^= 0x17
elif O == 0x0E:
if op == 0x0E0E0E0E:
for j in range(8):
prog[j] ^= 0x13
elif O == 0x13:
prog[L] = S
elif O == 0x14:
# cnt += prog[L] == flag[S]
flag[S] = chr(prog[L])
elif O == 0x16:
prog[L] ^= S
elif O == 0x17:
prog[L] -= S
prog[L] &= 0xFFFFFFFF
elif O == 0x18:
prog[L] += S
prog[L] &= 0xFFFFFFFF
elif O == 0xDE:
pass
return ''.join(flag)
elif O == 0xFE:
for j in range(8):
prog[j] = 0xFE
else:
print(f'Unknown opcode {O}!', file=sys.stderr)
exit(-1)
pc += 1

progs = (
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 323289088, 326041601, 328859650, 335478787, 329973764, 322371589, 320012294, 334102535, 235802126, 324403202, 184418306, 335544322, 334495752, 330956809, 334364682, 329252875, 328728588, 319946760, 320405517, 331415566, 235802126, 180027400, 408944648, 335609864, 318832640, 319094788, 320471048, 333053964, 318898177, 319160325, 320536585, 333119501, 318963714, 319225862, 320602122, 333185038, 319029251, 319291399, 320667659, 333316111, 235802126, 402718720, 403243008, 403308545, 408289280, 335675392, 235802126, -16843010, 235802126, 394133508, 335740932, 318767111, 373227527, 371130375, 370278407, 376897543, 385810439, 382009351, 386138119, 335806471, -570048785],
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 235802126, -16843010, 235802126, -16843010, 318767104, 318767105, 318767106, 318767107, 318767108, 335478789, 335413254, 335347719, 235802126, 323289090, 370343938, 370671618, 385744898, 376897538, 378077186, 385744898, 378077186, 336134146, 386400258, 336199682, 403111938, 336265218, 235802126, -16843010, -570176770],
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 319881216, 319881217, 319881218, 319881219, 319946756, 319946757, 319946758, 319946759, 320012296, 320012297, 320012298, 320012299, 403701768, 403832840, 336396296, 386138120, 336461832, 405995528, 336527368, 389152776, 336592904, 235802126, -16843010, 235802126, -16843010, 235802126, -16843010, 235802126, -570115394],
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -16843010, -16843010, -16843010, -16843010, 235802126, 235802126, 235802126, 235802126, -16781329, -269488145, -285278466, -16843010, 318767104, 318832641, 318898178, 318963715, 319029252, 319094789, 319160326, 319225863, 370409472, 403898368, 403832832, 337182720, 416284673, 395968513, 337248257, 402718721, 337313793, 386072577, 337379329, 385941505, 337444865, 402718721, 337510401, 369360897, 337575937, -16843010, 235802126, 235802126, 235802126, -16843010, -569901314, -16843010, 235802126, 235802126, 235802126, 235802126, 235802126, 235802126, 235802126, 235802126, 318767105, 318832642, 318963712, 318767104, 235802126],
)
for prog in progs:
prog = [x & 0xFFFFFFFF if x != -1 else x for x in prog]
print(run(prog))
```
which gives the following output
```
Aero{.................................
.........918..........................
.............51d2.....................
.........................9785451......
```

Since we got all the other characters earlier, the task is solved.