PE结构导入表
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PE结构导入表相关的知识,希望对你有一定的参考价值。
导入表地址
首先放一张PE结构
导入放在数据目录数组
中,所以我们先介绍这个数组相关结构。
首先有4个字节指示这个数组大小,然后再接上数组元素。
每一项数组的结构体
typedef struct _IMAGE_DATA_DIRECTORY
DWORD VirtualAddress; //内存的虚拟地址
DWORD Size;//大小
IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
上面的表示意义:的在VirtualAddress
内存处大小为Size
的内存是存放某个数据的
其中数组每一项有固定的意义。其中第二项表示的是当前程序的导入信息。比如MessageBoxA这个函数就是一个导入user32.dll而来的,这个信息就存在这。
我们看看我们这个程序的导入信息
我们现在虚拟内存地址如何换算?
- 计算出该地址落在哪个节区中
- 使用节区文件偏移结合该节区虚拟地址进行计算
以本例为例
第一个节区数据
第一个节区虚拟地址范围:1F8h 到230h 范围
第二个节区数据
节区虚拟地址范围:230h-2c4h
我们上文截图提到导入表的虚拟地址为240h 所以很明显落在第二个节区rdata
中
我们第二个节区的文件偏移 为 230h(PointerToRawData
).
假设导入表地址我们记为 IVA
,导入表文件偏移 记为 IFA
节区PointerToRawData
简写SPRD
,VirtualAddress
简写SVA
得出以下公式
IFA
=IVA
-SVA
+SPRD
带入上面的数据
IFA
=240h-230h+230h=240h
于是本案例中导入表地址在文件偏移240h
处,且大小为60
(十进制) 那么我们的范围就是240h
-27Ch
导入表结构
typedef struct _IMAGE_IMPORT_DESCRIPTOR
union
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
我只需要关心如下几个字段
name 只向导入dll名字
OriginalFirstThunk 导入名称表(INT)
FirstThunk 导入地址表 (IAT)
在默认情况下INT和IAT只想不同的地址但是一般地址保存的内容相同。但是在被加载器加入内存后IAT表内容会修改为跳转地址,INT不变。
我们首先了解下INT和IAT的结构
typedef struct _IMAGE_THUNK_DATA32
union
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
u1;
IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
首先在了解这个结构时候你首先要知道dll是区分序号导入和名称导入,而IMAGE_THUNK_DATA32的数据结构的最高位为1标识序号导入,为0为名称导入。如果是序号导入Ordinal字段就是导入的序号,如果是名称导入只想的就是名称地址。比较详细可参考 IAT hooking
了解基本结构后我们看看本例的具体例子:
IMAGE_IMPORT_DESCRIPTOR
占20个字节,而在头结点的时候告诉我们一共有60个字节那么证明将有二个导入表(最后一个全为0标识结束)。
我们首先分析第一个导入表
7C 02 00 00 表示 OriginalFirstThunk
00 00 00 00 表示 TimeDateStamp
00 00 00 00 表示 ForwarderChain
9A 02 00 00 表示 Name
30 02 00 00 表示 FirstThunk
首先 027C
表示INT 的虚拟地址,如果你需要换算文件偏移也是通上文那样换算, 027C
-230h
+230
=027C
我们不先关心28c具体的数据内容,继续观察结构的的字段
29A
表示这个dll名称的虚拟地址,换算为文件地址恰好为29A
230h
表示导入地址表,这个对应的文件偏移为230h
,里面保存的内容和导入名称表内容一般都是一样。
我们最后看看28c
存储的名称到底是什么,这段内存的数据结构如下所示
typedef struct _IMAGE_IMPORT_BY_NAME
WORD Hint; //used to find location of function faster
BYTE Name[1]; //pointer to the function name
IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
另一个导入表我们这里不再做演示:
我们画图总结如下:
内存视图下导入表
主模块地址为400000h
所以 上一节中某个导入的表的IAT
为400000+230
=400230h
.而INT
为400000+27c
=40027c
而你发现INT加载后没有变化依然为28c
推荐一款工具:CFF_Explorer可以快速得到文件地址和pe信息等
导入表注入
我们利用上学过的知识做一个小测试,我们修改一个软件注入一个dll进入进程中。
被注入的程序
#include <iostream>
#include<Windows.h>
#include<tchar.h>
int main()
MessageBox(NULL,"hello","wprdl",MB_OK);
return 0;
注入的dll
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
extern "C" __declspec(dllexport)
void Test()
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
switch (ul_reason_for_call)
case DLL_PROCESS_ATTACH:
OutputDebugString("你被注入了");
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
return TRUE;
原始程序编译出的节区信息
首先我们在.text这个节区选取一块空白内存(你当然也可以选取其他节区,只要有空白区域即可).本例中选取偏移位置为54C0
,然后拷贝原始的导入表到此处并额外补充一个导入表
修改对应节区的可写入特性(比如.text无写入权限)。
以上是关于PE结构导入表的主要内容,如果未能解决你的问题,请参考以下文章