Overview

We are going to take a look at one of the original "VM" protections used in malware, VM Zeus. Zeus is one of the orignal "botnets" that put ecrime on the map. The sourcecode was sold (and eventually leaked) which lead to many different variants. One of the variants implemented a custom VM that was used to protect the configuration file... known as VM Zeus (KINS).

Many of the Zeus variants have been catalogued in the the ZeusMuseum curated by @tildedennis.

In this analysis we are going to focus on the VM mechanics, how it works, and how to analyze it, as a way to understand VM protection fundamentals. There is a lot of prior research on this VM that we will reference below.

Sample

f792997cb36a477fa55102ad6b680c97e3517b2e63c83c802bf8d57ae9ed525e UnpacMe

References

Analysis

The VM consists of a dispatcher, a set of instructions handlers, a constant byte array with the code and a constant byte array with the data.

void __usercall mw_vm(char *arg_data@<esi>)
{
  byte *var_code_buff; // edi
  ctxt ctxt; // [esp+4h] [ebp-4Ch] BYREF

  var_code_buff = mw_cpy_to_buff(&g_code, (void *)0x1000);
  if ( var_code_buff )
  {
    mw_wrap_cpy(arg_data, &g_data, 0x370u);
    ctxt.value0 = 0;
    ctxt.data = (byte *)arg_data;
    ctxt.code = var_code_buff;
    g_data_buff = (int)arg_data;
    while ( ((char (__thiscall *)(ctxt *))ptr_instructions[*ctxt.code])(&ctxt) )
      ;
    mw_wrap_heap_free(var_code_buff);
  }
}

Instruction Set Archetecture

  • opcode is 1 byte
  • operands can be immediate or register index
  • regsiters are numerically indexed from 0-15

Instruction Pointer

  • next opcode is decrypted using unique hard-coded byte for each handler, and last byte of previous instruction
  • code buffer is incremented in handler

Data

There is a shared data buffer that is pre-populated with data but is also used like stack during instruction execution. Unclear what to call this.

Context

The VM context is used to pass the code buffer, data buffer, and registers to the instruction handlers. The value0 is likely flags but not confirmed yet!

struct __declspec(align(4)) ctxt
{
  byte *code;
  byte *data;
  DWORD value0;
  DWORD regs[16];
};

Marking Up Instruction Handlers

Thanks to this nice script from "anonymous" (not THAT anonymous) we can mark up a few instructions and notice a pattern.

import idaapi
funcs=[0x429D6D,0x429DA1,0x429DD8,0x429CDB,0x429D0C,0x429D3D,0x429F59,0x429F8F,0x429FC8,0x429EB3,0x429EE9,0x429F22,0x429E0D,0x429E43,0x429E7C,0x429BA5,0x429BDC,0x429C13]

for func in funcs:
    idc.SetType(func , "char __thiscall sub_429C13(struct ctxt *this)")


idaapi.set_name(0x429D6D, "mw_push_reg_to_data_b", idaapi.SN_FORCE)
idaapi.set_name(0x429DA1, "mw_push_reg_to_data_w", idaapi.SN_FORCE)
idaapi.set_name(0x429DD8, "mw_push_reg_to_data_dw", idaapi.SN_FORCE)

idaapi.set_name(0x429CDB, "mw_pop_data_to_reg_b", idaapi.SN_FORCE)
idaapi.set_name(0x429D0C, "mw_pop_data_to_reg_w", idaapi.SN_FORCE)
idaapi.set_name(0x429D3D, "mw_pop_data_to_reg_dw", idaapi.SN_FORCE)

idaapi.set_name(0x429F59, "mw_xor_data_with_reg_b", idaapi.SN_FORCE)
idaapi.set_name(0x429F8F, "mw_xor_data_with_reg_w", idaapi.SN_FORCE)
idaapi.set_name(0x429FC8, "mw_xor_data_with_reg_dw", idaapi.SN_FORCE)


idaapi.set_name(0x429EB3, "mw_subs_reg_from_data_b", idaapi.SN_FORCE)
idaapi.set_name(0x429EE9, "mw_subs_reg_from_data_w", idaapi.SN_FORCE)
idaapi.set_name(0x429F22, "mw_subs_reg_from_data_dw", idaapi.SN_FORCE)

idaapi.set_name(0x429E0D, "mw_add_reg_to_data_b", idaapi.SN_FORCE)
idaapi.set_name(0x429E43, "mw_add_reg_to_data_w", idaapi.SN_FORCE)
idaapi.set_name(0x429E7C, "mw_add_reg_to_data_dw", idaapi.SN_FORCE)


idaapi.set_name(0x429BA5, "mw_mov_reg2_to_reg1_b", idaapi.SN_FORCE)
idaapi.set_name(0x429BDC, "mw_mov_reg2_to_reg1_w", idaapi.SN_FORCE)
idaapi.set_name(0x429C13, "mw_mov_reg2_to_reg1_dw", idaapi.SN_FORCE)

Instruction Handlers

h_nop_b
h_nop_w
h_nop_dw
h_xor_data_imm_b
h_xor_data_imm_w
h_xor_data_imm_dw
h_add_data_imm_b
h_add_data_imm_w
h_add_data_imm_dw
h_sub_data_imm_b
h_sub_data_imm_w
h_sub_data_imm_dw
h_rol_b_data_b
h_rol_w_data_b
h_rol_dw_data_b
h_ror_b_data_b
h_ror_w_data_b
h_ror_dw_data_b
h_not_b_data
h_not_w_data
h_not_dw_data
h_dw_data_shuffle
h_rc4
h_set_value_imm_b
h_set_value_imm_w
h_set_value_imm_dw
h_add_data_imm_w
h_loop_b
h_loop_w
h_mov_reg_imm_b
h_mov_reg_imm_w
h_mov_reg_imm_dw
h_mov_reg_reg_b
h_mov_reg_reg_w
h_mov_reg_reg_dw
h_add_reg_reg_b
h_add_reg_reg_w
h_add_reg_reg_dw
h_sub_reg_reg_b
h_sub_reg_reg_w
h_sub_reg_reg_dw
h_xor_reg2_to_reg1_b
h_xor_reg2_to_reg1_w
h_xor_reg2_to_reg1_dw
h_reg_add_imm_b
h_reg_add_imm_w
h_reg_add_imm_dw
h_reg_sub_imm_b
h_reg_sub_imm_w
h_reg_sub_imm_dw
h_reg_xor_imm_b
h_reg_xor_imm_w
h_reg_xor_imm_dw
mw_add_reg_to_data_b
mw_add_reg_to_data_w
mw_add_reg_to_data_dw
mw_subs_reg_from_data_b
mw_subs_reg_from_data_w
mw_subs_reg_from_data_dw
mw_xor_data_with_reg_b
mw_xor_data_with_reg_w
mw_xor_data_with_reg_dw
h_mov_data_to_reg_b
h_mov_data_to_reg_w
h_mov_data_to_reg_dw
mw_push_reg_to_data_b
mw_push_reg_to_data_w
mw_push_reg_to_data_dw
h_vm_exit