资源表
PE中的相关资源可以通过程序进行深度定位,所获取的二进制字节码与资源脚本语句之间是一一对应的
这些数据可能是源代码内部需要用到的常景,比如 菜单选项、界面描述等;也可能是源代码外部的,比如程序的图标文件、背景音乐文件、配置文件等,以上这些数据统称为资源。
5.1 资源分类
资源数据在PE里是最复杂的一种。其难度主要体现在对资源数据的遍历定位上,以及资源块的不易阅读性。因为即使通过信息定位方法找到了资源块,其内部结构还需要进一步解析。
程序中常用的六类资源包括:
1.位图资源
2.光标资源
3.图标资源
4.菜单资源
5.对话框资源
6.自定义资源
5.1.1 位图、光标、图标资源
位图、光标和图标是标识程序用途、修饰程序的最简约的符号,一般对应ico、cur、ani 和bmp文件内容。
在对资源脚本文件进行定义时,通常使用文件名,最后由资源编译器rc.exe将像素数据读人,再转换为二进制格式存储在PE的资源表指向的位置。位图、光;标、图标这三类资源在脚本文件中的定义格式如下:
1.位图定义:namelD BITMAP [DISCARDABLE]位图文件名
2.光标定义:namelD CURSOR [DISCARDABLE]光标文件名
3.图标定义:namelD ICON [DISCARDABLE]图标文件名
1.namelD表示该资源的名字,在程序中使用资源时需要用到它,类似于文件的句柄。
2.BITMAP. CURSOR. ICON表示资源的类型。
3.DISCARDABLE关键字是可选项,表示在不用的时候可以从内存中暂时卸载掉。
注意 当文件名包含空格时,需要使用英文半角状态下的双引号引起来。
对应的外部文件可以使用绝对路径。
5.1.2 菜单资源
菜单是大部分应用程序都具备的资源。在资源脚本文件中,菜单的定义格式如下所示:
菜单 ID MENU [DISCARDABLE] BEGIN菜单项定义.......
END
其中,菜单ID可以是16位的整数,其赋值范围在1〜65535之间。菜单项的定义可以有三种,分别表示:
1.普通菜单项
2.菜单分隔符
3.弹出菜单
其语法结构分别如下所示:
MENUITEM 菜单文字,命令ID [,选项列表]MENUITEM SEPARATOR
POPUP 菜单文字[,选项列表]
EGIN菜单项定义.......
END
5.1.3 对话框资源
对话框也是大部分程序具备的一种资源。弹出式对话框人性化地排列若文本框、说明文 字和按钮等可视化控件,使复杂的计算机操作变得容易。
在资源脚本的定义中,对话框最为复杂,其语法如下:
对话枢 ID DIALOG [DISCARDABLE] x 坐标,y 坐标,宽度,高度[options]BEGIN
子窗口控件1
子窗口控件2
.......
END
5.1.4 自定义资源
通常,当开发者需要在PE文件中附带自定义数据时,可以使用自定义资源。其在资源 文件中的定义语法如下:
资源 ID 类型 ID (DISCARDABLE]
BEGIN数据定义.......
END
大部分情况下,都是将一个磁盘文件当做资源的内容。此时的语法简化为:
资源ID 类型ID [DISCARDABLE]文件名
类型ID可以是大于255的数值或字符串
5.2 PE资源表组织
5.2.1 资源表的组织方式
PE的资源组织方式类似于操作系统的文件管理方式。从根目录开始,下设一级子目滾、二级子目录和三级子目录:三级子冃录下才是文件。
一级子目录按照资源类型分类,如“光标” 一级子目录、“位图” 一级子目录、“菜单” 一级子目录、“字符串” 一级子目录、“加速键” 一级子目录等多个资源类型。
二级子目录按照资源的ID分类。例如,同样是“菜单” 一级子目录的内容,其下可以有: IDM_OPEN的ID号为2001、IDM_EXIT的ID号为2002、IDM_1的ID号为4000等多个菜单项。 三级子目录是按照资源的代码页分类,即不同的语言代码页对应不同的数据。其中,根 据语言可以分为简体中文、英文、繁体中文等多个代码页。
三级目录后即为节点,也就是所说的“文件”。这里的“文件”其实就是包含了资源数据 的指针和大小等信息的一个数据结构而已。对所有资源数据块的访问均可从这里开始。
从数据结构角度来看,资源表是一个四层的二叉排序树结构。其中,第一层为主干,第 二、三层为枝干,叶子节点为第四层。主干和枝干的节点即为资源目录结构单元
5.2.2 资源表数据定位
资源表是一张描述资源数据在PE中的分布情况的表。资源表是数据目录中注册的数据类型之一,其描述信息位于数据目录的第3个目录项中。
资源表数据所在地址RVA
源表数据大小
5.2.3 资源目录头 IMAGE_RESOURCE_DIRECTORY
资源表数据从第一级资源目录开始。资源的毎一级目录都会有一个资源目录头,它标识了该类资源的属性、创建日期和版本等信息.其中也包含了随后的目录项的数量描述信息。
详细结构定义如下:
IMAGE_RESOURCE_DIRECTORY STRUCT Characteristics //dd 0000h资源属性TimeDatestamp //dd 0004h时间戳MajorVersion //dw 0008h资源大版本号MinorVersion //dw 0008h资源小版本号NumberOfNamedEntries //dw 以名称命名的入口数量NumberOfIdEntries //dw 命名的入口数量
IMAGE RESOURCE DIRECTORY ENDS
各字段的详细解释:
73.IMAGE_RESOURCE_DIRECTORY.Characteristics
+0000h,双字。资源属性,保留为将来使用,必须为0。
74.IMAGE_RESOURCE_DIRECTORYTimeDateStamp
+0004h,双字。时间戳,即创建该资源的时间。
75.IMAGE_RESOURCE_DIRECTORY. MajorVersion IMAGE_RESOURCE_DIRECTORY. MinorVersion
+0008h,双字。资源的版本号。未用,大部分情况下为0。
76.IMAGE_RESOURCE_DIRECTORY. NumberOfNamedEntries
+000ch,双字。以名称命名的资源个数。
77.IMAGE_RESOURCE_DIRECTORY. NumberOfldEntries +000eh,双字。以ID命名的资源个数。
以上字段中,最重要的是76和77两个字段。在资源脚本文件中,定义资源时,既可以使用字符串作为名称来标识一个资源,也可以通过ID号来标识资源。资源目录项的数量等于两者之和。
5.2.4 资源目录项 IMAGE_RESOURCE_DIRECTORY_ENTRY
紧跟在资源目录后的数据结构,就是在资源目录中声明的资源目录项。一个资源目录可能有多个资源目录项(以名称定义的资源目录项或以ID定义的资源口录项,或者两者组合),目录项和目录项之间是线性排列的。首先按照字母升序(不分大小写)排列名称资源目录项, 然后再按ID升序排列ID资源目录项。
资源目录项数据结构的详细定义:
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;
77.IMAGE_RESOURCE_DIRECTORY_ENTRY.Name1
+0000h,双字。第一个union字段,它定义了目录项的名称或者ID。
该双字的髙位(即31位)如果为1,则表示低地址部分为一个指向Unicode字符串的指针 (注意,这里的字符串不是Ansi字符串,所以另有规定);如果为0,则表示该字段为一个编号。 资源中对字符串的定义全部采用Unicode编码,该指针并不直接指向一个以“\0”结尾 的字符串所在地址,而是指向了结构IMAGE_RESOURCE_DIR_STRING_U。该结构完善了指针的定义(即不仅包含指针,还包含指针指向的块长度,大家可以自己想想为什么这里需 要长度字段),其详细定义如下:
MAGE_RESOURCE_DIR_STRING_U STRUCT
Lengthl //dw OOOOh -字符串长度
Namestring //dw 0002h - Unicode 字符串,长度不确定
IMAGE_RESOURCE_DIR_STRING_U ENDS
78.IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData
+0004h,双字。这个字段是一个指针,当它的高位(第31位)为0时,指针指向的是描 述资源数据块的指针,通常出现在第三级目录中;当高位为1时,低位数据指向下一级目录块的起始地址。
提示:字段78和79中的地址并不是基于文件起始地址的,它的偏移是基于资源表的起始位置。
5.2.5 资源数据项 IMAGE_RESOURCE_DATA_ENTRY
资源数据项其实就是前面所说的“目录-文件”结构中的“文件”。它是通过三次目录 定位后找到的一个数据结构
如图,第三级目录项中的字段IMAGE_RESOURCE_DIRECTORY_ENTRY. OffsetToData指向了资源数据项,而资源数据项中的OfftetToData字段则指向了资源数据块。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JWfJBANr-1639892528787)(C:\Users\megaparsec\AppData\Roaming\Typora\typora-user-images\image-20211114191029716.png)]
IMAGE_RESOURCE_DATA_ENTRY STRUCTOffsetToData dd 0000h -资源数据的 RVASizel dd 0004h -资源数据的长度CodePage dd 0008h -代码页Reserved dd 000Ch -保留字段
IMAGE_RESOURCE_DATA_ENTRY ENDS
80.IMAGE_RESOURCE_DATA_ENTRY.OffsetToData
+0000h,双字。该字段是一个指向资源数据块的指针,是一个RVA值,在文件中访问时需要注意转换成文件偏移。此处指向的资源数据块还不是赤裸裸的资源信息,而是附加了一 些数据结构的资源块。后面7.4节还会对常用的资源块进行进一步的解析。
81.IMAGE_RESOURCE_DATA_ENTRYSizel
+0004h,双字。资源数据的大小。
82.IMAGE_RESOURCE_DATA_ENTRY.CodePage
+0008h,双字。代码页,未用,大多数情况下为0。
83.IMAGE_RESOURCE_DATA_ENTRY.Reserved
+000ch,双字。保留字段。总是为0。
对资源表的大部分编程,只要能解析出该结构中指定资源块所处的地址和资源块的大小
5.2.6 三级结构中目录项的区别
由于目录处的级别不同,目录中各字段所表述的内容也不一样;尽管它们具有相同的数 据结构和完全相同的字段,在不同级别的目录项中有些字段的含义是不一样的。本小节就专 门研究三级目录中目录项各字段不一样的地方。
1.IMAGE_RESOURCE_DATA_ENTRY.Name1
(1)字段最髙位(即31位)为1
当结构用于第一层目录时,表示这是一个非标准的类型。由该字段的低31位组成一个偏移值,该偏移是相对于资源基地址的特殊偏移地址。该地址指向一个IMAGE_RESOURCE_ DIR_STRING_U结构表示的Unicode字符串。字符串为非标准的类型的名字。类似于本章第 1节自定义资源中的“DLLTYPE”。
当结构用于第二层目录时,表示这是一个非标准的命名。由该字段的低31位组成一个偏移值,该偏移是相对于资源基地址的特殊偏移地址。该地址指向一个IMAGE_RESOURCE_ DIR_STRING_U结构表示的Unicode字符串。字符串为非标准的类型下的命名,类似于本章 第1节自定义资源中的“DIB_WINRESULT”。
当结构用于第三层目录时,表示这是一个标准的语言(没有预定义的代码页)。由该字段的低31位组成一个偏移值,该偏移足相对干资源基地址的特殊偏移地址。该地址指向一个IMAGE_ RESOURCE_DIR_STRING_U结构表示的Unicode字符串。字符串为彳麻准的语言的名字。
(2)字段第31位为0
当结构用于第一层目录时,表示这是标准的类型(预定义的类型)。由该字段的低16位组成整数标识符ID,由于该类型巳定义,所以可以通过该标识符获取预先定义的名字。例如 该值为03h,则名字表示预定义当中的“ICON”。
当结构用于第二层目录时,表示这是标准的命名(预定义的类型)。由该字段的低16位组成整数标识符ID来定义名字。
当结构用于第三层B录时,表示这是标准的语言代码(预定义的类型)。由该字段的低16位组成整数标识符ID,可以通过该标识符获取预先定义的语言的名称。如ID=2052,表 示该语言为Simpled_Chinese (简体中文)。大多数情况下,毎个资源的代码页只定义一种。
2.IMAGE_RESOURCE_DATA_ENTRY.OffsetToData
(1)字段第31位为1
当结构用于第-层目录时,由该字段低31位组成一个整数偏移地址。该地址是相对干资 源起始地址的偏移,该偏移指向下一个目录。
当结构用于第二层目录时,由该字段低31位组成一个整数偏移地址。该地址是相对于资 源起始地址的偏移,该偏移指向下一个目录。
第三层目录的该值第31位不为1。
(2)字段第31位为0
第一层目录的该值第31位不为0。 第二层目录的该值第31位不为0。
当结构用于第三层目录时,表示该卞段指向一个数据项IMAGE_RESOURCE_DATA_ ENTRY。 .
注意由低31位组成的地址是基于资源起始地址的。
5.2.7 遍历资源表
代码1:
// from:《WindowsPE权威指南》
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include<stdlib.h>
#include<stdio.h>#define FILE_PATH_IN "C:/Windows/System32/notepad.exe"static const char* szResName[0x11]
{0,"Corsor","Bitmap","Icon","Menu","Dialog","StringTable","FontDir","Font","Accelerator","RCDATA","MessageTable","GroupCursor","zz","GroupIcon","xx","Version"
};DWORD RVA2FOA(IN DWORD stRVA, IN LPVOID pFileBuffer)
{//重置头指针PIMAGE_DOS_HEADER pDosHeader = nullptr;PIMAGE_NT_HEADERS pNTHeader = nullptr;PIMAGE_FILE_HEADER pFileHeader = nullptr;PIMAGE_OPTIONAL_HEADER pOptionalHeader = nullptr;PIMAGE_SECTION_HEADER pSectionHeader = nullptr;pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + sizeof(pNTHeader->Signature));pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);//内存对齐DWORD dwMemAlignCount = pOptionalHeader->FileAlignment;//节区头个数DWORD dwSectionCount = pFileHeader->NumberOfSections;//距节区的偏移DWORD dwDiffer = 0;for (DWORD i = 0; i < dwSectionCount; i++,pSectionHeader++){//在内存中对齐后的大小DWORD dwBlockCount = 0;dwBlockCount = pSectionHeader->SizeOfRawData / dwMemAlignCount;dwBlockCount += (pSectionHeader->SizeOfRawData % dwMemAlignCount ? 1 : 0);DWORD dwBeginRVA = pSectionHeader->VirtualAddress;DWORD dwEndRVA = pSectionHeader->VirtualAddress + dwBlockCount * dwMemAlignCount;if (stRVA >= dwBeginRVA && stRVA < dwEndRVA){dwDiffer = stRVA - dwBeginRVA;return dwDiffer + pSectionHeader->PointerToRawData;}else if (stRVA < dwBeginRVA){return stRVA;}}return 0;
}DWORD ReadFile(OUT LPVOID* pFileBuffer,IN const char* lpszFile)
{LPVOID pTempFileBuffer = nullptr;FILE* pFile = nullptr;DWORD FileSize = 0;size_t n = 0;//打开文件pFile = fopen(lpszFile, "rb");if (pFile == nullptr){printf("文件打开失败");return 0;}//计算文件长度fseek(pFile, 0, SEEK_END);FileSize = ftell(pFile);fseek(pFile, 0, SEEK_SET);//分配缓冲区pTempFileBuffer = calloc(FileSize, sizeof(char));if (pTempFileBuffer == nullptr){printf("缓冲区分配失败");fclose(pFile);return 0;}//初始化缓冲区memset(pTempFileBuffer, 0, FileSize);//读取文件n = fread(pTempFileBuffer, FileSize, 1, pFile);if (n == 0){printf("读取文件失败");fclose(pFile);free(pTempFileBuffer);pTempFileBuffer = nullptr;return 0;}//关闭文件fclose(pFile);*pFileBuffer = pTempFileBuffer;pTempFileBuffer = nullptr;return n;
}void PrintResourceTable()
{PIMAGE_DOS_HEADER pDosHeader = nullptr;PIMAGE_NT_HEADERS pNTHeader = nullptr;PIMAGE_FILE_HEADER pFileHeader = nullptr;PIMAGE_OPTIONAL_HEADER pOptionalHeader = nullptr;PIMAGE_DATA_DIRECTORY pDataDirectory = nullptr;PIMAGE_RESOURCE_DIRECTORY pResourceTable = nullptr;PIMAGE_RESOURCE_DIRECTORY_ENTRY pResourceEntry = nullptr;LPVOID pFileBuffer = nullptr;DWORD dwSize = 0;dwSize = ReadFile(&pFileBuffer, FILE_PATH_IN);if (dwSize == 0 || pFileBuffer == nullptr){printf("读取文件失败");}pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + sizeof(pNTHeader->Signature));pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);//定位资源表pDataDirectory = (PIMAGE_DATA_DIRECTORY)(&pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]);pResourceTable = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pFileBuffer + RVA2FOA(pDataDirectory->VirtualAddress, pFileBuffer));pResourceEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResourceTable + 1);//解析第一层DWORD dwTypeCount = pResourceTable->NumberOfIdEntries + pResourceTable->NumberOfNamedEntries;for (DWORD i = 0; i < dwTypeCount; i++){//最高位为0if (pResourceEntry[i].NameIsString == 0){if (pResourceEntry[i].Id < 0x11){printf("资源类型ID:%d %s\n", pResourceEntry[i].Id, szResName[pResourceEntry[i].Id]);}else{printf("资源类型ID:%d\n", pResourceEntry[i].Id);}}//最高位为1else if (pResourceEntry[i].NameIsString == 1){PIMAGE_RESOURCE_DIR_STRING_U pStr = (PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pResourceTable + pResourceEntry[i].NameOffset);WCHAR szStr[MAX_PATH] = { 0 };memcpy(szStr, pStr->NameString, pStr->Length * sizeof(WCHAR));printf("资源类型名称:%ls\n", szStr);}//解析第二层if (pResourceEntry[i].DataIsDirectory == 1){printf("第二层目录偏移:%x\n", pResourceEntry[i].OffsetToDirectory);PIMAGE_RESOURCE_DIRECTORY pRes2 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pResourceTable + pResourceEntry[i].OffsetToDirectory);PIMAGE_RESOURCE_DIRECTORY_ENTRY pResEntry2 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRes2 + 1);DWORD dwCount = pRes2->NumberOfIdEntries + pRes2->NumberOfNamedEntries;for (DWORD i = 0; i < dwCount; i++){//最高位为0if (pResEntry2[i].NameIsString == 0){printf(" ->资源标识ID:%d\n", pResEntry2[i].Id);}else{PIMAGE_RESOURCE_DIR_STRING_U pStr = (PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pResourceTable + pResEntry2[i].NameOffset);WCHAR szStr[MAX_PATH] = { 0 };memcpy(szStr, pStr->NameString, pStr->Length * sizeof(WCHAR));printf(" ->资源名称:%ls\n", szStr);}//解析第三层if (pResEntry2[i].DataIsDirectory == 1){PIMAGE_RESOURCE_DIRECTORY pRes3 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pResourceTable + pResEntry2[i].OffsetToDirectory);PIMAGE_RESOURCE_DIRECTORY_ENTRY pResEntry3 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRes3 + 1);printf(" -->代码页标号为:%x\n", pResEntry3->Id);if (pResEntry3->DataIsDirectory == 0){PIMAGE_RESOURCE_DATA_ENTRY pResDataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)((DWORD)pResourceTable+pResEntry3->OffsetToData);printf(" --数据RVA:%x\n", pResDataEntry->OffsetToData);printf(" --数据大小:%x\n", pResDataEntry->Size);}}}}}}int main()
{PrintResourceTable();return 0;
}
代码2:
// from:《WindowsPE权威指南》
void RESOURCE_Dlg::MakeTree(HTREEITEM hitem,IMAGE_RESOURCE_DIRECTORY* lpIMAGE_RESOURCE_DIRECTORY)
{WORD nCount = lpIMAGE_RESOURCE_DIRECTORY->NumberOfIdEntries + lpIMAGE_RESOURCE_DIRECTORY->NumberOfNamedEntries;IMAGE_RESOURCE_DIRECTORY_ENTRY* lpRESOURCE_DIRECTORY = (struct _IMAGE_RESOURCE_DIRECTORY_ENTRY *)(lpIMAGE_RESOURCE_DIRECTORY + 1);for (int i = 0 ; i < nCount; i ++){if (lpRESOURCE_DIRECTORY->DataIsDirectory == 1){CString csTemp;DWORD RESOURCEAdder = lpRESOURCE_DIRECTORY->OffsetToDirectory + (DWORD)m_lpIMAGE_RESOURCE_DIRECTORY;csTemp.Format("资源编号%d : 目录地址%p",lpRESOURCE_DIRECTORY->Id,lpRESOURCE_DIRECTORY->OffsetToDirectory + m_dwMemImageBase);HTREEITEM newhitem = m_TreeRESOURCE.InsertItem(csTemp,hitem);MakeTree(newhitem,(struct _IMAGE_RESOURCE_DIRECTORY *)RESOURCEAdder);}else{CString csTemp;DWORD RESOURCEAdder = lpRESOURCE_DIRECTORY->OffsetToDirectory + (DWORD)m_lpIMAGE_RESOURCE_DIRECTORY;IMAGE_RESOURCE_DATA_ENTRY * lpRESOURCE_DATA = (struct _IMAGE_RESOURCE_DATA_ENTRY *)(lpRESOURCE_DIRECTORY->OffsetToDirectory + (DWORD)m_lpIMAGE_RESOURCE_DIRECTORY);csTemp.Format("数据地址%p,数据长度%p",lpRESOURCE_DATA->OffsetToData,lpRESOURCE_DATA->Size);m_TreeRESOURCE.InsertItem(csTemp,hitem);}lpRESOURCE_DIRECTORY++;}return;
}BOOL RESOURCE_Dlg::OnInitDialog()
{CDialog::OnInitDialog();m_TreeRESOURCE.DeleteAllItems();HTREEITEM hitem = m_TreeRESOURCE.InsertItem("资源表");MakeTree(hitem,m_lpIMAGE_RESOURCE_DIRECTORY);SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE)|TVS_CHECKBOXES|TVS_HASBUTTONS|TVS_HASLINES|TVS_LINESATROOT);return TRUE; // return TRUE unless you set the focus to a control// EXCEPTION: OCX Property Pages should return FALSE
}
5.4 PE资源表解析
5.4.1 资源脚本
PE.exe的资源定义在文件pe.rc中,文件中一共定义了三种资源:图标、菜单和对话框。 详细定义如下:
// from:《WindowsPE权威指南》
#include <resource.h>
#define ICO_MAIN 1000;常量定义
#define DLG_MAIN 1000
#define IDC_INFO 1001
#define IDM_MAIN 2000
#define IDM_OPEN 2001
#define IDM_EXIT 2002#define IDM_1 4000
#define IDM_2 4001
#define IDM_3 4002
#define IDM_4 4003ICO_MAIN ICON "mian.ico" ;图标定义DLG_MAIN DIALOG 50,50,544,399 ;对话框定义STYLE DS_MODALFRAME | WSPOPUP | WS_VISIBLE | WS_CAPTlON | WS_SYSMENU CAPTION "PE 文件基本信息 by qixiaorui"MENU IDM_MAINFONT 9,"宋体"BEGINCONTROL "",IDC_INFO,"RichEdit20A",196 | ES_WANTRETURN | WSCHILD | ES_READONLY | WS_VISIBLE |WSBORDER | WSVSCROLL | WSTABSTOP,0,0,540,396 ENDIDM MAIN menu discardable ;主菜单定义BEGINPOPUP "文件(&F)" BEGINmenuitem "打开文件(&0)…",IDM_OPEN menuitem separator menuitem "退出(&x) " , IDM_EXITENDPOPUP "查看"BEGINmenuitem "源文件",IDM_1 menuitem "窗口透明度",IDM 2menuitem separatormenuitem "大小",IDM_3 menuitem "宽度",I DM 4END
END
5.4.2 要明确的概念
1.资源表中的Unicode字符
资源文件中的所有字符串都以Unicode格式存储,每个字符都由一个16位(单字)值表 示,字符串以UNICODE_NULL (该值是两个“\0”字节)结束。
**资源编译器调用Windows API中的MultiByteToWideChar函数将ASCII字符串转换为Unicode字符串,所有溢出的字符 都被当做合法的Unicode字符直接存储。**当这些字符串被程序以ASCII字符读出(例如使用 LoadString API)时,系统将它们再由Unicode转换为ASCII字符。
仅有的例外是在RCDATA语句中的字符串。这些“伪”字符串并不是真正的字符串,只 被当做一些字节的集合。用户可能会用RCDATA i§句存储一些自定义的数据结构,如果一个“伪”字符串被自动转换为Unicode字符串存储起来,这个“伪”字符串就会按照Unicode 字符串的格式被存储,从而导致这个“伪”字符串字节码内容的改动。假设这个“伪”字符 串是一个PE文件的字节码,再次被释放以后,这个PE文件就可能无法运行了。因此,这些 “伪”字符串必须以它的本来面目存储下来,即字节码。若想在RCDATA语句中包含Unicode 字符串,用户可以使用带“L”前缀的字符串。
2.资源字节码对齐
为使二进制资源文件更容易读写,在Win32下,文件中的所有对象都是双字对齐的,包 括头信息和数据项。这并不会改变资源数据结构中每个字段的顺序,但会在这些字段中间增 加一些填充域;通常情况下,这些填充域的值均为0。
资源中的大部分类型都遵循该规则,但有两个除外,一个是字体(font),另一个是字体 目录(fontdir)。因为这两个结构直接复制自别的文件,它们并不被资源编译器所使用。
3.一个字段的多重定义
在接下来的数据结构中,大家会看到类似于“[名称或序数]”这样的字段描述。“[名称或序数]”字段的第一个单字,标志这个字段到底是一个数字还是一个字符串。如果它等于Oxffff (一个非法的Unicode字符),那么在它后面的单字信息就是一个类型序号(一个数字);否则, 这个字段就是一个Unicode字符串。
如果类型域是一个数字,那它就代表一个标准的或者用户自定义的资源类型。所有标准的Windows资源类型都被赋予一个特定的值(如下所示),它包含了绝大多数资源类型的类型序数。
/* 预定义的资源类型*/
#define RT_NEWRESOURCE 0x2000
#define RT_ERROR 0x7fff
#define RT_CURSOR 1
#define RT_BITMAP 2
#define RT_ICON 3
#define RT_MENU 4
#define RT_DIALOG 5
#define RT_STRING 6
#define RT_FONTDIR 7
#define RT_FONT 8
#define RT_ACCELERATORS 9
#define RT RCDATA 10
#define RT_MESSAGETABLE 11
#define RT_GROUP_CURSOR 12
#define RT_GROUP_ICON 14
#define RT_VERSION 16
#define RT_NEWBITMAP (RT_BITMAP | RT_NEWRESOURCE)
#define RT_NEWMENU (RT_MENU | RT_NEWRESOURCE)
#define RT_NEWDIALOG (RT_DIALOG | RT_NEWRESOURCE)
5.4.3 菜单资源表
菜单资源表数据结构
菜单资源由一个菜单头加一个菜单项的序列组成。菜单项有两种:弹出式(POPUP)菜 単项和普通菜单项。
菜单头结构定义如下:
MenuHeader STRUCTwVersion //dw 版本号。暂时取值为0cbHeaderSize //dw 头大小。暂时取值为0
MenuHeader ENDS
菜单头后面紧跟着菜单项,不同的菜单项具有不同数据结构的定义。
一般菜单项数据结构完整定义如下:
NormalMenuItem STRUCTfItemFlags //dw 菜单标志wMenuID //dw 菜单IDszItemText //dd UnicOde 格式字符串,不确定长度
NormalMenuItem ENDS
其中,菜单分隔符MENUITEM SEPARATE也是普通菜单项,不过,其名称为空,ID为0, 标志也为0
fltemFlags是描述菜单项的标志集合。如果POPUP位被设置,则此项为弹出式的菜单项, 否则就是一个普通的菜单项。
弹出式菜单项数据结构定义如下:
PopupMenuItem STRUCTfltemFlags //dw ? 菜单标志szItemText //dd ? Unicode 字符.大小不确定
PopupMenuItem ENDS
5.4.4 图标资源
图标有一套标准的大小和属性格式,且通常是小尺寸的。以ICO文件为例,一个ICO文 件就是一套相似的图片,每一张图片具有不同的尺寸和颜色数,目的是适应不同的计算机操 作系统和显示设备。
操作系统在显示一个图标时,会按照一定的标准选择图标中最适合当前显示环境和状态的图像。
1.ICO文件结构
每个ICO文件的开始都有一个头部信息。该头部信息描述了 ICO文件中存在多少个图 标,以及毎个图标的基本信息,头部信息的结构:
ICON_DIR STRUCTidreserved dw ;保留字,必須为0idtype dw ;资源类别,如粟是1表示为ICO文件idcount dw ;图标敎.量icondirentry ICON_DIR_ENTRY [idcount] <?> ;图标项,一个图标一项
ICON_DIR ENDS
idcount表示该ICO文件包含图标的数量。理论上,一个ICO文件最多可以包含65535个图标。接下来,是该文件所包含的毎一个图标的详细描述。
ICON_DIR_ENTRY STRUCTbWidth db ;宽度bHeight db ;高度bColorCount db ;颜色数bReserved db ;保留字.必埙为0wPlanes dw ;调色板wBitCount dw ;每个像素的位数dwBytesInRes dd ;资源长度
dwImageOffset dd ;资源在文件偏移
ICON_DIR_ENTRY ENDS
ICON_DIR_ENTRY结构记录了每一个图标的尺寸、色深、图标资源占用的字节数。 dwImageOffset是一个文件偏移地址,指向图标资源数据起始位置。PE文件中的图标保存格 式与“.ico”文件中图标的保存格式略有不同。PE文件中,把ICON_DIR和图标资源作为两 种资源类型分别保存,前者是RT_GROUP_ICON类型,后者是RT_ICON类型。
在“.ico”文件中,ICON_DIR_ENTRY结构最后一个成员dwImageOffset表示图标资源 文件偏移地址,是一个双字;而在PE文件中,GRP_ICON_DIR_ENTRY结构最后一个成员 nID是一个字,表示图标的索引ID。
ICO文件分为三部分:图标头、图标项和图标数据。这三部分在PE文件 中会被電新组合,其中,图标头会被i新组合为资源类型RT_GROUP_ICON,图标项和图标 数据则被组合为资源类型RT_ICON。
图标头的idcount字段定义ICO 里图标的个数;每个图标的属性由图标项定义;图标项的字段dwImageOffset则指向了 ICO文 件中该图标数据的位置。
5.4.4 对话框资源
对话框的头是一个DialogBoxHeader结构
PE中的资源表实际是一个四层的 二叉树 二进制排序树的典型应用
注:二进制排序树 来自该链接:PE Format 中The .rsrc Section部分:Resources are indexed by a multiple-level binary-sorted tree structure.