1 DLL 加载机制

  • 显式链接:程序使用DLL时加载,使用完毕后释放内存
  • 隐式链接: 程序开始时即一同加载DLL,程序终止时再释放占用的内存。IAT提供的机制即与隐式链接有关。

1.1 导入表机制

​ 程序调用系统API时并非直接调用而是通过获取程序PE文件中.text节区的内存区域( 更确切地说是IAT内存区域)中存储的API的地址来获取API在内存中的真正地址。API地址内存地址获取途径

NT可选头 ---> DataDirectory[1]. VirtualAddress ---> IMAGE_ IMPORT_ DESCRIPTOR ---> IMAGE_THUNK_DATA.u1->Functiion

为什么不直接调用而是通过IAT机制?

  • 在不同系统环境中kernel32.dll的版本各不相同,API函数加载到内存中的位置(地址)也不相同
  • DLL重定位:DLL文件的ImageBase值一般为10000000。比如某个程序使用a.dll 与b.dll 时,PE 装载器先把a.dll装载到内存的10000000(lmageBase) 处, 然后尝试把b.dll也装载到该处。但是由于该地址处已经装载了a.dll, 所以PE装载器查找其他空白的内存空间(ex:3E000000), 然后将b.dll 装载进去。

1.2 导入表结构体 : IMAGE_ IMPORT_ DESCRIPTOR

IMAGE_ IMPORT_ DESCRIPTOR结构体中记录着PE文件要导入哪些库文件。它不在PE头而在PE体中,但查找其位置的信息在PE头中IMAGE_OPTIONAL_HEADER32.DataDirectory[1]. VirtualAddress 的值即是IMAGE_IMPORT_ DESCRIPTOR结构体数组的起始地址(RVA值)。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;    
        DWORD   OriginalFirstThunk; //INT(Import Name Table)地址(RVA)
    };
    DWORD   TimeDateStamp;        
    DWORD   ForwarderChain;         
    DWORD   Name;                    //库名称字符串的地址(RVA)
    DWORD   FirstThunk;             //IAT的地址(RVA)
} IMAGE_IMPORT_DESCRIPTOR;

//INT与IAT是长整型(4个字节数据类型)数组,以NULL结束(未另外明确指出大小)。

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

//INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针(有时IAT也拥有相同的值)

1.3 IAT工作原理

  1. 读取IMAGE_ IMPORT_ DESCRIPTORName成员, 获取库名称字符串(“kernel32. dll “)。
  2. 装载相应库,Loadlibrary (“kernel32.dll”)
  3. 读取 IMAGE_ IMPORT_ DESCRIPTOROriginalFirstThunk成员
  4. 逐一读取INT中数组的值,获取相应IMAGE_IMPORT_BY_NAME地址(RVA)
  5. 使用IMAGE_IMPORT_BY_NAMEHint (ordinal)Name项,获取相应函数的起始地址,GetProcAddress (“GetCurrentThreadld”)
  6. 读取IMAGE_ IMPORT_ DESCRIPTORFirstThunk (IAT) 成员, 获符IAT地址。
  7. 将上面获得的函数地址输入相应IAT数组值。
  8. 重复以上步骤4-7, 直到INT结束(遇到NULL 时)

其中FirstThunk是指向 MAGE_THUNK_DATA结构体得指针

IMAGE_THUNK_DATA
{
    union u1
    {
    DWORD ForwarderString; //指向一个转向者字符串的RVA
    DWORD Function ; //被输入的函数的内存地址
    DWORD Ordinal ; //被输入的API的序数值
    DWORD AddressOfData ; //高位为0则指向IMAGE_IMPORT_BY_NAME 结构体二
    };
}

1.4 EAT 导出地址表

EAT机制可以使不同的应用程序可以调用库文件中提供的函数是,只有通过EAT才能准确求得从相应库中导出函数的起始地址。 IMAGE_EXPORT_DIRECTORY结构体如下

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics; 
    DWORD   TimeDateStamp;      //时间戳
    WORD    MajorVersion;       
    WORD    MinorVersion;       
    DWORD   Name;               //指向改导出表文件名字符串
    DWORD   Base;               //导出表的起始序号
    DWORD   NumberOfFunctions;  //实际导出函数的个数
    DWORD   NumberOfNames;      //Export函数中具名的函数个数
    DWORD   AddressOfFunctions;     //导出函数地址数组,总大小NumberOfFunctions * 4字节
    DWORD   AddressOfNames;         //导出函数名称数组,总大小为NumberOfNames * 4字节
    DWORD   AddressOfNameOrdinals;  //导出函数序号表,大小为NumberOfNames * 2字节
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

1.5 库函数导出机制

从库中获得函数地址的API为GetProcAddress()函数。该API引用EAT来获取指定API的地址。GetProcAddress()API拥有函数名称,EAT原理如下:

  1. 利用AddressOfNames 成员转到函数名称数组
  2. 函数名称数组”中存储着字符串地址。通过比较( strcmp) 字符串, 查找指定的函数名称
  3. 利用AddressOfNameOrdinals 成员, 转到ordinal数组。
  4. ordinal数组中通过name_index 查找相应ordinal 值。
  5. 利用AddressOfFunctions 成员转到“函数地址数组” (EAT)。
  6. 在"函数地址数组” 中将刚刚求得的ordinal 用作数组索引,获得指定函数的起始地址。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注