VMP 在进行 RSA 解密时,会在堆上专门开辟并规划出一块内存区域(我称其为“VM运算区”)。在这个VM运算区内,VMP 确实会把明文 n 的片段一个个地、位置并不明确地、写入内存。但对于一个完全黑盒的程序而言,当我们找到了一些疑似写入明文 n 片段地位置并试图记录这些片段时会发现:即使我们通过硬件断点或日志追踪,捕获了目标地址所有 DWORD/QWORD (32/64位) 的写入操作,保存下来的日志依然犹如天书。因为在这个 VM 运算区里面,伴随着真实 n 片段的写入,VMP 还会插入大量的垃圾指令和虚假写入。在我们完全不知道原始明文 n 的任何一个片段的情况下,面对日志里成千上万次真假难辨的内存操作,想要从中精准挑出真正的 n 的片段并拼凑完整,无异于大海捞针。
既然在黑盒环境下死磕模数 n 是一条看不到尽头的死胡同,那我们不妨跳出底层虚拟机指令的泥潭,重新审视 RSA 算法本身的数学根基。
回顾 RSA 的核心解密方程,一直以来,几乎所有的攻防焦点都集中在 n 身上,逆向者们绞尽脑汁想要替换它,却往往忽略了公式中另一个至关重要的参数——公钥指数 e。
VMP 无论有多厉害,它的虚拟化和混淆无论有多抽象,它都不可能颠覆冯诺依曼架构,更不可能打破物理 CPU 运算的客观规律。
既然明确了在几乎全段 VM 状态下,它不可能直接使用原生真实的 push 和 mov 暴露意图,那么它的虚拟化抽象指令就必然要在内部模拟数据流转的过程。而无论这个过程被伪装得多复杂,当它真正要将数据送入底层运算单元时,那些关键参数(指针、长度)就必然要在物理内存中“实体化”,其数据结构的内存布局也必然会暴露出现实世界中标准密码学组件的痕迹。那么此时,我们就可以在程序的 VM 内存工作区中,直接利用这些客观存在的物理特征,跨过指令的迷宫,直接搜索出它喂给解密函数的参数,其中就必然包含那个静态的 e 的相对位置。