COFF文件解析 | CoffLdr
字数 2085 2025-08-30 06:50:27

COFF文件解析与CoffLdr实现详解

1. COFF文件概述

COFF(Common Object File Format)是在程序编译过程中生成的二进制文件,通常以.o.obj为扩展名。这些文件随后会被链接器处理,生成最终的PE可执行文件。

COFF文件结构

COFF文件主要由以下几个部分组成:

  1. CoffHead:文件头,不包含StringTable的偏移信息
  2. Section Header:节区头表
  3. SectionData:每个节的实际数据
  4. Reloc Table:重定位表
  5. Symbol Table:符号表
  6. String Table:字符串表

2. 现有C2平台COFF加载器的问题分析

2.1 函数槽位不足问题

在Cobalt Strike中,MAX_DYNAMIC_FUNCTIONS宏被硬编码为32(4.5版本),当BOF需要调用超过32个函数时会抛出"No slot for function"错误。

解决方案

  • 应像Havoc框架那样,通过遍历symbolName动态分配GOT(Global Offset Table)大小
  • 避免硬编码函数槽位限制

2.2 BSS段处理问题

BSS段用于存放未初始化的全局变量,不同编译器处理方式不同:

  • Mingw(基于GCC):显式创建.bss段
  • MSVC:不创建.bss段,将未初始化变量放入数据段或堆中

问题表现

  • Cobalt Strike 4.9.1:Beacon崩溃
  • BRC4:需要新API格式
  • AdaptixC2:BOF解析逻辑缺失

解决方案

  • 对于MSVC编译的COFF,需要手动创建BSS Table实现兼容
  • 处理BSS段后,MSVC和Mingw编译的BOF都能正常输出

2.3 异常捕获问题

BOF崩溃会导致整个Beacon崩溃,解决方案:

  1. 使用VEH(Vectored Exception Handling)捕获异常
  2. 异常触发时将RIP指向CoffeeFunctionReturn(COFF的返回地址)
  3. 添加额外判断兼容某些BOF抛出的异常

2.4 OpSec问题

BOF执行会分配内存或踩踏内存,可能触发检测机制。

改进方案

  • 使用Native API调用
  • 实现模块踩踏(Module Stomping),使调用栈更正常
  • 其他OpSec技术(留给读者探索)

2.5 BeaconGate替代方案

Cobalt Strike 4.10引入BeaconGate控制API调用,在没有此功能时可实现类似效果:

  1. IAT Hook:控制BOF的API调用
  2. 实现自定义"Gate"机制,提供开箱即用的规避效果

2.6 参数解析问题

Cobalt Strike Beacon有特定API用于参数解析:

  • BeaconDataParse
  • BeaconDataExtract
  • BeaconDataInt
  • BeaconPrintf

参数格式解析规则

  • 跳过Buffer总长度字段(前4字节)
  • 统一BeaconDataIntBeaconDataExtract的结构

3. COFF解析实现细节

3.1 总大小计算(TotalSize)

定义ParseTotalSize函数:

  1. 遍历每个节,记录节大小和对齐
  2. 在每个节中查找重定向条目
  3. 查找每个符号,记录BSS Table Size和GOT Table Size
  4. 分配完整内存区域(而非多次分配)

3.2 BSS和GOT处理

处理流程:

  1. 判断是否为Beacon内部函数
    • 是:遍历函数数组返回地址
    • 维护BeaconApi数组实现后续行为函数
  2. 如果是library$functionfunction格式
    • 使用GetModule/LoadDll获取函数地址
  3. 处理.bss段
    • 跳过BSS Table的前4字节
    • 返回BSS Table的Offset
    • pCoffSymbol->SectionNumber改为对应的BSS Table Section和GOT Section

3.3 重定位处理(Process Relocation)

四种主要情况:

  1. IMAGE_REL_AMD64_REL32 && functionPtr != NULL
  2. IMAGE_REL_AMD64_REL32IMAGE_REL_AMD64_REL32_5
  3. IMAGE_REL_AMD64_ADDR32NB
  4. IMAGE_REL_AMD64_ADDR64

3.4 获取入口点(Get EntryPoint)

  • 遍历所有符号
  • 获取EntryPoint地址
  • 支持自定义入口点(不限于"go")

3.5 运行COFF(RunCoffee)

  1. 注册VEH捕获运行时崩溃
  2. 实现线程欺骗:
    • 挂起线程创建
    • 恢复线程执行

4. 实现效果展示

部分BOF运行效果:

  • Askcreds.obj
  • whoami.o
  • CheckAV.o

5. 参考资源

COFF文件解析与CoffLdr实现详解 1. COFF文件概述 COFF(Common Object File Format)是在程序编译过程中生成的二进制文件,通常以 .o 或 .obj 为扩展名。这些文件随后会被链接器处理,生成最终的PE可执行文件。 COFF文件结构 COFF文件主要由以下几个部分组成: CoffHead :文件头,不包含StringTable的偏移信息 Section Header :节区头表 SectionData :每个节的实际数据 Reloc Table :重定位表 Symbol Table :符号表 String Table :字符串表 2. 现有C2平台COFF加载器的问题分析 2.1 函数槽位不足问题 在Cobalt Strike中, MAX_DYNAMIC_FUNCTIONS 宏被硬编码为32(4.5版本),当BOF需要调用超过32个函数时会抛出"No slot for function"错误。 解决方案 : 应像Havoc框架那样,通过遍历symbolName动态分配GOT(Global Offset Table)大小 避免硬编码函数槽位限制 2.2 BSS段处理问题 BSS段用于存放未初始化的全局变量,不同编译器处理方式不同: Mingw(基于GCC) :显式创建.bss段 MSVC :不创建.bss段,将未初始化变量放入数据段或堆中 问题表现 : Cobalt Strike 4.9.1:Beacon崩溃 BRC4:需要新API格式 AdaptixC2:BOF解析逻辑缺失 解决方案 : 对于MSVC编译的COFF,需要手动创建BSS Table实现兼容 处理BSS段后,MSVC和Mingw编译的BOF都能正常输出 2.3 异常捕获问题 BOF崩溃会导致整个Beacon崩溃,解决方案: 使用VEH(Vectored Exception Handling) 捕获异常 异常触发时将RIP指向 CoffeeFunctionReturn (COFF的返回地址) 添加额外判断兼容某些BOF抛出的异常 2.4 OpSec问题 BOF执行会分配内存或踩踏内存,可能触发检测机制。 改进方案 : 使用Native API调用 实现模块踩踏(Module Stomping),使调用栈更正常 其他OpSec技术(留给读者探索) 2.5 BeaconGate替代方案 Cobalt Strike 4.10引入BeaconGate控制API调用,在没有此功能时可实现类似效果: IAT Hook :控制BOF的API调用 实现自定义"Gate"机制,提供开箱即用的规避效果 2.6 参数解析问题 Cobalt Strike Beacon有特定API用于参数解析: BeaconDataParse BeaconDataExtract BeaconDataInt BeaconPrintf 参数格式解析规则 : 跳过Buffer总长度字段(前4字节) 统一 BeaconDataInt 和 BeaconDataExtract 的结构 3. COFF解析实现细节 3.1 总大小计算(TotalSize) 定义 ParseTotalSize 函数: 遍历每个节,记录节大小和对齐 在每个节中查找重定向条目 查找每个符号,记录BSS Table Size和GOT Table Size 分配完整内存区域(而非多次分配) 3.2 BSS和GOT处理 处理流程: 判断是否为Beacon内部函数 是:遍历函数数组返回地址 维护 BeaconApi 数组实现后续行为函数 如果是 library$function 或 function 格式 使用 GetModule / LoadDll 获取函数地址 处理.bss段 跳过BSS Table的前4字节 返回BSS Table的Offset 将 pCoffSymbol->SectionNumber 改为对应的BSS Table Section和GOT Section 3.3 重定位处理(Process Relocation) 四种主要情况: IMAGE_REL_AMD64_REL32 && functionPtr != NULL IMAGE_REL_AMD64_REL32 → IMAGE_REL_AMD64_REL32_5 IMAGE_REL_AMD64_ADDR32NB IMAGE_REL_AMD64_ADDR64 3.4 获取入口点(Get EntryPoint) 遍历所有符号 获取EntryPoint地址 支持自定义入口点(不限于"go") 3.5 运行COFF(RunCoffee) 注册VEH捕获运行时崩溃 实现线程欺骗: 挂起线程创建 恢复线程执行 4. 实现效果展示 部分BOF运行效果: Askcreds.obj whoami.o CheckAV.o 5. 参考资源 Havoc Framework CoffeeLdr实现 CenCoffLdr项目