GetProcAddress的二分查找
只要稍微熟悉PE结构就能很快写出来,GetProcAddress的原型如下
FARPROC WINAPI GetProcAddress(HMODULE hModule,LPCSTR lpProcName)
hModule是指模块的基地址,比如用LoadLibrary加载dll返回的地址
lpProcName顾名思义就是函数或者变量的名称,但也可以是函数或者变量在模块中的序号(ordinal)
本文实现的GetProcAddress没有实现序号的功能,仅仅是通过名字字符串来得到地址,主要是因为dll版本的不同会导致序号发生改变,因此这里不提倡使用序号来获取地址(虽然序号可以不用通过查找而直接获得地址)
首先要知道PE的输出表结构EAT,数据结构为IMAGE_EXPORT_DIRECTORY(winnt.h中定义)
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; // RVA from base of image DWORD AddressOfNames; // RVA from base of image DWORD AddressOfNameOrdinals; // RVA from base of image } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
GetProcAddress中用到的几个重要的数据成员需要科普一下
NumberOfFunctions指的是EAT中函数或者变量的总个数,如果该值为0,则表示该模块没有导出任何的函数或者变量
NumberOfNames指的是EAT中函数或者变量名称的总个数,这个值总是小于或者NumberOfFunctions,原因是有些模块并不想把一些重要的函数或者变量导出来供他人使用,比如微软就经常这么干
AddressOfFunctions指的是一个指向函数或者变量地址的RVA数组
AddressOfNames指的是一个指向函数名或者变量名的ASCII字符串RVA数组,该数组是排好序了的,因此可以通过二分查找来搜索加快查找速度
AddressOfNameOrdinals指的是输出表序数的RVA数组,这是一个USHORT的数组
搜索的方法主要是通过名称字符串和AddressOfNames数组中的字符串进行比较,然后找到相应的偏移,再然后定位AddressOfNameOrdinals数组得到它的序号,最后定位AddressOfFunctions找到地址
下面给出两段代码,在ring0和ring3层中都可以使用
一种是通过顺序查找GetExportGeneral
ULONG_PTR GetExportGeneral(ULONG_PTR ImageBase,PCHAR ExportName) { PIMAGE_DOS_HEADER DosHeader; PIMAGE_NT_HEADERS NtHeader; PIMAGE_EXPORT_DIRECTORY ExportDirectory; PULONG AddressOfNames; PULONG AddressOfFunctions; PUSHORT AddreessOfOrdinals; ULONG dwOffset; USHORT NameOrdinal; PCHAR FuncName; __try{ DosHeader=(PIMAGE_DOS_HEADER)ImageBase; if (DosHeader->e_magic!=IMAGE_DOS_SIGNATURE) return 0; NtHeader=(PIMAGE_NT_HEADERS)(ImageBase+DosHeader->e_lfanew); if (NtHeader->Signature!=IMAGE_NT_SIGNATURE) return 0; ExportDirectory=(PIMAGE_EXPORT_DIRECTORY)(ImageBase+NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); if (!ExportDirectory) return 0; AddressOfFunctions=(PULONG)(ImageBase+ExportDirectory->AddressOfFunctions); AddressOfNames=(PULONG)(ImageBase+ExportDirectory->AddressOfNames); AddreessOfOrdinals=(PUSHORT)(ImageBase+ExportDirectory->AddressOfNameOrdinals); for (dwOffset=0;dwOffset<ExportDirectory->NumberOfNames;dwOffset++) { FuncName=(PCHAR)(ImageBase+AddressOfNames[dwOffset]); if (!strcmp(ExportName,FuncName)) { NameOrdinal=AddreessOfOrdinals[dwOffset]; return ImageBase+AddressOfFunctions[NameOrdinal]; } } } __except(EXCEPTION_EXECUTE_HANDLER) { return 0; } return 0; }
另一种通过二分查找GetExportBinarySearch
ULONG_PTR GetExportBinarySearch(ULONG_PTR ImageBase,PCHAR ExportName) { PIMAGE_DOS_HEADER DosHeader; PIMAGE_NT_HEADERS NtHeader; PIMAGE_EXPORT_DIRECTORY ExportDirectory; PULONG_PTR AddressOfNames; PULONG_PTR AddressOfFunctions; PUSHORT AddressOfNameOrdinals; DWORD NumberOfNames; DWORD low,mid,high; USHORT NameOrdinal; INT CompareResult; __try{ DosHeader=(PIMAGE_DOS_HEADER)ImageBase; if (DosHeader->e_magic!=IMAGE_DOS_SIGNATURE) return 0; NtHeader=(PIMAGE_NT_HEADERS)(ImageBase+DosHeader->e_lfanew); if (NtHeader->Signature!=IMAGE_NT_SIGNATURE) return 0; ExportDirectory=(PIMAGE_EXPORT_DIRECTORY)(ImageBase+NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); if (!ExportDirectory||(AddressOfNames=(PULONG_PTR)(ImageBase+ExportDirectory->AddressOfNames), AddressOfFunctions=(PULONG_PTR)(ImageBase+ExportDirectory->AddressOfFunctions), AddressOfNameOrdinals=(PUSHORT)(ImageBase+ExportDirectory->AddressOfNameOrdinals), NumberOfNames=ExportDirectory->NumberOfNames, low=0,high=NumberOfNames-1,high<0)) return 0; while (low<=high) { mid=(high+low)>>1; CompareResult=strcmp(ExportName,(const char*)ImageBase+AddressOfNames[mid]); if(CompareResult>=0) { if (CompareResult<=0) break; low=mid+1; } else high=mid-1; } if (low<=high &&(NameOrdinal=AddressOfNameOrdinals[mid],NameOrdinal<ExportDirectory->NumberOfFunctions)) return ImageBase+AddressOfFunctions[NameOrdinal]; } __except(EXCEPTION_EXECUTE_HANDLER){ return 0; } return 0; }
好了,代码都比较的简单,稍微看一下就能懂,有兴趣的话可以用GetProcAddress(ring3)或者MmGetSystemRoutineAddress(ring0)来验证一下~~~如果有问题的话欢迎指正!
BTW:博客园有代码着色的插件么?客户端的插入代码插件怎么显示行号?