转载注明来自看雪
Enigma的1.3x版本加入了VM保护,除了VM外,Enigma的脱法和以前版本类似,1.3x里面还加入了随机异常,让脱壳者不能通过定位异常来判断位置,别的无非就是偷取api,iat redirection,偷取库函数等等,都是一些老的技术,本文主要是对其VM进行一番探索.
Enigma VM执行的过程是:
壳程序在每个虚拟块的开头,用
0040144C 68 6FB8AB95 push 95ABB86F //cipher_index
00401451 - E9 86C3E400 jmp VM_Entry
当程序运行到这里的时候,会带着cipher_index,进入VM_Entry
cipher_index是把虚拟块的索引用用随机的rc4加密,进入VM_Entry后,会解密回来,通过这个索引找到VM_Data
然后把VM_Data还原成x86的代码,把这些代码送到中VirtualAlloc的内存空间里面,然后跳到这个VirtualAlloc中去执行,所以可以看出Enigma的虚拟机其实不是真正意义上的虚拟机,它没有常见VM的Context保护,PCode执行,它只是一个代码还原执行的载体.可想而知,强度就不会太大.
首先是存放Enigma VM的数据结构:
Enigma的虚拟块存放在一个List里面,在初始化VM的时候,会给这个List复制.
这个List的每个Item结构是:
ITEM
{
0h: index //这个是虚拟块的索引,通过解密cipher_index,找到当前虚拟块的数据
4h: rva //这个是VM块在程序里面的rva,要加上ImageBase
8h: vm_run_addr //这个是虚拟块还原出来的x86的地址,第一次运行虚拟块,这个地址就产生了
Ch: vm_data //这个就是最重要的虚拟数据
}
Enigma主程序的虚拟块一共有0x577处,要想完全修复得花一定功夫.
CODE:0041DA21 loop_write1: ; CODE XREF: Fix_VM+19Fj
CODE:0041DA21 mov edi, ebx
CODE:0041DA23 add edi, edi
CODE:0041DA25 mov eax, [ebp+0] ; jmp_list
CODE:0041DA28 lea edx, [ebx+1] ; index
CODE:0041DA2B mov [eax+edi*8], edx
CODE:0041DA2E push offset rand_jmp_key_20h
CODE:0041DA33 mov eax, [ebp+0]
CODE:0041DA36 lea eax, [eax+edi*8]
CODE:0041DA39 lea edx, [esp+14h+var_4]
CODE:0041DA3D mov ecx, 4
CODE:0041DA42 call Cipher_Index
CODE:0041DA47 mov eax, [ebp+0]
CODE:0041DA4A mov eax, [eax+edi*8+4]
CODE:0041DA4E mov edx, ds:pImageBase
CODE:0041DA54 add eax, [edx]
CODE:0041DA56 mov [esp+10h+var_C_data_p], eax
CODE:0041DA5A lea eax, [esp+10h+var_C_data_p]
CODE:0041DA5E mov edx, [esp+10h+var_4]
CODE:0041DA62 call MakePush
CODE:0041DA67 mov edx, offset VM_Entry
CODE:0041DA6C sub edx, [esp+10h+var_C_data_p]
CODE:0041DA70 lea eax, [esp+10h+var_C_data_p]
CODE:0041DA74 call MakeJmp
CODE:0041DA79 inc ebx
CODE:0041DA7A dec esi
CODE:0041DA7B jnz short loop_write1
这里就是对VM_List赋值的地方.
下面再看vm_data,vm_data的结构如下
第一个DWORD 是虚拟代码的总共个数
然后是每个block的内容,它的VM_BLOCK的size=0x15,具体见下面
typedef struct _VM_BLOCK
{
DWORD dwVMType; //代表VMType
DWORD dwParam1;
DWORD dwReg2;
DWORD dwOffset;
DWORD dwReg3;
BYTE bJunk; //bJunk=1 产生花指令
}VM_BLOCK;
进入VM_Entry后,在这里生成还原的x86代码
CODE:0041D2F7 loop_: ; CODE XREF: InitVM_Code+129j
CODE:0041D2F7 imul edi, ebx, 15h ; block_offset
CODE:0041D2FA mov eax, [ebp+var_8_vm_data]
CODE:0041D2FD cmp dword ptr [eax+edi], 0
CODE:0041D301 jz get_next
CODE:0041D307 mov eax, [ebp+var_28_list]
CODE:0041D30A mov edx, [ebp+var_24_mem_p]
CODE:0041D30D mov [eax+ebx*4], edx
CODE:0041D310 mov eax, [ebp+var_8_vm_data]
CODE:0041D313 mov eax, [eax+edi+4]
CODE:0041D317 mov [ebp+var_10_dwParam1], eax
CODE:0041D31A mov eax, [ebp+var_8_vm_data]
CODE:0041D31D mov eax, [eax+edi+8]
CODE:0041D321 mov [ebp+var_14_dwReg2], eax
CODE:0041D324 mov eax, [ebp+var_8_vm_data]
CODE:0041D327 mov eax, [eax+edi+0Ch]
CODE:0041D32B mov [ebp+var_18_offset], eax
CODE:0041D32E mov eax, [ebp+var_8_vm_data]
CODE:0041D331 mov eax, [eax+edi+10h]
CODE:0041D335 mov [ebp+var_1C_dwReg3], eax
CODE:0041D338 mov eax, [ebp+var_8_vm_data]
CODE:0041D33B cmp byte ptr [eax+edi+14h], 0 //bJunk是否要产生花指令
CODE:0041D340 jz short flag_0
CODE:0041D342 lea eax, [ebp+var_24_mem_p]
CODE:0041D345 call JunkProduce
CODE:0041D34A
CODE:0041D34A flag_0: ; CODE XREF: InitVM_Code+B4j
CODE:0041D34A mov eax, [ebp+var_14_dwReg2]
CODE:0041D34D push eax
CODE:0041D34E mov eax, [ebp+var_18_offset]
CODE:0041D351 push eax
CODE:0041D352 mov eax, [ebp+var_1C_dwReg3]
CODE:0041D355 push eax
CODE:0041D356 mov eax, [ebp+var_8_vm_data]
CODE:0041D359 mov edx, [eax+edi] ; type
CODE:0041D35C lea eax, [ebp+var_24_mem_p]
CODE:0041D35F mov ecx, [ebp+var_10_dwParam1]
CODE:0041D362 call GetVMCode ; eax=pMem
CODE:0041D362 ; ecx=VM_Param1
CODE:0041D362 ; edx=VM_Type
CODE:0041D367 test al, al
CODE:0041D369 jnz short get_next
CODE:0041D36B mov eax, [ebp+var_8_vm_data] ;
CODE:0041D36E mov eax, [eax+edi]
CODE:0041D371 xor edx, edx
CODE:0041D373 push edx
CODE:0041D374 push eax
CODE:0041D375 lea eax, [ebp+var_30]
CODE:0041D378 call @Sysutils@IntToStr$qqrj ; Sysutils::IntToStr(__int64)
CODE:0041D37D mov ecx, [ebp+var_30]
CODE:0041D380 lea eax, [ebp+var_2C]
CODE:0041D383 mov edx, offset aInvalidVmInstr ; "Invalid VM instruction: "
CODE:0041D388 call @System@@LStrCat3$qqrv ; System::__linkproc__ LStrCat3(void)
CODE:0041D38D mov eax, [ebp+var_2C]
CODE:0041D390 call @System@@LStrToPChar$qqrx17System@AnsiString ; System::__linkproc__ LStrToPChar
(System::AnsiString)
CODE:0041D395 mov ds:dword_4486A4, eax
CODE:0041D39A push 0
CODE:0041D39C push offset aLoaderError ; "Loader error!"
CODE:0041D3A1 mov eax, ds:dword_4486A4
CODE:0041D3A6 push eax
CODE:0041D3A7 push 0
CODE:0041D3A9 call sub_42081C
CODE:0041D3AE call Fuck_Debugger
CODE:0041D3B3
CODE:0041D3B3 get_next: ; CODE XREF: InitVM_Code+75j
CODE:0041D3B3 ; InitVM_Code+DDj
CODE:0041D3B3 inc ebx
CODE:0041D3B4 dec esi ; size-1
CODE:0041D3B5 jnz loop_ ; block_offset
GetVMCode是最核心的部分,它通过VMType,一共处理了0xDF中代码,具体见程序.
生成x86代码以后,还有进行jmp/jxx/call的修复,jmp/jxx/call的VM_BLOCK里面的参数,只是代表VM_DATA中的偏移,而不是实际x86代码中的偏移,所以还要还原成真实的偏移.
它有两种情况,一种:
1.dwParam1=0xFFFFFFFF 这个时候dwReg2+ImageBase就是jmp/jxx/call的跳转地址
2.dwParam1!=0xFFFFFFFF 这个时候就是VM_DATA中的偏移,要通过实际的x86代码的偏移来替换掉
另外:
Enigma的虚拟有个Bug,那就是在处理VMType=0xDC处的时候:
VMType=0xDC 是 cmp [reg1],imm32
源代码如下
CODE:0040EB5C sub_40EB5C proc near ; CODE XREF: GetVMCode+F55p
CODE:0040EB5C push ebx
CODE:0040EB5D push esi ; cmp [reg1],imm32
; edx是reg1 type 0-7
; ecx是imm32
CODE:0040EB5E push edi
CODE:0040EB5F mov esi, 6
CODE:0040EB64 mov ebx, [eax]
CODE:0040EB66 mov byte ptr [ebx], 81h
CODE:0040EB69 inc dword ptr [eax]
CODE:0040EB6B lea ebx, [edx+38h]
CODE:0040EB6E mov edi, [eax]
CODE:0040EB70 mov [edi], bl
CODE:0040EB72 inc dword ptr [eax]
CODE:0040EB74 cmp dl, 4
CODE:0040EB77 jnz short loc_40EB83
CODE:0040EB79 inc esi
CODE:0040EB7A mov edx, [eax]
CODE:0040EB7C mov byte ptr [edx], 24h
CODE:0040EB7F inc dword ptr [eax]
CODE:0040EB81 jmp short loc_40EB96
CODE:0040EB83 ; ---------------------------------------------------------------------------
CODE:0040EB83
CODE:0040EB83 loc_40EB83: ; CODE XREF: sub_40EB5C+1Bj
CODE:0040EB83 cmp dl, 5
CODE:0040EB86 jnz short loc_40EB96
CODE:0040EB88 mov edx, [eax]
CODE:0040EB8A dec edx
CODE:0040EB8B mov byte ptr [edx], 7Dh
CODE:0040EB8E inc esi
CODE:0040EB8F mov edx, [eax]
CODE:0040EB91 mov byte ptr [edx], 0
CODE:0040EB94 inc dword ptr [eax]
CODE:0040EB96
CODE:0040EB96 loc_40EB96: ; CODE XREF: sub_40EB5C+25j
CODE:0040EB96 ; sub_40EB5C+2Aj
CODE:0040EB96 mov edx, [eax]
CODE:0040EB98 mov [edx], cx ;显然这里出错了,应该是mov [edx],ecx
CODE:0040EB9B add dword ptr [eax], 4
CODE:0040EB9E mov eax, esi
CODE:0040EBA0 pop edi
CODE:0040EBA1 pop esi
CODE:0040EBA2 pop ebx
CODE:0040EBA3 retn
CODE:0040EBA3 sub_40EB5C endp
希望作者下个版本能修改.
附带程序,修复了Enigma OEP处的VM_Code,别的地方如法炮制.^_^
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!