<pre><code class="language-markdown">## 一、有一次的实战经历:杀软无情干掉了我的Payload

大概两个月前,我接到一个内网渗透项目,目标是一家中型企业。我很快通过邮件钓鱼成功拿下了一台外网暴露的 Windows 终端。按照常规操作,我生成了一个 Cobalt Strike 的 Beacon 载荷,准备用来建立后续的 C2通信。然而问题出现了,我的恶意载荷刚准备执行,目标上的杀软就直接弹窗击杀了我的进程,甚至还把文件标记为高危并隔离。

像这种杀软拦截问题,我相信各位渗透测试工程师都遇到过。后来,我针对这个问题进行了一系列免杀优化,最终成功在目标内网横向渗透,完成了任务。今天,我就结合这个案例,详细拆解下恶意载荷免杀的一些实战技巧和思路,希望对各位有所帮助。

---

二、免杀背后的原理:杀软到底在看什么?

在聊免杀技术之前,我们先搞清楚一个问题:杀软到底是怎么检测恶意载荷的?只有明白它的工作机制,才能有针对性地进行绕过。

1. 静态检测

静态检测是杀软最基础的检测方式,通过扫描文件的二进制特征或者代码特征来判断是否恶意。比如:

  • 特征字符串匹配:某些关键代码片段(如&quot;Cobalt Strike&quot;、&quot;Mimikatz&quot;)被杀软直接标记成恶意;
  • 文件头特征:PE 文件的元数据(如编译器签名、时间戳)可能会暴露你的工具来源;
  • 代码结构分析:杀软会分析文件中的函数、字符串表、导入表,如果发现某些恶意行为对应的模式,就会判定为恶意。

2. 动态检测

动态检测是杀软通过行为分析判断文件是否恶意的方式,它会监控文件运行时的行为,比如:

  • API 调用监控:如果你的载荷频繁调用可疑 API(如 VirtualAlloc、WriteProcessMemory、CreateRemoteThread),很可能会被标记;
  • 内存注入检测:某些杀软会实时扫描目标进程的内存,发现异常代码段时直接拦截;
  • 沙盒分析:部分杀软会在独立的环境中运行你的程序,观察其行为是否恶意。

3. 基于 AI 的检测

近年来,越来越多的杀软引入了机器学习模型。它们会通过大量样本训练一个恶意行为模型,再用这个模型动态判定你的文件是否存在风险。

黑客示意图

因此,想要绕过杀软,我们需要针对不同的检测方式来设计免杀技术,接下来我将逐步分析具体的实现技巧。

---

三、免杀实战:从特征混淆到内存加载

在实战中,免杀的核心目标就是让我们的恶意载荷既能隐匿自身,又能顺利执行。我常用以下几个套路:

1. 文件特征混淆

静态检测通常是杀软的第一道防线,因此我们需要尽量混淆文件特征,让它无法匹配到恶意样本的签名。以下是几种常用的方法:

方法一:代码混淆

可以通过改变代码的结构,让杀软无法直接匹配到特征字符串。例如,对恶意代码中的 API 调用进行重命名或加密。以下是一个简单的例子: </code></pre>ruby

原始恶意代码

require 'fiddle'

kernel32 = Fiddle.dlopen('kernel32') VirtualAlloc = Fiddle::Function.new(kernel32['VirtualAlloc'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_UINT, Fiddle::TYPE_UINT], Fiddle::TYPE_VOIDP) VirtualAlloc.call(0, 4096, 0x1000, 0x40) # 分配可执行内存 <pre><code> 如果直接这样写,可能会被杀软识别为恶意行为。我们可以通过重命名和加密的方法来混淆: </code></pre>ruby

混淆后的代码

require 'fiddle'

k = Fiddle.dlopen('kernel32') v = Fiddle::Function.new(k[('V'+'irtual'+'Alloc').downcase], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_UINT, Fiddle::TYPE_UINT], Fiddle::TYPE_VOIDP) v.call(0, 4096, 0x1000, 0x40) # 调用前先混淆函数名 <pre><code> 这样可以有效躲过杀软的特征扫描。

方法二:文件加壳

可以使用一些简单的加壳工具对文件进行加密或压缩,比如 UPX。这种方法可以对抗大多数静态检测。 </code></pre>shell

使用 UPX 对文件加壳

upx -9 payload.exe <pre><code> 当然,市面上很多杀软已经能识别 UPX 的壳,所以更推荐自己写一个定制的加壳工具。

---

2. 动态行为伪装

绕过动态检测的关键是让恶意代码表现得“不像恶意”。以下是几种实用的方法:

方法一:延迟执行

大多数沙盒分析的时间都在 1-2 分钟左右,我们可以通过延迟执行来规避沙盒。例如,在恶意代码中加入一个长时间的睡眠: </code></pre>ruby

延迟执行示例

sleep(120) # 先睡 2 分钟,等沙盒超时退出 exec("powershell.exe -c 'Write-Host \"Payload执行中...\"'") <pre><code>

方法二:正常行为掩护

可以将恶意行为藏在正常的系统操作中,比如把恶意代码注入到合法的系统进程中。以下是一个简单的例子: </code></pre>ruby

将代码注入到 explorer.exe 进程中

require 'fiddle'

pid = tasklist | find "explorer.exe".split[1].to_i

注入代码的逻辑省略,可以调用 Windows API 完成

<pre><code> ---

3. 内存加载技术

将恶意载荷直接加载到内存中执行,是目前最常用的免杀技术之一。这样可以完全避开静态检测。以下是一个 Ruby 示例: </code></pre>ruby

使用 Fiddle 实现内存加载

require 'fiddle'

shellcode = [0xfc, 0x48, 0x83, 0xe4, 0xf0, ...] # 省略部分shellcode字节 ptr = Fiddle::Pointer.malloc(shellcode.size) ptr[0, shellcode.size] = shellcode.pack('C*')

黑客示意图

create_thread = Fiddle::Function.new(Fiddle.dlopen('kernel32')['CreateThread'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_UINT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_UINT) create_thread.call(nil, 0, ptr.to_i, nil, 0, nil) <pre><code> 这种方式可以完全避免生成落盘文件,直接在内存中运行恶意代码。

---

四、如何应对检测策略的升级?

杀软和攻击者的对抗是一个“猫鼠游戏”,随着杀软防御策略的升级,我们的免杀技术也需要不断迭代。以下是我总结的一些应对策略:

  1. 动态调试杀软:用调试器分析杀软的行为,找到拦截的核心逻辑,并进行针对性绕过。
  2. 使用多阶段加载:将恶意载荷分成多个部分,逐步加载进内存,减少一次性暴露的风险。
  3. 多样化工具链:不要只依赖一种工具,比如 Cobalt Strike,可以结合 Sliver 或自写工具。

---

五、个人经验分享:免杀是门艺术

回到最开始的那个案例,我最终通过代码混淆和内存加载技术完成了目标的突破。从这个过程我也深刻感受到,免杀不仅是技术活,更是一项“心理战”。你需要时刻站在杀软的角度思考,找到它的盲点并加以利用。

当然,所有这些技术只能用于授权环境,切勿用于非法用途。希望本次分享对大家的实战渗透有所帮助!</code></pre>

黑客示意图