Виртуелно, виртуелно и машински

Rate this Entry
VirtualMachines are really boring, the real art of unpacking is changed with pointless vm reversing. It's really pointless as same result are achived with appending vm, but some authors really crack me up:

(88) mov vm_reg298, 11B0A5B4h
(39) add vm_reg298, 00000060h
(97) mov vm_reg298, [vm_reg298]
(DB) mov vm_reg244, 01101640
(64) add vm_reg244, vm_reg298
(6C) mov vm_regDC, 01107414h
(64) add vm_regDC, vm_reg298
(97) mov vm_regDC, [vm_regDC]
(85) mov vm_reg1A4, vm_reg1C
(26) add vm_reg244, 000000C4h
(97) mov vm_reg2FC, [vm_reg244]
(C3) add vm_reg2FC, vm_regDC
(25) add vm_reg1A4, vm_reg2FC
(38) sub vm_reg244, 000000C4h
(DE) mov vm_reg1A4, [vm_reg1A4]
(A9) mov vm_reg80, vm_reg1C
(F4) add vm_reg244, 000000C8h
(D0) mov vm_reg2FC, [vm_reg244]
(C3) add vm_reg2FC, vm_regDC
(37) add vm_reg80, vm_reg2FC
(B3) sub vm_reg244, 000000C8h
(19) mov vm_reg80, [vm_reg80]
(98) mov vm_regAC, vm_reg1C
(5E) add vm_reg244, 000000CCh
(56) mov vm_reg2FC, vm_reg244
(C3) add vm_reg2FC, vm_regDC
(C3) add vm_regAC, vm_reg2FC
(72) sub vm_reg244, 000000CCh
(19) mov vm_regAC, [vm_regAC]
(66) mov vm_reg1F4, vm_reg1C
(4E) add vm_reg244, 000000D0h
(97) mov vm_reg2FC, [vm_reg244]
(64) add vm_reg2FC, vm_regDC
(C3) add vm_reg1F4, vm_reg2FC
(B3) sub vm_reg244, 000000D0h
(A6) mov vm_reg1F4, [vm_reg1F4]
(85) mov vm_reg2F4, vm_reg1C
(4E) add vm_reg244, 000000D4h
(D6) mov vm_reg2FC, [vm_reg244]
(37) add vm_reg2FC, vm_regDC
(64) add vm_reg2F4, vm_reg2FC
(52) sub vm_reg244, 000000D4
(A6) mov vm_reg2F4, [vm_reg2F4]
(66) mov vm_regF4, vm_reg1C
(26) add vm_reg244, 000000D8h
(97) mov vm_reg2FC, [vm_reg244]
(64) add vm_reg2FC, vm_regDC
(93) push vm_reg8

(43) mov vm_reg10, 00000000h                            <---- silent update in vm_context

(0B) add vm_reg3F4, AD3AC892h                           <---- wrongly decoded instruction, due to "silent" update
This disassembly doesn't give too much hint when doing it staticaly, but when performing it in the live process it makes much more sense. Well especially part where magic filed in vm_context is silently updated... funny... with static tracing this can be located easily as you will get bunch of wrong disasm(last instruction in static tracing), but with properly decoded vm (eg. emulated in live process where we have access to whole progy memory) this lille trick wouldn't even be noticed + we could simply put (in case of emulation) our code instead of making file 40mb larger. Does this count as an owning? Or we have to restore x86 opcodes back... let me know

for comparing reasons, vm emulator 38kb, all stuff dumped ~20mb... in both cases protection is defeated, but... we make user friendly dump with extra 38kb of code, neet isn't it

anyway I'm off to play some poker... that thingie is addictive

aaaaaand game goes on:

(24) mov vm_reg3C8, 0424448Bh
(11) mov vm_reg3CC, 08244C8Bh
(24) mov vm_reg3D0, 0004C969h
(DB) mov vm_reg3D4, 30800000
(DA) mov vm_reg3D8, FAE240F1
(AA) mov vm_reg3DC, 505A5958h
(AA) mov vm_reg3E0, 9090C351h
(24) mov vm_reg320, F1F17899h
(24) mov vm_reg324, 85706DF1h
(DB) mov vm_reg328, F179F5D5
(6C) mov vm_reg32C, 6C61F1F1h
(E2) mov vm_reg330, D5957CA9h
(11) mov vm_reg334, D5ED780Dh
(9F) mov vm_reg338, 2ED453FEh
(D8) mov vm_reg33C, 7A0E0E0Eh
(E2) mov vm_reg340, 957CD5EDh
(6C) mov vm_reg344, 3270F5D5h
(DA) mov vm_reg348, F1F1F1A9
(24) mov vm_reg34C, D5DD70A5h
(AA) mov vm_reg350, F1F1F1F1h
(24) mov vm_reg354, F5D5957Ch
(AA) mov vm_reg358, 70A5F278h
(E2) mov vm_reg35C, F195D5DDh
(F9) mov vm_reg360, 957CF1F1h
(D8) mov vm_reg364, 1D70F5D5h
(DA) mov vm_reg368, F1F1F1F5
(AA) mov vm_reg36C, 4AD5ED78h
(E2) mov vm_reg370, 1220F5E8h
(E2) mov vm_reg374, 02700D82h
(DB) mov vm_reg378, 3653F99B
(43) mov vm_reg37C, AAD5EDD8h
(E2) mov vm_reg380, 61616132h
(11) mov vm_reg388, 00000019h
(66) mov vm_reg38C, vm_reg14
(1A) add vm_reg38C, 00000320h
(93) push vm_reg388
(93) push vm_reg38C
(98) mov vm_reg38C, vm_reg14
(9B) add vm_reg38C, 000003C8h
(8F) call vm_reg38C
(F9) mov vm_reg388, 4A35B626h
(87) sub vm_reg2FC, vm_reg58
(C3) add vm_regF4, vm_reg2FC
Let's see what is going on when reg38C is executed:

seg000:018C03C8                 mov     eax, [esp+4]
seg000:018C03CC                 mov     ecx, [esp+8]
seg000:018C03D0                 imul    ecx, 4
seg000:018C03D6 __more_bre_dekriptujeme:                ; CODE XREF: seg000:018C03DAj
seg000:018C03D6                 xor     byte ptr [eax], 0F1h
seg000:018C03D9                 inc     eax
seg000:018C03DA                 loop    __more_bre_dekriptujeme
seg000:018C03DC                 pop     eax
seg000:018C03DD                 pop     ecx
seg000:018C03DE                 pop     edx
seg000:018C03DF                 push    eax
seg000:018C03E0                 push    ecx
seg000:018C03E1                 retn
and code which is being decrypted:

seg000:018C0320                 push    89h ; ''
seg000:018C0325                 pushf
seg000:018C0326                 xor     dword ptr [esp+4], 88h
seg000:018C032E                 nop
seg000:018C032F                 popf
seg000:018C0330                 pop     eax
seg000:018C0331                 lea     esp, [esp-4]
seg000:018C0335                 mov     [esp], ebx
seg000:018C0338                 cpuid
seg000:018C033A                 and     eax, 0FFFFFFDFh
seg000:018C033F                 mov     ebx, [esp]
seg000:018C0342                 lea     esp, [esp+4]
seg000:018C0346                 add     ebx, 58h ; 'X'
seg000:018C034C                 push    esp
seg000:018C034D                 sub     dword ptr [esp], 0
seg000:018C0354                 lea     esp, [esp+4]
seg000:018C0358                 mov     [ebx], eax
seg000:018C035A                 push    esp
seg000:018C035B                 sub     dword ptr [esp], 64h ; 'd'
seg000:018C0362                 lea     esp, [esp+4]
seg000:018C0366                 sub     esp, 4
seg000:018C036C                 mov     [esp], ebx
seg000:018C036F loc_18C036F:                            ; CODE XREF: seg000:018C0374j
seg000:018C036F                 mov     ebx, 0E3D10419h
seg000:018C0374                 jnb     short near ptr loc_18C036F+3
seg000:018C0376                 xor     ebx, 0C7A2086Ah
seg000:018C037C                 sub     [esp], ebx
seg000:018C037F                 pop     ebx
seg000:018C0380                 retn
basicaly this is hiden cpuid check, and value of cpuid is stored to vm_reg58, later on in virtual machine, right after call vm_reg we have this code:

(87) sub vm_reg2FC, vm_reg58
(C3) add vm_regF4, vm_reg2FC
vm_reg2FC already has imm+cpuid value, so here we have yet another vm anti-dump...

some API redirection:

(85) mov vm_regF0, vm_reg1C
(7A) add vm_regF0, 00000024h      <-- eip offset
(D0) mov vm_regF0, [vm_regF0]     <-- get ret addy
(B4) mov vm_regF4, vm_reg1C
(C4) add vm_regF4, 00000028h
(F0) mov [vm_regF4], vm_regF0     <-- save ret addy
(34) single-step check
(29) mov vm_regF4, vm_reg1C
(A8) add vm_regF4, 00000024h
(F9) mov vm_reg90, 10FC5180h
(19) mov vm_reg90, [vm_reg90]     <-- get api from 10FC5180h
(FB) mov [vm_regF4], vm_reg90     <-- save it to eip
(8B) vm_exit                      <-- and we go on...
API executed from 10FC5180h:

.idata:10FC5180 ; DWORD GetCurrentThreadId(void)
.idata:10FC5180                 extrn GetCurrentThreadId:dword
basically when translated to something user friendly = call dword ptr[10FC5180]

game continues

Submit "Виртуелно, виртуелно и машински" to Digg Submit "Виртуелно, виртуелно и машински" to Submit "Виртуелно, виртуелно и машински" to StumbleUpon Submit "Виртуелно, виртуелно и машински" to Google

Updated October 17th, 2007 at 18:03 by deroko



  1. Pyrae's Avatar
    Hi deroko,
    thanks for sharing your thoughts on this one.
    Though i'm kind of a fundamentalist as far as code reconstruction is concerend (i.e. I don't consider anything less than the best possible reconstruction of native code "ownage"), your approach is certainly a lot more advanced than the usual dump-it-all-and-fix-it one you mentioned.
    I'm sure you are aware though that for this 'dynamic decompiling' method to give perfect results quite some extra care (like e.g. preventing execution of the actual opcode operation/handling possible crash conditions, detecting and handling conditional branches etc.) must be taken.
    Defining the term 'ownage' is kinda boring as after all the methods (mixture of static and dynamic components of the approach) depend on the reverser's goals and personal taste.
    I can say however, that trying to achieve best possible VM-to-x86 conversion using more static methods is a very intriguing task as well, e.g. working out algos to reduce the complexity introduced by the larger register set of most VMs.
    Another interesting - yet not so 'leet' - approach could be to actively(!) relocate the stuff bloating up the images of the 'idiot's approach', effectively giving marginally larger resulting binaries.

    P.S. Seems like you created some really sexy target, yates. Just have a look at the blogs in here...
    Updated October 16th, 2007 at 21:56 by Pyrae
  2. deroko's Avatar
    Hi Pyrae,

    hook of kiuserexceptiondispatcher will solve that problem with wrong access
    I'm still wondering how cpuid checks will deal with hw virtualisation anyhow it's not problem to keep vm context updated in static trace, only have to edit code a lille bit to handle context in decompiler code.