• 2.8 PE结构:资源表详细解析


    在Windows PE中,资源是指可执行文件中存放的一些固定不变的数据集合,例如图标、对话框、字符串、位图、版本信息等。PE文件中每个资源都会被分配对应的唯一资源ID,以便在运行时能够方便地查找和调用它们。PE文件中的资源都被组织成一个树形结构,其中最顶层为根节点(Root),下一级为资源类型(Type),再下一级为资源名称(Name),最终是实际的资源内容。

    PIMAGE_RESOURCE_DIRECTORY是Windows PE可执行文件中的一个结构类型,用于描述资源(Resource)的树形结构,其中包括了每个资源的类型(Type)、名称(Name)和语言(Language),以及指向下一级PE资源目录的地址和相关信息等。

    typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY
    {
        union {
            struct {
                DWORD NameOffset:31;
                DWORD NameIsString:1;
            } DUMMYSTRUCTNAME;
            DWORD   Name;
            WORD    Id;
        } DUMMYUNIONNAME;
        union {
            DWORD   OffsetToData;
            struct {
                DWORD   OffsetToDirectory:31;
                DWORD   DataIsDirectory:1;
            } DUMMYSTRUCTNAME2;
        } DUMMYUNIONNAME2;
    } IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    PIMAGE_RESOURCE_DIRECTORY描述了Windows PE资源的目录结构,每个资源目录包括以下字段:

    • Characteristics:指定该目录的属性,如是否允许命名、是否允许ID等;
    • TimeDateStamp:指定该目录的时间戳;
    • MajorVersion / MinorVersion:指定PE文件中允许的最高版本和最低版本;
    • NumberOfNamedEntries:指定该目录中已经命名的资源条目数量;
    • NumberOfIdEntries:指定资源ID类型的数量;
    • PIMAGE_RESOURCE_DIRECTORY_ENTRY:指针,指向资源入口表,即PE文件中每个资源的入口地址。
    • PIMAGE_RESOURCE_DIRECTORY_ENTRY用于引用PE文件中资源的名称、类型和语言信息,它包括了Name/Id:指定资源的名称或ID,根据缩小范围的优先级进行查找,ID的优先级高于名称;
    • OffsetToData:指向该资源的数据偏移地址或其Resource Data Entry的地址。

    读者在解析时通常需要在数据目录表PIMAGE_DATA_DIRECTORY中定位到IMAGE_DIRECTORY_ENTRY_RESOURCE资源表,通过循环的方式以此遍历出PIMAGE_RESOURCE_DIRECTORY_ENTRY中的每一个节点,最终输出资源信息,这段输出代码如下所示;

    // --------------------------------------------------
    // 定义资源表解析结构
    // --------------------------------------------------
    static char* szResName[0x11] = { 0, (char*)"鼠标指针", (char*)"位图", (char*)"图标", (char*)"菜单", (char*)"对话框", (char*)"字符串列表", (char*)"字体目录", (char*)"字体", (char*)"快捷键", (char*)"非格式化资源", (char*)"消息列表", (char*)"鼠标指针组", (char*)"zz", (char*)"图标组", (char*)"xx", (char*)"版本信息" };
    
    
    int main(int argc, char * argv[])
    {
        BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);
    
        if (PE == TRUE)
        {
            // 获取数据目录表
            PIMAGE_DATA_DIRECTORY pData = NtHeader->OptionalHeader.DataDirectory;
    
            // 获取到资源目录表
            pData = &(pData[IMAGE_DIRECTORY_ENTRY_RESOURCE]);
    
            // 获取资源目录表的偏移
            DWORD dwResOffset = RVAtoFOA(pData->VirtualAddress);
    
            // 获取到资源目录表
            PIMAGE_RESOURCE_DIRECTORY pRes = (PIMAGE_RESOURCE_DIRECTORY)(GlobalFileBase + dwResOffset);
    
            // 获取紧跟着的IMAGE_RESOURCE_DIRECTORY_ENTRY的个数
            DWORD dwResSize = pRes->NumberOfNamedEntries + pRes->NumberOfIdEntries;
    
            // 获取到PIMAGE_RESOURCE_DIRECTORY_ENTRY 
            PIMAGE_RESOURCE_DIRECTORY_ENTRY  pResEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRes + 1);
    
            printf("资源类型ID \t 类型 \n");
    
            for (DWORD i = 0; i < dwResSize; i++)
            {
    
                // 如果为0则执行
                if (!pResEntry[i].NameIsString)
                {
                    if (pResEntry[i].Id < 0x11)
                    {
                        // printf("资源类型ID: %p --> 类型: %s\n", pResEntry[i].Id, szResName[pResEntry[i].Id]);
                        printf("%p \t %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);
                        printf("%p \t %s \n", pResEntry[i].Id, type);
                    }
                }
                // 如果为1则执行
                else
                {
                    PIMAGE_RESOURCE_DIR_STRING_U pstcString = (PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pRes + pResEntry[i].NameOffset);
                    WCHAR szStr[MAX_PATH] = { 0 };
                    memcpy_s(szStr, MAX_PATH, pstcString->NameString, pstcString->Length * sizeof(WCHAR));
                    // printf("资源字符串: %ls\n", szStr);
                }
            }
        }
        else
        {
            printf("非标准程序 \n");
        }
    
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    编译并运行上述程序片段,则读者可以看到当前程序中所包含的所有资源信息,为了简单可用此处并没有输出递归资源,仅仅输出了第一层,输出效果图如下所示;

  • 相关阅读:
    【新日标习题集】第13課 までのまとめ (discarded)
    Redis主从同步原理
    mysql
    C++面试八股文:用过STL吗?
    innodb与myisam的区别
    【JS缓存技术】-本地存储
    链表的带环问题
    深度学习中常用的注意力模块及其原理和作用
    【Java设计模式】二、单例模式
    Java8新特性1——函数式接口&lambda表达式
  • 原文地址:https://blog.csdn.net/lyshark_csdn/article/details/132753384