• PE基础3-资源表-重定位表-TLS表-DLL延迟加载表


    PE基础3

    导入表的作用是什么? 没有它exe能运行吗?

    导入外部模块,提供的API,变量,类 可以没有导入表(这个程序没有用到其它模块)

    导出表的作用是什么? 没有它exe能运行吗?

    导出模块名,函数(序号),变量,类 通常导出表用于dll,没有导出表程序也可以运行

    已知一个dll名,和一个dll导出函数的名字,如何得到这个函数名的地址?

    导出表中查找 ENT(导出名称表) EOT(导出序号表) EAT(导出地址表)

    怎么才能知道一个exe都使用了哪些API?

    通过遍历导入表(INT,IAT)

    如何判断导入函数是以序号导入或是以名称导入?

    IMAGE_THUNK_DATA.DWORD 最高位1,说明序号导入

    IMAGE_THUNK_DATA.DWORD 最高位0,说明名称导入

    怎么才知道导出函数是仅以序号导出还是以名称导出?

    遍历导出地址表,再遍历导出序号表,序号表中的值与导出地址表下标对应,

    说明这个函数是名称导出, 如果序号表中的值没有与地址表下标对应,那么它是序号导出(序号+BASE)

     

    资源表

    概述

    windows的资源有菜单、图标、快捷键、版本信息以及其它未格式化的二进制资源比如菜单、图标、快捷键、 版本信息以及其它未格式化的二进制资源。。。

    资源由三层一样的结构体组成

    第一层:资源的类型是什么(图标,位图,菜单....)

    第二次:资源的叫什么名字 (1.png, 2.png)

    第三层:资源的语言,资源的信息(大小,文件中位置)

     

    资源表结构

    资源表位于数据目标表,下标为2

    资源目录结构体

    typedef struct _IMAGE_RESOURCE_DIRECTORY {  
       DWORD   Characteristics;        // (1) 资源属性标识
       DWORD   TimeDateStamp;      // (2) 资源建立的时间  
       WORD    MajorVersion;       // (3) 资源主版本
       WORD    MinorVersion;       // (4) 资源子版本  
       WORD    NumberOfNamedEntries;   // (5) 资源名称条目个数  
       WORD    NumberOfIdEntries;  // (6) 资源ID条目个数
    } IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
    typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { 
       union {      
           struct {      
               DWORD NameOffset   :31; // (1) 资源名偏移  
               DWORD NameIsString:1;   // (2) 资源名为字符串  
          };    
           DWORD   Name;               // (3) 资源/语言类型  
           WORD    Id;                 // (4) 资源数字ID    
      };  
       union {  
           DWORD   OffsetToData;       // (5) 数据偏移地址    
           struct {          
               DWORD   OffsetToDirectory:31;// (6) 子目录偏移地址    
               DWORD   DataIsDirectory   :1;// (7) 数据为目录  
          };
      };
    }IMAGE_RESOURCE_DIRECTORY_ENTRY,*PIMAGE_RESOURCE_DIRECTORY_ENTRY;

    当资源的名字为字符时,它指向这样一个结构体

    typedef struct _IMAGE_RESOURCE_DIR_STRING_U {  
       WORD    Length;     // (1) 字符串长度  
       WCHAR   NameString[ 1 ];    // (2) 字符串数组
    } IMAGE_RESOURCE_DIR_STRING_U,*PIMAGE_RESOURCE_DIR_STRING_U;

    最后一层指向真正数据的信息

    typedef struct _IMAGE_RESOURCE_DATA_ENTRY {  
       DWORD   OffsetToData;   // (1) 资源数据的RVA  
       DWORD   Size;       // (2) 资源数据的长度  
       DWORD   CodePage;       // (3) 代码页    
       DWORD   Reserved;       // (4) 保留字段
    } IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;

    解析资源表

    //获取资源表
    PIMAGE_RESOURCE_DIRECTORY PE::GetResourceDirectory()
    {
        //资源表位于数据目录表, 下标为2
        DWORD dwResourceRva = 
            GetNtHeader()->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
    ​
        DWORD dwResourceFoa = RvaToFoa(dwResourceRva) + (DWORD)m_pBuff;
    ​
        return (PIMAGE_RESOURCE_DIRECTORY)(dwResourceFoa);
    }
    //自定义
    const WCHAR* RES[20] = {
        L"光标",
        L"位图",
        L"图标",
        L"菜单",
        L"对话框",
        L"字符串列表",
        L"字体目录",
        L"字体",
        L"快捷键",
        L"非格式化资源",
        L"消息列表",
        L"鼠标指针数组",
        L"NULL",
        L"图标组",
        L"NULL",
        L"版本信息",
    };
    ​
    //显示资源表
    void PE::ShowResourceInfo() {    //1. 获取资源目录表 
        PIMAGE_RESOURCE_DIRECTORY pResourceOne = GetResourceDirectory();   
        //获取资源数组项  
        PIMAGE_RESOURCE_DIRECTORY_ENTRY pResouceOneEntry =         (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResourceOne + 1);
        //1.2 遍历所有资源类型  
        //资源总个数  
        DWORD dwResourceNumber = pResourceOne->NumberOfIdEntries + pResourceOne>NumberOfNamedEntries;  
        for (int i = 0; i < dwResourceNumber; i++)   
        {      
            //1.3 判断资源类型,是数字还是字符串    
            if (pResouceOneEntry[i].NameIsString)   
            {          
                //资源ID是字符串     
                //那么NameOffset 有效(基于资源表的偏移)   
                PIMAGE_RESOURCE_DIR_STRING_U szName =                 (PIMAGE_RESOURCE_DIR_STRING_U)(pResouceOneEntry[i].NameOffset + (DWORD)pResourceOne); 
                TCHAR szBuff[100];    
                wcsncpy_s(szBuff, szName->NameString, szName->Length);  
                printf("%S
    ", szBuff);    
            }       
            else {   
                //资源ID是数字      
                // 系统定义的 0 - 16 
                if (pResouceOneEntry[i].Id < 16) 
                {     
                    wprintf(L"%s
    ", RES[pResouceOneEntry[i].Id]); 
                }    
                // 自定义的    
                else {     
                    printf("%02d
    ", pResouceOneEntry[i].Id);
                }    
            }    
            //2 解析第二层数据   
            //2.1 是否有第二层数据    
            if (pResouceOneEntry[i].DataIsDirectory)    
            {         
                //2.2 获取第二层资源表     
                PIMAGE_RESOURCE_DIRECTORY pResourceTwo =                 (PIMAGE_RESOURCE_DIRECTORY)(pResouceOneEntry[i].OffsetToDirectory + (DWORD)pResourceOne);            //获取资源数组项       
                PIMAGE_RESOURCE_DIRECTORY_ENTRY pResouceTwoEntry =                (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResourceTwo + 1);
                //这种资源有多少个    
                DWORD dwNumber2 = pResourceTwo->NumberOfIdEntries + pResourceTwo>NumberOfNamedEntries;
                //2.3 遍历资源    
                for (int i = 0; i < dwNumber2; i++)      
                {           
                    //资源名字是 数字,还是字符串   
                    if (pResouceTwoEntry[i].NameIsString)  
                    {           
                        PIMAGE_RESOURCE_DIR_STRING_U szName =                        (PIMAGE_RESOURCE_DIR_STRING_U)(pResouceTwoEntry[i].NameOffset + (DWORD)pResourceOne);
                        TCHAR szBuff[100];  
                        wcsncpy_s(szBuff, szName->NameString, szName->Length);   
                        printf("    %S
    ", szBuff);
                        
                    }          
                    else {       
                        printf("   %02d
    ", pResouceTwoEntry[i].Id);  
                    }       
                    //3. 解析第3层  
                    //3.1 是否有第三层数据   
                    if (pResouceTwoEntry[i].DataIsDirectory)   
                    {             
                        //3.2 获取第三层资源表           
                        PIMAGE_RESOURCE_DIRECTORY pResourceThree =                        (PIMAGE_RESOURCE_DIRECTORY)(pResouceTwoEntry[i].OffsetToDirectory + (DWORD)pResourceOne);                   PIMAGE_RESOURCE_DIRECTORY_ENTRY pResourceThreadEntry =                        (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResourceThree + 1);
                        //第三层 执行真正数据                    
                        PIMAGE_RESOURCE_DATA_ENTRY pResourceData =                        (PIMAGE_RESOURCE_DATA_ENTRY)(pResourceThreadEntry->OffsetToData + (DWORD)pResourceOne);                   
                        printf("            资源位置 %08X,资源大小 %02d
    ", pResourceData>OffsetToData, pResourceData->Size);
                  
                    }    
                }     
            }   
        }
    }

    重定位表

    概述

    由于Windows系统中DLL(动态链接库)文件并不能每次都能加载到预设的基址(ImageBase)上,因此基址 重定位主要应用于DLL文件中

    假如我们程序的默认基址为0x00400000,那么如果我们想将此程序偏移为0x100处的数据地址压入堆栈的话可 定会用到汇编代码: push 0x00400000 由于EXE文件能保证每次加载的基址均为0x00400000(没有随机基址),因此这条汇编指令执行起来是没有任 何问题的,但是微软已经告诉了我们DLL文件并不能保证每次都加载到预设基址上,因此当DLL程序加载到 0x50000000的位置上时,就必须使用0x50000100才能访问到正确的数据,如果仍试图用0x10000100取数据 的话显然是错误的,而基址重定位表就是为了避免这个错误发生而设计的

    重定位表结构

    重定位表也是一个数组,这个数组以一个全零结尾的结构体

    重定位表位于数据目标表,下标为5

    typedef struct _IMAGE_BASE_RELOCATION {   
       DWORD   VirtualAddress; // (1) 需重定位数据的起始RVA  
       DWORD   SizeOfBlock;    // (2) 本结构与TypeOffset总大小 //  
       WORD    TypeOffset[1];  // (3) 原则上不属于本结构
    } IMAGE_BASE_RELOCATION; typedef  IMAGE_BASE_RELOCATION UNALIGNED IMAGE_BASE_RELOCATION;

    TypeOffset的元素个数 = (SizeOfBlock - 8 )/ 2

    TypeOffset的每个元素都是一个自定义类型结构

    struct {    
       WORD Offset:12;  // (1) 大小为12Bit的重定位偏移
       WORD Type :4;   // (2) 大小为4Bit的重定位信息类型值
    }TypeOffset;        // 这个结构体是A1Pass总结的

    类型为3的说明这个位置的4个字节须要修复

    解析重定位表

    //获取重定位表
    PIMAGE_BASE_RELOCATION PE::GetRelocation()
    {
        //重定位表位于数据目录表, 下标为5
        DWORD dwRelocationRva =
            GetNtHeader()->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
    ​
    ​
        return (PIMAGE_BASE_RELOCATION)(RvaToFoa(dwRelocationRva)+(DWORD)m_pBuff);
    }
    ​
    //显示
    void PE::ShowBaseRelocation() { 
        //每一个重定位数据都是一个结构体,记录类型,与偏移 
        typedef struct TYPEOFFSET {   
            WORD Offset : 12;           //一页种的偏移  
            WORD Type : 4;          //重定位数据类型,3表示这个数据需要重定位   
        }*PTYPEOFFSET;
        //1. 获取重定位表  
        PIMAGE_BASE_RELOCATION pRelocation = GetBaseRelocaltion();
        //2. 遍历重定位    //重定位表是以一项全为零结尾的数   
        while (pRelocation->SizeOfBlock != 0)  
        {      
            //3.遍历重定位项      
            //重定位项是以0x1000页为一块,每一块负责一页 
            //每一页有有多少块 (sizeblock - 8) /2     
            DWORD dwCount = (pRelocation->SizeOfBlock - 8) / 2;      
            for (int i = 0; i < dwCount; i++)     
            {         
                PTYPEOFFSET pBlock = (PTYPEOFFSET)(pRelocation + 1);  
                //这个数据需要重定位吗?     
                if (pBlock->Type == 3)
                    
                {              
                    //需要重定位数据的位置  (RVA)     
                    DWORD RvaOffset = pRelocation->VirtualAddress + pBlock[i].Offset; 
                    //文件种的位置  (FOA)     
                    DWORD FoaOffset = RVAToFoa(RvaOffset);    
                    //需要重定位的数据据是       
                    DWORD Data = *(DWORD*)(FoaOffset + m_pbuff);
                    
                    //显示重定位数据在内存中位置    
                    //VA = Rva+ imagebase             
                    printf("%08x:[%08x]
    ",       
                           RvaOffset + GetNtHeader()->OptionalHeader.ImageBase, 
                           Data      
                          );    
                }    
            }   
            //找到下一个重定位表       
            pRelocation =             (PIMAGE_BASE_RELOCATION)((DWORD)pRelocation + pRelocation->SizeOfBlock);
        }
    }
    ​

    TLS 线程局部存储

    概述

    为了解决多线程变量同步问题 声明为TLS变量后,当线程去访问这个全局变量是,会将这个变量拷贝到自己线程中的TLS空间中。 TLS经常会用于反调试,抢占式执行。

    动态TLS线程局部存储

    TlsGetVallue

    TlsAlloc

    TlsFree

    TlsSetValue

     

    tls实例

    #pragma comment(linker, "/INCLUDE:__tls_used")
    // TLS变量 
    __declspec (thread) int g_nNum = 0x11111111;
    __declspec (thread) char g_szStr[] = "TLS g_nNum = 0x%p ...
    "; 
    //当有线程访问tls变量时,该线程会复制一份tls变量到自己tls空间
    //线程只能修改自己的空间tls变量,不会修改到全局变量
    // TLS回调函数A
    void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Red)
    {  
        if (DLL_THREAD_DETACH == Reason) // 如果线程退出则打印信息   
            printf("t_TlsCallBack_A -> ThreadDetach!
    "); 
        return; } 
    // TLS回调函数B
    void NTAPI t_TlsCallBack_B(PVOID DllHandle, DWORD Reason, PVOID Red) 
    {   
        if (DLL_THREAD_DETACH == Reason) // 如果线程退出则打印信息 
            printf("t_TlsCallBack_B -> ThreadDetach!
    ");
        /* Reason 什么事件触发的   
        DLL_PROCESS_ATTACH   1   
        DLL_THREAD_ATTACH    2 
        DLL_THREAD_DETACH    3  
        DLL_PROCESS_DETACH   0        */   
        return; }
    /* 
    * 注册TLS回调函数,".CRT$XLB"的含义是:
    * CRT表明使用C RunTime机制 
    * X表示标识名随机 
    * L表示TLS callback section
    * B其实也可以为B-Y的任意一个字母 
    */ #pragma data_seg(".CRT$XLB") 
    PIMAGE_TLS_CALLBACK p_thread_callback[] = 
    {   
        t_TlsCallBack_A,  
        t_TlsCallBack_B,  
        NULL };
    #pragma data_seg()
    DWORD WINAPI t_ThreadFun(PVOID pParam)
    {   
        printf("t_Thread ->  first printf:");  
        printf(g_szStr, g_nNum);  
        g_nNum = 0x22222222; // 注意这里  
        printf("t_Thread -> second printf:");  
        printf(g_szStr, g_nNum);  
        return 0;
    } 
    int _tmain(int argc, _TCHAR* argv[]) 
    {   
        printf("_tmain -> TlsDemo.exe is runing...
    
    "); 
        CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0); 
        Sleep(100);  // 睡眠100毫秒用于确保第一个线程执行完毕  
        printf("
    "); 
        CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0);  
        system("pause"); 
        return 0;
    }

    TLS表结构

    位于数据目录表 下标为9

    typedef struct  _IMAGE_TLS_DIRECTORY32 { 
       DWORD   StartAddressOfRawData;  //内存起始地址
       DWORD   EndAddressOfRawData;    //内存结束地址
       DWORD   AddressOfIndex;         // TLS数据索引
       DWORD   AddressOfCallBacks;     // TLS回调函数数组
       DWORD   SizeOfZeroFill;         //0填充区域的字节数
       union {      
           DWORD Characteristics;      //保留    
           struct {        
               DWORD Reserved0 : 20;  
               DWORD Alignment : 4;  
               DWORD Reserved1 : 8;    
          } DUMMYSTRUCTNAME;  
      } DUMMYUNIONNAME;
    } IMAGE_TLS_DIRECTORY32;

    DLL延迟加载

    概述

    延迟加载机制为了提高进程加载效率的技术 延迟加载机制没有对dll任何特殊要求,也就是说任意的一个DLL都可以被延迟加载 DLL延迟加载使用

    包含必要的头文件及静态库

    #include <windows.h> 
    #include <delayimp.h>
    #pragma comment(lib, "Delayimp.lib")
    • 设置“连接器”>“输入”>“延迟加载的DLL”选项中的值为我们需要延迟加载的DLL名称(大小写必须完全一致 )

    • 如果我们需要用到卸载功能,需要设置“连接器”>“高级”>“卸载延迟加载的DLL” 可以直接使用延迟加载的函数,

    • 当调用这个函数才会加载这个dll DLL延迟加载机制的核心就是delayLoadHelper()函数,

    • 对延迟加载函数的调用实际上是对delayLoadHelper()函 数的调用,该函数引用特殊的Delay Import节,

    • 并且会在其内部自动调用LoadLibrary()之后再调用 GetProcAddress()以获取函数地址

    注意事项

    手动卸载dll时,需要通过__FUnloadDelayLoadedDLL2(“xxx.dll”)卸载,不允许使用FreeLibray函数卸载。

    延迟加载表结构

    延迟加载表位于数据目录表 下标为13项

    typedef struct _IMAGE_DELAYLOAD_DESCRIPTOR {  
       DWORD AllAttributes;         //1(没用)为0  
       DWORD DllNameRVA;            //2(有用)延迟加载的 Dll名字
       DWORD ModuleHandleRVA;       //3(有用)  
       DWORD ImportAddressTableRVA; //4(重要)延迟载入IAT的RVA (函数地址(VA))  
       DWORD ImportNameTableRVA;    //5(重要) 延迟载入INT的RVA(PIMAGE_THUNK_DAT
       DWORD BoundImportAddressTableRVA;//6(有用)绑定IAT的RVA  
       DWORD UnloadInformationTableRVA; //7 卸载函数  
       DWORD TimeDateStamp;             //8() if not bound
    } IMAGE_DELAYLOAD_DESCRIPTOR, *PIMAGE_DELAYLOAD_DESCRIPTOR;

     

  • 相关阅读:
    JAVA_NIO 与Netty框架
    Socket编程
    P3368树状数组2(树状数组//改段求点)
    P3373 树状数组1(树状数组//改点求段)
    树状数组
    P1197 星球大战(并查集+链式向前星)
    P2024 食物链(种类并查集||带权并查集)
    P1111 修复公路(kruscal+并查集)
    P1387 最大正方形+P1736 创意吃鱼法(矩形上的dp+预处理)
    P2330 繁忙的城市(krusal最小生成树)
  • 原文地址:https://www.cnblogs.com/ltyandy/p/11093641.html
Copyright © 2020-2023  润新知