导入表与导出表(1)
2018-01-31 本文已影响0人
bluewind1230
首先说几个基本问题:
1.导入表的作用是什么?没有它exe能运行么?
作用:记录了一个exe或者一个dll所用到的其他模块导出的函数;
所记录的信息有:用了哪些模块(用了哪些dll),用了dll的哪些函数
2.导出表的作用?没有它exe能运行么?
作用:记录了导出符号的地址,名称,与序号
(提示:exe文件中很少有导出表的,大多数dll都有导出表,某些存放资源文件的dll就没有导出表)
下面截取自笔记,便于理解导入表与导出表的关系:
![](https://img.haomeiwen.com/i5676193/8fe9c97008d00523.png)
![](https://img.haomeiwen.com/i5676193/f6994fa1aed15068.png)
3.怎样才能知道一个exe用了哪些API?
通过遍历导入表就可以知道导入了哪些dll以及dll中的哪些API
4.已知一个dll名和dll导出函数的名字,如何得到这个函数名的地址
通过Loadlibrary(GetModelHandle)将dll模块映射进内存并返回一个可以被GetProcAddress函数使用的句柄,再利用GetProcAddress得到dll的加载地址,通过遍历导出表就可以得到该函数的地址.
5.如何判断导入函数是以序号导入还是以名称导入?
在IMAGE_THUNK_DATA32这个结构体(结构体里面就是一个联合体,大小为32位)里面,判断结构体字段中的最高位,
最高位为1:以序号导入
最高位为0:以名称导入
6.怎样才能知道导出函数是以序号导出还是以名称导出?
遍历序号表,判断地址表的下标有没有存在与序号表中,存在就说明是以名称导出,不存在就说明是以序号导出
导入表几个简图(笔记中的,将就看吧):
![](https://img.haomeiwen.com/i5676193/049b90b2a31a73cf.png)
![](https://img.haomeiwen.com/i5676193/884fad31f6c35854.png)
![](https://img.haomeiwen.com/i5676193/25cb1795eff6654d.png)
![](https://img.haomeiwen.com/i5676193/88072bf479433ee3.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 );
// 6. 使用导入表结构体指针指向导入表所在的内存地址
IMAGE_IMPORT_DESCRIPTOR* pImpTab = NULL;
pImpTab = (IMAGE_IMPORT_DESCRIPTOR*)( dwImpOfs + (ULONG_PTR)pDosHdr );
// 7. 遍历所有的导入表
// 导入表是以一个全0元素作为结束的标志
while( pImpTab->Name != 0 ) {
// 7.1 获取这个导入所导入的DLL的名称,导入表里面的Name也是一个RVA
ULONG_PTR dwNameOfs = RvaToOffset( pNtHdr , pImpTab->Name );
char* pName = nullptr;
pName = (char *)( dwNameOfs + (ULONG_PTR)pDosHdr );
printf( "导入的DLL:%s\n" , pName );
//IMAGE_THUNK_DATA32
// 7.2 遍历从这个dll导入的函数名称
DWORD dwINToffset = RvaToOffset(pNtHdr, pImpTab->OriginalFirstThunk);
// INT : 导入名称表
// 这个表记录了所有导入函数名称的地址(RVA)
// 每个地址都是4/8字节(如果是32位PE文件,就是4字节,如果是64位的PE文件就是8字节)
ULONG_PTR* pInt = (ULONG_PTR*)( dwINToffset + (ULONG_PTR)pDosHdr );
while( *pInt != 0 ) {
// 导入函数有两种方式:
// 1. 以序号导入
// 2. 以名称导入
// 如果INT表中保存的值,最高位是0的时候,说明这个值是一个函数名称的RVA
// 否则这个值的低16位就是一个导入的序号。
if( IMAGE_SNAP_BY_ORDINAL( *pInt ) ) {
// 以序号方式导入
// 以序号方式导入, 保存的是序号,需要只有2字节
printf( "\t0x%04X\n" , *pInt & 0xFFFF );
}
else {
// 以名称方式导入,数组保存的RVA,并非是一个字符串的RVA
// 而是一个结构体(IMAGE_IMPORT_BY_NAME)的RVA
IMAGE_IMPORT_BY_NAME* pImpByName = NULL;
DWORD dwNameRva = *pInt;
dwNameOfs = RvaToOffset( pNtHdr , dwNameRva );
pImpByName = (IMAGE_IMPORT_BY_NAME*)( dwNameOfs + (ULONG_PTR)pDosHdr );
printf( "\t序号:%04X,名称:%s这是以名称方式导入的\n" ,
pImpByName->Hint ,
pImpByName->Name );
}
// 得到下一个导入函数的名称的地址
++pInt;
}
// 得到下一个导入表的地址
++pImpTab;
}
system( "pause" );
return 0;
}
另外一种方式;
遍历导入表中的INT修复IAT
while (exeimport->Name != NULL) //遍历模块
{
HMODULE h_dllModule = apis.pfnLoadLibraryA((char *)(exeimport->Name + ImageBase));
PIMAGE_THUNK_DATA import_Int = (PIMAGE_THUNK_DATA)(exeimport->OriginalFirstThunk+ImageBase);
PIMAGE_THUNK_DATA import_IAT = (PIMAGE_THUNK_DATA )(exeimport->FirstThunk+ ImageBase);
while (import_Int->u1.Ordinal != 0) //遍历函数
{
UCHAR *buf = (UCHAR *)apis.pfnHeapAlloc(heap, HEAP_ZERO_MEMORY, 10);
buf[0] = 0xb8;
buf[5] = 0xff;
buf[6] = 0xe0;
//new char[20]{ "\xB8\x00\x00\x00\0x00\0xff\0xe0" };
DWORD opl = 0;
apis.pfnVirtualProtect((LPVOID)buf, 20, PAGE_EXECUTE_READWRITE, &opl);
if (import_Int->u1.Ordinal &0x80000000) //这里是重点!!!序号导出, 最高位为1,这里是获取最高位,如果最高位为1,就执行下面里面的语句,即
//以序号导入 ,否则以名称导入,执行else中的语句
{
//获取序号函数
LPVOID apiaddr =
apis.pfnGetProcAddress(h_dllModule, (char *)(import_Int->u1.Ordinal & 0xFFFF));
*(DWORD*)&buf[1] = (DWORD)apiaddr; //函数写入shellcode
//DWORD funaddr = ;
apis.pfnVirtualProtect((LPVOID)(import_IAT ), 4, PAGE_EXECUTE_READWRITE, &opl);
*(DWORD*)((DWORD)import_IAT ) = (DWORD)buf; //将函数写入到iat
}
else
{
//DWORD Faddr = *(DWORD*)(import_Int->u1.AddressOfData + ImageBase);
PIMAGE_IMPORT_BY_NAME funname = (PIMAGE_IMPORT_BY_NAME)(import_Int->u1.AddressOfData+ImageBase);
LPVOID apiaddr =
apis.pfnGetProcAddress(h_dllModule, funname->Name);
*(DWORD*)&buf[1] = (DWORD)apiaddr; //函数写入shellcode
apis.pfnVirtualProtect((LPVOID)(import_IAT), 4, PAGE_EXECUTE_READWRITE, &opl);
*(DWORD*)((DWORD)import_IAT ) = (DWORD)buf; //将函数写入到iat
// DWORD funaddr =import_IAT->u1.Function ; //获取iat地址
//
// apis.pfnVirtualProtect((LPVOID)funaddr, 4, PAGE_EXECUTE_READWRITE, &opl);
// *(DWORD*)(funaddr) = (DWORD)buf; //将函数写入到iat
}
import_Int++;
import_IAT++;
}
exeimport++;
}