VMP学习笔记之Handle块优化与壳模板初始化(四)

参考资料:

本文大量内容抄袭看雪作者:waiWH的VMP系列

1、名称:谈谈vmp的还原(2)

网址:[原创]谈谈vmp的还原(1)-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

2、名称:汇编指令之OpCode快速入门

网址:[原创]汇编指令之OpCode快速入门-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

3、名称:X86指令编码内幕 --- 指令 Opcode 码

网址:X86指令编码内幕 --- 指令 Opcode 码_晓风残月的技术天地-CSDN博客

说明:

1、流程 == 加密函数个数(一般demo版本只允许加密一个流程)

 2、用户代码跟壳自身代码(模板)都会用Vmp_AllDisassembly函数解析

 2、1 保存用户代码结构体

它们0x8跟0x10都是指向相同的结构体

 2、2 保存壳自身模板结构体

它们0x8跟0x10都是指向相同的结构体

 3、我比较了下1.10跟1.21,发现1.21自带Encoding of a p-code保护

1.10

 1.21

 未加密前的一般长这样子(1.10未启用加密的):

 加密后(1.21自带加密):

主要干了什么?

1、初始化壳模板:指令变形、等价替换

例如:

jmp = push + retn 或则 lea + jmp

lods byte ptr ds:[esi] = mov al,[esi] + inc esi 或则 mov al,[esi] + add esi,1

等等

2、优化Handle块代码,将不使用的直接删除

ESIResults[X] == 0表示不使用,这种就会优化

ESIResults[X] == 1表示使用

3、找出填充虚拟机上下文的两个Handle块

正文:

1、找出壳模板push 0xFACE0002与mov edi,0xFACE0003

总结:

1、循环遍历作者设计的Handle块找到符合条件的例如:

规律if(v227 & 0xFFFFFF00) == 0xFACE0000

执行前转换执行后
push 0xFACE0002------->Push 重定位值
mov edi,0xFACE0003------->Mov edi,VMContext

2、所使用的结构体如下:

2、根据pNtHeader_OptionalHeader.Magic筛选ESI_Matching_Array数组

 首先我们得到的信息有:

1、ESI_Matching_Array每一组是8个字节,一共有0Xcc组,也就是总长度是0x660=8*0xCC

目前已知:

ESI_Matching_Array[0]  == 与Magic有关

ESI_Matching_Array[1]  == ??

ESI_Matching_Array[2]  == VMOpcode

ESI_Matching_Array[3]  == VMOpcode

ESI_Matching_Array[4]  == 模式

ESI_Matching_Array[5]  == Size

ESI_Matching_Array[6]  == ??

ESI_Matching_Array[7]  == 未使用,都是0

2、v184数组保存所有结果0或则1

3、第一个判断:v16 = _bittest(v15, Type & 0x7F)成立条件

我们查询汇编指令得知BT是位测试

目前只讨论win32PE结构,Type都是=1

我们发现*(byte*)(ESI_Matching_Array+0)的值只有6(‭0110‬)或则4(‭0100‬)

6=0110     bt 1      第一位的值给CF,就是1,不成立继续执行第二个判断

 4=0100     bt 1      第一位的值给CF,就是0,成立v17=0,不继续执行第二个判断

4、总结:

1、v184保存结果的什么时候使用、区别这个有什么意义?1跟0有什么区别?

答:

1表示使用

0表示未使用(后期优化掉)

 

 2、ESI_Matching_Array与VmHandle块对应(我整理了一份,未必全对的只作为参考)

 3、判断用户解析Opcode有没有需要特殊处理的指令

这些都是一些不常用的指令,如果存在就在ESIResults[X]=1,表示使用

假设找到的话执行Vmp_GetVmHandleIndex函数 

v184==ESIResults就是我们前面筛选的,如果有就在指定位置+1,表示使用

4、将Jmp Handle跟Jmp VMDispatcher分别存储 

4、1 Jmp Handle处理方法

首先来看ESI_Matching_Array[0]==6,v52成立的条件

 

 然后拿v214->FunAddr去struc_SaveAllDisasmFunData数组里面找到符合

DateTimeToStr_2函数查找过程,查找到返回该数组下标,然后用GetItem_7读取出该数组:

条件是:v214->FunAddr == struct_DisassemblyFunction->LODWORD_VMP_Address

基础版(struc_SaveAllDisasmFunData):

然后判断ESIResults[Number_1]==1,如果成立将找到的DisassemblyFunction结构体保存起来

 最后将该找到的DisassemblyFunction结构保存到v7->Esi_Addr[4 * Number_1]

 4、2 Jmp VMDispatcher处理方法

1、直接去struc_SaveAllDisasmFunData数组查找,找到直接保存

2、与Jmp Handle相比少了ESIResults[Number_1]过滤跟将结果保存到*(_DWORD *)&v7->Esi_Addr[4 * Number_1]

 4、3 执行完毕结果

IDA定义的结构体:

 OD显示:

4、4 Jmp VMDispatcher与Jmp Handle的含义是什么意思?

如图所示:

Jmp VMDispatcher就是:

注意看jmp short 00474989这一句,每个Handle块执行完毕都是跳回到VMDispatcher进行下一轮字节解析

Jmp Handle就是:

VmpHandle块,每个Handle对应不同的功能

5、根据前面符合Jmp Handle满足条件的Number_1作为循环因子 

1、经过前面筛选Number_1=0XCC,一般HandleX与ESI_Matching_Array都是一一对应,大小都是0xCC 

2、只是设置的基本的Mod信息跟VmpOpcode=0x23

3、new出来的struct_VmFunctionAddr结构只是设置了助记符=0xB

4、强行扩充到0xFF大小,不足的new struc_SavePartDisasmFunData和struct_VmFunctionAddr结构,具体作用不明

5、扩充前后对比

未扩充前:

基础大小:0x312

基础数组:

 特殊大小:0x177

特殊数组:

 扩充后(多了0x34个组):

基础大小:0x346

基础数组:

 特殊大小:0x1AB

特殊数组:

6、将不符合条件的struc_SaveAllDisasmFunData和struc_SavePartDisasmFunData1从数组中删除

1、专门找ESIResults[X] == 0的

2、ESIResults[X]与v7->Esi_Addr[4 * X]一一对应

3、找到VmpOpcode值是:0~9、0xC则退出,符合条件的基本上是:Jmp VMDispatcher找到后把该数组元素删除

4、清零v7->Esi_Addr[4 * X] = 0

5、看了一圈基本上是把整个HandleX解析信息的都删除,jmp XXXX标志结束

6、未被删除的

 

 总结:

1、ESIResults[X]与v7->Esi_Addr[4 * X]一一对应

2、ESIResults[X]==0,那么取对应的v7->Esi_Addr[4 * X]数组内容(struc_SaveAllDisasmFunData结构体)

3、struc_SaveAllDisasmFunData与struc_SavePartDisasmFunData1数组里删除该HandleX信息

4、判断到jmp XXXX为结束点,也就是整个Handle解析的信息都清除掉

 5、ESIResults[X]==0就是不使用的了

疑问:

1、这个不删除会如何?删除会如何?后面待分晓

7、随机数填充struct_VmpOpcodePY_80结构 

 sub_49FB90函数分析:

 sub_49F958函数分析:

 1、通过随机数取word_4EE0D8数组的下标,符合条件的跳到赋值的地方

2、退出条件是:要Add添加几组元素由Constant(参数2)决定,外加一句RandInt(1),百分之50%几率再来一次

 3、它们使用的结构如下:

 总结:

0、变形总结对照

RandomWord_4EE0EC是对add al,bl的变形

RandomWord_4EE0D8是对add bl,al的变形

 1、填充这些数据到底怎么使用?

2、struc_47数据使用

我们发现执行完毕后一共有6组

 第一组:

struc_47->RandomWord_4EE0D8=0x29 ->inc

struc_47->AddrRandomBuff=0x1

第二组:

struc_47->RandomWord_4EE0D8=0x43 ->rol

struc_47->AddrRandomBuff=0x5

第三组:

struc_47->RandomWord_4EE0D8=0x5C ->not

struc_47->AddrRandomBuff=0x5

第四组:

struc_47->RandomWord_4EE0D8=0x34 ->sub

struc_47->AddrRandomBuff=0xB0

第五组:

struc_47->RandomWord_4EE0D8=0x5C ->not

struc_47->AddrRandomBuff=0x0

第六组:

struc_47->RandomWord_4EE0D8=0x05 ->xor

struc_47->AddrRandomBuff=0x7A

刚好对应以下6句,因为1、3、5是单操作数所以struc_47->AddrRandomBuff不使用

 3、struct_VmpOpcodePY_80->RandomWord_4EE0EC使用

第一种RandomWord_4EE0EC=0x4,注意看405069跟40507B这两句是add

 第二种RandomWord_4EE0EC=0x34,注意看405069跟40507B这两句是sub

 第三种RandomWord_4EE0EC=0x5,注意看405069跟40507B这两句是xor

8、使用struct_VmpOpcodePY_80~A0结构

 1、目前发现符合if条件的只有register寻址方式的并且是add aXX,BXX这种,每次都是两条组合出现

 2、通过i来区分到底取struct_VmpOpcodePY_80、struct_VmpOpcodePY_84~90、struct_VmpOpcodePY_94~A0其中一组

3、判断v227->RandomWord_4EE0EC!=4

 第一种:RandomWord_4EE0EC!=4执行流程

 第二种:RandomWord_4EE0EC==4执行流程

 1、注意v277的值是struct_VmpOpcodePY_80~A0其中一组内容

2、根据随机值来执行不同的流程填充struct_DisassemblyFunction结构

3、针对第一种RandomWord_4EE0EC!=4执行流程主要是修改add aXX,BXX变成xor或则sub

 4、针对第二种RandomWord_4EE0EC==4将第二句add bXX,aXX进行变形

8、1 SetDisassemblyFunction函数分析

如果说Vmp_Disassembly函数是将Opcode解析

那么SetDisassemblyFunction就是将解析后的Opcode再重新组装回去

 判断是否存在前缀

 根据前面Opcode选择读取对应主操作码,假设该Opcode操作码需要依赖Mod寻址就执行sub_49DFD0

 根据ModRm_Mod寻址方式判断,从而构造不同的指令

 举例子说明:

VmpOpcode=0x29

 执行完毕后,指令就构造完毕:FE C0 对应汇编代码:inc al

 

 总结:

1、设置struct_DisassemblyFunction的内容

2、用struct_DisassemblyFunction 提供的Opcode信息还原回一条完整的汇编指令

8、2 总结:

0、第一次执行才使用struct_VmpOpcodePY_80,非第一次都是使用struct_VmpOpcodePY_84~90或则struct_VmpOpcodePY_94~A0

1、针对壳模板的add指令进行修改变形处理

符合条件的如下:

 变形成

9、保存寄存器环境的代码,注意后面会随机乱序的

原因:

为什么壳起始代码 push环境每次都是乱序的?如何实现的?

 对应代码如下:

 8个对应8个保存环境的push xxx

笔者为了方便测试所以全写成0,可见0 == push eax跟OD通用寄存器对应顺序一样

 乱序代码:

只要打乱数组存放顺序就可以实现乱序了

 它们保存在:

struct_VmpOpcode->struc_PushRegister指向的结构体

10、找到lods byte ptr ds:[esi]并保存起来

10、1 构造出PushRegister那几条指令

1、将不符合条件的全部删除,直到找到push 0xFACE0002这条为止

2、因为Vmp保存寄存器环境代码是随机性的,原始壳模板的是固定的所以要替换掉

 3、因为 pushfd pushad模板后面必然是push 0xFACE0002

 4、根据寄存器不同而设置不同的VmpOpcode,进行构造填充struct_DisassemblyFunction结构

5、返回:lods byte ptr ds:[esi]在数组第几个元素

 OD最终效果图如下:

 OD数组struc_SaveAllDisasmFunData->ArrayAddress排列顺序如下:

10、2 现在该处理lods byte ptr ds:[esi]指令了

0、lods byte ptr ds:[esi]指令介绍:

指令规定源操作数为(DS:SI),目的操作数隐含为AL(字节)或AX(字)寄存器。三种指令都用于将目的操作数的内容取到AL或AX寄存器,字节还是字操作由寻址方式确定,并根据寻址方式自动修改SI的内容。

一句指令相当于以下两句:

mov al,[esi]

inc esi

1、初始化v245跟v246数组,具体用处待定

 2、找到处理的地方Vmp == 0x36

3、struct_DisassemblyFunction结构重新赋值

 4、找到该struct_DisassemblyFunction所在的数组位置

5、并重新new个新的struct_DisassemblyFunction

6、根据随机数构造命令:INC、Add、lea,实际上只要实现esi+1都行

 7、OD最终效果图:

 8、原始模板的

 9、前面的构造出了inc esi(add lea),那么还差一句mov al,[esi]

10、注意v158 = GetRandInt0123((int)&savedregs);这一句是随机获取0~3,也就是Reg:0=al、1=cl、2=dl、3=bl

 11、注意这一句跟后面的指令都是有关联的,换了后面影响的指令都要换不同的Reg

10、3 处理Jmp Ret指令

1、通过随机数决定jmp ret指令是变换成:

随机数==2

lea exx,dword ptr ds:[eax*4+0x474FCF]

jmp [exx]

随机数==1

push dword ptr ds:[eax*4+0x4051BB]

retn

2、注意v238 = GetRandInt0123((int)&savedregs);这一句,表示它的Mod.Reg寄存器是随机的0~3

10、4 处理Handle里面的Vmp_Ret函数

0、跟前面一样,将popad复杂化,变成pop eax、pop ecx等等

1、ESI_Matching_Array[2] == VMOpcode,符合条件的是:Vmp_Ret指令(pop xx popad popfd这种)

,{ 0x06,0x01,0x09,0x00,0x00,0x02,0x00,0x00, }

//00474FCB 58 pop eax; 123.0047499B

//00474FCC 61 popad

//00474FCD 9D popfd

,{ 0x06,0x00,0x08,0x00,0x00,0x02,0x01,0x00, }

//00474FC7 58 pop eax; 123.0047499B

//00474FC8 61 popad

//00474FC9 9D popfd

2、将popad跟popfd删除,直到遍历到ret就退出

 3、将前面v7->struc_PushRegister保存的寄存器递减方式存储,注意去掉Esp寄存器

 4、总结:

原始的:

pop eax

popad

popfd

ret

修改成:

pop eax

pop xx

pop xx

pop xx

xxxxx

ret

11、找到填充虚拟机上下文的Handle块

1、根据GetSize的返回值填充v223数组

 2、根据大小跟助记符再过滤一遍Handle块,将符合条件的下标保存起来

 3、符合条件的有2处(填充虚拟机上下文的Handle块和还原真实堆栈的Handle块):

{ 0x06,0x01,0x01,0x00,0x02,0x02,0x00,0x00, }

//0047499B 80E0 3C and al,0x3C

//0047499E FF3407 push dword ptr ds : [edi + eax]

,{ 0x06,0x01,0x02,0x00,0x02,0x02,0x00,0x00, }

//00474AC3 80E0 3C and al, 0x3C

//00474AC6 8F0407 pop dword ptr ds : [edi + eax]; 123.0047499B

4、未初始化的地方填充随机数

 5、效果图:

实际有用的只有22跟0,其他都是随机数填充的

12、总结

1、其实这部分代码都是针对部分特殊指令进行变形替换

例如:jmp可以变成 jmp+ret

例如:lods byte ptr ds:[esi]可以变成 mov aXX,[ESI] INC esi 等等

2、涉及重定位的代码还是没有修复

例如:

push 0xFACE0002

mov edi,0xFACE0003

jmp dword ptr ds:[eax*4+0x474FCF]

jmp short 00474984

3、找到填充虚拟机上下文的两个Handle块

后续介绍:

1、剩下重定位修复

2、伪代码构造

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值