- 前言
- Cobalt Strike 简介
- Cobalt Strike 基本使用
- Cobalt Strike Beacon 命令
- Cobalt Strike 脚本使用
- Cobalt Strike 脚本编写
- Cobalt Strike 扩展
- Cobalt Strike 原理介绍
- Cobalt Strike 攻击防御
Beacon Object File(BOF 实现原理)
注:本文不是讲解 BOF 代码开发和使用而是讲解 BOF 实现原理。
介绍
Beacon Object File(BOF) 从 Cobalt Strike4.1 开始所添加的新功能,关于这个东西大家应该都有所了解它能加载运行 obj File,也就是编译后但未链接的目标文件。obj 链接过后就会变成 PE 文件或其他格式的本机程序
其实这个东西有点 shellcode 和 loader 的味道,先简单说一下过程
Beacon 在接收执行 obj 前,Cobalt Strike 会先对这个 obj 文件进行一些处理,比如解析 obj 文件中一些需要的段.text,.data,在处理一些表比如 IMAGE_RELOCATION,IMAGE_SYMBOL 等等,然后在经过一系列的处理后,会把需要的部分按照一定格式打包起来随后在发送给 Beacon,这时 Beacon 接收到的是 Cobalt Strike 已经解析处理过的 obj 文件数据,并非是原本的 obj 文件,所以 Beacon 主要做的是必须是在进程内才能确定并完成的事情比如处理重定位,填充函数指针等等,最后去执行 go 入口点
上面呢把基本流程说了一下接下来简单说一下 obj 文件相关的知识,并在最后来写一个 Demon 来实现这样的功能来研究一下其中的原理
obj 目标文件
obj 目标文件就是源代码编译之后但是未进行链接的那些中间文件(Windows 下的.obj 和 Linux 下的.o,本文主要指 windows 下的 obj)
维基百科解释链接: https://zh.wikipedia.org/wiki/%E7%9B%AE%E6%A0%87%E4%BB%A3%E7%A0%81
根据上面的简介不难看出 obj 文件它已经编译完成只差链接了,它里面已经包含编译好的二进制代码了
我们使用 cl.exe /c /GS- Demon.c /Fo Demon.obj 命令编译如下代码(CS 官方 BOF 代码示例,请编译为 x64)
#include <stdio.h>
#include <windows.h>
#include "beacon.h"
int go(char * args, int alen) {
BeaconPrintf(CALLBACK_OUTPUT, "Hello %s", args);
return 0;
}
因为 obj 是 COFF 文件格式的变种 PE COFF 所以我们要想解析还得去看看 PE COFF 结构(说实话讲 PE 结构的很多但是讲 PE COFF 结构的是真的少我唯一找到非常不错的就是微软官方的文档其他的基本没啥能看的)
PE COFF 格式我也懒得说了自己去看文档吧本文最后有,至于 PE COFF 文件结构查看工具我这里推荐 PEview 和 dumpbin 以及 objdump 这几个都还行
使用 PEview 查看一下 Demon.obj
主要关注的就是这些圈出来的
使用 objdump 工具-d 可以反汇编 obj 中特定属性的 section
可以看到我们编写的 go 函数
有过一定汇编经验的应该知道
lea rdx,[rip+0x0] 是在取"Hello %s"的地址,而 call QWORD PTR [rip+0x0]则对应调用 BeaconPrintf 函数,但是如果你仔细看你会发现上述两句指令的后四字节全部为 0,简单说这是因为在编译时无法确定地址所以需要在 obj 链接时由链接器修复这些问题,对其进行重定位。
(md 发现这个地方越想写的越多,要是真每块都写细太多还是别写了,如果想清楚的了解每个细节请去看编译原理链接部分和 PE COFF)
Demon 代码
我说一下代码的整体思路 1.加载读取 obj 文件 2.解析 IMAGE_FILE_HEADER 头 3.解析 IMAGE_SECTION_HEADER 节表 4.解析 IMAGE_SYMBOL 符号表 5.处理数据重定位和填充函数指针 6.根据符号表寻找 go 函数的地址然后 call 过去
以下代码使用 cl.exe /c /GS- Demon.c /Fo Demon.obj 编译为 obj(注:请使用 cl 并且编译为 x64 位)
#include <stdio.h>
#include <windows.h>
DECLSPEC_IMPORT void vPrintf(char * fmt, ...);
DECLSPEC_IMPORT DWORD WINAPI User32$MessageBoxA(HWND,LPCTSTR,LPCTSTR,UINT);
void go() {
vPrintf("Hello World");
User32$MessageBoxA(NULL,"hello",NULL,NULL);
}
//这里的 vPrintf 函数其实就相当于 Beacon 里的 Beaconxxxx 函数
//至于其他的 win api 使用方式我还是按照 CS 的来采用 DllName$FunName 这种格式
loader code
//仅支持 cl 编译的 x64 obj
#include "stdafx.h"
#include<Windows.h>
void vPrintf(char * fmt) {
printf(fmt);
}
int main()
{
HANDLE hFile = CreateFile(L"Demon.obj", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile error.\n");
return 0;
}
int file_size = 0;
file_size = GetFileSize(hFile, NULL);
char *buff;
buff = (char*)malloc(file_size);
DWORD dwRead;
if (!ReadFile(hFile, buff, file_size, &dwRead, NULL))
{
printf("ReadFile error.\n");
return 0;
}
//COFF 文件头
PIMAGE_FILE_HEADER PECOFF_FileHeader = (PIMAGE_FILE_HEADER)buff;
printf("Machine: %x \n", PECOFF_FileHeader->Machine);
printf("NumberOfSections %d \n", PECOFF_FileHeader->NumberOfSections);
printf("TimeDateStamp %d \n", PECOFF_FileHeader->TimeDateStamp);
printf("PointerToSymbolTable %d \n", PECOFF_FileHeader->PointerToSymbolTable);
printf("NumberOfSymbols %d \n", PECOFF_FileHeader->NumberOfSymbols);
printf("SizeOfOptionalHeader %d \n", PECOFF_FileHeader->SizeOfOptionalHeader);
printf("Characteristics %x \n", PECOFF_FileHeader->Characteristics);
//SizeOfOptionalHeader no
//COFF 节表处理
PIMAGE_SECTION_HEADER* PECOFF_SectionHeader_arr = (PIMAGE_SECTION_HEADER*)malloc(PECOFF_FileHeader->NumberOfSections * sizeof(PIMAGE_SECTION_HEADER));
memset(PECOFF_SectionHeader_arr, 0, PECOFF_FileHeader->NumberOfSections * sizeof(PIMAGE_SECTION_HEADER));
PIMAGE_SECTION_HEADER PECOFF_SectionHeader = (PIMAGE_SECTION_HEADER)(buff + sizeof(IMAGE_FILE_HEADER));
for (size_t i = 0; i <= PECOFF_FileHeader->NumberOfSections-1; i++)
{
PECOFF_SectionHeader_arr[i] = PECOFF_SectionHeader;
printf("段名称 %s \n", PECOFF_SectionHeader->Name);
printf("段大小 %d \n", PECOFF_SectionHeader->SizeOfRawData);
PECOFF_SectionHeader++;
}
//重定位表
int Relocation_len=0;
for (int i = 0; i <= PECOFF_FileHeader->NumberOfSections-1; i++)
{
Relocation_len+=PECOFF_SectionHeader_arr[i]->NumberOfRelocations;
}
int x = 0;
PIMAGE_RELOCATION* PECOFF_Relocation_arr = (PIMAGE_RELOCATION*)malloc(Relocation_len * sizeof(PIMAGE_RELOCATION));
memset(PECOFF_Relocation_arr, 0, Relocation_len * sizeof(PIMAGE_RELOCATION));
for (int i = 0; i <= PECOFF_FileHeader->NumberOfSections-1; i++)
{
if (PECOFF_SectionHeader_arr[i]->NumberOfRelocations)
{
PIMAGE_RELOCATION PECOFF_Relocation = (PIMAGE_RELOCATION)(buff + PECOFF_SectionHeader_arr[i]->PointerToRelocations);
for (int y = 0; y < PECOFF_SectionHeader_arr[i]->NumberOfRelocations; y++)
{
PECOFF_Relocation_arr[x] = PECOFF_Relocation;
PECOFF_Relocation++;
x++;
}
}
}
//打印输出
//符号表
PIMAGE_SYMBOL PECOFF_SYMBOL = (PIMAGE_SYMBOL)(buff + PECOFF_FileHeader->PointerToSymbolTable);
PIMAGE_SYMBOL* PECOFF_SYMBOL_arr = (PIMAGE_SYMBOL*)malloc(PECOFF_FileHeader->NumberOfSymbols * sizeof(PIMAGE_SYMBOL));
memset(PECOFF_SYMBOL_arr, 0, PECOFF_FileHeader->NumberOfSymbols * sizeof(PIMAGE_SYMBOL));
for (int i = 0; i <= PECOFF_FileHeader->NumberOfSymbols-1; i++)
{
PECOFF_SYMBOL_arr[i] = PECOFF_SYMBOL;
PECOFF_SYMBOL++;
}
//无需处理 NumberOfAuxSymbols
//处理重定位和函数指针
char* Fun_ptr = buff + PECOFF_SectionHeader_arr[0]->PointerToRawData;
for (int i = 0; i <= PECOFF_FileHeader->NumberOfSections - 1; i++)
{
if (PECOFF_SectionHeader_arr[i]->NumberOfRelocations)
{
PIMAGE_RELOCATION PECOFF_Relocation = (PIMAGE_RELOCATION)(buff + PECOFF_SectionHeader_arr[i]->PointerToRelocations);
for (int y = 0; y < PECOFF_SectionHeader_arr[i]->NumberOfRelocations; y++)
{
int sys_index = PECOFF_Relocation->SymbolTableIndex;
if (PECOFF_SYMBOL_arr[sys_index]->StorageClass == 3)
{
char* patch_data = buff + (PECOFF_Relocation->VirtualAddress + PECOFF_SectionHeader_arr[i]->PointerToRawData);
*(DWORD*)patch_data = ((DWORD64)(buff + ((PECOFF_SYMBOL_arr[sys_index]->Value) + (PECOFF_SectionHeader_arr[PECOFF_SYMBOL_arr[sys_index]->SectionNumber-1]->PointerToRawData))) - (DWORD64)(patch_data + 4));
}
else
{
if (!(PECOFF_SYMBOL_arr[sys_index]->N.Name.Short))
{
char* pstr = (buff + PECOFF_FileHeader->PointerToSymbolTable) + (PECOFF_FileHeader->NumberOfSymbols * sizeof(IMAGE_SYMBOL));
pstr += (DWORD)(PECOFF_SYMBOL_arr[sys_index]->N.Name.Long);
if (!strcmp(pstr, "__imp_vPrintf"))
{
char* patch_data = buff + (PECOFF_Relocation->VirtualAddress + PECOFF_SectionHeader_arr[i]->PointerToRawData);
*(DWORD64*)Fun_ptr = (DWORD64)vPrintf;
*(DWORD*)patch_data = ((DWORD64)Fun_ptr - (DWORD64)(patch_data + 4));
DWORD64* ptr = (DWORD64*)Fun_ptr;
ptr++;
Fun_ptr = (char*)ptr;
}
else
{
pstr += 6;
char *dllname;
char *funname;
dllname = strtok(pstr, "$");
funname = strtok(NULL, "$");
DWORD64 fun_add=(DWORD64)GetProcAddress(LoadLibraryA(dllname), funname);
char* patch_data = buff + (PECOFF_Relocation->VirtualAddress + PECOFF_SectionHeader_arr[i]->PointerToRawData);
*(DWORD64*)Fun_ptr = (DWORD64)fun_add;
*(DWORD*)patch_data = ((DWORD64)Fun_ptr - (DWORD64)(patch_data + 4));
DWORD64* ptr = (DWORD64*)Fun_ptr;
ptr++;
Fun_ptr = (char*)ptr;
}
}
}
PECOFF_Relocation++;
}
}
}
//寻找 go 函数作为入口点
DWORD oep;
for (int i = 0; i < PECOFF_FileHeader->NumberOfSymbols - 1; i++)
{
if (!strncmp((char*)(PECOFF_SYMBOL_arr[i]->N.ShortName),"go",2))
{
oep = PECOFF_SYMBOL_arr[i]->Value;
}
}
char * jmp;
for (int i = 0; i < PECOFF_FileHeader->NumberOfSections - 1; i++)
{
if (!strncmp((char*)PECOFF_SectionHeader_arr[i]->Name, ".text", 5))
{
jmp = (buff + PECOFF_SectionHeader_arr[i]->PointerToRawData + oep);
}
}
printf("0x%016I64x \n", jmp);
DWORD Protect;
if (VirtualProtect(buff, file_size, PAGE_EXECUTE_READWRITE, &Protect)!=0)
{
((void(*)(void))jmp)();
};
//printf("%x",GetLastError());
return 0;
}
演示效果如图所示
需要说明的是以上 loader 代码有硬编码的地方而且还有一些其他问题,不过作为一个 Demon 代码足以,剩下的自己研究一下吧不说了(其实也没啥好说的无非就是处理重定位和函数指针之类的)
微软官方 pe coff 文档:https://github.com/WBGlIl/CobaltStrike-file/blob/master/pecoff_v11.pdf
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论