PE-资源表

资源表

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 STRUCT
     OffsetToData	dd 0000h  -资源数据的 RVA
     Sizel	        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++)
    {
    
    
        //最高位为0
        if (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);
            }
        }
        //最高位为1
        else 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++)
            {
    
    
                //最高位为0
                if (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	4003

ICO_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_MAIN
 FONT 9,"宋体"
 BEGIN
   CONTROL "",IDC_INFO,"RichEdit20A",196 | ES_WANTRETURN | WSCHILD | ES_READONLY | WS_VISIBLE |WSBORDER                 | WSVSCROLL | WSTABSTOP,0,0,540,396 
 END
 IDM MAIN menu discardable	;主菜单定义
 BEGIN
    POPUP "文件(&F)" 
 BEGIN
   menuitem "打开文件(&0)…",IDM_OPEN 
   menuitem separator 
   menuitem "退出(&x) " , IDM_EXIT
 END
 POPUP "查看"
 BEGIN
   menuitem "源文件",IDM_1 
   menuitem "窗口透明度",IDM 2
   menuitem separator
   menuitem "大小",IDM_3 menuitem "宽度",I DM 4
  END
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 STRUCT
   wVersion	    //dw 版本号。暂时取值为0
   cbHeaderSize	//dw 头大小。暂时取值为0
MenuHeader ENDS

菜单头后面紧跟着菜单项,不同的菜单项具有不同数据结构的定义。
一般菜单项数据结构完整定义如下:

NormalMenuItem STRUCT
  fItemFlags	//dw	菜单标志
  wMenuID	    //dw	菜单ID
  szItemText	//dd	UnicOde 格式字符串,不确定长度
NormalMenuItem ENDS

其中,菜单分隔符MENUITEM SEPARATE也是普通菜单项,不过,其名称为空,ID为0, 标志也为0
fltemFlags是描述菜单项的标志集合。如果POPUP位被设置,则此项为弹出式的菜单项, 否则就是一个普通的菜单项。

弹出式菜单项数据结构定义如下:

PopupMenuItem STRUCT
  fltemFlags //dw ?	菜单标志
  szItemText //dd ?  Unicode 字符.大小不确定
PopupMenuItem ENDS

5.4.4 图标资源

图标有一套标准的大小和属性格式,且通常是小尺寸的。以ICO文件为例,一个ICO文 件就是一套相似的图片,每一张图片具有不同的尺寸和颜色数,目的是适应不同的计算机操 作系统和显示设备。
操作系统在显示一个图标时,会按照一定的标准选择图标中最适合当前显示环境和状态的图像。

1.ICO文件结构
每个ICO文件的开始都有一个头部信息。该头部信息描述了 ICO文件中存在多少个图 标,以及毎个图标的基本信息,头部信息的结构:

ICON_DIR STRUCT
  idreserved       dw 	;保留字,必須为0
  idtype	       dw 	;资源类别,如粟是1表示为ICO文件
  idcount	       dw 	;图标敎.量
  icondirentry ICON_DIR_ENTRY [idcount] <?>	;图标项,一个图标一项
ICON_DIR ENDS	

idcount表示该ICO文件包含图标的数量。理论上,一个ICO文件最多可以包含65535个图标。接下来,是该文件所包含的毎一个图标的详细描述。

ICON_DIR_ENTRY STRUCT
  bWidth	    db		;宽度
  bHeight	    db		;高度
  bColorCount	db		;颜色数
  bReserved	    db		;保留字.必埙为0
  wPlanes	    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.

猜你喜欢

转载自blog.csdn.net/weixin_51732593/article/details/122023177