In this post, we will take a look at one of the simple reversing challenges from MalwareTech. Challenge can be downloaded from his blog.

We are given an 8 bit VM and a dump of RAM extracted from the program. As stated in the challenge, we shouldn't use debugger but rely only on the static analysis.

So let us start. First, we can open the given program in Ghidra. The program is quite simple and there are only three custom functions.

undefined4 entry(void)

{
  HANDLE hHeap;
  char *lpText;
  DWORD dwFlags;
  SIZE_T dwBytes;
  MD5 local_94 [144];

  MD5(local_94);
  dwBytes = 507;
  dwFlags = 0;
  hHeap = GetProcessHeap();
  DAT_0040423c = (char *)HeapAlloc(hHeap,dwFlags,dwBytes);
  memcpy(DAT_0040423c,&DAT_00404040,507);
  vm_loop();
  lpText = digestString(local_94,DAT_0040423c);
  MessageBoxA((HWND)0x0,lpText,"We\'ve been compromised!",0x30);
  ExitProcess(0);
  return 0;
}

void vm_loop(void)

{
  byte bVar1;
  uint i;
  byte bVar2;
  byte local_5;

  local_5 = 0;
  do {
    i = (uint)local_5;
    bVar1 = local_5 + 1;
    bVar2 = local_5 + 2;
    local_5 = local_5 + 3;
    i = vm_handler((byte *)(uint)*(byte *)(DAT_0040423c + 0xff + i),
                   (uint)*(byte *)(DAT_0040423c + 0xff + (uint)bVar1),
                   *(undefined *)(DAT_0040423c + 0xff + (uint)bVar2));
  } while ((i & 0xff) != 0);
  return;
}

uint vm_handler(byte *param_1,int param_2,undefined param_3)

{
  if (param_1 == (byte *)0x1) {
    *(undefined *)(DAT_0040423c + param_2) = param_3;
  }
  else {
    if (param_1 == (byte *)0x2) {
      param_1 = (byte *)(DAT_0040423c + param_2);
      DAT_00404240 = *param_1;
    }
    else {
      if (param_1 != (byte *)0x3) {
        return (uint)param_1 & 0xffffff00;
      }
      param_1 = (byte *)(DAT_0040423c + param_2);
      *(byte *)(DAT_0040423c + param_2) = *param_1 ^ DAT_00404240;
    }
  }
  return CONCAT31((int3)((uint)param_1 >> 8),1);
}

We see that it is a simple switch case based VM. Implemented VM has three instructions and one register DAT_00404240. All opcodes take two parameters even if the second one is unused except in case of opcode 0x1. Since the size of the given RAM dump matches perfectly size of DAT_0040423c we can assume we are given all necessary data and we can try writing our own handler. By simply rewriting the handler in python we can solve this challenge:

data = []
reg = 0

def vm_handler(ins, operand1, operand2):
    global reg
    global data
    if ins == 1:
        data[operand1] = operand2
    elif ins == 2:       
        reg = data[operand1]
    elif ins == 3:
        data[operand1] = data[operand1] ^ reg
    else:       
        return False
    return True

# VM loop
with open('ram.bin','rb') as f:
    data = list(f.read())

    res = True
    counter = 0
    while res:
        counter += 3
        res = vm_handler(data[counter + 255], data[counter + 255 + 1], data[counter + 255 + 2])

    print(f'Counter: {counter}')
    print([chr(x) for x in data[:25]])

- F3real