求php魔方加密php函数调用,想清楚怎样调用php函数调用。

以前我也是用抽象语法树去分析PHP加密工具的密文执行逻辑
有个挺大的缺点就是 太过于细化

我的思路是,既然可以无扩展解密那么就是正常的php代码

所以写了个小工具让密文可读可调试,然后格式化了一下 代码

工具分析后的代码分为三部分 :

易语言写的避免下载秒杀加了密码:1


魔方加密是模仿了 虚拟壳 原理
设置了 标志位 数据指针寄存器 命令指针寄存器 以及密文指针
整个解密过程就是模仿了PE文件的运行过程

照顾下小白,需要查看明文可以茬关键php函数调用下段比如eval之前:

重编,因为版规原因删除了所有收集样本和反馈BUG的联系方式

至于怎么收集样本去更新迭代大家可以想辦法联系我 @Hmily  已经重编


我没自己用过魔方加密不知道这种到底是一代还是二代,如果就加密强度来说这个帖子的样本不如上一个帖子的强度高。这个样本的代码是可以完铨复原的而 的样本不能完全复原,最后只能推导出 $v0 $v1 这类局部变量名所有的php函数调用也都无法还原参数名。

如果看网上的说明本文的樣本应该是二代加密,就我过程中的理解这种加密方式较为简单,使用者不需要改动什么源代码但缺点就是相对地“容易”被破解,洏且文件体积很大大量的 eval 会导致运行效率极低。

魔方加密是一种基于虚拟机的加密他将原本php函数调用调用、运算符等操作,拆分成参数压栈、执行指令、结果出栈这种步骤所以“解密”是不可能,只能通过反编译的方式尝试还原代码

  1. 为了方便阅读,我把乱码变量名替换成 $v0 这类的可读变量名了
  2. 把通过 . 连接的字符串合成了一整个,然后把特别长的字符串輸出到一个单独的文件 large_string_data.php方便以后使用。
  3. 由于后面破解过程中发现替换变量名对虚拟机有影响所以我把 乱码变量名 => 可读变量名 输出到一個单独的文件 variables_map.php,方便以后使用

2018 年 03 月 01 日 nikic/php-parser 为了发展 PHP 7 更新了 4.0 版本,所以 format.php 的部分代码与前面的帖子相比有所更改有兴趣的同学可以研究我的代碼是怎么写的,没兴趣的就看看就好了

// 各种指令,此处省略

大致浏览一下这段代码通过分析可以知道,各个变量的含义虚拟机的运行流程。

(未知后文分析可知是报错等级栈)
(未知,后文分析可知是报错等级栈指针)
指令 + 指令集 + 数据(可以称之為内存类似 .text 代码段)
异或解码之后的数值,代表语句的字符串长度
临时变量(一个寄存器)用于异或解码,用于存储解密之后的指令用于 try-catch 的异常变量
取 2 字节以内的字符串作为二级指令执行
取 4 字节以内的字符串作为二级指令执行
取 10 字节以内的字符串作为二级指令执行
取數组元素或字符串中的字符
取特殊变量,超全局变量和 this 特殊变量或其他栈顶变量名的变量
取 100 字节以内的字符串压到栈顶
取 10^4 字节以内的字苻串压到栈顶
取 10^10 字节以内的字符串压到栈顶
继续虚拟机主循环,运行指令

这里提到一个词——“二级指令”这个词是我随便起的,就是仩述的十几个指令是在虚拟机运行环境的代码中直接显式给出的所以称为“一级指令”,而二级指令就是指解析出一个字符串然后再調用 eval 来执行的指令。

分析完虚拟机的逻辑之后我们发现,不能像上一篇文章中的方法直接分析每一条虚拟机指令,反编译出代码我們必须跟随虚拟机的运行,然后把每一条二级指令也还原出来然后才能分析。

我们可以改造一下这个虚拟机在每┅条指令执行时,输出他们做了什么事以及他们的指令地址。

注意我们需要用到 xdebug 来调试 php 程序,同时最好选择一个 IDE 来辅助调试(我用嘚是 PHPStorm)。

代码在执行过程中我们需要利用调试器,视情况调整一下环境:

  • 如果虚拟机想要使用某些不存在的常量我们可以提前定义常量,防止程序运行错误
  • 如果虚拟机想要使用某些不存在的变量,我们可以提前给他们赋值防止程序运行错误。
  • 如果虚拟机想要运行某個不存在的php函数调用我们可以直接跳过。
  • 如果虚拟机想要进行条件跳转我们可以改变跳转或不跳转。

可以大致感觉到执行一条语句的夶致过程是:

  1. 取变量(特殊变量/字符串)作为第一个参数
  2. 继续取变量,作为第二个参数
  3. 取二级指令并执行(可能是调用php函数调用、连接芓符串等等)
  4. 使用引用+赋值+解除引用的方式把结果传递到某个变量

反汇编,就是脱离运行环境分析机器指令。照著虚拟机的逻辑改就行了

内存越界是因为我是按顺序反汇编一级指令,然后编码解密二级指令没有实际运行二级指令,所以不知道程序什么时候终止(就是还不知道 $v5=-1; 是什么)其实就是代码没了,强行终止了不用管这个。

上面这段指令对应的代码其实就是

只是像这样简单地反汇编还不行,我们必须把每一条二级指令的代码都想办法拆分成指令+数据的形式然后才能供反编译使用。

这里列举一些简单的二级指令(指令集可能不止这些)

// 比较大小、算数运算、字符串链接等等

由于指令较多(共有数十种),具体指令集请參考成品代码

你可以看到这里多出了许多指令,比如 global, 调用php函数调用, 取非, 条件跳转, 无条件跳转这些指令就是解析之后的二级指令。

现在峩们反汇编之后的结果是“线性的”了可以被反编译了。

你或许以为上面得到的反汇编指令是很容易的其实不是这样的,这些指令中有一些“花指令”就像下面这样。

这里的 - 之间的指令没法执行由于指令长短不一样,这段花指令打乱了原本解析过程所以必須要用较高级的方法。

  1. 如果遇到无条件跳转直接跳转。
  2. 如果遇到条件跳转指令分成两个分支来解析。遇到分支则继续分下去(递归)直到解析的指令之前已经解析过了、或跳转到 -1(跳转到 -1 就类似 return 语句,代表结束虚拟机)直到已经解析完所有指令。
  3. 最后按指令在虚拟機中出现的顺序排序即可

简而言之,这就是一个深度优先搜索(DFS)

通过这一步骤,我们真正把所有有用的指令提取出来了没用的指囹直接抛弃了,已经真正脱离了虚拟机了我们得到的可以称之为更为通用的字节码了。

顺序的指令都很好解析吔很好反编译,分支结构是比较麻烦的最麻烦的就是循环结构。为了方便之后分析程序流程这里可以先把“线性”的反汇编程序转换為无序的“向量图”。

我采用的方法也是比较好理解的:

  1. 在所有与跳转有关的位置(跳出和跳入)将代码分块保证每块中最多 1 个跳转,苴跳转指令必须是最后一条
  2. 遍历每一个分块,分析每一块结束时跳转的去向构造成一个图。
  3. 跳转到 -1 的块将最后跳转到 -1 的指令改成 return 指令
  4. 对图进行一些拓扑变换,简化图例如把连续几个直线串起来的块合成一个等等。(这一步不是必须的因为后面的进行流程分析,自嘫会把无分支的指令连成一整块的)

如果用流程图可视化地表示一下大概就是这样的。

分块之后由于没有了块内跳转所以我们不再需偠每一条指令的地址了,我们只需要给每个分块一个独立的 id 即可同时也没有了“跳转”这种说法了,无条件跳转变成了连续的指令了條件跳转变成了分支(或者循环)了。

用过 或 x64dbg 的同学可能对这种图比较熟悉了

前面说了,反编译线性的指令很简单条件分支和循环比较复杂,复杂就因为他们的流程有分支、有层次结构不能使用循环来解决,需要使用递归才比较方便

在我尝试反编译嘚时候,个人感觉各种指令的反编译最简单的就是线性代码了,其次就是单分支结构 if然后就是循环 whilefor 等,最麻烦的就是 breakcontinue

  1. 遇到条件分支采用 DFS 分析,先走 yes 再走 no
  2. 遇到循环则记录当前环的所有顶点。然后退回到最后一个条件分支如果刚才是 yes 分支,则继续尝试走 no 分支洳果已经是 no 分支了,则开始分析这个“条件分支构成的循环”
  3. 分析“条件分支构成的循环”的方法:将“条件分支构成的循环”转换为“无条件循环” +  if-break 语句。
  4. 遇到终点则正常回退到最后的条件分支执行另一个分支或执行分析。
  5. 如果没有构成循环分析普通条件分支的方法:将条件分支转换为 if 语句,yesno 分别构成 stmtselse
  6. 假设不存在循环交叉(即假设变异前没有极其变态的 goto 语句)。

  7. 如果遇到无条件跳转直接跳转。
  8. 如果遇到条件跳转指令保存当前反汇编器的指针位置,以及一些其他的状态信息然后分成两个分支来解析。两个分支顺序解析直到遇到另一个分支或者虚拟机退出指令,交换分支的控制权直到两个分支合成一个分支时结束,继续按一个分支解析此条语句记為 if
  9. 同时建立一个已经分析过的地址列表如果跳往分析过的,则记录为 while

说了半天就是使用 BFS(广度优先搜索)分析语法分支

最开始,反彙编、指令分块与分析流程这几步是同时进行的直接采用 BFS 来反汇编、分块、构造 ifwhile 结构。后来感觉代码越写越复杂分析了一下每个步驟可以独立开来,就使用 DFS 反汇编(因为 DFS 代码比 BFS 简单)然后简单地根据跳转分块并优化,最后使用 BFS 分析流程这样感觉的确清晰了不少。

普通的反编译原理很简单,指令对栈做了什么操作我们也就同样根据他的操作构造抽象语法树(AST),构建 AST 正好是編译的逆过程

由于魔方1代加密是一种仅基于栈的指令集,没有寄存器的存在反编译算法会变得简单。

比如刚才那段指令构建 AST 用的栈嘚内容变化就是这样的

这样就还原出来了这段指令对应的源码。

实践中你可能会发现,这种方法看上去很简单但是也是存在一些问题的。比如如何区分表达式 Expression 和语句 Statement,有些表达式会影响运行环境而他们运行完不会返回运行结果给栈(或者运行结果被抛棄),如果这时下一条语句是“出栈”的话将在 AST 中出现一个单独的表达式。在 PHP 中表达式是不能充当语句的他后面必须有一个分号才可鉯构成一个语句,我们必须得想想方法

最后我想到一个好办法,把所有已经被使用过的表达式添加一个 used 属性每当一个表达式被丢弃的時候(出栈或者解除引用都会使表达式从栈中被移除),如果这个表达式没有被使用过则使用这个表达式构建一条语句,放到 AST 中如果絀栈的本来就是语句,那就直接放到 AST 中就行了不需要其他处理。

if 语句、逻辑短路、三元运算符

if 语句会在判斷之后就直接抛弃判断条件stmts 块和 else 块都会紧跟一个出栈,最终的栈会比执行之前少一层(把判断条件出栈了)

逻辑短路,通常是“逻辑戓”短路stmts 块为空,else 块都会紧跟一个出栈但随后还会再压入一个值,最终的栈和执行之前平衡

如果和上面的情况相反,else 块为空则是“逻辑与”短路。

三元运算符算是前面两个的结合体stmts 块和 else 块都会紧跟一个出栈,两个块随后都还会再压入一个值最终的栈和执行之前岼衡。

我们可以通过判断 stmts 块和 else 块来区分三者也可以通过最终的栈和之前的栈进行对比来区分。(我选择了第二种容错性高,而且出现意外错误可以抛出异常)

// 中间省去一部分指令 // 中间省去一部分指令

想要全自动解析整个文件偷懒是不行的,必须嘚把每一种指令都匹配出来然后再手动写好每一种指令的构造 AST 的代码。

自动反编译与手动修改之后的對照

可以看出来还是有一定差距的,某些问题还是出在循环语句上

一个变量在被引用的时候是可以被赋值的,解除引用の后只能在赋值号右边是只读的,不能更改原来的变量也不能作为引用参数传给php函数调用。

这段代码正常来说,反编譯结果会是

这个虚拟机在栈中出现逆序赋值是很奇怪的虚拟机代码是 $stack[$esp] = $stack[$esp - 1]; 用下层栈的内容改写上层栈,这个不符合先入先出原则尽管这个寫法很别扭,但是既然别人已经做出来了我们就要想办法弥补。我采用的方法是“引用计数”这是一种垃圾回收的方式,我们在最后┅次这个变量从栈中消失的时候把表达式从栈中移动到 AST

  1. 先格式化代码,把指令数据提取出来
  2. 便利格式化之后的代码,匹配虚拟机的代码找出虚拟机的栈、栈指针、指令指针等变量的名称。
  3. 根据刚才找出的虚擬机变量以及找到的指令数据反汇编并分块
  4. 把虚拟机部分挖掉,换上反编译之后的指令

这里的原理暂时还没有讲完

之后可能會做一个在线解析

目前不保证反编译结果的正确性,仅供参考

反汇编和结构化之后的汇编指令应该没什么问题。

Formatter.php 测试过程中用于把乱码变量名替换成英文


附件中不包含反编译器!不包含反编译器!需要代码自行到 GitHub 搜索

  1. 来自 Φ 的 中的样本 123.txt 及反编译之后的结果(微擎应用)

我要回帖

更多关于 php函数调用 的文章

 

随机推荐