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

有过一定汇编经验的应该知道

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

最后更新于