标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-3285] 作者: 小螺号 发表于: [2023-03-17]
本文共 [339] 位读者顶过
一、什么是PHP-ParserPHP-Parser是由Nikic开发的一款PHP抽象语法树(AST)解析工具。能够将PHP代码转换为抽象语法树,安全研究员可以通过生成的语法树对PHP样本进行控制流图生成、静态分析和污点检测等操作,同时其组合模式的设计使得每个节点操作的处理相互独立,后期维护十分方便。因此在PHP Webshell检测领域中被广泛使用。同时对于生成的抽象语法树也可以进行操作,从代码节点的角度对PHP文件进行修改,所以通过PHP-Parser进行代码的混淆和反混淆是十分方便的。在非安全领域,PHP-Parser也可以自动帮你补全单元测试框架、检查代码问题。 接下来从笔者从事相关工作的经验来探讨下通过PHP-Parser进行反混淆的方法,通过对某CTF混淆样本进行反混淆让大家初步掌握使用PHP-Parser反混淆的方法。 二、PHP-Parser入门此章主要介绍PHP-Parser的创建过程以及在实战中需要用的方法、节点类型等。 1. 创建解析器实例 要使用PHP-Parser,首先需要创建实例,在创建时选择解析的语言版本,声明格式如下: 其中ParserFactory的参数有以下四种: 2. 解析PHP代码 通过解析器的parse方法将PHP代码解析成抽象语法树: 3. 输出抽象语法树 通过Node Dumping我们可以生成一个直观的AST,例如我们使用view.php来解析sample.php: sample.php的解析效果如下: 4. 抽象语法树上的Node节点 PHP-Parser解析成抽象语法树之后会存在140多种节点,主要有以下几类: 5. AST转回PHP代码 PHP-Parser中的“PhpParser\PrettyPrinter“类用来打印AST转换之后的代码,在我们对抽象语法树进行修改之后可以使用其生成新的PHP代码: 6. 节点遍历 节点遍历是PHP-Parser提供的最关键的接口,它为我们提供了遍历语法树节点的方式,通过编写特定的操作我们可以还原指定的代码。所有的Visitor都需要实现“PhpParser\NodeVisitor“接口,该接口定义4个遍历方法: 三、PHP-Parser实战这部分将从最基础的还原拼接字符串入手,一步步分析语法树特点,编写还原操作;然后尝试对函数进行编码解码,最后通过CTF赛题实战完整的解混淆一个文件,加深对于PHP-Parser的掌握。 1. 字符二元操作符还原 针对字符串的异或、拼接、与或非等操作进行还原,基础样本如下: 首先输出AST进行查看 这个例子比较单一,连接节点为“Node\Expr\BinaryOp\Concat“,且左右都为”Scalar“类型的节点,可以直接进行拼接操作,所以我们可以编写Visitor类如下,使用”leaveNode“或者”enterNode“将左右节点连接并返回: 代码考虑的比较简单,没有对左右节点为其他变量类型的情况作限制,这样可以把连接操作符的字符串还原,效果如下: 因为变量的还原涉及到作用域、存储结构等问题,这里不做探讨。 2. 字符串编码解码 这部分尝试将字符串替换为`base64`加密之后的结果,思路如下: · 判断当前节点是否为”Scalar\String_” · 将节点的”value”值进行“base64_encode“编码 · 替换原节点为“FuncCall“类型 代码如下: 效果如下: 3. CTF混淆文件还原实战 经过上面两个例子,已经掌握了PHP-Parser的基础运用,接下来通过还原混淆文件深化一下对于节点的理解,样本是2020年高校战“疫”网络安全分享赛中Hardphp题目的混淆文件,我们将从WriteUP逆向推导出反混淆思路,混淆文件如下: 首先观察可以发现,混淆文件通过“unserialize(base64_decode(“的方式将字符串解码结果赋值给”GLOBALS“数组,然后通过数组值进行运算。 由于存在部分乱码的变量名,首先将所有的乱码变量批量重命名。思路如下: · 筛选所有“Variable“类型的节点 · 通过正则表达式匹配出乱码变量,这种变量名中不会出现字母数字等字符 · 通过一个数组存放重命名的变量名,如果某个乱码变量再次出现,通过数组查询新的变量名进行替换 代码如下: 执行效果如下: 可以看到原本的不可见变量名已经被重命名成了“v_“格式的变量。同时可以观察到“GLOBALS“变量的键名也是乱码字符,借鉴变量名重命名的思路对所有”GLOBALS“数组的键名进行重命名: · 筛选所有的“ArrayDimFetch“类型节点,且代码样式为”$GLOBALS[XX][X]“,对”$node->var“和”$node->dim“也进行判断 · 通过正则表达式匹配出乱码数组键名,这种键名中不会出现字母数字等字符 · 通过一个数组存放重命名的键名名,如果某个乱码键名再次出现,通过数组查询新的变量名进行替换 和上面不同的是我们恢复的是二维数组,所以要多包含一层判断: 执行之后文件还原如下: 接下来就是解`unserialize(base64_decode(`混淆,可以先用在线代码运行工具输出一下解码的结果: 通过解码结果可以看到还存在`unserialize(base64_decode(`混淆,把密文复制再解一次: 结果看起来只剩基本函数了,通过在线的还原可以确定一下解混淆思路: · 筛选“FuncCall”节点,判断节点的”$node->expr->name->parts[0]”是否为”unserialize”,节点”$node->expr->args[0]->value->name->parts[0]”的值是否为”base64_decode” · 筛选”FuncCall”节点,判断节点的”$node->expr->name->value”是否为”unserialize”,节点”$node->expr->args[0]->value->name->value”的值是否为”base64_decode”,这种判断是因为上图中的第二次还原的调用形式为”('unserialize')(('base64_decode')('xxx')”,PHP支持字符串调用的方式,在AST中会解析为”String_”节点 · 获取加密的值,直接返回”unserialize(base64_decode(密文))”的值 · 同时还原数组是需要判断”GLOBALS”的值是否存在 代码如下: 注意:因为字符是嵌套的”unserialize(base64_decode(“,所以这里需要进行还原两次,效果如下: 接下来我们处理一下字符运算,观察代码发现运算都是“x + (y - z)“格式,所以我们的返回值格式也固定为”$a + $b - $c“: 通过观察发现还剩下`chr`、`str_rot13`以及字符串的连接符`.`三种,可以通过例二延伸出解法: 最后执行结果如下: 这样一来该文件的可读性已经很好了,短短的几行代码经过混淆后的代码量还是挺多的。 四、结语正如文章开头所言,PHP-Parser中的每个Visitor都是独立的,按照“addVisitor“的顺序进行调用,这是组合模式这种设计模式的应用,这样的模块化设计非常适合进行后续的维护。通过PHP-Paser开发者可以便捷地解析和修改PHP代码,具备了元编程能力。在此基础上,我们可以实现静态代码分析、污点追踪等操作,或者排查潜在BUG、优化项目代码,减少重复劳动。 本文大致介绍了PHP-Parser的基础使用方式,了解通过PHP-Parser进行反混淆,除这以外还可以实现更多更好用的功能,以此抛砖引玉。 |