初探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,失败则解析为PHP5PREFER_PHP5:优先解析PHP5,失败则解析为PHP7ONLY_PHP7:只解析PHP7ONLY_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节点分为三类:
- 语句节点(Stmts):不返回值且不能出现在表达式中,如类定义
- 表达式节点(Expr):返回值的语言构造,如变量(
$var)和函数调用(func()) - 标量节点(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接口的四个方法:
beforeTraverse(array $nodes):遍历前调用enterNode(Node $node):进入节点时调用leaveNode(Node $node):离开节点时调用afterTraverse(array $nodes):遍历后调用
二、PHP代码混淆与反混淆
2.1 phpjiami混淆分析
2.1.1 常见反调试技巧
- CLI环境检测:
php_sapi_name()=="cli" ? die() : ''
- 运行环境检测:
if (!isset($_SERVER["HTTP_HOST"]) && !isset($_SERVER["SERVER_ADDR"]) && !isset($_SERVER["REMOTE_ADDR"])) {
die();
}
- 执行时间检测:
$v46 = microtime(true) * 1000;
eval("");
if (microtime(true) * 1000 - $v46 > 100) {
die();
}
- 文件完整性检查:
!strpos(func2(substr($v51, func2("???"), func2("???"))), md5(substr($51, func2("??"), func2("???")))) ? $52() : $53;
2.1.2 解密流程
- 解密核心代码:
$v55 = str_rot13(@gzuncompress(func2(substr($v51,-974,$v55))));
- 最终通过eval执行解密后的代码
2.2 enphp混淆分析
2.2.1 混淆特点
- 将字符串、函数等替换为
$GLOBALS{乱码}[num]形式 - 使用全局数组存储原始字符串
2.2.2 反混淆方法
- 提取全局字符串数组:
$split=$ast[2]->expr->expr->args[0]->value->value;
$all=$ast[2]->expr->expr->args[1]->value->value;
$str_arr=explode($split,$all);
- 替换节点类型:
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]);
}
- 处理函数调用:
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);
}
- 美化变量名:
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;
}
三、实用技巧
- 格式化混淆代码:
use PhpParser\PrettyPrinter;
$prettyPrinter = new PrettyPrinter\Standard;
$prettyCode = $prettyPrinter->prettyPrintFile($ast);
- 节点查找:
use PhpParser\NodeFinder;
$nodeFinder = new NodeFinder;
$Funcs = $nodeFinder->findInstanceOf($ast, PhpParser\Node\Stmt\Function_::class);
- 调试节点结构:
$nodeDumper = new NodeDumper;
echo $nodeDumper->dump($stmts), "\n";
// 或使用命令行工具
// vendor/bin/php-parse test.php