红队队开发基础-基础免杀(二)
字数 1752 2025-08-27 12:33:43

红队开发基础:系统调用与API调用模式规避技术

引言

本文详细介绍了红队开发中两种基础免杀技术:使用直接系统调用并规避"系统调用标记"和规避常见的恶意API调用模式。这些技术主要用于绕过EDR(终端检测与响应)产品的检测机制。

系统调用基础知识

权限级别与系统调用流程

  • Windows系统有四个权限级别(R0-R3),其中:

    • R0:内核态,最高权限
    • R3:用户态,最低权限
    • R1和R2用于运行设备驱动
  • 用户态(R3)到内核态(R0)的转换通过ntdll.dll中的Native API实现

  • Native API函数以"Nt"和"Zw"开头,没有官方文档

系统调用机制

基本系统调用汇编形式:

mov r10, rcx
mov eax, xxh  ; xxh为系统调用号
syscall

系统调用号决定了调用哪个内核函数,完整列表参考:
https://j00ru.vexillium.org/syscalls/nt/64/

直接系统调用实现

基础实现步骤

  1. 定义函数原型(参考MSDN文档)
EXTERN_C NTSTATUS SysNtCreateFile(
    PHANDLE FileHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PIO_STATUS_BLOCK IoStatusBlock,
    PLARGE_INTEGER AllocationSize,
    ULONG FileAttributes,
    ULONG ShareAccess,
    ULONG CreateDisposition,
    ULONG CreateOptions,
    PVOID EaBuffer,
    ULONG EaLength);
  1. 调用函数示例
RtlInitUnicodeString(&fileName, (PCWSTR)L"\\??\\c:\\temp\\test.txt");
ZeroMemory(&osb, sizeof(IO_STATUS_BLOCK));
InitializeObjectAttributes(&oa, &fileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
SysNtCreateFile(
    &fileHandle,
    FILE_GENERIC_WRITE,
    &oa,
    &osb,
    0,
    FILE_ATTRIBUTE_NORMAL,
    FILE_SHARE_WRITE,
    FILE_OVERWRITE_IF,
    FILE_SYNCHRONOUS_IO_NONALERT,
    NULL,
    0);

动态系统调用技术

Hell's Gate(地狱之门)

  1. 遍历NtDLL导出表
  2. 根据函数名hash找到函数地址
  3. 通过特征字节码获取系统调用号
  4. 动态生成syscall汇编代码

特征字节码识别:

if (*((PBYTE)pFunctionAddress + cw) == 0x4c 
    && *((PBYTE)pFunctionAddress + 1 + cw) == 0x8b 
    && *((PBYTE)pFunctionAddress + 2 + cw) == 0xd1 
    && *((PBYTE)pFunctionAddress + 3 + cw) == 0xb8 
    && *((PBYTE)pFunctionAddress + 6 + cw) == 0x00 
    && *((PBYTE)pFunctionAddress + 7 + cw) == 0x00) {
    BYTE high = *((PBYTE)pFunctionAddress + 5 + cw);
    BYTE low = *((PBYTE)pFunctionAddress + 4 + cw);
    pVxTableEntry->wSystemCall = (high << 8) | low;
    break;
}

SysWhispers2

  • 使用Python生成.c源码文件
  • 在PE中查找导出表并通过函数hash匹配系统调用号
  • 使用INT 2EH替代syscall进行混淆

Halo's Gate(光环之门)

应对Native API被hook的情况:

  • syscall有32字节存根
  • 在内存中向上向下每32字节搜索未被hook的Native API
  • 获取系统调用号并减去移动步数

TartarusGate

增加对hook的判断:

  • 检测5字节和7字节hook
  • 通过判断函数开头第一个和第四个字节是否为E9(JMP指令)

GetSSN

不同思路获取系统调用号:

  1. 获取所有Zw开头的函数地址
  2. 按地址排序
  3. 排序后的序号即为系统调用号

弱化syscall特征

问题

直接使用syscall指令具有明显静态特征,容易被检测

解决方案

  1. INT 2EH替代:早期方案,现已被检测

  2. EggHunter技术

    • 使用唯一模式(如"w00tw00t")替换syscall指令
    • 运行时在内存中搜索并替换为syscall
    • 示例:
    DB 77h ; "w"
    DB 0h  ; "0"
    DB 0h  ; "0"
    DB 74h ; "t"
    DB 77h ; "w"
    DB 0h  ; "0"
    DB 0h  ; "0"
    DB 74h ; "t"
    
  3. RIP重定向

    • 搜索内存中ntdll的syscall地址
    • 直接jmp到该地址,使RIP指向ntdll

SysWhispers3

整合多种技术:

# 普通SysWhispers,32位模式
py .\syswhispers.py --preset all -o syscalls_all -m jumper --arch x86

# 使用WOW64的32位模式(仅特定函数)
py .\syswhispers.py --functions NtProtectVirtualMemory,NtWriteVirtualMemory -o syscalls_mem --arch x86 --wow64

# Egg-Hunting SysWhispers,绕过"syscall标记"
py .\syswhispers.py --preset common -o syscalls_common -m jumper

# Jumping/Jumping Randomized SysWhispers,绕过动态RIP验证
py .\syswhispers.py --preset all -o syscalls_all -m jumper -c mingw

规避恶意API调用模式

Windows API Hook原理

  1. 获取目标函数地址:
LPVOID lpDllExport = GetProcAddress(hJmpMod, jmpFuncName);
  1. 修改前7字节为跳转指令:
unsigned char jmpSc[7]{ 0xB8, b[0], b[1], b[2], b[3], 0xFF, 0xE0 };
// 对应汇编:mov eax,xxxx ; jmp eax
  1. 写入内存:
WriteProcessMemory(hProc, lpDllExport, jmpSc, sizeof(jmpSc), &szWritten);

Windows内存分配规则

  • 最小分配粒度:4kB(内存分页大小)
  • VirtualAllocEx分配的内存向上取整到AllocationGranularity(64kB)
    • 例如在0x40000000分配4kB,整个0x40010000(64kB)区域将不可重新分配

规避EDR检测的技术

  1. 内存分配策略

    • 分配小块连续内存(<64KB)
    • 标记为NO_ACCESS
    • 按块大小写入shellcode
  2. 执行延迟

    • 在各操作之间引入延迟
    • 淡化连续执行模式
  3. 函数劫持

    • 钩住RtlpWow64CtxFromAmd64等函数
    • 执行恶意shellcode

DripLoader实现

  1. 搜索空闲内存区域:

    • 预定义64位基址列表
    • 使用VirtualQueryEx查找适合shellcode的内存区域
  2. 计算所需内存块数:

    • cVmResv = shellcode长度/内存块大小 + 1
  3. 确保内存分配:

    • 使用syscall调用NtAllocateVirtualMemory
    • 确保每块不超过64KB,以4KB为单位分配
  4. 写入内存:

    • 以4位为单位分批写入
  5. 函数劫持:

    • 获取函数地址并hook
    • 跳转到shellcode起始地址
  6. 创建进程执行shellcode

总结

本文详细介绍了两种主要的EDR绕过技术:

  1. 直接系统调用技术

    • 绕过用户态hook
    • 多种动态获取系统调用号的方法
    • 弱化syscall特征的技术
  2. API调用模式混淆

    • 非常规内存分配策略
    • 执行流程混淆
    • 函数劫持技术

这些技术需要结合使用,并根据目标环境的特点进行调整,才能有效绕过现代EDR产品的检测机制。

红队开发基础:系统调用与API调用模式规避技术 引言 本文详细介绍了红队开发中两种基础免杀技术:使用直接系统调用并规避"系统调用标记"和规避常见的恶意API调用模式。这些技术主要用于绕过EDR(终端检测与响应)产品的检测机制。 系统调用基础知识 权限级别与系统调用流程 Windows系统有四个权限级别(R0-R3),其中: R0:内核态,最高权限 R3:用户态,最低权限 R1和R2用于运行设备驱动 用户态(R3)到内核态(R0)的转换通过ntdll.dll中的Native API实现 Native API函数以"Nt"和"Zw"开头,没有官方文档 系统调用机制 基本系统调用汇编形式: 系统调用号决定了调用哪个内核函数,完整列表参考: https://j00ru.vexillium.org/syscalls/nt/64/ 直接系统调用实现 基础实现步骤 定义函数原型(参考MSDN文档) 调用函数示例 动态系统调用技术 Hell's Gate(地狱之门) 遍历NtDLL导出表 根据函数名hash找到函数地址 通过特征字节码获取系统调用号 动态生成syscall汇编代码 特征字节码识别: SysWhispers2 使用Python生成.c源码文件 在PE中查找导出表并通过函数hash匹配系统调用号 使用INT 2EH替代syscall进行混淆 Halo's Gate(光环之门) 应对Native API被hook的情况: syscall有32字节存根 在内存中向上向下每32字节搜索未被hook的Native API 获取系统调用号并减去移动步数 TartarusGate 增加对hook的判断: 检测5字节和7字节hook 通过判断函数开头第一个和第四个字节是否为E9(JMP指令) GetSSN 不同思路获取系统调用号: 获取所有Zw开头的函数地址 按地址排序 排序后的序号即为系统调用号 弱化syscall特征 问题 直接使用syscall指令具有明显静态特征,容易被检测 解决方案 INT 2EH替代 :早期方案,现已被检测 EggHunter技术 : 使用唯一模式(如"w00tw00t")替换syscall指令 运行时在内存中搜索并替换为syscall 示例: RIP重定向 : 搜索内存中ntdll的syscall地址 直接jmp到该地址,使RIP指向ntdll SysWhispers3 整合多种技术: 规避恶意API调用模式 Windows API Hook原理 获取目标函数地址: 修改前7字节为跳转指令: 写入内存: Windows内存分配规则 最小分配粒度:4kB(内存分页大小) VirtualAllocEx分配的内存向上取整到AllocationGranularity(64kB) 例如在0x40000000分配4kB,整个0x40010000(64kB)区域将不可重新分配 规避EDR检测的技术 内存分配策略 : 分配小块连续内存( <64KB) 标记为NO_ ACCESS 按块大小写入shellcode 执行延迟 : 在各操作之间引入延迟 淡化连续执行模式 函数劫持 : 钩住RtlpWow64CtxFromAmd64等函数 执行恶意shellcode DripLoader实现 搜索空闲内存区域: 预定义64位基址列表 使用VirtualQueryEx查找适合shellcode的内存区域 计算所需内存块数: cVmResv = shellcode长度/内存块大小 + 1 确保内存分配: 使用syscall调用NtAllocateVirtualMemory 确保每块不超过64KB,以4KB为单位分配 写入内存: 以4位为单位分批写入 函数劫持: 获取函数地址并hook 跳转到shellcode起始地址 创建进程执行shellcode 总结 本文详细介绍了两种主要的EDR绕过技术: 直接系统调用技术 : 绕过用户态hook 多种动态获取系统调用号的方法 弱化syscall特征的技术 API调用模式混淆 : 非常规内存分配策略 执行流程混淆 函数劫持技术 这些技术需要结合使用,并根据目标环境的特点进行调整,才能有效绕过现代EDR产品的检测机制。