导入表与导出表

您所在的位置:网站首页 dll文件怎么隐藏内容 导入表与导出表

导入表与导出表

2024-07-12 16:51| 来源: 网络整理| 查看: 265

文章目录 首先说几个基本问题:双桥结构解析导入表:导入表:

首先说几个基本问题:

1.导入表的作用是什么?没有它exe能运行么? 作用:记录了一个exe或者一个dll所用到的其他模块导出的函数; 所记录的信息有:用了哪些模块(用了哪些dll),用了dll的哪些函数

2.导出表的作用?没有它exe能运行么? 作用:记录了导出符号的地址,名称,与序号 (提示:exe文件中很少有导出表的,大多数dll都有导出表,某些存放资源文件的dll就没有导出表)

下面截取自笔记,便于理解导入表与导出表的关系 在这里插入图片描述

在这里插入图片描述

3.怎样才能知道一个exe用了哪些API? 通过遍历导入表就可以知道导入了哪些dll以及dll中的哪些API

4.已知一个dll名和dll导出函数的名字,如何得到这个函数名的地址 通过Loadlibrary(GetModelHandle)将dll模块映射进内存并返回一个可以被GetProcAddress函数使用的句柄,再利用GetProcAddress得到dll的加载地址,通过遍历导出表就可以得到该函数的地址.

5.如何判断导入函数是以序号导入还是以名称导入? 在IMAGE_THUNK_DATA32这个结构体(结构体里面就是一个联合体,大小为32位)里面,判断结构体字段中的最高位, 最高位为1:以序号导入 最高位为0:以名称导入

6.怎样才能知道导出函数是以序号导出还是以名称导出? 遍历序号表,判断地址表的下标有没有存在与序号表中,存在就说明是以名称导出,不存在就说明是以序号导出

双桥结构

导入表几个简图(笔记中的,将就看吧):

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

解析导入表: #include "stdafx.h" #include 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 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++; } 导入表:

(exe例子为:内存管理-VirtualAlloc) LordPE查看其导入表RVA:

在这里插入图片描述 010editor中验证: 在这里插入图片描述

导入表位置,落在了.idata段: 在这里插入图片描述

1A000 【7800】 1A1C8 【79C8】

79C8:(这个值为文件中的偏移,就是它在文件中的起始位置,利用79C8在010editor里面可以找到导入表) 在010editor中:

在这里插入图片描述

typedef struct _IMAGE_IMPORT_DESCRIPTOR{ union { DWORD Characteristics; DWORD OriginalFirstThunk;//指向一个结构体数组的相对位移,RVA to original unbound IAT (PIMAGE_THUNK_DATA) }DUMMYUNIONNAME; DWORD TimeDataStamp; DWORD ForwarderChain; DWORD Name; //导入的PE文件的名字的相对位移(RVA) DWORD FirstThunk ;//指向一个结构体数组的相对位移(RVA to IAT) }

在这里插入图片描述

18 A2 01 00【OriginalFirstThunk:INT(Import Name Table)导入名称表地址RVA】 00 00 00 00 00 00 00 00 22 A4 01 00【DLL名称的RVA】 00 A0 01 00【IAT(Import Address Table)导入地址表地址RVA】

1.dll的名字【22 A4 01 00(小端)—>0001A422(因为1A000对应7800,故这里dll在文件中偏移为7C22)】 在这里插入图片描述

2.看下INT(OriginalFirstThunk):1A218【7A18】(以全0结尾)

在这里插入图片描述

函数名数组(对应IMAGE_THUNK_DATA32结构体数组,每一个结构体就是一个联合体)

E0 A3 01 00----0001A3E0【7BE0】最高位为0,说明是以名称导入的,不是序号导入的; 一共导入了25个函数,这里只写一个,其他依次类推! 注意:IAT和INT都指向下面的这个数据结构,4Byte

typedef struct _IMAGE_THUNK_DATA32{ union{ DWORD ForwarderString; DWORD Function;//导入函数的地址,在加载到内存之后,这里才起作用 DWORD Ordinal;//假如是序号导入的,会用到这里 DWORD AddressOfData;//假如是函数名导入,会用到这里,它指向另外一个结构体PIMAGE_IMPORT_BY_NAME }u1; }IMAGE_THUNK_DATA32; //如果是函数名导入的,AddressOfData会指向下面这个结构体 typedef struct _IMAGE_IMPORT_BY_NAME{ WORD Hint;//序号 CHAR Name[1];//不定长,字符串 }

由上可知:是按照函数名导入的(大多数都是按名称导入的),故上面的地址值,就会指向一个PIMAGE_IMPORT_BY_NAME的结构体 【7BE0】 在这里插入图片描述

在这里插入图片描述

看下IAT(00 A0 01 00->0001A000–>【7800】)

在这里插入图片描述

发现最高位也都是0,所以,也是名称导入的,另外,还可以发现,这个位置的值,和INT的值是一样的,因此,不再赘述了 这里的kernel32.dll里面有25个函数

在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3