Binary:COFF文件加载

1. 问题

问题是这样的,有某个运行场景,编译完成的二进制代码无法进行链接操作,只能维持在 .o(Linux).obj(windows) 状态,但是在运行时需要使用这些二进制文件中的代码。所以需要编写一个加载器来手动加载这些未经链接的二进制文件。

2. COFF文件

这个状态的文件有个名字COFF(Common Object Format File)。该文件的与PE文件非常类似(目前我只看了windows平台上的情况),可以说PE文件就是它的子集。有关COFF文件的详细介绍可以查看微软官方文档

3. 加载过程

COFF文件加载核心是重定位,既有函数的也有变量的。当然这种重定位只能局限在当前文件内,当前文件外的内容也没有办法。这里只是分享应当如何被加载,希望达到的效果就是与IDA打开之后相同的内容,不过呈现的地方在内存中罢了

3.1 结构体

解析COFF文件的结构体都包含在了winnt.h头文件中,可以不用自己来创建相关的结构体进行解析。需要用到大概这4个结构体:IMAGE_FILE_HEADERIMAGE_SECTION_HEADERIMAGE_SYMBOLIMAGE_RELOCATION。他们的定义如下:

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER;

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER;

typedef struct _IMAGE_SYMBOL {
    union {
        BYTE    ShortName[8];
        struct {
            DWORD   Short;     // if 0, use LongName
            DWORD   Long;      // offset into string table
        } Name;
        DWORD   LongName[2];    // PBYTE [2]
    } N;
    DWORD   Value;
    SHORT   SectionNumber;
    WORD    Type;
    BYTE    StorageClass;
    BYTE    NumberOfAuxSymbols;
} IMAGE_SYMBOL;

typedef struct _IMAGE_RELOCATION {
    union {
        DWORD   VirtualAddress;
        DWORD   RelocCount;             // Set to the real count when IMAGE_SCN_LNK_NRELOC_OVFL is set
    } DUMMYUNIONNAME;
    DWORD   SymbolTableIndex;
    WORD    Type;
} IMAGE_RELOCATION;

3.2 节偏移

COFF文件中包含有很多节,节在文件中存储时是紧挨着的,当加载到内存中时需要以8对齐,这就是地址能够被8整除。所以需要事先更具每个节的大小计算出每个节在内存中的偏移地址。

偏移的计算是从第1个节开始,第0节的偏移是0,不需要计算。将上一个节的大小向上与8对齐,然后加上上一个节的偏移就能够得到当前节的内存偏移。这里的计算与IDA加载时有些不同,当某个节的大小为0时我不会为其分配8字节,而IDA会保证每个节至少有8字节。

		//save the offset of section in the memory
		for (int i = 1; i < FileHeader->NumberOfSections; i++) {
			int SectionSize = Section[i - 1].SizeOfRawData;
			//make the section align with LOADER_ALIGN
			if (SectionSize % LOADER_ALIGN) {
				SectionSize = (SectionSize / LOADER_ALIGN + 1) * LOADER_ALIGN;
			}
			SectionMemOffset[i] = SectionMemOffset[i - 1] + SectionSize;
			DBPRINT("SectionMemOffset %d is 0x%x\n", i, SectionMemOffset[i]);
		}

3.3 重定位

3.3.1 重定位地址(VirtualAddress)

重定位地址,就是需要写入数据的地址。这个地址可以通过遍历Reloc_Table来获得,其中的VirtualAddress就是相对于本节的重定位地址。

进行重定位前要对Type进行确认,只有当其为IMAGE_REL_AMD64_REL32才有需要进行重定位,其他类型可以暂时不考虑。

SymbolTableIndex可以用来寻找重定位数据所在的位置。

typedef struct _IMAGE_RELOCATION {
    union {
        DWORD   VirtualAddress;
        DWORD   RelocCount;            
    } DUMMYUNIONNAME;
    DWORD   SymbolTableIndex;
    WORD    Type;
} IMAGE_RELOCATION;

3.3.2 重定位数据(Value)

重定位数据,就是数据所在的地址。这个地址可以通过重定位表中的SymbolTableIndex找到对应的符号表项,其中的Value就是相对于本节的地址。

SectionNumber是以1开始计数的节索引,可以用来得到该符号所在的节。
StorageClass需要在重定位前进行判断,判断是否为IMAGE_SYM_CLASS_STATIC类型,如果是则需要重新考虑Value的计算方式。

typedef struct _IMAGE_SYMBOL {
	...
    DWORD   Value;
    SHORT   SectionNumber;
    WORD    Type;
    BYTE    StorageClass;
	...
} IMAGE_SYMBOL;

3.3.3 .rdata节数据

对于.rdata节有关的数据进行重定位需要进行特殊对待。

此时符号表中的StorageClassIMAGE_SYM_CLASS_STATIC类型,且符号表中的Value为0,而修正的数据在存放在VirtualAddress所在的位置。

3.3.4 结果计算

假设VirtualAddress所在的节为iValue所在符号表项为Clause,节偏移表为SectionMemOffset

需要注意地址的计算起始是下一条指令,而偏移本身为4字节,所以需要加上一个4进行修正。

int Address = Value - (VirtualAddress + 4) - (SectionMemOffset[i] - SectionMemOffset[Clause->SectionNumber - 1]);

3.4 整体代码

#include <Windows.h>
#include <stdio.h>
#include <stdint.h>

//#define DEBUG
#ifdef DEBUG
#define DBPRINT(str, ...) printf(str, ##__VA_ARGS__);
#define SETCOLOR(color) SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
#define DBPRINTCOLOR(color, str, ...) SETCOLOR(color); printf(str, ##__VA_ARGS__); SETCOLOR(CMD_WHITE);
#define CMD_RED	FOREGROUND_RED
#define CMD_WHITE FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
#define CMD_YELLOW FOREGROUND_RED |FOREGROUND_GREEN
#define CMD_GREEN FOREGROUND_GREEN
#define CMD_BLUE FOREGROUND_BLUE
#define CMD_PINK FOREGROUND_RED | FOREGROUND_BLUE
#define CMD_CYAN FOREGROUND_GREEN | FOREGROUND_BLUE
#else
#define DBPRINT(str, ...)
#define SETCOLOR(color)
#define DBPRINTCOLOR(color, str, ...)
#endif // DEBUG
#define LOADER_ALIGN 0x8

INT64* Loader(char* FileName) {	
	FILE* file = fopen(FileName, "rb");
	if (file == NULL) {
		printf("open %s file error\n", FileName);
		return NULL;
	}

	//get the size of the file
	fseek(file, 0, SEEK_END);
	int FSize = ftell(file);
	fseek(file, 0, SEEK_SET);

	//read the file
	char* buf = (char*)malloc(FSize);
	char* oribuf = buf;
	int readsize = fread(buf, 1, FSize, file);
	if (readsize != FSize) {
		return NULL;
	}
	SETCOLOR(CMD_YELLOW);
	DBPRINT("Loader %s\n", FileName);
	SETCOLOR(CMD_WHITE);

	IMAGE_FILE_HEADER* FileHeader = (IMAGE_FILE_HEADER*)buf;
	IMAGE_SECTION_HEADER* Section = (IMAGE_SECTION_HEADER*)(buf + sizeof(IMAGE_FILE_HEADER));
	IMAGE_SYMBOL* SymbolTable = (IMAGE_SYMBOL*)(buf + FileHeader->PointerToSymbolTable);
	IMAGE_RELOCATION* RelocTable = NULL;
	char* StringTable = (char*)&SymbolTable[FileHeader->NumberOfSymbols];	//this is for the debug
	int* SectionMemOffset = (int*)malloc(FileHeader->NumberOfSections * sizeof(int));
	memset(SectionMemOffset, 0, FileHeader->NumberOfSections * sizeof(int));
	char* RawData = NULL;

	//save the offset of section in the memory
	for (int i = 1; i < FileHeader->NumberOfSections; i++) {
		int SectionSize = Section[i - 1].SizeOfRawData;
		//make the section align with LOADER_ALIGN
		if (SectionSize % LOADER_ALIGN) {
			SectionSize = (SectionSize / LOADER_ALIGN + 1) * LOADER_ALIGN;
		}
		SectionMemOffset[i] = SectionMemOffset[i - 1] + SectionSize;
		DBPRINT("SectionMemOffset %d is 0x%x\n", i, SectionMemOffset[i]);
	}
	DBPRINT("\n");

	//relocate the data
	for (int i = 0; i < FileHeader->NumberOfSections; i++) {
		char* FileAddress = NULL;
		IMAGE_SYMBOL* Clause = NULL;
		int RVAOffset = 0;
		int BaseOffset = 0;
		//1. when relocate the function, the Value field of SymbolTable is the Value.
		//2. however, when it comes to relocate data referring rdata, the Value become zero
		//the Value needed is saved in the VirtualAddress position.
		int Value = 0;
		RelocTable = (IMAGE_RELOCATION*)(buf + Section[i].PointerToRelocations);
		RawData = buf + Section[i].PointerToRawData;
		for (int j = 0; j < Section[i].NumberOfRelocations; j++) {
			if (RelocTable[j].Type != IMAGE_REL_AMD64_REL32) {
				continue;
			}
			Clause = &SymbolTable[RelocTable[j].SymbolTableIndex];
			FileAddress = RawData + RelocTable[j].VirtualAddress;
			//the rdata
			if (Clause->StorageClass == IMAGE_SYM_CLASS_STATIC) {
				Value = *(int*)FileAddress;
			}
			//the function
			else {
				Value = Clause->Value;
			}
			RVAOffset = Value - (RelocTable[j].VirtualAddress + 4);
			BaseOffset = SectionMemOffset[i] - SectionMemOffset[Clause->SectionNumber - 1];
			*(int*)FileAddress = RVAOffset - BaseOffset;
						
			//Below is the Debug Information
			//the Name is less than 8 bytes
			if (Clause->N.Name.Short) {
				SETCOLOR(CMD_CYAN);
				DBPRINT("Name : %s\n", Clause->N.ShortName);
				SETCOLOR(CMD_WHITE);
			}
			//the Name is more than 8 bytes
			else {
				SETCOLOR(CMD_CYAN);
				DBPRINT("Name : %s\n", StringTable + Clause->N.Name.Long);
				SETCOLOR(CMD_WHITE);
			}
			DBPRINT("Value 0x%x\n", Value);
			DBPRINT("VirtualAddress 0x%x\n", RelocTable[j].VirtualAddress);
			DBPRINT("BaseOffset 0x%x\n", BaseOffset);
			DBPRINT("Relocation Offset 0x%x\n", *(int*)FileAddress);
			DBPRINT("\n");
		}
	}

	//write to the execute memroy
	int TotalSize = SectionMemOffset[FileHeader->NumberOfSections - 1] + Section[FileHeader->NumberOfSections - 1].SizeOfRawData;
	LPVOID pAddr = VirtualAlloc(NULL, TotalSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	memset(pAddr, 0x90, TotalSize);

	//loader to the executable memory
	for (int i = 0; i < FileHeader->NumberOfSections; i++) {
		memcpy((char*)pAddr + SectionMemOffset[i], buf + Section[i].PointerToRawData, Section[i].SizeOfRawData);
	}
	DBPRINT("\n");
	fclose(file);
	free(oribuf);
	free(SectionMemOffset);

	return (INT64*)pAddr;
}

4. 探索历程

这一章是我的私货内容,记录一下上述代码的探索历程。

刚接触这个问题的时候也是一脸茫然的,进行搜索时没有找到相应的代码,还在至少找到了微软对于COFF文件的官方文档。探索的过程主要使用了两个工具010 editorIDA,这两个工具使用起来太方便的。

通过010 editor可以看到COFF文件的具体二进制形式,同时相应的模板也使用了对应的结构体对于文件进行了分析。

在这里插入图片描述 其中可以清晰的得到每个字段的内容。

IDA中则是已经加载好了COFF文件内容,当COFF被加载到内存中是应当与其相似。
在这里插入图片描述
一开始我并不知道如何计算重定位所需要的内容,IDA中是已经加载好的内容,而在010 editor中重定位地址处的值为0(.rdata节数据除外)。通过010 editor中看到的数据进行手动尝试,不断的计算与IDA中地址进行比较,最后得到了3.3.4中的计算公式。

如果某位读者想尝试手动计算,也可以试试,通过010获取相应的数据按照给出的公式进行计算,然后使用IDA中的结果进行验证。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值