首页
论坛
专栏
课程

[系统底层] [原创]PE文件格式资源表解析

2019-2-19 14:18 1713

[系统底层] [原创]PE文件格式资源表解析

2019-2-19 14:18
1713
好久没发帖了,最近沉迷脱壳跟密码学,才想起来PE还差这个没解析,今天发出来,用论坛存档下~哈哈
十五之前都是年,抓住过年的小尾巴给大家拜个年!祝论坛的表哥们越来越长!
=============================================================================================
资源在PE中的存储是按照目录存储的一般是三层,每一层都是有一个IMAGE_RESOURCE_DIRECTORY作为一个索引后边跟着IMAGE_RESOURCE_DIR_ENTRY,他的这个三层存储是最恶心的,最后想了一下可以把这个资源目录项看成是用二叉树的排序树存储的,
每一个 IMAGE_RESOURCE_DIRECTORY 都是一个节点, IMAGE_RESOURCE_DIR_ENTRY 是边这样打印起来可能会方便一些,啥DFS,BFS也都能用上了
但是太抽象,我用不了深度优先跟广度优先。
大概的结构是这样的。


对于IMAGE_RESOURCE_DIRCTORY
typedef struct _IMAGE_RESOURCE_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    WORD    NumberOfNamedEntries;                    ////以名称(字符串)命名的入口数量
    WORD    NumberOfIdEntries;                            ////以ID(整型数字)命名的入口数量
//  IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

里边有用的只有后两项表示在这个 IMAGE_RESOURCE_DIRCTORY ,后边紧跟的 IMAGE_RESOURCE_DIR_ENTRY 数量

对于 IMAGE_RESOURCE_DIRECTORY_ENTRY,比较恶心,个人感觉重要的是这个NameISString,这个用来判断是不是系统规定的一些资源名。

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
	union {
		struct {
			DWORD NameOffset : 31;			//资源名偏移
			DWORD NameIsString : 1;			//资源名为字符串
		};
		DWORD   Name;					//资源/语言类型
		WORD    Id;					//资源数字ID
	};
	union {
		DWORD   OffsetToData;				//数据偏移地址
		struct {
			DWORD   OffsetToDirectory : 31;         //子目录偏移地址
			DWORD   DataIsDirectory : 1;	        //数据为目录
		};
	};
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
IMAGE_RESOURCE_DIR_STRING_U  当NameISString是1的时候nameoffet就起作用了,这时候指向的是这个IMAGE_RESOURCE_DIR_STRING_U结构
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
	WORD    Length;//字符串长度
	WCHAR   NameString[1];//字符串数组
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;

最后还有一个结构体,这个结构体是用来资源数据的信息,前两个有用其他的没有用。
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
	DWORD   OffsetToData;//资源数据的RVA
	DWORD   Size;//资源数据的长度
	DWORD   CodePage;//代码页
	DWORD   Reserved;//保留字段
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
打印的时候有一个坑
结构里面涉及的下一个目录项和指向资源块的地址都是相对于资源表的起始位置而不是文件起始位置。
大家打印的时候注意一下:
上代码,资源目录项还是比较恶心的,三层结构大家搞得时候一定要弄清楚……
VOID PrintfResourceTable(){
	//定义一个名称数组
	static char* szResName[0x11] =

	{ 0, "鼠标指针", "位图", "图标", "菜单", "对话框", "字符串列表",

	"字体目录", "字体", "快捷键", "非格式化资源", "消息列表",
	"鼠标指针组", "zz", "图标组", "xx", "版本信息"
	};


	//读取文件
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNtHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
	PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = NULL;

	printf("打印的文件是:%s!\n", INFILEPATH);
	//定义文件缓冲区
	LPVOID pFileBuffer = NULL;
	ReadPEFile(INFILEPATH, &pFileBuffer);

	if (pFileBuffer != 0){
		printf("文件打开成功!\n");
	}
	else
	{
		printf("文件打开失败!\n");
		free(pFileBuffer);
		return;
	}
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNtHeader) + 4);
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
	pDataDirectory = (PIMAGE_DATA_DIRECTORY)(((DWORD)pOptionalHeader) + 96);

	pDataDirectory += 2;

	PIMAGE_RESOURCE_DIRECTORY  pResourceDirectory = NULL;

	DWORD RESRVA = pDataDirectory->VirtualAddress;
	DWORD RESFOA = 0;
	RESFOA = RVATOFOA(RESRVA, RESFOA, pFileBuffer);

	//定位资源目录
	pResourceDirectory = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pFileBuffer + RESFOA);

	//printf("NumberOfNameEntries:%d,NumberOfIdEntries:%d\n", pResourceDirectory->NumberOfNamedEntries, pResourceDirectory->NumberOfIdEntries);

	//获取RESOURCEENTRY 的个数

	size_t NumEntry = pResourceDirectory->NumberOfIdEntries + pResourceDirectory->NumberOfNamedEntries;

	//资源目录表的宽度

	size_t dwResourceData = sizeof(PIMAGE_RESOURCE_DIRECTORY);

	//定位资源目录节点

	PIMAGE_RESOURCE_DIRECTORY_ENTRY pResEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pResourceDirectory + 16);

	//获取第一层
	for (size_t i = 0; i < NumEntry; i++)
	{
		if (!pResEntry[i].NameIsString){//0
			if (pResEntry[i].Id < 0x11)
				//如果id大于0x11就是自己写的ID资源
			{
				printf("资源类型ID:%p %s\n", pResEntry[i].Id, szResName[pResEntry[i].Id]);
			}
			else{
				char  type[20];
				sprintf_s(type, "%d", pResEntry[i].Id);
				printf("资源类型ID:%p %s\n", pResEntry[i].Id, type);
			}
		}
		else{//1那么这个第一个联合体的最高位为1,也就是说NameIsString为1,如果资源是未知的,这种资源属于字符串作为资源标识, Name就不会起作用了,NameOffset会指向IMAGE_RESOUCE_DIR_STRING_U的位置

			//先获取偏移
			PIMAGE_RESOURCE_DIR_STRING_U pStringRes = (PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pResourceDirectory + pResEntry[i].NameOffset);
			//定义一个用来接收自定义字符串的宽数组然后直接复制
			WCHAR szStr[MAX_PATH] = { 0 };
			memcpy_s(szStr, MAX_PATH, pStringRes->NameString, pStringRes->Length*sizeof(WCHAR));

			printf("资源名称:%ls \n", szStr);
		}

		//第二层
		if (pResEntry[i].DataIsDirectory){
			printf("第二层目录偏移是:%p\n", pResEntry[i].OffsetToDirectory);
			//定义二层目录的目录头 以及entry
			PIMAGE_RESOURCE_DIRECTORY pResDirectory2 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pResourceDirectory + pResEntry[i].OffsetToDirectory);

			PIMAGE_RESOURCE_DIRECTORY_ENTRY pResEntry2 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResDirectory2 + 1);
			//获得ENtry个数
			size_t NumEntry2 = pResDirectory2->NumberOfIdEntries + pResDirectory2->NumberOfNamedEntries;

			for (DWORD i = 0; i < NumEntry2; i++)
			{
				if (!pResEntry2[i].NameIsString)
				{
					printf("->资源标识ID:%d\n", pResEntry2[i].Id);
				}
				else
				{
					// 显示资源字符串
					// NameOffset为相对资源的文件偏移
					// 字符串偏移为 资源基地址+NameOffset
					PIMAGE_RESOURCE_DIR_STRING_U pstcString = (PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pResourceDirectory + pResEntry2[i].NameOffset);
					WCHAR szStr[MAX_PATH] = { 0 };
					memcpy(szStr,
						pstcString->NameString,
						pstcString->Length*sizeof(WCHAR)
						);
					printf("->资源字符串:%ls\n", szStr);
				}
			}
			//第三层
			PIMAGE_RESOURCE_DIRECTORY pResourceDirectory3 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pResourceDirectory + pResEntry2[i].OffsetToDirectory);
			printf("第三层目录:%d\n", pResourceDirectory3->NumberOfIdEntries);
			PIMAGE_RESOURCE_DIRECTORY_ENTRY pResEntry3 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResourceDirectory3 + 1);
			if (!pResEntry3[i].DataIsDirectory)
			{
				// 取数据偏移,显示数据
				PIMAGE_RESOURCE_DATA_ENTRY pResData = (PIMAGE_RESOURCE_DATA_ENTRY)((DWORD)pResourceDirectory + pResEntry3->OffsetToData);
				printf("->->->数据RVA:%x\t数据大小:%x\n", pResData->OffsetToData,pResData->Size);
				//printf("->->->数据大小:%x\n", pResData->Size);
				//printf("->->->数据RVA:%x\n", pResData->OffsetToData);
			}

		}
		printf("\n\n\n");
	}
}
下边是在CSDN上看的两个表哥的解析很有用 放一下
https://blog.csdn.net/u013761036/article/details/54934336
https://blog.csdn.net/obuyiseng/article/details/50260671
毕竟PE对程序员来说还是不够友好的,写起来会非常恶心。。。。


[招聘]欢迎市场人员加入看雪学院团队!

最新回复 (1)
Editor 2019-2-19 14:24
2
0
感谢分享!
游客
登录 | 注册 方可回帖
返回