对虚拟机壳WProtect加密Demo bin的逆向分析
以前没有接触过脱壳,非常感谢handsomexiaowei@gmail.com开源WProtect,才对虚拟机壳有了一点认识。
文中分析的bin来自于XiaoWei发布于看雪的WProtect,本文分析其中的add_test.wp.exe.
文题为逆向分析,笔者在实际分析中交叉使用了windbg,并且大量阅读了源码。
贴出add_test的源码
section .text
jmp start
d_add:
add eax,1
ret
nop
w_add:
add ax,1
ret
bl_add:
add al,1
ret
nop
nop
bh_add:
add ah,1
ret
nop
nop
start:
xor eax,eax
call d_add
xor eax,eax
call w_add
xor eax,eax
call bl_add
xor eax,eax
call bh_add
ret
add_test.wp.exe对里面的4个call进行了加密。
.text:00401017 ; START OF FUNCTION CHUNK FOR start
.text:00401017
.text:00401017 loc_401017: ; CODE XREF: startj
.text:00401017 xor eax, eax
.text:00401019 call j_s_1002
.text:0040101E xor eax, eax
.text:00401020 call j_s_1007
.text:00401025 xor eax, eax
.text:00401027 call j_s_100C
.text:0040102C xor eax, eax
.text:0040102E call j_s_1011
.text:00401033 retn
.text:00401033 ; END OF FUNCTION CHUNK FOR start
j_s_1002是第一个函数,内容本来是“add eax,1”、“ret”,此处明显“入瓮”了;
eax=00000000 ebx=7ffdf000 ecx=0022ffb0 edx=7c92e4f4 esi=0292f758 edi=0292f6ee
eip=00401002 esp=0022ffc0 ebp=0022fff0
.text:00401002 j_s_1002 proc near ; CODE XREF: start+19p
.text:00401002 jmp s_1002
.text:00401002 j_s_1002 endp
这里跳到s_1002,注意这个地址,位于WProtect新增的区段内,
.WProtec:004043A4 s_1002 proc near ; CODE XREF: j_s_1002j
.WProtec:004043A4 push esi
.WProtec:004043A5 push ecx
.WProtec:004043A6 push edx
.WProtec:004043A7 push ebp
.WProtec:004043A8 push edi
.WProtec:004043A9 push ebx
.WProtec:004043AA push eax
.WProtec:004043AB push 1E240h
.WProtec:004043B0 pushf
.WProtec:004043B1 mov ebx, 12345678h
.WProtec:004043B6 mov ebp, esp
.WProtec:004043B8 sub esp, 0C8h
.WProtec:004043BE mov edi, esp
.WProtec:004043C0 mov esi, offset byte_4042D1 ; esi = pcode
.WProtec:004043C5 jmp dispatch
.WProtec:004043C5 s_1002 endp
前面8个push加一个pushf,有个1E240,在源码内是取个了变量的地址,作用是某Key;
offset byte_4042D1 这里是虚拟指令的开始。然后跳入了dispatch,也就是vm_handle_dispatch;
.WProtec:00403CD3 dispatch: ; CODE XREF: s_1007+21j
.WProtec:00403CD3 ; s_100C+21j ...
.WProtec:00403CD3 not bx
.WProtec:00403CD6 sub bx, 0F3E9h
.WProtec:00403CDB movzx eax, byte ptr [esi-1]
.WProtec:00403CDF dec esi
.WProtec:00403CE0 ror al, 4
.WProtec:00403CE3 not al
.WProtec:00403CE5 ror al, 9
.WProtec:00403CE8 ror al, 0Dh
.WProtec:00403CEB push ds:dword_4038D7[eax*4] ; eax = handle_index
.WProtec:00403CF2 retn
.WProtec:00403CF2 ; END OF FUNCTION CHUNK FOR s_100C
esi指向虚拟指令,源码里可以指定这里的地址增长方向,此处是递减;
.WProtec:00403CEB 处的eax是实际的handle_index,笔者下到的源码有50+个handle,
对应于N多条指令的解析和若干辅助作用的handle;
对于不同的bin或者说不同的虚拟机实体,handle_index是不确定的,源码里对handle_table进行了乱序操作。
对下面两处下断:
.text:00401019 call j_s_1002
.text:0040101E xor eax, eax
步入call j_s_1002;
然后对00403CEB下断,然后连续F5,直到0040101E断点命中,可以看到j_s_1002内dispatch被执行了25次;
其中每次断到00403CEB push ds:dword_4038D7[eax*4],eax即是handle_index;
可见eax=0x22的handle被调用了很多次,原因是一开始需要save_vm_context(8个push和1个pushf共9次);
保存最开始push的寄存器值,到栈中的vm_context里;这个操作是用d_pop_reg来实现的,d_pop_reg在该bin中的handle_index为0x22;
而在对于指令的模拟中,WProtect是用栈来传递值的,所以在指令的模拟中也大量用到了d_pop_reg。
.WProtec:004035BC f_22_d_pop_reg proc near
.WProtec:004035BC
.WProtec:004035BC ; FUNCTION CHUNK AT .WProtec:0040379A SIZE 00000017 BYTES
.WProtec:004035BC
.WProtec:004035BC not ebx
.WProtec:004035BE sub bl, 3Ah
.WProtec:004035C1 add ebx, 6690842Eh
.WProtec:004035C7 not bl
.WProtec:004035C9 ror ebx, 7
.WProtec:004035CC xor ebx, 2E2EDB1Ah
.WProtec:004035D2 xor bl, 0BBh
.WProtec:004035D5 mov al, [esi-1]
.WProtec:004035D8 dec esi
.WProtec:004035D9 add al, bl
.WProtec:004035DB sub al, bl
.WProtec:004035DD rol al, 2
.WProtec:004035E0 ror al, 7
.WProtec:004035E3 ror al, 1Eh
.WProtec:004035E6 inc al
.WProtec:004035E8 movzx eax, al
.WProtec:004035EB mov edx, [ebp+0]
.WProtec:004035EE mov [edi+eax], edx
.WProtec:004035F1 add ebp, 4
.WProtec:004035F4 jmp j_checkstack
.WProtec:004035F4 f_22_d_pop_reg endp
重点是:
mov edx, [ebp+0]
mov [edi+eax], edx
add ebp, 4
这里edi是vm_context的基址,里面存一开始push的9个值,eax代表偏移,跟踪每次的变化,结合开始push的顺序,得:
edi = 0x22fed4 --- vm_context_Base
Offset, Value, Register
0022ff9c 00000246 0001e240 00000000 7ffde000
0022ffac 0292f6ee 0022fff0 7c92e4f4 0022ffb0
0022ffbc 0292f758
+44 0292f758 push esi
+30 0022ffb0 push ecx
+28 7c92e4f4 push edx
+48 0022fff0 push ebp
+50 0292f6ee push edi
+ c 7ffde000 push ebx
+14 00000000 push eax
+5c 0001e240 push 1E240h
+10 00000246 pushf
接着就是对实际指令的模拟了,可见跳入了handle_index(eax = d)中;
eax=0000000d
00403ceb push dword ptr image00400000+0x38d7 (004038d7)[eax*4]
00403cf2 ret
.WProtec:00403316 f_d_Push_Imm proc near
.WProtec:00403316 inc ebx
.WProtec:00403317 not ebx
.WProtec:00403319 ror ebx, 19h
.WProtec:0040331C mov eax, [esi-4]
.WProtec:0040331F sub esi, 4
.WProtec:00403322 sub eax, ebx
.WProtec:00403324 sub eax, ebx
.WProtec:00403326 sub ebp, 4
.WProtec:00403329 mov [ebp+0], eax
.WProtec:0040332C jmp j_checkstack
.WProtec:0040332C f_d_Push_Imm endp
跟到这里,重点是:
sub ebp, 4
mov [ebp+0], eax
前后指令是对已加密的pcode的解密,动态调试只要跟到这里就能看到真值eax = 1,
可见此handle在本次调用中意义是push 1;
后面跳入了handle_index(eax = 6)中;
eax=00000006
00403ceb push dword ptr image00400000+0x38d7 (004038d7)[eax*4]
00403cf2 ret
.WProtec:00403254 f_6_d_push_reg proc near
.WProtec:00403254 inc bx
.WProtec:00403256 ror ebx, 4
.WProtec:00403259 dec ebx
.WProtec:0040325A mov al, [esi-1]
.WProtec:0040325D dec esi
.WProtec:0040325E xor al, bl
.WProtec:00403260 inc al
.WProtec:00403262 sub al, bl
.WProtec:00403264 rol al, 7
.WProtec:00403267 rol al, 1Bh
.WProtec:0040326A movzx eax, al
.WProtec:0040326D mov eax, [edi+eax]
.WProtec:00403270 sub ebp, 4
.WProtec:00403273 mov [ebp+0], eax
.WProtec:00403276 jmp j_checkstack
.WProtec:00403276 f_6_d_push_reg endp
重点是:
mov eax, [edi+eax] ;eax = 0x14
sub ebp, 4
mov [ebp+0], eax
结合前面save_vm_context我们记录的表,
可以得出这里把[edi+eax]代表的寄存器的值(eax)压到栈里,就是push eax;
后面跳入了handle_index(eax = c)中;
eax=0000000c
00403ceb push dword ptr image00400000+0x38d7 (004038d7)[eax*4]
00403cf2 ret
.WProtec:004032ED f_c_AddEbpEbp4 proc near
.WProtec:004032ED ror bl, 8
.WProtec:004032F0 xor bl, 0F1h
.WProtec:004032F3 dec ebx
.WProtec:004032F4 sub ebx, 15C852E7h
.WProtec:004032FA add ebx, 24DA07FEh
.WProtec:00403300 xor ebx, 6FA1625Ch
.WProtec:00403306 dec ebx
.WProtec:00403307 mov eax, [ebp+0]
.WProtec:0040330A add [ebp+4], eax
.WProtec:0040330D pushf
.WProtec:0040330E pop dword ptr [ebp+0]
.WProtec:00403311 jmp j_checkstack
.WProtec:00403311 f_c_AddEbpEbp4 endp
重点是:
mov eax, [ebp+0]
add [ebp+4], eax
pushf
pop dword ptr [ebp+0]
结合上面,push 1,push eax,可见把这两个值相加,结果存到[ebp+4],eflag存到[ebp+0];
后面跳入了handle_index(eax = 22)f_22_d_pop_reg中;
mov edx,dword ptr [ebp]
mov dword ptr [edi+eax] ;eax = 0x10
add ebp, 4
可见把前面add后的eflag经过栈传回了vm_context;
再一个f_22_d_pop_reg把add的结果传回vm_context中的eax中。
总结上面的若干vm_handle,共同实现了add eax, 1
=====================================================================
d_add:
add eax,1
ret
nop
WProtect里忽略了nop,所以d_add里的有效指令只有两条,也就是ret是最后一条指令,
实际会在return前把vm_contect里的值退回Register中。
{ // 这两步是对程序结果是无影响的
继续跟,进入到handle_index(eax = d)f_d_Push_Imm中;
eax=00404367
ebp=0022ffbc
sub ebp, 4
mov dword ptr [ebp],eax
后面跳入了handle_index(eax = 22)f_22_d_pop_reg中;
mov edx,dword ptr [ebp] ; ebp=0022ffbc
mov dword ptr [edi+eax], edx ; eax = 0x20
add ebp, 4
}
后面跳入了handle_index(eax = 6)f_6_d_push_reg中(8次);
.WProtec:0040326D mov eax, [edi+eax]
.WProtec:00403270 sub ebp, 4
.WProtec:00403273 mov [ebp+0], eax
edi=0022fed4
push [edi+0x10] eflag
push [edi+0x14] eax
push [edi+0x30] ecx
push [edi+0x28] edx
push [edi+0x c] ebx
push [edi+0x48] ebp
push [edi+0x44] esi
push [edi+0x50] edi
后面跳入了handle_index(eax = a)f_a_ret中;
.WProtec:004032B8 f_a_ret proc far
.WProtec:004032B8 mov esp, ebp
.WProtec:004032BA pop edi
.WProtec:004032BB pop esi
.WProtec:004032BC pop ebp
.WProtec:004032BD pop ebx
.WProtec:004032BE pop edx
.WProtec:004032BF pop ecx
.WProtec:004032C0 pop eax
.WProtec:004032C1 popf
.WProtec:004032C2 retn
.WProtec:004032C2 f_a_ret endp
.WProtec:004032C2 处这一个 retn就破壳而出了,注意这里的8个pop是与8个f_6_d_push_reg相对应的。
这个retn为什么能出壳,纵观一下栈的变化:
入壳时push 4Bytes*9,
f_22_d_pop_reg*9,
f_d_Push_Imm
f_6_d_push_reg
f_22_d_pop_reg*2
{ // 无意义
f_d_Push_Imm,
f_22_d_pop_reg
}
f_6_d_push_reg*8
pop 4Bytes*8
retn
-END-
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: