导入表与导出表(1)
首先说几个基本问题:
1.导入表的作用是什么?没有它exe能运行么?
作用:记录了一个exe或者一个dll所用到的其他模块导出的函数;
所记录的信息有:用了哪些模块(用了哪些dll),用了dll的哪些函数
2.导出表的作用?没有它exe能运行么?
作用:记录了导出符号的地址,名称,与序号
(提示:exe文件中很少有导出表的,大多数dll都有导出表,某些存放资源文件的dll就没有导出表)
下面截取自笔记,便于理解导入表与导出表的关系:

image.png

image.png
3.怎样才能知道一个exe用了哪些API?
通过遍历导入表就可以知道导入了哪些dll以及dll中的哪些API
4.已知一个dll名和dll导出函数的名字,如何得到这个函数名的地址
通过Loadlibrary(GetModelHandle)将dll模块映射进内存并返回一个可以被GetProcAddress函数使用的句柄,再利用GetProcAddress得到dll的加载地址,通过遍历导出表就可以得到该函数的地址.
5.如何判断导入函数是以序号导入还是以名称导入?
在IMAGE_THUNK_DATA32这个结构体(结构体里面就是一个联合体,大小为32位)里面,判断结构体字段中的最高位,
最高位为1:以序号导入
最高位为0:以名称导入
6.怎样才能知道导出函数是以序号导出还是以名称导出?
遍历序号表,判断地址表的下标有没有存在与序号表中,存在就说明是以名称导出,不存在就说明是以序号导出
导入表几个简图(笔记中的,将就看吧):

image.png

image.png

image.png

image.png
解析导入表:
#include "stdafx.h"
#include <windows.h>
DWORD RvaToOffset( IMAGE_NT_HEADERS* pNtHdr , DWORD dwRva ) {
// 1. 找Rva所在的区段
// 2. 用Rva减去所在区段的首Rva ,再用减出来的差,加上所在
// 区段的首区段偏移
IMAGE_SECTION_HEADER* pSechdr = NULL;
pSechdr = IMAGE_FIRST_SECTION( pNtHdr );
for( int i = 0 ; i < pNtHdr->FileHeader.NumberOfSections; ++i ) {
if( dwRva >= pSechdr[ i ].VirtualAddress
&& dwRva <= pSechdr[ i ].VirtualAddress + pSechdr[ i ].SizeOfRawData ) {
dwRva -= pSechdr[ i ].VirtualAddress;
dwRva += pSechdr[ i ].PointerToRawData;
return dwRva;
}
}
return -1;
}
int main( ) {
//typedef struct _IMAGE_IMPORT_DESCRIPTOR {
// union {
// DWORD Characteristics;
// DWORD OriginalFirstThunk; /*保存导入函数名称表(INT)的地址(RVA)*/
// } DUMMYUNIONNAME;
// DWORD TimeDateStamp;
// DWORD ForwarderChain;// 是否是dll转发
// DWORD Name;/*导入的模块名(DLL的名字)*/
// DWORD FirstThunk;/*导入函数地址表(IAT)(RVA)*/
//} IMAGE_IMPORT_DESCRIPTOR;
// 解析导入表
// 1. 读取文件到内存
printf( "请输入要解析的DLL的路径:" );
char szPath[ MAX_PATH ];
gets_s( szPath , MAX_PATH );
HANDLE hFile = INVALID_HANDLE_VALUE;
hFile = CreateFileA( szPath , GENERIC_READ , FILE_SHARE_READ ,
NULL , OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL );
if( hFile == INVALID_HANDLE_VALUE ) {
printf( "文件不存在\n" );
system( "pause" );
return 0;
}
DWORD dwHeight = 0;
DWORD dwFileSize = GetFileSize( hFile , &dwHeight );
BYTE* pBuff = new BYTE[ dwFileSize ];
ReadFile( hFile , pBuff , dwFileSize , &dwFileSize , NULL );
// 2. 解析出Dos头,Nt头,扩展头
IMAGE_DOS_HEADER* pDosHdr = NULL;
IMAGE_NT_HEADERS* pNtHdr = NULL;
IMAGE_OPTIONAL_HEADER* pOptHdr = NULL;
IMAGE_DATA_DIRECTORY* pDataDir = NULL;
IMAGE_EXPORT_DIRECTORY* pExortTable = NULL;
pDosHdr = (IMAGE_DOS_HEADER*)pBuff;
pNtHdr = (IMAGE_NT_HEADERS*)( (ULONG_PTR)pDosHdr + pDosHdr->e_lfanew );
pOptHdr = &pNtHdr->OptionalHeader;
// 3. 得到扩展头中的数据目录表
pDataDir = pOptHdr->DataDirectory;
// 4. 通过数据目录表中的第1个元素得到导入表的RVA
DWORD dwImpRva = pDataDir[ 1 ].VirtualAddress;
if( dwImpRva == 0 ) {
printf( "没有导入表" );
system( "pause" );
return 0;
}
// 5. 将导入表的RVA转换成文件偏移
DWORD dwImpOfs = RvaToOffset( pNtHdr , dwImpRva
