论坛首页 逆向工程技术区 阅读主题

[原创]Android-ARM64的VMP分析和还原

365 浏览 14 回复
#1 楼主 2026-06-01 21:09:11
[原创]OLLVM扁平化还原—新角度:状态机里面分享了代码保护之一的控制流扁平化的还原思路。控制流扁平化,实际上就是对基本块之间的调用关系进行调整,但基本块的内部保持不变。而更严格的代码保护,是对基本块内部的指令进行保护,而这篇文章分享的是指令级别的代码保护(VMP)对应的分析和还原。  样本来自金罡大佬的https://bbs.kanxue.com/thread-282300.htm,里面是对某个大厂so的详细分析。金罡大佬是基于汇编直接进行分析,给出了3个虚拟指令的分析过程,并且贴了最终的结果。这篇文章更多是聊补阙漏,结合操作系统的知识,试着推测出VMP逆向分析的思路和VMP正向设计的理念。  以前分析PC的VMProtect,是用Ollydbg跟踪trace出汇编指令,然后再写脚本去解析导出的文本内容。这个样本的so比较简单,可以在IDA上直接F5转成伪代码。所以除了基于汇编分析,还结合了IDA的伪代码和Microcode,让分析和还原的过程更加便捷和易懂。

  逆向常说的VMP,一般是指PC上的VMProtect工具;但更广泛的意思是Virtual Machine Protection(虚拟机保护);而更准确的说法应该是Virtualization-based Protection(基于虚拟化的代码保护)。本质上,VMP就是把真实代码变成虚拟代码+虚拟机器,从而实现代码保护的目的。  在样本里面对应:  以加法指令做例子,格式如下:  真实指令的格式:  可以看出:真实指令的硬编码是把32bit切割成多个字段,每个字段代表不同的含义
  这里只是简单做演示,具体细节可以看ARM64的指令编码规则:c71K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2S2M7X3#2Q4x3X3g2U0L8$3#2Q4x3V1k6V1L8$3y4#2L8h3g2F1N6r3q4@1K9h3!0F1i4K6u0r3k6r3c8A6x3o6j5H3x3W2)9J5c8U0t1H3x3U0g2Q4x3X3b7H3z5g2)9J5c8V1u0S2M7$3g2Q4x3X3c8u0L8Y4y4@1M7Y4g2U0N6r3W2G2L8Y4y4Q4x3@1k6D9j5h3&6Y4i4K6y4p5k6h3^5`.

  虚拟指令的格式:
  相对应的:虚拟指令的硬编码也可以对32bit重新切割成多个字段,并且赋予不同的含义  如果想知道真实指令是怎样通过写代码转化成虚拟指令,可以参考开源的xVMP:db5K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6s2b7f1&6s2c8e0j5$3y4W2)9J5c8Y4S2h3e0g2l9`.

  ARM64 遵循 RISC(精简指令集)原则:  换句话说,真实机器需要提供寄存器,方便真实指令进行计算处理。  此外,真实指令在真实机器上的运行流程如下:  对应的,虚拟机器也需要提供虚拟寄存器:  虚拟代码在虚拟机器上的运行过程和真实代码在真实机器上的运行过程也类似,4个步骤在虚拟机器里面对应如下:  以上是偏理论的讲解,对VMP有一个简单的了解之后,接下来就是看看虚拟机器对虚拟代码的具体运行过程:  先从调用虚拟机器开始说起(1)虚拟机器(VitrualMachine): 0x2C18
  vPC指向虚拟代码的指针,就是虚拟的指令寄存器(也就是vIP)(2)虚拟代码(VitrualCodes): dword_B090
  这里的虚拟代码是32bit的虚拟硬编码  X0是第一个参数vPC
  取指就是从vPC取出虚拟指令:vInsn  取出vInsn之后,就开始解析翻译
  具体来说就是对32位的数据进行切分  译码对应的汇编:  寄存器映射关系:  切分的示意图如下:  根据这个切分规则,可以写出对应的切分脚本:  指令切分得到:操作码和操作数,执行就是根据操作码的含义,对操作数进行处理。换句话说,执行需要对操作码进行分发处理:  基于汇编,可以写出跳转表的脚本:  因为编译器优化会把类似的操作聚合在一起,导致多个case可能对应同一个地址:  在ida的伪代码里面就很明显:
  虽然基于汇编的脚本可以继续优化得到类似的输出,但更舒服的方式是基于Microcode,因为Microcode已经对跳转表进行解析和统计:  基于Microcode的脚本:  也正因为编译器优化聚合,导致有差异的操作在聚合之后,需要再次进行分发:
我们是要分析同一条虚拟指令完整的执行过程,对应有3个思路:
  1、如果基于ida的伪代码,直接肉眼去跟进,但函数级别比较大容易眼花缭乱;
  2、也可以trace得到真实执行的汇编指令列表,然后挨条指令去跟进,但汇编级别的工作量又会比较多;
  3、除此之外,还可以按照基本块级别进行分析,工作量就适中了。具体来说就是做一个Microcode模拟器,模拟Microcode的执行过程,从而得到基本块的跳转关系。具体可以参考D-810的Microcode模拟器:643K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8D9j5h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6W2M7$3S2S2M7X3c8Q4x3V1k6V1z5o6p5H3i4K6u0r3i4K6u0V1i4K6u0r3j5X3I4G2j5W2)9J5c8X3#2S2M7%4c8W2M7W2)9J5c8X3b7^5x3e0m8Q4x3V1k6W2L8i4g2D9j5i4c8G2M7W2)9J5k6i4m8&6

  这里拿第1条虚拟指令的虚拟操作码:21(0x15)  前面提到ARM是基于寄存器进行运算,这就意味者:  从而可以进一步推测出:  虚拟操作码对应的格式就是:  代入具体的数据就可以得到第1条虚拟指令的含义:  虚拟指令执行完,接下来就是更新:  可以看出:vPC通过加4进行更新,也就是按顺序执行。但也有可能是跳转执行,甚至是调用外部函数。所以接下来就是处理跳转执行和调用外部函数:  PCUpdate(更新)之后,又继续下一轮的Fetch(取指),有一个细节需要关注一下:  这就意味着,某条指令修改flag == 2,继续执行下一条指令后
  在下一条指令的开头:因为flag == 2,修改flag = 3
  在下一条指令的结束:因为flag == 3,进行跳转

  举个例子,按虚拟代码的顺序,应该是先00000078,再0000007C
  但虚拟机器有了这个特殊的调度流程,实际上效果变成:先0000007C,再00000078  在前面提到虚拟机器需要提供虚拟寄存器,方便虚拟指令进行运算。从虚拟机器的运行过程,我们也能得知虚拟机器依赖一些特殊变量充当特殊角色。而虚拟机器的状态VMState就是包含这些虚拟寄存器和特殊的变量,为虚拟指令提供可以使用的上下文环境。  在上面的分析过程中已经知道:  从vX0可以推测出其它通用寄

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-288731.htm
#2 2026-06-01 21:09:11
现在Vmprotect最新的3.10.1好像已经支持安卓了,官网好像有demo版可以下
#3 2026-06-01 21:09:11
学这么快?
#4 2026-06-01 21:09:11
EX呵呵


现在Vmprotect最新的3.10.1好像已经支持安卓了,官网好像有demo版可以下

是的,10月1号更新了
#5 2026-06-01 21:09:11
didiid


学这么快?

站在大佬的jb上
#6 2026-06-01 21:09:11
过来瞧瞧
#7 2026-06-01 21:09:11
过来瞧瞧
#8 2026-06-01 21:09:11
过来瞧瞧
#9 2026-06-01 21:09:11
学就是了
#10 2026-06-01 21:09:11
学习
#11 2026-06-01 21:09:11
向大佬学习
#12 2026-06-01 21:09:11
好看, 爱看
#13 2026-06-01 21:09:11
学习一下
#14 2026-06-01 21:09:11
干货十足!从指令虚拟化到状态机设计都讲得很清楚,对逆向和加固开发都很有帮助!为方便大家理解文章脉络,我已将核心知识点整理成结构化思维导图。

最后于 2025-10-11 10:47
被mb_cizqyhuh编辑

,原因:
#15 2026-06-01 21:09:11
感谢分享

请登录后参与讨论

立即登录 注册账号