一、前言
C2植入物的功能模块中一般包含了RunPE,execute-shellcode,execute-assembly等等最基础的渗透功能,但是光靠这些是无法支持后渗透的复杂任务,为此我们需要引入BOF执行器。由于我用Go实现implant,使用Go来完成BOF执行器还是太过复杂,为了缩减implant的体积,有必要引入”扩展”的功能。
早期CS要实现复杂的功能,往往需要用到fork&run技术,CS 默认会临时启动一个牺牲进程(如 rundll32),再把反射DLL注入进去,有着明显的进程创建事件、父子进程链,并且该进程本身在EDR看来就是可疑的,因为它没有关联磁盘上的dll文件且无签名。
当然不能以现在的视角去评价过去的反射DLL注入技术,在当时这种做法是先进的,不过随着攻防对抗强度的升级,fork&run的做法不再符合OPSEC要求。
命运的转折点发生在2020年,Cobalt Strike 4.1 正式推出了Beacon Object File(BOF) 技术,用来替代早期“fork&run”模式,此项技术一经发布就被红队视为“OPSEC分水岭”,这一历史时刻注定是红队的狂欢,它把对抗强度从“进程链”拉回到“内存行为”层面,也直接催生了后续各类加载器(COFFLoader、coffee、goffloader等)和更激进的内存免杀研究,安全研究员们开发了数以百计的BOF,从简单的信息枚举到复杂的横向移动、凭证窃取,几乎覆盖了渗透测试的所有环节。COFFLoader 等项目更是证明了BOF理念的可移植性,使其不再局限于Cobalt Strike。
现如今,回顾这一技术里程碑,我们不仅能欣赏其设计之美,更能深刻理解攻防对抗螺旋式上升的内在动力:防御方在更高维度上布防,攻击方就必须在更深层次上突破。BOF正是这样一个推动整个领域向前迈进的关键节点。
要实现C2中的植入物,有一个必定要实现的功能就是BOF执行器,这可以说是现代C2的标配
二、CoffeeLoader的原理和代码实现
Windows CoffeeLoader的代码主要参考下面的两个项目:
- Cen4enCen/CenCoffLdr: A parser for COFF files.
- Havoc/payloads/Demon/src/core/CoffeeLdr.c at main · HavocFramework/Havoc
说是参考,其实90%的代码都是抄Cen4enCen师傅的实现,他的代码解决了大部分CoffeeLoader存在的问题,是一个非常优秀的CoffeeLoader实现,具体来说是:
- 动态分配GOT和BSS的大小。
- 处理.BSS段,适配MSVC和MINGW。
- 增加捕获异常的VEH,避免CoffeeLoader崩溃而导致整个Beacon崩溃。
- OPSEC操作,比如说模块踩踏、设置上下文劫持注入等等。
本文会详细的分析Cen4enCen师傅的项目,并写下自己学习CoffeeLoader思考过程,增加X86的适配。我觉得没有必要再造一个轮子项目,所以我不打算开源自己修改后的CoffeeLoader,有需要的就参考 Cen4enCen/CenCoffLdr: A parser for COFF files.,记得给Cen4enCen师傅点star啊。
CoffeeLoader大致步骤:
- 读取COFF文件到内存,此时还是磁盘文件的形式,并没有进行映射操作。
- 解析COFF文件,计算总大小、GOT表大小和BSS段大小。
- 使用模块践踏计算得到一个可写内存空间,把各节复制进去,并在尾部预留GOT和 .bss。
- 遍历 relocation,把外部符号解析成真实函数地址,把节内引用修补成正确偏移。
- 找到BOF 入口点(
go或_go),把.text设成可执行。 - 注册VEH,构造一个挂起线程的上下文,让线程从BOF入口开始跑。
- BOF跑完后,通过 RtlExitUserThread 退出,主线程等待结束并清理资源。
看起来好像很简单,但和反射DLL技术相比,CoffeeLoader真正困难的地方并不在“把文件读进内存”,而在于后续对COFF对象的手工处理。
对 CoffeeLoader来说,最重要也最复杂的两个步骤就是重定位和符号解析,因为这两步本质上是在模拟链接器的工作。也就是说,我们不是像正常程序那样把 .o 文件交给编译器和链接器去处理,而是要在运行时自己把对象文件中的节、符号、外部引用和地址关系重新组织起来,最终把 .o 文件链接到当前进程申请出的那块内存中,并让它能够像一个已经完成链接的模块一样正常执行。
⚠请注意,下面的流程分析不会给完整的源代码,会忽略很多细节方面的东西!
2.1 InitWin32Api
为了提高CoffeeLoader的OPSEC性,我们需要实现动态获取API的功能,并将其封装到一个 Win32Api 结构体中,请注意声明一个全局变量 win32Api 以备后续其他模块或者.c文件使用。

声明全局变量在某个头文件,然后在其他.c文件的做法是很多C/C++类型的Beacon的惯用做法。就比如Havoc在Demon.h中这样写 extern PINSTANCE Instance;。

然后在某一个 .c 文件里做定义,比如说Havoc的 Demon.c 里定义了Instance变量。

再然后其他.c文件(比如 CoffeeLdr.c)包含 Demon.h 文件,这样就可以使用到Instance中定义的一些变量,比如说 Win32 变量,以备使用动态获取到的WindowsAPI。


为了再动态获取API中避免使用到模块名和函数名等命名字符串,我们需要选择字符串hash算法得到唯一的hash值,至于算法的抉择,我选择使用ROTR算法,然后在计算 GetModuleHandleA、GetProcAddress、LoadLibraryA 和 CreateThread 的函数名hash,并在 CoffeeProcessSymbol 函数中使用到了存储着这些函数名hash的变量 LdrApi,我把CoffeeLoader放到有卡巴斯基的环境中测试,居然报毒了,通过简单控制变量法,我定位到其实是用到了 LdrApi 的这部分代码报毒,更进一步地排查出是使用到了这些函数名的ROTR13的hash值。

可能是太多C2使用到了ROTR13算法了吧,我将ROTR13改成ROTR14,即32位整数右移14位,这样卡巴斯基就不报毒了。

当然也可以参考Havoc的djb2风格的滚动字符串哈希吗,Cen4enCen师傅就使用到了这种Hash算法,目前卡巴斯基不报毒,请各位师傅放心参考!

也可以选择CRC32哈希算法,算法的选择是多样的,只要能将字符串作为输入参数,得到32位整数即可。
扯远了,回归正题,我们在最上面不是说声明了一个全局变量 Win32Api 吗,接下来就是用动态获取API获得目标Windows API,初始化 Win32Api 。
我定义了一个 InitWin32Api 函数,专门负责初始化 Win32Api 变量。具体来说是
- 使用
GetModuleByPeb获取Kernel32和ntdll这两个模块的地址。 GetApiAddressByHash根据指定的模块hash + 函数名hash找到目标APi的地址。

GetModuleByPeb 是经典的
- 获取
PEB - 从
PEB中获取Ldr - 从
Ldr中获取InMemoryOrderModuleList,遍历链表中的每一个LIST_ENTRY - 从
LIST_ENTRY获取LDR_DATA_TABLE_ENTRY从而拿到DllBase和BaseDllName,计算BaseDllName的hash值,与指定的模块hash值匹配。 - 返回目标模块的句柄。

GetApiAddressByHash 也是经典的操作
- DOS头->NT头->导出表
- 取出导出表里的三张关键表AddressOfNames,AddressOfFunctions,AddressOfNameOrdinals
- 获取导出函数名,计算导出函数名的hash,然后与目标
模块哈希 + 函数哈希匹配 - 如果匹配成功,则返回目标函数的地址。

我这里增加了x86的支持,具体是增加了一个 GetCurrentPeb 函数。

2.2 BofPack
一个合格的BOF加强器,给 ColbaltStrike BOF 的参数必须按照特定方式格式化,这样能更好的融入庞大的BOF生态。

常参数打包应该遵循如下的规则:
1 | [totalSize][arg1Size][arg1Data][arg2Size][arg2Data]... |
我们可以通过BofPack来打包任意个数的参数。

打包说完了,那么解析呢?一般BOF文件先用 BeaconDataParse 来解析参数,解析完后,parser->buffer指向第一个参数长度
1 | [arg1Size] [arg1Data] [arg2Size] [arg2Data]... |
再根据参数长度,选择 BeaconDataPtr、BeaconDataInt、BeaconDataShort、BeaconDataLength 和 BeaconDataExtract 获取参数,其中 BeaconDataExtract 的作用是“从当前游标位置取一个变长参数”,通常就是字符串或字节块。BeaconDataExtract 的流程是:
- 先读当前项的长度 Length
- buffer += 4,跳过长度字段
- 把当前位置当作数据起始地址返回
- 再把游标继续向后移动 Length
比如说下面的这个例子,先用 BeaconDataParse 来解析参数,随后用 BeaconDataExtract 获取lpwComputername和lpwPassword。

2.3 ParseTotalSize
在真正“装载并修补” COFF 之前,先预估整个BOF运行时需要多大内存,并顺便算出GOT表和 .bss要占多少空间。所以它本质上是在回答:
- 所有节复制进内存要多大
- 外部导入函数地址表要多大(对应got表的大小)
- 外部未解析数据区要多大(对应.bss)
遍历所有节,每次取出一个节 COFF_SECTION 并获取到该节的第一个重定位项 COFF_RELOC 的地址。

然后把该节的原始大小加入总大小,然后再对总大小做页对齐(4KB大小)操作。

遍历当前节的所有重定位项,每个重定项 COFF_RELOC 都会关联一个符号 CoffSymbol,其中 COFF_RELOC 决定重定位的类型,而 CoffSymbol 存储着符号名。
取出符号名。短名字(CoffSymbol->First.Value[0] != 0):直接从CoffSymbol.First.Name取8字节,长名字:符号名超过8字节,则从字符串表中取。

识别外部符号,只关注:StorageClass == IMAGE_SYM_CLASS_EXTERNAL、SectionNumber == 0,这表示它是外部未定义符号。然后根据外部符号区分是函数导入还是 .bss(全局未初始化的变量) 项。判断的标准是符号名是不是 __imp_ 这类导入符号前缀。
如果是:
- 说明它是外部函数引用
- dwNumberOfFunc++
如果不是:
- 说明它被当成 .bss
- 累加pCoffSymbol->Value到
*pstBSSSize - dwBssEntryNum++

最后汇总总大小
sizeof(PVOID) * dwNumberOfFunc:给GOT预留空间*pstBSSSize:给 .bss 预留空间0x4:额外预留的一个小标记/偏移保留区
为什么会有 *stTotalSize += 0x4 呢,这行的真实意思不是“COFF规范要求保留 4 字节”,而是这个项目自己定义的一套 .bss 偏移约定,我们看下面这个条件判断语句来自 CoffeeProcessSection 函数:
1 | if (!pvFunctionPtr && dwBssEntryOffset) |
dwBssEntryOffset隐含着:
- dwBssEntryOffset == 0:说明这不是 .bss
- dwBssEntryOffset != 0:说明这是 .bss
所以如果第一个 .bss 变量的偏移真的是0,那这里就没法区分,这是第一个 .bss 变量,还是根本没有 .bss 偏移。比如下图的一个例子,某个.bss变量在bss表中的偏移就是0,

为了解决这个问题,于是Cen4enCen用了一个很直接的办法:
- 把 .bss 的真实可用偏移整体后移4
- 让 0 永远只表示“无效/没有 .bss”
一句话总结:这 4 个字节不是给 .bss 数据本身用的,而是为了让 .bss 偏移从 4 开始,这样 0 就可以继续被当作“没有 .bss 符号”的标记值。大部分C2并没有实现bss段的解析,这一部分代码我感觉是Cen4enCen的精妙的解法。
不过调试分析的时候我发现GOT和BSS存在冗余分配的现象。
比如说下面的一个bof文件。

它的汇编代码如下图

按照ParseTotalSize的逻辑,dwNumberOfFunc 和 dwBssEntryNum 实际上表示所有外部符号在汇编代码中出现的次数。
__imp_BeaconPrintf 在代码中出现了了两次,出现一次占位则GOT表分配8字节,等待后续填充。
因为 num1 这个符号出现了2次,大小为 2 × dword = 8字节,str1 这个符号出现了2次,大小为 2 × qword = 16,故bss段的大小为 8 + 16 = 24字节。

冗余分配,但是精确记录。如果这个符号之前已经登记过了,就直接复用原来的偏移,只有第一次见到该符号时,才真正给它分配空间。以下是 CoffeeProcessSymbol 的确保精确记录的代码。
1 | if (BssEntry[dwCnt].pvSysmbolAddr == (PVOID)pCoffSymbol) |
下图是正确修正后并使用GOT和BSS的bof汇编代码。

2.4 CoffeeModuleStomping
模块践踏它不是自己新申请一块显眼的可执行内存去放恶意代码,而是先加载一个正常DLL,然后把这个 DLL映像里的部分内容覆盖掉,让自己的代码“住进”这个合法模块的内存区域里执行,显著的优点是:不出现新的可执行匿名内存。
CoffeeModuleStomping的代码如下

它的目标很明确:优先尝试加载并践踏,“clipwinrt.dll”作为载体;如果失败就退化为 NtAllocateVirtualMemory分配RW内存。之后在这篇内存区域内复制各节、布局GOT、布局BSS、做 relocation、执行 BOF。
2.5 节区映射与init Bss Entry
把COFF文件里的各个节手动复制到一块连续的新内存里,每复制完一个section,就把下一个section的起始地址对齐到页边界,一般是 0x1000。

所有节都复制到新内存后,紧接着构造GOT表和BSS。

所以内存布局大致像这样:
1 | 新内存起始地址 |
2.6 CoffeeProcessSymbol
符号解析:我引用的这个未定义的外部符号到底是谁
把一个重定项里引用到的未定义的外部符号,判断它到底是Beacon API、普通DLL导入函数,还是.bss变量,然后给出对于函数符号,给出最终函数地址;对于bss变量,则返回它在BSS段中的偏移。
Cen4enCen师傅的代码只能识别x64的外部函数符号,我在此基础上增加了x86的判断逻辑。
判断的依据是
x64的格式:
__imp_libraryName$functionName , __imp_functionName__imp_Beacon前缀
x86的格式:
__imp__libraryName$functionName和__imp__functionName__imp__Beacon前缀
.bss变量:如果不属于上面两种情况,则认为是.bss变量。
(一)Beacon API 符号
- GetImportPrefixSize() 判断是不是
__imp_或__imp__ - GetBeaconPrefixSize() 再判断是不是
__imp_Beacon或__imp__Beacon
如果是 Beacon 符号,流程是:
- 跳过
__imp_或__imp__前缀 - 去掉可能存在的前导下划线
- 去掉 x86 stdcall 后缀,比如 @8
- 把规范化后的名字拿去和
BeaconApi[]表逐项哈希比较 - 找到后把对应本地实现地址写入 *
pvFunctionAddr - 返回 TRUE

例如:
x64
x86
(二)普通导入符号
如果不是 Beacon,但有 __imp_ 或 __imp__ 前缀,就按DLL导入函数处理。
它支持两种情况。
- 标准格式:
__imp_library$function或__imp__libraryName$functionName
例如:
x64
x86
流程是:
- 检查符号名里有没有 $
- $ 前面当库名,后面当函数名
- 规范化库名,如果没有 .dll 就补 .dll
- 尝试先从
PEB找模块 - 找不到就
LoadLibraryA - 然后调用
LdrGetProcedureAddress拿函数地址 - 成功后写入
*pvFunctionAddr

- 非标准格式:
__imp_function或__imp__functionName

这种情况下它尽力而为,去 LdrApi[] 这个内置表里匹配一些常见函数,比如:
- LoadLibraryA
- GetProcAddress
- FreeLibrary
- CreateThread
- toWideChar
这类情况函数挺常见的,例如
x64
x86
(三) .bss 符号
如果不属于上面两种情况,则认为是.bss变量。
流程是:
- 遍历全局
BssEntry[] - 看这个
pCoffSymbol之前有没有登记过 - 如果登记过,就找到它前面所有
BSS变量大小之和 - 如果没登记过,就把当前符号的大小
pCoffSymbol->Value记录进去 - 最后把偏移写到
*pdwBssAddr

2.7 CoffeeProcessSection
重定位:我已经知道符号对应的目标是谁了,那当前这条指令/这块数据里应该写入什么值。
因为section被我们手动搬到了一块新内存里,原来对象文件里的地址/位移就不对了。
所以要根据relocation type,把引用位置修补成正确值。
主流程是:
- 遍历每个 section
- 取到该 section 的重定位表
- 遍历每一个 relocation
- 找到这个 relocation 对应的符号
- 判断这个符号是:
- 外部函数
- Beacon API
- .bss 变量
- 本地 section 内符号
- 根据重定位类型,把 pvRelocAddr 处的值修好
具体流程我就不过多介绍了,随便问个AI就能说的明明白白,下文我会着重讲解不同类型的重定位项如何修正。
(一)外部函数重定位

这段代码:
①真实函数地址放进GOT的一个槽位里。
1 | pCoffee->GOT[dwNumberOfFunc] = pvFunctionPtr; |
②把当前重定位字段修成指向这个GOT槽位的RIP相对偏移,因为 IMAGE_REL_AMD64_REL32 修的是一个 32位相对位移字段,不是64位绝对地址字段。
公式:
1 | offset = GOT槽位地址 - 重定位字段地址 - 4 |
或者更简洁地表示:
1 | offset = Target - NextIP |
举一个例子,我要修正 call cs:__imp_BeaconPrintf 这个指令
1 | …… |
- 000000000000002E -> FF 15
- 0000000000000030 -> 7C 00 00 00(重定位字段)
此时根据下面的代码,我们能得出重定位字段地址pvRelocAddr = 0x0000000000000030
1 | pvRelocAddr = pCoffee->SecMap[dwSectionCnt].Ptr + pCoffee->Reloc->VirtualAddress; |
NextIP = 重定位字段地址 + 重定位字段的长度,即NextIP = pvRelocAddr + 4 = 0000000000000034
GOT表的地址很简单,取地址就行:&pCoffee->GOT[dwNumberOfFunc]
最终
1 | Offset = GOT槽位地址 - (重定位字段地址 + 4) |
(二)本地 section 符号或 .bss 符号
当重定位类型位类型为 IMAGE_REL_AMD64_REL32 ~ REL32_5,这是RIP相对重定位,写进去的是 32 位位移,不是绝对地址,与上文分析过程类似,只不过多了后缀,其后缀 _1~_5 表示重定位字段后面跟着多长的操作数。
公式:
1 | Offset = Target - NextIP - extra |
这里:
①Target 是目标运行时地址,比如说本地section符号(如.data定义的字符串常量)
又或者.bss变量

对于.bss 符号,其bss变量地址为
1 | ulBssAddr = (ULONG_PTR)pCoffee->BSS + dwBssEntryOffset; |
pCoffee->BSS表示BSS节的地址dwBssEntryOffset表示bss变量在节内的偏移
本地section符号,其字符串常量地址为
1 | pvSymbolSecAddr + pCoffSymbol->Value |
pvSymbolSecAddr表示字符串常量所在的节的地址pCoffSymbol->Value表示字符串常量的节内偏移
②NextIP = pvRelocAddr + 4
③extra = pCoffee->Reloc->Type - 4
比如
- 当Type = IMAGE_REL_AMD64_REL32,extra = 4 - 4 = 0
- 当Type = IMAGE_REL_AMD64_REL32_1,extra = 5 - 4 = 1
- 当Type = IMAGE_REL_AMD64_REL32_2,extra = 6 - 4 = 2
- 当Type = IMAGE_REL_AMD64_REL32_3,extra = 7 - 4 = 3
- 当Type = IMAGE_REL_AMD64_REL32_4,extra = 8 - 4 = 4
- 当Type = IMAGE_REL_AMD64_REL32_5,extra = 9 - 4 = 5

有实际的例子吗,当然有啊,比如说下图,就是一个典型的Type = IMAGE_REL_AMD64_REL32_4

C7 05表示指令mov89 00 00 00是目标文件静态布局下的占位位移,编译器帮我们算出来的,大小为dword,即32位7B 00 00 00表示操作数7Bh,大小为dword,即32位
调试一下,修正 mov cs:num1, 7Bh 的地址。

计算得出offset = 0x00004ffd,并不是 89,因为我们的bof的各节是按页对齐,相比之前的磁盘文件形式,对齐后的文件体积会更大,计算出的偏移也会更大。

红框圈住的是 兜底机制,上面条件都不满足会进入到这里,这里不保证Offset的正确性。

剩余的 IMAGE_REL_AMD64_ADDR32NB、IMAGE_REL_AMD64_ADDR64 的重定位类型并不场景,就不过多介绍了。
还有x86的部分,有需要的就参考一下Havoc的实现 Havoc/payloads/Demon/src/core/CoffeeLdr.c at main · HavocFramework/Havoc

2.8 RunCoff
RunCoff 只负责找到BOF入口点,注册VEH,然后把它安全地跑起来
首先是找到BOF入口点:它遍历符号表,用 _Strcmp 把符号名和传进来的 szBofEntryPoint 比较。算出入口函数的运行时地址:pvCoffeeEntryPoint = SecMap[].Ptr(section被映射到内存后的基址) + Symbol.Value(该符号在所属section内的偏移)。

给 .text 节改执行权限:遍历section,找到名字哈希等于.text的代码节,然后调用NtProtectVirtualMemory把对应内存页改成PAGE_EXECUTE_READ

注册一个VEH 异常兜底:如果BOF跑崩了,不让整个Beacon一起崩溃,而是把线程执行流改到ExitThread上,尽量平滑退出。
用伪造线程上下文的方式传入参数,执行入口点。

2.9 VectoredExceptionHandler
VEH,即向量化异常处理,主要用于系统监控、反调试、全局异常捕获。注册VEH,并将优先级设置为最先 (0=最先,1=最后)之后,本进程发生异常时,系统会先按VEH链依次调用已注册的处理函数;某个处理函数可以选择继续分发,也可以修改上下文后直接恢复执行。
VectoredExceptionHandler:发生异常就把线程上下文改成 ExitThread(0),返回 EXCEPTION_CONTINUE_EXECUTION,线程从ExitThread(0) 开始跑并退出,loader主线程继续存活。

Cen4enCen提到某些BOF会抛出一个特殊的异常,该异常是 0xE06D7363,是MSVC C++ 异常常见的异常码。比如这个BOF:C2-Tool-Collection/BOF/Askcreds/SOURCE/Askcreds.c at main · outflanknl/C2-Tool-Collection
不过我在win11上用 mingw编译上述的bof,并用Cen4enCen的coffeeloader复现时,并没有出现异常 0xE06D7363,于是我编写一个bof文件,主动去触发 0xE06D7363 异常,我这么做的目的是验证是否所有的 0xE06D7363 异常都不影响CS的inline-execute,结果beacon崩溃了。算了我不研究了,这种情况应该不多见。
下图是win11上用 mingw编译上述的Askcreds.c,没有出现异常。

如果我们使用CS的inline-execute命令去执行这个BOF,也不会出现任何的问题。

所以这种情况应该很少见,但为了兼容某些BOF所抛出的异常,不至于直接结束BOF文件,我们可以添加下面的一个判断逻辑:如果状态异常码为 0xE06D7363,返回 EXCEPTION_CONTINUE_SEARCH 表示:“我不处理,让后面的VEH/SEH继续来”。

2.10 HitCoffeeEntryPoint

- 初始化 CONTEXT 和对象属性

- 创建一个挂起线程

具体来说就是使用 pfnNtCreateThreadEx 在本进程内创建一个挂起的线程,线程的起始地址为 win32Api.ulTpReleaseCleanupGroupMembers + 0x450,这个起始地址随意,比如我可以修改为 win32Api.pfnRtlExitUserThread 等合法的地址,毕竟后面代码马上会用NtSetContextThread把真实起点改掉

- 取出挂起线程的当前上下文并伪造 BOF 的执行现场
我增加了x86的代码逻辑
对于x64
- rip = BOF的入口点
- rcx = BOF的第一个参数(BOF执行所需的参数)
- rdx = BOF的第二个参数(BOF执行所需的参数的大小)
- ctx.Dr0 = ctx.Dr1 = ctx.Dr2 = ctx.Dr3 = 0,去掉硬件断点痕迹
- rsp = 返回地址,即RtlExitUserThread
对于x86,首先压入返回地址,之后通过栈来传递参数,从右往做依次入栈。

除了上面的执行方式外,我们也可以函数指针的方式执行,havoc就是这种执行方式。

- 写回上下文并恢复线程
这里就是把刚才伪造好的寄存器状态写回线程,然后恢复运行。恢复之后,线程就会从你指定的BOF入口点开始执行。

三、实验
执行whoami.o

执行Askcreds.o

总结
本文通过分析 Cen4enCen/CenCoffLdr: A parser for COFF files. 项目,学习现代红队的BOF加载器的原理和实现,并增加了x86的逻辑,对原项目进行扩展。
文章写的很随意,毕竟我没有打算发在先知社区,大家就在我的博客看个乐子就行。
我计划:从现在开始,到7月中旬,在本博客里每隔一个月发表至少一篇文章,不限长短,当然长篇文章居多,就当是我的一次小爆发吧。7月后我将实施一个更宏大的目标,这里暂且保密。
参考资料
- Cen4enCen/CenCoffLdr: A parser for COFF files.
- Havoc/payloads/Demon/src/core/CoffeeLdr.c at main · HavocFramework/Havoc
- trustedsec/CS-Situational-Awareness-BOF: Situational Awareness commands implemented using Beacon Object Files
- outflanknl/C2-Tool-Collection: A collection of tools which integrate with Cobalt Strike (and possibly other C2 frameworks) through BOF and reflective DLL loading techniques.