PHP 混淆代码解密记录

  1. 样本结构
  2. 第一层
  3. 逐层处理
  4. 结果
  5. 参考

样本结构

样本第一层省略长字符串后如下:

<?php
$table = base64_decode("...");
$decoder = $table[3].$table[6].$table[33].$table[30];
$decoder .= $table[22].$table[36].$table[29].$table[26];
eval($decoder("..."));
?>

函数名通过字符表索引动态拼接,真实代码放在 eval() 参数中。

第一层

eval 改成 echo

-eval($decoder("..."));
+echo($decoder("..."));

输出仍然是 PHP:

$payload = "...";
eval('?>'.$decoder($transform($payload)));

第二层出现两个结构相同的代码块,输出分别位于 ?> 前后。继续替换新一层的 eval,把输出拼回当前 PHP。

逐层处理

每轮保留一份中间文件:

layer-01.php
layer-02.php
layer-03.php
layer-04.php

执行环境使用无网络容器:

docker run --rm \
  --network none \
  --read-only \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  -v "$PWD/work:/work:rw" \
  -w /work \
  php:8.2-cli \
  php layer-01.php > layer-01.out

本次样本的循环逻辑:

from pathlib import Path
import re
import subprocess


work = Path("work")
source = (work / "sample.php").read_text(encoding="utf-8")

for index in range(1, 9):
    instrumented = re.sub(r"\beval\s*\(", "print(", source)
    layer = work / f"layer-{index:02d}.php"
    layer.write_text(instrumented, encoding="utf-8")

    result = subprocess.run(
        ["php", str(layer)],
        check=True,
        capture_output=True,
        text=True,
    ).stdout

    (work / f"layer-{index:02d}.out").write_text(
        result,
        encoding="utf-8",
    )

    if result.lstrip().startswith("<?php"):
        source = result
    else:
        source = source.removesuffix("?>") + result + "?>"

该脚本只对应这份样本的输出结构。实际执行时,subprocess.run() 位于上面的无网络容器中。

结果

第四轮输出恢复为正常 PHP 源码,与原分析帖给出的最终结果一致。每层变化固定为:

动态拼接函数名
    -> 解码长字符串
    -> eval 执行下一层
    -> 新的动态拼接函数名

第四轮解密结果对比,作者联系方式已打码

原 Joplin 笔记末尾混入了一段导出元数据,本次整理时已删除。

参考

T00ls 原讨论


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。

文章标题:PHP 混淆代码解密记录

字数:427

本文作者:

发布时间:2022-11-27, 03:46:00

最后更新:2026-08-26, 14:57:00

原始链接:https://cnlnn.pages.dev/posts/php-obfuscated-code-decryption/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。