导入表编程-枚举导入表
导入表编程-枚举导入表
思路:
首先导入表的RVA地址,就在optional Header的DataDirectory的第二个元素中。通过它我们定位到导入表。
导入表类似一个二级索引。一级是一个模块目录(IMAGE_IMPORT_DESCRIPTOR数组,这里把目录理解为一个以全0字节为结束的数组),它的每个元素代表了一个DLL。二级是导入地址表IAT(即 IMAGE_THUNK_DATA 数组,一个指针数组),每个元素指向一个 IMAGE_IMPORT_BY_NAME结构(该结构含有一个函数序号和一个函数名称字符串)。
总结一下,我们的定位过程:
(1)通过 NtHeaders.OptionalHeader.DataDirectory[1].VirtualBase --> 定位到导入表(IID Table)。
(2)遍历每个 IID,直到遇到全0为止。
通过 IID.Name -> 定位到 DLL 名字。
通过 IID.OriginalFirstThunk 或者 FirstThunk -> 定位到IAT ( image_thunk_data32[] );
遍历指针目录,知道遇到NULL为止。
通过 thunk_data.AddressOfData -> 定位到一个 IMAGE_IMPORT_BY_NAME 的地址,再根据它寻址到真正的函数序号和函数名称。
代码:
// C++Test.cpp : 定义控制台应用程序的入口点。 //#include "stdafx.h" #include "stdafx.h" #include <stdio.h> #include <windows.h> #include <Dbghelp.h> //ImageRvaToVa #pragma comment(lib, "Dbghelp.lib")int main(int argc, char* argv[]){ int i, j; HANDLE hFile = CreateFile(L"C:\\A.exe", //PE文件名 GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if(hFile == INVALID_HANDLE_VALUE){ printf("Create File Failed.\n"); return 0; }HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);if (hFileMapping == NULL || hFileMapping == INVALID_HANDLE_VALUE) { printf("Could not create file mapping object (%d).\n", GetLastError()); return 0; }LPBYTE lpBaseAddress = (LPBYTE)MapViewOfFile(hFileMapping, // handle to map object FILE_MAP_READ, 0, 0, 0);if (lpBaseAddress == NULL) { printf("Could not map view of file (%d).\n", GetLastError()); return 0; }PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(lpBaseAddress + pDosHeader->e_lfanew);//导入表的rva:0x2a000; DWORD Rva_import_table = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;if(Rva_import_table == 0){ printf("no import table!"); goto UNMAP_AND_EXIT; }//这个虽然是内存地址,但是减去文件开头的地址,就是文件地址了 //这个地址可以直接从里面读取你想要的东西了PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa( pNtHeaders, lpBaseAddress, Rva_import_table, NULL );//减去内存映射的首地址,就是文件地址了。。(很简单吧) printf("FileAddress Of ImportTable: %p\n", ((DWORD)pImportTable - (DWORD)lpBaseAddress));//现在来到了导入表的面前:IMAGE_IMPORT_DESCRIPTOR 数组(以0元素为终止) //定义表示数组结尾的null元素! IMAGE_IMPORT_DESCRIPTOR null_iid; IMAGE_THUNK_DATA null_thunk; memset(&null_iid, 0, sizeof(null_iid)); memset(&null_thunk, 0, sizeof(null_thunk));//每个元素代表了一个引入的DLL。 for(i=0; memcmp(pImportTable + i, &null_iid, sizeof(null_iid))!=0; i++){ //LPCSTR: 就是 const char* LPCSTR szDllName = (LPCSTR)ImageRvaToVa( pNtHeaders, lpBaseAddress, pImportTable[i].Name, //DLL名称的RVA NULL);//拿到了DLL的名字 printf("-----------------------------------------\n"); printf("[%d]: %s\n", i, szDllName); printf("-----------------------------------------\n");//现在去看看从该DLL中引入了哪些函数 //我们来到该DLL的 IMAGE_TRUNK_DATA 数组(IAT:导入地址表)前面 PIMAGE_THUNK_DATA32 pThunk = (PIMAGE_THUNK_DATA32)ImageRvaToVa( pNtHeaders, lpBaseAddress, pImportTable[i].OriginalFirstThunk, //【注意】这里使用的是OriginalFirstThunk NULL);for(j=0; memcmp(pThunk+j, &null_thunk, sizeof(null_thunk))!=0; j++){ //这里通过RVA的最高位判断函数的导入方式, //如果最高位为1,按序号导入,否则按名称导入 if(pThunk[j].u1.AddressOfData & IMAGE_ORDINAL_FLAG32){ printf("\t [%d] \t %ld \t 按序号导入\n", j, pThunk[j].u1.AddressOfData & 0xffff); }else{ //按名称导入,我们再次定向到函数序号和名称 //注意其地址不能直接用,因为仍然是RVA! PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa( pNtHeaders, lpBaseAddress, pThunk[j].u1.AddressOfData, NULL);printf("\t [%d] \t %ld \t %s\n", j, pFuncName->Hint, pFuncName->Name); } } }UNMAP_AND_EXIT: //关闭文件,句柄。。 UnmapViewOfFile(lpBaseAddress); CloseHandle(hFileMapping); CloseHandle(hFile); getchar(); return 0; }
【注意】在上面的代码中使用的是 OriginalFirstThunk ,对于没有事先绑定的PE文件来说, OriginalFirstThunk 和 FirstThunk 是并行的内容相同的数组。但是对于已经经过绑定的PE文件来说, FirstThunk 数组中的元素会被设置成真正的函数地址(VA)!因此如果这时用 FirstThunk 数组尝试获取函数名称是得不到的。所以上面的代码应该使用 OriginalFirstThunk ,这样无论对于绑定还是未绑定的PE文件,都能够定向到相应的函数名称。
运行结果:
对应源文件信息:
总结
以上是生活随笔为你收集整理的导入表编程-枚举导入表的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 通过修改EIP寄存器实现强行跳转并且注入
- 下一篇: 虚拟桌面模拟查找点击自绘控件