PHP加密中的“VMProtect”——魔方加密反编译过程

这篇文章中的东西我研究了大概两天,但愿点进来的你能在十几分钟之内看懂。

(看不懂就算了,真的没什么大用,我就是发出来装逼的)

另一篇 PHP 解密分析:【原创】某PHP加密文件调试解密过程

本文部分内容在上一篇文章中提到过,这里只做简要说明。

样本

文件样本由上一篇分析文章的 70# 层 提供。

下文称这个编译方法为

静静地看着魔方加密吹牛逼

魔方加密的编译系统有这些特点:

自主开发

有完整的中间表示设计(中间指令和抽象语法树两种形式)

有完整的中间表示到目标 PHP 代码的映射规则

有与目标代码配套的虚拟机设计,包括虚拟机结构、与 PHP 环境的交互方法

魔方加密的程序不对外公开,随时保持更新,跟进最新的学术研究成果,在根本上杜绝了潜在攻击者的试探、分析等行为,保证了核心算法的安全,也保证了加密代码的安全。

说明

本文涉及大量汇编语言及反编译,需要有一定基础。而且学这个东西真的还不一定有用。

开始破解

格式化代码

我用的是 的 AST 分析器进行的代码格式化,因为我发现这个库对乱码变量名的支持很好。

分析代码

代码最前面是一堆拥有同样形式的函数,都是 7 个输入变量,都是以引用的方式传递参数。

后面是一个函数,包含一个静态变量,这个静态变量等于一个超长的字符串,一个由一堆字符串使用 点符号连接起来的超长的字符串。

最后面是一句调用函数。

然后进行单步调试,跟踪一会就回发现,这特么不是一个虚拟机的形式吗!以下为了方便描述,我就使用 x86 指令集的某些描述方式了(只研究过一些 x86 指令集)。

初步分析结果

经过一段时间的调试,我们大概弄明白了最后一个函数类似于 指令,其他的每个函数的 7 个参数分别为:数据内存、指令指针、栈、栈指针、基址指针、报错等级栈、报错等级栈指针。

将变量名替换成有意义的名称之后的代码如下

修复局部变量名

经过观察发现,这个加密算法的函数中只有局部变量,所以我们可以轻松地进行变量名替换,而不会影响函数执行结果。

我这里依旧使用 先进行语法分析,然后再替换变量名,最后格式化输出。

在解析代码与格式化输出之间添加如下代码

修复变量名之后之后我们继续调试。

调试的过程中,可以看出这套指令集中,数据与指令是混在一起的,并不是 与 分开的,或者说使用了大量的立即数。

在所有的函数调用、、 或 处下断点,单步调试看看到底是什么逻辑。

然后单步跟踪一会就回发现每个函数的作用,所有的函数都是通过栈来进行数据交换的。

有的函数负责申请栈空间,有的负责清除栈空间,有的函数负责跳转 、 之类的,有的负责函数调用,总之前面的 65 个函数可以称之为 的指令集。

我们要破解这个文件必须把他的指令集中每一条指令都分析一下,然后对他的 VM 中间函数进行 Hook 操作,提取出关键 Opcode,然后根据 Opcode 对应的操作还原出原始代码,这个过程和 IDA 的还原代码很像,这个过程靠的是脑子和经验,但是最费的还是体力。

我以前没学过虚拟机的原理,破解这个的过程真的学到了很多。

提取原始 Opcode

先把 变量输出出来,免得原来的文件看起来费劲。

在 赋值语句之后插入

执行一次,之后改成

函数重命名

为了消除程序乱码,我想了一个方法,就是把所有的函数名称改成 之类的名字,然后动态地把乱码函数名代理到我们替换之后的函数。

这样做之后有几个好处,我可以随意地修改程序,而不用担心编码错误。因为有代理这一层,我可以随意 Hook 其中的步骤。

我又重新修改了我的 ,不过试了一下感觉效果并不是特别好,所以这个暂时就先放弃(不过之后肯定还是要把 Opcode 翻译成汇编语言的)。

继续调试

上面的图中就是我在分析每一个函数的作用,把他们写成汇编语言,顺便分析函数调用的规则,方便我们写 (是反编译器,而不是 反汇编器)

举个例子

这个汇编语言是我自己随便发明的语法, 表面后面的 的整数值占 位数(十进制)。

最前面的数值是十进制,表示指令开始位置 表示指令占 6 字节(这 字节包含指令调用的函数名和指令参数相对位置),根据指令循环的代码, 是异或加密的秘钥 是指令调用的函数名(以后就称之为指令名吧), 是本条指令的操作数位置或下一条指令的位置

在这个程序中,压栈会导致栈指针增大,出栈使用 而不是 ,同理为局部变量分配空间是 而不是使用 。

我们应该把上面的代码反编译成什么呢?

再举个例子

这里把 增加了 ,这意味着我们当前运行的这个代码块中就要有 个局部变量。

但是,我们得想办法用代码来实现上述功能。

反编译分析

我们要开始规规矩矩地分析了。

首先我们手工分析一下,然后根据我们手工分析的方法,用代码实现反编译。

片段 1

第一句是压栈,这个 没有 那种直接压入变量的指令,只能先压入一个 ,然后再向当前栈顶写入内容。

这是接下来的一段,第二句可以反编译成

其实,更准确的说法应该是 ,用 代表 就是符号的意思,这个符号肯定要被后文用到。

第三句是

这时我们就要查找前面对寄存器写入的语句了,反编译成

然后下一句是 ,而且在同一层栈中操作的,就是反编译成

其实是我为了方便的,应该写成 才对,反编译应该就是

最后一句 恢复栈平衡是一定要有的,由于这套指令集没有符号位,所以必须依赖栈作为判断依据,判断后无论是否跳转,肯定都要移动一下栈指针,把刚才判断的那个数值移到栈外。

片段 2

这里的 指令表示令 esp 执向 ebp+(int(1))[eip] 的引用,这里看 的值, 那么后面那一整个操作数就相当于第 个局部变量 。所以这段代码可以反编译成

或者说

然后这里就是提取一个字符串到栈中

然后这里有一个,赋值语句,要注意这里面包含被引用的变量,所以意思不一样。

上面代码有一个典型的结构,就是 ,这一套结构的用途就是给一个局部变量赋值。

片段 3

熟练了吗?

片段 4

注意,这里有个很奇怪的东西,就是连续两次 指令,我分析的原因是 的编译结果,我们可以认为是花指令,把它直接简化为

指令是用栈中最深的做函数名,函数名上方的都做参数,返回值直接把函数名覆盖掉。 后面要把栈多余的参数都移除。

之后一定会有一个出栈的操作。

现在我们已经发现了函数调用和 语句的结构了,可以想想怎么反编译了。

自动化反编译

如果遇到 则要新建一个 指令,if 指令有 3 部分 、、,其中 由前面的指令提供, 由跳转后的指令决定, 内容由紧随其后的指令决定。

因为 是在 之后解析的,所以我们知道按照 来解析,但是我们再解析 的时候,并不知道这个内容是被 使用的,所以也要像运行时一样,使用一个栈来存储未完成的表达式片段。

比如遇到读取 时,我们就要进行下列内容(使用 )

同理,

然后就是 指令了,

两次 指令

指令

由于我们不知道这个 指令到底走哪条路,我们就要开始 dfs 搜索了,按照两条路各走一遍,分别添加到 块和 块。

不过,想一想,这里会不会有什么问题?

如果这个跳转不是 而是 呢?跳转之后执行到某处又会跳转回来呢?

就算确定了就是是 ,我们又怎么知道 块和 块会合的位置呢?

想想 IDA Pro,我们是不是又代码片段的说法,我们通过 和 语句把代码分成片段, 一定是 段的开始, 是 段的结束。 的目标地址是 段的开始, 的目标地址是 段的结束。

因为 php 中没有 goto 语句,所以我们可以大胆地使用上面的猜想。

比如下面这个标准的 if 嵌套

如果不存在花指令的话,按上述分析应该很容易解析得到 if 语句

部分成果

功能代码已略去,仅保留验证部分

可以看到许多“编译”的产物,比如连续三个“逻辑非”,比如说只有 没有 块。

破解这个东西之后,我真的觉得 IDA 实在太牛逼了,想做到反编译必须得已知大量的编译前后的对应方法。

总结

说实话,真的挺恶心,我需要把他的虚拟机的每一个 opcode 都看一遍,然后翻译成 的 AST 构造代码。

这个虚拟机有 65 个 opcode,我看了两天时间,相当于把基本的自动反编译器的原理弄懂些了,写了一个基本的反编译器。

我真的没学过编译原理什么的,感觉研究完这个东西收获挺大的,有兴趣的同学也可以尝试一下反编译,还是有很多独特的技巧的,比如 dfs 搜索代码,或者如何找到代码会合的路径。

完全可以使用这个网站来加密 php,加密效果应该说是不错的,我给 90 分。不过这个加密的性能损失应该不小,凭感觉能在500%以上的性能损失,而且看样子好像没有关于 的 opcode,只能针对面向过程的 php 文件。

附录

反编译器还没完全写完,目前只能手动一段一段地输出代码,还不能直接全文反编译。

这里有另一个样本可以供大家研究

上个帖子的 92# 层

看样子同样是虚拟机加密方式

最后再说一句,你不应该把这个当成练习破解,而应该将其看做学习反编译原理。

--官方论坛

--推荐给朋友

公众微信号:吾爱破解论坛

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180205G0C9RJ00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券