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\n", szBuff);    
        }       
        else {   
            //资源ID是数字      
            // 系统定义的 0 - 16 
            if (pResouceOneEntry[i].Id < 16) 
            {     
                wprintf(L"%s\n", RES[pResouceOneEntry[i].Id]); 
            }    
            // 自定义的    
            else {     
                printf("%02d\n", 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\n", szBuff);
                    
                }          
                else {       
                    printf("   %02d\n", 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\n", 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]\n",       
                       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 ...\r\n"; 
//当有线程访问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!\r\n"); 
    return; } 
// TLS回调函数B
void NTAPI t_TlsCallBack_B(PVOID DllHandle, DWORD Reason, PVOID Red) 
{   
    if (DLL_THREAD_DETACH == Reason) // 如果线程退出则打印信息 
        printf("t_TlsCallBack_B -> ThreadDetach!\r\n");
    /* 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...\r\n\r\n"); 
    CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0); 
    Sleep(100);  // 睡眠100毫秒用于确保第一个线程执行完毕  
    printf("\r\n"); 
    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;

 

猜你喜欢

转载自www.cnblogs.com/ltyandy/p/11093641.html