要正确使用这个插件,要求其实是蛮高的:对虚拟机的结构要有所了解!而且插件作者也假设使用者已经具备这些基础知识。
关于CISC,可读读softworm较早的两篇文章“Themida 1.9.1.x CISC VM简单分析”和“Themida v1.8.0.0 Demo虚拟机分析”,很经典的。
而对RISC,可看一下Deathway的“Oreans RISC machine documentation”。
现在Oreans新的VM还没有见到谁有过公开的分析发布。关于TIGER VM,我曾写过一个非常初浅的说明,在
这里;Deathway曾明确表示,他的插件暂时不会支持TIGER。
进入TIGER VM时有如下特征:
[FONT="Courier"]016058F6 68 A4FD5F00 PUSH 5FFDA4 ; pPCODE(RVA)
016058FB 68 97040000 PUSH 497 ; dwFirstHandlerNum
01605900 E9 0692EDFF JMP 014DEB0B ; VM_ENTRY[/FONT]
不细说它了,反正插件也没法用。
下面只说CISC和RISC,比如以下代码:
[FONT="Courier"]00408BEF E9 A7E84F00 JMP 0090749B ; to VM Section
0090749B 68 747A4674 PUSH 0x74467A74 ; Key
009074A0 E9 C6EBFEFF JMP 008F606B ; VM_Entry[/FONT]
这里,要特别注意代码的地址所处的区段:00408BEF位于户代码段(Base: 00401000, Size: 001C7000);而0090749B位于目标的倒数第三个区段(Base: 005D3000, Size: 00485000),即要进VM了。
这个例子是RISC的,它是准备调用一个Winlicense SDK函数,当然这个SDK函数肯定是VM了的。另一种情况是用户代码有被保护的片段时,也是同样的跳转。被保护的系统API不属于此讨论范围。
不是随便在任何一个“JMP 目标地址”都可以使用插件的Unvirtualize命令的!比如,在地址009074A0处Unvirtualize,一定会报"machine signature not found";在地址00408BEF处就能成功Unvirtualizing。
从代码的特征,我们可以得到以下结论:
使用Unvirtualize命令那个JMP的目标地址一定是"PUSH KEY/JMP VM_Entry"指令序列,否则命令失败!
插件就是这样设计的,是它找VM入口(VM_Entry)的必要条件。
所以,"Find Referrences"命令在户代码段使用比较有意义!需要识别出JMP的目标地址是指向倒数第三个区段(VM区段)的才有效。
而如果在VM区段使用这个命令,就毫无意义了。原因在于,任何一个采用VM技术的产品,代码的乱序和膨胀是非常重要的一环,否则研究的人可以容易地,从而很快地搞懂你的VM结构。命令会找到无数多个用于乱序的JMPs。
这是最容易识别的,即你通常会看到一大片连续的PUSH/JMP序列。
还有一种隐蔽的情况,通常出现在Oreans的壳自身代码部分,到达OEP或Near OEP之前。即Oreans的某个功能模块结束,下一个模块的代码已经SMC完成,转向新的模块地址时,我称之为“转场”。
用户代码中,有关键代码被保护时,也可能出现这种情况。比如下面代码位于VM所在区段:
[FONT="Courier"]0084FC64 83EC 04 SUB ESP, 0x4
0084FC67 891424 MOV DWORD PTR [ESP], EDX
0084FC6A 89E2 MOV EDX, ESP
0084FC6C 81C2 04000000 ADD EDX, 0x4
0084FC72 83EA 04 SUB EDX, 0x4
0084FC75 871424 XCHG DWORD PTR [ESP], EDX
0084FC78 5C POP ESP
0084FC79 57 PUSH EDI
0084FC7A 893424 MOV DWORD PTR [ESP], ESI
0084FC7D 68 435C0000 PUSH 0x5C43
0084FC82 891C24 MOV DWORD PTR [ESP], EBX
0084FC85 53 PUSH EBX
0084FC86 68 6415E81D PUSH 0x1DE81564
0084FC8B 5B POP EBX
0084FC8C C1EB 04 SHR EBX, 0x4
0084FC8F 81C3 A406736D ADD EBX, 0x6D7306A4
0084FC95 53 PUSH EBX
0084FC96 812C24 35361274 SUB DWORD PTR [ESP], 0x74123635
0084FC9D 5E POP ESI
0084FC9E 81C6 35361274 ADD ESI, 0x74123635
0084FCA4 5B POP EBX
0084FCA5 68 75080000 PUSH 0x875
0084FCAA 892424 MOV DWORD PTR [ESP], ESP
0084FCAD 810424 04000000 ADD DWORD PTR [ESP], 0x4
0084FCB4 5B POP EBX
0084FCB5 56 PUSH ESI
0084FCB6 8F43 08 POP DWORD PTR [EBX+0x8]
0084FCB9 8B1C24 MOV EBX, DWORD PTR [ESP]
0084FCBC 50 PUSH EAX
0084FCBD 89E0 MOV EAX, ESP
0084FCBF 05 04000000 ADD EAX, 0x4
0084FCC4 05 04000000 ADD EAX, 0x4
0084FCC9 870424 XCHG DWORD PTR [ESP], EAX
0084FCCC 5C POP ESP
0084FCCD 8B3424 MOV ESI, DWORD PTR [ESP]
0084FCD0 52 PUSH EDX
0084FCD1 89E2 MOV EDX, ESP
0084FCD3 81C2 04000000 ADD EDX, 0x4
0084FCD9 83C2 04 ADD EDX, 0x4
0084FCDC 871424 XCHG DWORD PTR [ESP], EDX
0084FCDF 5C POP ESP
0084FCE0 E9 D182FFFF JMP 00847FB6 ; VM_Entry
Stack:
0012FF30 6F5187FA ; Key[/FONT]
某个JMP指令,或RET指令到本段代码的入口:0084FC64。到0084FCE0之前的代码属于膨胀变形,实际就一条指令"PUSH 6F5187FA"。
这时,显然Unvirtualize命令没法用。这里怎么让这个命令可用,留给大家去思考——很简单的!
简单说下VM_Entry的特征:
[FONT="Courier"]; RISC时
00847FB6 6A 00 PUSH 0x0 ; VM_ENTRY
...
00847FD6 9C PUSHFD
...
00847FF4 60 PUSHAD
...
013C6476 61 POPAD
013C6477 9D POPFD
013C6478 C3 RETN ; VM_EXIT
; CISC时
0041F7FC 9C PUSHFD ; VM_1_ENTRY
...
00420593 61 POPAD
00420594 E9 6BA20000 JMP 0042A804
...
0042A804 9D POPFD
0042A805 ^ E9 969EFFFF JMP 004246A0
...
004246A0 C3 RETN ; VM_1_EXIT[/FONT]
你会注意到,RISC一定以"PUSH 0"指令开始,接下来一定有个"PUSHFD";而CISC一定是直接从"PUSHFD"开始。
然后是"PUSHAD",但可能转变成好多条指令来完成,"PUSHAD"就消失了。
VM出口一定是"POPAD/POPFD/RETN",中间可能会有少量膨胀。
CISC的出口地址仅一个,而RISC可能有好几个。VM的出口可以通过VM的Busy寄存器清零来定位,Busy用于防止多线程时的重入(Thread-safe)。
这些东西看起来比较困难,其间夹杂着大量垃圾代码,参考插件生成的Risc_ZeroData.txt文件,比较清爽。
RISC和CISC相比,还有两点不同:1) 有Stack切换。2) VM_Context和RISC VM代码不在目标区段的内存段,所以在Dump一个TMD/WL RISC VM目标时需要补区段。
RISC的特征代码:
A.
add esp,1ffc 栈切换后指向栈顶,栈的地址空间也位于另一个单独的内存段。
B.
jmp dword ptr [edi+XXX] 到VM_Context段代码。
C.
jmp dword ptr [esi] 到RISC VM段。
印象中LCF-AT的脚本是用这些特征来区分CISC和RISC的。
最后,重要的区别在于Key。
有个32位常数,它是目标被保护时随机生成的,在一个已被保护的目标中是固定的,又不同于其它目标。softworm称其为DeltaOffset,Deathway叫做Align。
在Oreans很常见的寻址方式"
DWORD PTR [EBP+0xXXXXXXXX]"中,很容易在EBP找到这个值,如果对VM结构不熟悉的话。
在CISC中,PCODE的地址计算:
pPCODE = Key + DeltaOffset(Align)。
而在RISC中,Key为一个索引值,用它查表得到的值,加上DeltaOffset(Align)才得到pPCODE。
VM Handlers的入口地址,也是这样定位的。非常类似PE导入表(Import)的IAT,所以Deathway也把它称为CISC_Iat或RISC_Iat。
可见,Oreans的RISC实现远比CISC来得复杂。这也导致插件在处理RISC VM时,可能出现错误。
后来我发现是插件在处理"Zero area"的代码时,有些垃圾指令无法正确识别、过滤造成的。曾跟Deathway沟通过,那时他状态不好、很郁闷。现在的v1.8有改进,偶尔还是有问题。
我以前在论坛有两篇文章,也大致讲过插件怎么用,感兴趣的可找找看。