PE基础2-导出表-导入表
怎么找到Nt头?
(PIMAGE_NT_HEADER)(DOS.e_lfanew + (DWORD)m_pBuff)
怎么找到第一个区段表?
区段头位置 = pNt + 4 + 文件头的大小 + 扩展头大小 IMAGE_FIRST_SECTION()
区段表中的VirtualAddress字段保存的是什么?PointToRawData呢?
VirtualAdddress : 区段在内存的相对虚拟地址
RVA PointToRawData: 区段在文件中偏移Offset
记录OEP的字段在哪里?
扩展头.AddressOfEntryPoint (OEP)
怎么判断一个PE文件是32位的还是64位的?
扩展头.magic x86(10B) , x64(20B)
文件头.machine x86(014c) , x64(8664)
文件头.SizeOfOptionalheader x84(E0), x64(F0)
exe的默认加载基址是多少?DLL的默认加载基址?
exe: 0x00400000
dll: 0x10000000
文件对齐粒度一般是多少? 内存对齐粒度一般?
文件对齐值:0x200
内存对齐值:0x1000 (4kb)
FOA = RVA - 区段在内存中的相对虚拟地址 + 区段在文件中偏移
导出表
导出回顾 dll导出函数的几种方式
声明导出: _declspec(dllexport)
def文件导出
dll函数调用
隐式链接 :
包含头文件,载入lib库
显示连接 :
LoadLibray,GetProcAddress
导出表作用
导出是PE为其它程序提供API的一种函数实例导出行为
windows下存在导出表的可执行文件,可以提供其它第三方程序使用
导出表支持符号导出,序号导出,这两种方式可以共存
导出表位于数据目录表中第0选项(默认我们以数组下标0开始数)
导出表结构
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // (1) 保留,恒为0x00000000
DWORD TimeDateStamp; // (2) 时间戳
WORD MajorVersion; // (3) 主版本号,一般不赋值
WORD MinorVersion; // (4) 子版本号,一般不赋值
DWORD Name; // (5) 模块名称*
DWORD Base; // (6) 索引基数*
DWORD NumberOfFunctions; // (7) 导出地址表中成员个数*
DWORD NumberOfNames; // (8) 导出名称表中成员个数*
DWORD AddressOfFunctions; // (9) 导出地址表(EAT)*
DWORD AddressOfNames; // (10) 导出名称表(ENT)*
DWORD AddressOfNameOrdinals; // (11) 指向导出序号数组*
}IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;
手工解析导出表
需要注意的是导出表的地址都是RVA 导出表中有三张表分别是 EAT,ENT,EOT。
EAT: 导出地址表 ENT: 导出名称表(与EOT一一对应)
EOT:导出序号表
代码解析导出表
void PE::ShowExportTable() { PIMAGE_EXPORT_DIRECTORY p = GetExportDirectory(); //输出导出表信息 char * dllName = (char*)(RVAToFoa(p->Name) + (DWORD)m_pbuff); printf("dll名 %s\n", dllName); printf("导出函数地址表个数 %2d\n", p->NumberOfFunctions); printf("导出函数名称表个数 %2d\n", p->NumberOfFunctions); //遍历导出函数 //导出序号表 WORD *EOT = (WORD*)(RVAToFoa(p->AddressOfNameOrdinals) + (DWORD)m_pbuff); //导出名称表 DWORD *pENT = (DWORD*)(RVAToFoa(p->AddressOfNames) + (DWORD)m_pbuff); //导出序号表 DWORD *pEAT = (DWORD*)(RVAToFoa(p->AddressOfFunctions) + (DWORD)m_pbuff); //遍历地址表所有函数 for (int i = 0; i < p->NumberOfFunctions; i++) { printf("序号%d ", i + p->Base); int j = 0; //遍历所有序号表,找到有名字的函数 for (; j < p->NumberOfNames; j++) { if (i == EOT[j]) { // 导出名称表[ 序号表[j] ] int RvaName = pENT[EOT[j]]; char *szName = (char*)(RVAToFoa(RvaName) + (DWORD)m_pbuff); printf("函数名 %s ", szName); break; } } //没有找到函数名 if (j > p->NumberOfNames) { printf("函数名 NULL "); } //打印导出函数地址 printf("%08X\n", *(DWORD*)(RVAToFoa(pEAT[i]) + (DWORD)m_pbuff)); } }
导入表
导入表作用
导入表是PE文件从其它第三方程序中导入API,以供本程序调用的机制(与导出表对应)
在exe运行起来的时候, 加载器会遍历导入表, 将导入表中所有dll 都加载到进程中,被加载的DLL的DllMain就会 被调用
通过导入表可以知道程序使用了哪些函数
当然有写程序可以没有导入表,自己实现动态加载功能 导入表结构,
这个导入表是一个数组,以全为零结尾
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;//(1) 指向导入名称表(INT)的RAV*
};
DWORD TimeDateStamp; // (2) 时间标识
DWORD ForwarderChain; // (3) 转发链,如果不转发则此值为0
DWORD Name; // (4) 指向导入映像文件的名字*
DWORD FirstThunk; // (5) 指向导入地址表(IAT)的RAV*
} IMAGE_IMPORT_DESCRIPTOR;
导入表解析
导入表中重要的字段 : OriginalFirstThunk(INT)
导入表中重要的字段 : FirstThunk(IAT)
导入表中重要的字段 :Name
其中IAT 与 INT都指向 IMAGE_THUNK_DATA32
typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString; // (1) 转发字符串的RAV
PDWORD Function; // (2) 被导入函数的地址
DWORD Ordinal; // (3) 被导入函数的序号
PIMAGE_IMPORT_BY_NAME AddressOfData; // (4) 指向输入名称表
} u1;
} IMAGE_THUNK_DATA32;
IAT与INT区别在于,在文件中他们都指向一个函数名字或序号,在内存中IAT会被填充到正确的函数地址
IMAGE_THUNK_DATA32中值的最高位为1,表示序号导入,
Ordinal IMAGE_THUNK_DATA32中值的最高位为0,表示函数名导入,
AddressOfData字段有效 PIMAGE_IMPORT_BY_NAME 指向导入函数序号与函数名
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // (1) 需导入的函数序号
BYTE Name[1]; // (2) 需导入的函数名称
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
void PE::ShowImportInfo() { //获取导入表 PIMAGE_IMPORT_DESCRIPTOR pImport = GetImportDirectory(); //遍历所有导入表,最后一项以0结尾 while (pImport->Name) { //显示导入模块的名字 char * szDllName =(char*)( RvaToFoa(pImport->Name)+ (DWORD)m_pBuff); printf("%s\n", szDllName); //显示导入函数的名称 //遍历导入地址表IAT(导入名称表INT) //IAT PIMAGE_THUNK_DATA pIat = (PIMAGE_THUNK_DATA)(RvaToFoa(pImport->FirstThunk) + (DWORD)m_pBuff); //INT PIMAGE_THUNK_DATA pInt = (PIMAGE_THUNK_DATA)(RvaToFoa(pImport->OriginalFirstThunk) + (DWORD)m_pBuff); //IAT表个数不确定,最后一项以全为0结尾 while (pIat->u1.Ordinal) { //判断是否名称导出还是序号导出 //通过最高位是否为1 ,如果为1,那么序号导出 //如果为0,名称导出 if (pIat->u1.Ordinal & 0x80000000) { //最高位为1,序号导入 //打印一下序号 printf(" 序号%2d\n", pIat->u1.Ordinal & 0xFFFF); } else { //最高位位0,名称导入 //显示序号,和名称 PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)(RvaToFoa(pIat->u1.AddressOfData) + (DWORD)m_pBuff); printf(" 序号%2d", pName->Hint); printf(" %s\n", pName->Name); } pIat++; } pImport++; } }