VMP学习笔记之反汇编引擎学习
2019-09-01 13:00:00 Author: mp.weixin.qq.com(查看原文) 阅读量:60 收藏

本文为看雪论坛优秀文章

看雪论坛作者ID:黑手鱼

说明:
0)我调试的是VMProtect1.21,系统是win7 32
1)将struct_VmFunctionAddr结构体称为需要二次解析的称为:特殊Opcode(SetDisassemblyFunction_Address函数填充)
2struct_DisassemblyFunction结构体称为:基 础  Opcode(Vmp_Disassembly填充)
3)壳模板代码和用户加密代码都是调用Vmp_AllDisassembly函数解析,只是保存的位置不一样而已
4)注意struct_DisassemblyFunction是按顺序存放的
例如:
push 1
push 2
push 3
那么push 1 肯定存放在Address[0]
那么push 2 肯定存放在Address[1]
那么push 3 肯定存放在Address[2]
>>>>

1、Vmp_AllDisassembly框架详解

总结:

1)核心部分在于Vmp_Disassembly函数,里面就是解析Opcode指令

2)部分Opcode需要二次处理

3)解析壳自身代码跟用户Opcode都是调用这个函数

>>>>

2、Vmp_Disassembly解析Opcode函数分析

随便拿条Opcode实例说明:

举例Opcode:0047497B 8B7424 28 mov esi,dword ptr ss:[esp+0x28]

(1)读取主操作码或则前缀
读取主操作码或则前缀,因为Prefix 与 Opcode 共同占用这个空间。
由于 x86/x64 是 CISC 架构,指令不定长。解码器解码的唯一途径就是按指令编码的序列进行解码,关键是第 1 字节是什么?如:遇到 66h,它就是 prefix,遇到 89h,它就是 Opcode。
a. GetSize_0函数
函数作用:用来区别读取字节长度
哪里找到赋值的?
Vmp_ReadPEInformation函数,我整个文件导了个遍都只发现赋值为1,没有2?
b. 三个比较重要的变量(legacy prefix 的作用)

UPX0:00481DDE;546:v529 = 0;
UPX0:00481DDE 094 C6 45 F5 00 mov [ebp+var_B], 0
UPX0:00481DE2 ;547:v531 = 0;
UPX0:00481DE2 094 C6 45 F7 00 mov [ebp+var_9], 0
UPX0:00481DE6 ;548:v530 = 0;
UPX0:00481DE6 094 C6 45 F6 00 mov [ebp+var_A], 0

它们在哪里赋值?
* v529 赋值的地方:
case 0x66u:// 指令前缀:66H—操作数大小重载前缀,也可被用作某些指令的强制性前缀。
v529 = 1;
v531 赋值的地方:
case 0x67u:// 指令前缀:67H—地址尺寸重载前缀
v531 = 1;
v530 赋值的地方:(大概猜测是Rex前缀,因为没有Magic=2)
REX前缀是16个编码操作码的集合,包含40H到4FH。这些操作码在IA-32模式和兼容模式中代表有意义的指令。在64位模式中,相同的操作码则代表REX前缀,不再当做单独的指令看待。
(2)根据switch执行不同的流程解析Opcode
a. 通过手册我们得知8B对应的是MOV r32,r/m32(Gv, Ev)
b. Register_Or_Memory = (v13 & 2) == 2这句代码是什么意思?(只针对我举例的,这里只是说明如何找)
我们翻看手册发现了规律是判断目标操作数:G 是寄存器或则E 是寄存器或者内存操作数。
Gv, Ev 表示:
(1)两个 Operands 分别是:目标操作数 Gv,源操作数 Ev 或说:frist operand 是 Gv, second operand 是 Ev
(2)Gv 表示:G 是寄存器操作数,v 是表示操作数大小依赖于指令的 Effective Operand-Size,可以是 16 位,32 位以及 64 位。
(3)Ev 表示:E 是寄存器或者内存操作数,具体要依赖于 ModRM.r/m,操作数大小和 G 一致。
4 个字符便可以很直观的表示出:操作数的个数以及寻址方式,更重要的信息是这个 Opcode 的操作数需要 ModRM 进行寻址。
举例子说明:

0047497B 8B7424 28 mov esi,dword ptr ss:[esp+0x28]
0047497B 8A7424 28 mov dh,byte ptr ss:[esp+0x28]

0047497B 887424 28 mov byte ptr ss:[esp+0x28],dh
0047497B 897424 28 mov dword ptr ss:[esp+0x28],esi

v14 =88 Register_Or_Memory =0
v14 =89 Register_Or_Memory =0
v14 =8a Register_Or_Memory =1
v14 =8b Register_Or_Memory =1


总结:
1)这句代码是判断目标操作数是:G 是寄存器或则E 是寄存器或者内存操作数
2)Ev是包含不确定性具体要依赖于 ModRM.r/m
c. 通过上文描述就可以解释作者为何设计成要区分Register_Or_Memory来区分先执行SetReg跟ModRm
因为假设是Mov Gv,Ev这种类型的:目标操作数是确定Gv,但是源操作数是Ev是包含不确定性具体要依赖于 ModRM.r/m。
我们举例的很明显就是MOV r32,r/m32(Gv, Ev),目标已知,源带有未知性。
d. 我们先来分析Decode_SetReg函数
一共有3组,每组0x17个字节,包含结尾表示0xFFFFFFFF,这些都是保存目标操作数或则源操作数信息的
v6 = (v9 >> 3) & 7;
首先v9=0x74,继续我们的查表
转换成二进制如下:
0x74=‭‭01 110 100‬
很明显v6=ModRM.reg(Esi)
e. 分析Decode_ModRM结构:
i. 首先解析ModRm判断寻址模式:
先将ModRM转换下
转换成二进制如下:0x74=‭‭01 110 100‬
0x40=01,0x80=10,0xC0=11以此类推
ii判断是否需要SIB寻址方式
R/M==8(100),只有ModRM.mod寻址模式是11(寄存器是不带SIB的)如上图所示
第一种这里是ModRM.mod 提供寻址模式: 11 = register(寄存器)
直接保存ModRM.r/m
第二种情况存在SIB寻址方式
根据上文找到的地址发现是[--][--]”,表示有SIB表
SIB结构如下:
转换成2进制如下:
0x24=‭00101100‬
[1]读取SIB字节
[2]((unsigned __int8)v10 >> 3) & 7; // SIB.index 提供 index 寄存器寻址
[3]v9->SIB_base = SIB & 7; // SIB.base 提供 base 寄存器寻址
[4]if ( v9->ModRM_Reg_Or_SIB_index_Or_ModRM_rm == 4 )// SIB.index 提供 index 寄存器寻址是否是none 4 = (100)
[5]假设index寄存器!=4就保存SIB.scale 提供 index 寄存器乘数因子 scale
[6]判断SIB.base 提供 base 寄存器寻址是否是[*] 5 = (101)

最后读取Displacement_Immediate
还有一些Opcode需要ModRM进行补充的
单纯的一个FF无法表达它到底是CALL、INC、jmp、push需要ModRm辅助的,具体看ModRm.Reg
Vmp_Disassembly使用的结构体如下:
所有解析后的数据都保存在struc_SaveAllDisasmFunData结构里面
>>>>

3、特殊Opcode解析

一般例如jmp call都是需要二次解析的
随便拿条命令举例说明:
jmp dword ptr ds:[eax*4+0x474FCF] 这一条命令就是跳到不同的Handle块执行
jmp它Opcode是FF所以对应的VMOpcode=0xC
根据跳转类型判断,判断E8 E9 EA 近 段间 短跳转
例如像那种:jmp VMDispatcher就会符合条件
我们这个是else if ( DisassemblyFunction->Magic == 2 )
首先解析sub_4918E8函数
看一看sub_494F60函数
[1]保存VmOpcode信息跟Displacement
[2]根据大小读取
设置ModRM信息
v5->First.ModRM_mod__Or__Size = 2;
jmp dword ptr ds:[eax*4+0x474FCF] 就是这种寻址方式
SetDisassemblyFunction_Address函数解析:

if ( v8->VMOpcode == 1 ||(result = v8->VMOpcode - 3, v8->VMOpcode == 3) )成立条件
v533->VMOpcode=1
case 0xA8
case 0x16u:
case 0x1Eu:
case 0x50~0x57
case 0x68u:
等等
v533->VMOpcode=3
case 0xC7u:
case 0x20
case 0x22
等等

SetDisassemblyFunction_Address函数涉及到的结构体如下:
执行依次SetDisassemblyFunction_Address标记结尾
注意这里参数2就是:0xC
>>>>

4、历史遗留问题


1)保存的所有struct_DisassemblyFunction(基础)、struct_VmFunctionAddr(特殊)该如何使用,这个留到后面揭晓
2)v530 赋值的地方:(大概猜测是Rex前缀,因为没有Magic=2)
3)Magic==2满足条件(这个我没仔细跟)
参考资料

1、名称:谈谈vmp的还原(1)
网址:https://bbs.pediy.com/thread-225278.htm
2、名称:汇编指令之OpCode快速入门
网址:https://bbs.pediy.com/thread-113402.htm
3、名称:X86指令编码内幕 --- 指令 Opcode 码
网址:https://blog.csdn.net/xfcyhuang/article/details/6230542

- End -

看雪ID:黑手鱼  

https://bbs.pediy.com/user-585205.htm 

*本文由看雪论坛 黑手鱼 原创,转载请注明来自看雪社区

推荐文章++++

*Windows Search远程代码执行漏洞简单分析及流量侧检测防御思路

*Windows RDP再曝远程代码执行漏洞,几乎通杀所有受支持的Windows系统

*Talosec硬件钱包的侧信道攻击测试分析 Part1

Linux Kernel Exploit 内核漏洞学习(2)-ROP

HW行动 rdpscan后门简单分析

进阶安全圈,不得不读的一本书


“阅读原文”一起来充电吧!

文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458298632&idx=3&sn=869e500e62e989b7da2b7fcec0fcb3f3&chksm=b181998286f610944d24690fc2fb6a0d07ffadecc4c774c67fee67638744b86850fb9a06b0d6#rd
如有侵权请联系:admin#unsafe.sh