初探PHP-Parser和PHP代码混淆
字数 884 2025-08-19 12:41:58

PHP-Parser与PHP代码混淆技术详解

一、PHP-Parser基础

PHP-Parser是由nikic用PHP编写的PHP5.2到PHP7.4解析器,主要用于简化静态代码分析和操作。

1.1 创建解析器实例

use PhpParser\ParserFactory;
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);

ParserFactory支持的参数:

  • PREFER_PHP7:优先解析PHP7,失败则解析为PHP5
  • PREFER_PHP5:优先解析PHP5,失败则解析为PHP7
  • ONLY_PHP7:只解析PHP7
  • ONLY_PHP5:只解析PHP5

1.2 解析PHP脚本为AST

<?php
use PhpParser\Error;
use PhpParser\ParserFactory;

$code = file_get_contents("./test.php");
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);

try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
}
var_dump($ast);
?>

1.3 节点类型

PHP-Parser节点分为三类:

  1. 语句节点(Stmts):不返回值且不能出现在表达式中,如类定义
  2. 表达式节点(Expr):返回值的语言构造,如变量($var)和函数调用(func())
  3. 标量节点(Scalars):表示标量值,如字符串、数字等

1.4 节点遍历

使用NodeTraverser遍历节点:

use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node;

class MyVisitor extends NodeVisitorAbstract {
    public function leaveNode(Node $node) {
        if ($node instanceof Node\Scalar\String_) {
            echo $node->value,"\n";
        }
    }
}

$traverser = new NodeTraverser;
$traverser->addVisitor(new MyVisitor);
$stmts = $traverser->traverse($ast);

NodeVisitor接口的四个方法:

  1. beforeTraverse(array $nodes):遍历前调用
  2. enterNode(Node $node):进入节点时调用
  3. leaveNode(Node $node):离开节点时调用
  4. afterTraverse(array $nodes):遍历后调用

二、PHP代码混淆与反混淆

2.1 phpjiami混淆分析

2.1.1 常见反调试技巧

  1. CLI环境检测:
php_sapi_name()=="cli" ? die() : ''
  1. 运行环境检测:
if (!isset($_SERVER["HTTP_HOST"]) && !isset($_SERVER["SERVER_ADDR"]) && !isset($_SERVER["REMOTE_ADDR"])) {
    die();
}
  1. 执行时间检测:
$v46 = microtime(true) * 1000;
eval("");
if (microtime(true) * 1000 - $v46 > 100) {
    die();
}
  1. 文件完整性检查:
!strpos(func2(substr($v51, func2("???"), func2("???"))), md5(substr($51, func2("??"), func2("???")))) ? $52() : $53;

2.1.2 解密流程

  1. 解密核心代码:
$v55 = str_rot13(@gzuncompress(func2(substr($v51,-974,$v55))));
  1. 最终通过eval执行解密后的代码

2.2 enphp混淆分析

2.2.1 混淆特点

  • 将字符串、函数等替换为$GLOBALS{乱码}[num]形式
  • 使用全局数组存储原始字符串

2.2.2 反混淆方法

  1. 提取全局字符串数组:
$split=$ast[2]->expr->expr->args[0]->value->value;
$all=$ast[2]->expr->expr->args[1]->value->value;
$str_arr=explode($split,$all);
  1. 替换节点类型:
if ($node instanceof PhpParser\Node\Expr\ArrayDimFetch
    && $node->var instanceof PhpParser\Node\Expr\ArrayDimFetch
    && $node->var->var instanceof PhpParser\Node\Expr\Variable
    && $node->var->var->name==="GLOBALS"
    // ...其他条件
){
    return new PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]);
}
  1. 处理函数调用:
if (($node instanceof Node\Expr\FuncCall
    || $node instanceof Node\Expr\StaticCall
    || $node instanceof Node\Expr\MethodCall)
    && $node->name instanceof Node\Scalar\String_) {
    $node->name = new Node\Name($node->name->value);
}
  1. 美化变量名:
function BeautifyVariables($code){
    $v = 0;
    $map = [];
    $tokens = token_get_all($code);
    foreach ($tokens as $token) {
        if ($token[0] === T_VARIABLE && !isset($map[$token[1]])) {
            $code = str_replace($token[1], '$v' . $v++, $code);
            $map[$token[1]] = $v;
        }
    }
    return $code;
}

三、实用技巧

  1. 格式化混淆代码
use PhpParser\PrettyPrinter;
$prettyPrinter = new PrettyPrinter\Standard;
$prettyCode = $prettyPrinter->prettyPrintFile($ast);
  1. 节点查找
use PhpParser\NodeFinder;
$nodeFinder = new NodeFinder;
$Funcs = $nodeFinder->findInstanceOf($ast, PhpParser\Node\Stmt\Function_::class);
  1. 调试节点结构
$nodeDumper = new NodeDumper;
echo $nodeDumper->dump($stmts), "\n";
// 或使用命令行工具
// vendor/bin/php-parse test.php

四、参考资源

  1. PHP-Parser官方文档
  2. enphp项目
  3. PHP代码混淆与反混淆技术讨论
PHP-Parser与PHP代码混淆技术详解 一、PHP-Parser基础 PHP-Parser是由nikic用PHP编写的PHP5.2到PHP7.4解析器,主要用于简化静态代码分析和操作。 1.1 创建解析器实例 ParserFactory支持的参数: PREFER_PHP7 :优先解析PHP7,失败则解析为PHP5 PREFER_PHP5 :优先解析PHP5,失败则解析为PHP7 ONLY_PHP7 :只解析PHP7 ONLY_PHP5 :只解析PHP5 1.2 解析PHP脚本为AST 1.3 节点类型 PHP-Parser节点分为三类: 语句节点(Stmts) :不返回值且不能出现在表达式中,如类定义 表达式节点(Expr) :返回值的语言构造,如变量( $var )和函数调用( func() ) 标量节点(Scalars) :表示标量值,如字符串、数字等 1.4 节点遍历 使用 NodeTraverser 遍历节点: NodeVisitor接口的四个方法: beforeTraverse(array $nodes) :遍历前调用 enterNode(Node $node) :进入节点时调用 leaveNode(Node $node) :离开节点时调用 afterTraverse(array $nodes) :遍历后调用 二、PHP代码混淆与反混淆 2.1 phpjiami混淆分析 2.1.1 常见反调试技巧 CLI环境检测: 运行环境检测: 执行时间检测: 文件完整性检查: 2.1.2 解密流程 解密核心代码: 最终通过eval执行解密后的代码 2.2 enphp混淆分析 2.2.1 混淆特点 将字符串、函数等替换为 $GLOBALS{乱码}[num] 形式 使用全局数组存储原始字符串 2.2.2 反混淆方法 提取全局字符串数组: 替换节点类型: 处理函数调用: 美化变量名: 三、实用技巧 格式化混淆代码 : 节点查找 : 调试节点结构 : 四、参考资源 PHP-Parser官方文档 enphp项目 PHP代码混淆与反混淆技术讨论