要实现修改导入段来拦截API必须对PE文件格式有很好的了解。网上关于它的资料铺天盖地,自己搜吧。此处不打算介绍。
为了修改要拦截的函数在导入段的地址,首先要获得PE文件导入段的地址。这可以调用ImageDirectoryEntryToData.函数。
PVOID WINAPI ImageDirectoryEntryToData( __in PVOID Base, __in BOOLEAN MappedAsImage, __in USHORT DirectoryEntry, __out PULONG Size );
Base为要获得导入段所在模块的基地址。它一般是GetModuleHandle的返回值。
MappedAsImage,它为true时,系统将该模块以映像文件的形式映射,否则以数据文件的形式映射。
DirectoryEntry,要获得的段的索引。此函数不仅仅能够获得导入段的地址,根据此索引的不同,该函数返回对应段的地址。此处我们使用IMAGE_DIRECTORY_ENTRY_IMPORT表示我们要获得导入段的地址。
Size返回表项的大小。注意其类型。
看代码:
ULONG size; HMODULE hModule=GetModuleHandle(NULL); PIMAGE_IMPORT_DESCRIPTOR pImport= ImageDirectoryEntryToData(hModule,true,IMAGE_DIRECTORY_ENTRY_IMPORT,&size); while(pImport->FirstThunk) { int i=0; char *ModuleName=(char*)((BYTE*)hModule+pImport->Name); PIMAGE_THUNK_DATA pThunk=(PIMAGE_THUNK_DATA) ((BYTE*)hModule+pImport->FirstThunk); while(pThunk->u1.Function) { char*Func=(char*)((BYTE*)hModule+pThunk->u1.AddressOfData+2); If(Func=="MessageBoxA") { MessageBox("找到函数"); } } }
IMAGE_IMPORT_DESCRIPTOR结构是导入段的基本结构,导入段是由此类型组成的数组构成,每个DLL对应数组中的一项。数组的最后一项为NULL。每一项中有两个关键成员,指出IMAGEA_THUNK_DATA类型的数组的偏移量,这两个数组分别列出了从此DLL导入的函数名称,以及它它们在本进程地址空间的RVA。
以下是其定义:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk;// 指向一个 IMAGE_THUNK_DATA 结构数组的RVA } DWORD TimeDateStamp;// 文件生成的时间 DWORD ForwarderChain;// 这个数据一般为0,可以不关心 DWORD Name; // RVA,指向DLL名字的指针,ASCII字符串 DWORD FirstThunk; //指向一个 IMAGE_THUNK_DATA 结构数组的RVA,这个数据与IAT所指向的地址一致 }IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR
OriginalFirstThunk和FirstThunk是此结构中的关键成员。它们指出这两个IMAGE_THUNK_DATA类型的数组。
Name是指向DLL名称的指针。
pImport指向模块的导入段,可以使用它来访问IMAGE_IMPORT_DESCRIPTOR结构的每一个成员。
注意:以上所有偏移量都是相对于模块基地址的,因此在实际使用中一定要加上模块的地址。
OriginalFirstThunk指向NAT,NAT列出了从该DLL导入的所有导入函数的名称。
FirstThunk指向IAT,IAT列出了与NAT相对应的导入函数的地址。
看此结构定义:
typedef struct _IMAGE_THUNK_DATA32 { union { PBYTE ForwarderString; PDWORD Function; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; } u1; } IMAGE_THUNK_DATA32;
上述代码中,外层循环while(pImport->FirstThunk)用于遍历所有DLL,内层循环用于遍历查找每个DLL中的导入函数名称。我们以查找MessageBoxA函数为例来做介绍。此处我们通过对NAT进行扫描,将其与我们给出的字符串类型的函数名MessageBoxA进行比较。注意要使用大小写不敏感的函数:
int WINAPI lstrcmpi( __in LPCTSTR lpString1, __in LPCTSTR lpString2 );
这是一种方法,另一种方法是比较函数地址。我们可以对IAT进行扫描,将得到的地址与我们给出的函数地址进行比较。这两种方法是经常使用的方法。
下面给出使用地址比较的方法。
HMODULE hModule=GetModuleHandle(NULL); ULONG size; PIMAGE_IMPORT_DESCRIPTOR pImport=(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData (hModule,true,IMAGE_DIRECTORY_ENTRY_IMPORT,&size); UpdateData(); //获得MessageBoxA的地址。 PROC pfnOld=GetProcAddress(GetModuleHandle("User32.dll"),"MessageBoxA"); while(pImport->FirstThunk) { char *ModuleName=(char*)((BYTE*)hModule+pImport->Name); PIMAGE_THUNK_DATA pThunk=(PIMAGE_THUNK_DATA)((BYTE*)hModule+pImport->FirstThunk); while(pThunk->u1.Function) { PROC *ppfn=(PROC*)&(pThunk->u1.Function); if(*ppfn==pfnOld)pfnOld为要查询的函数地址。 { MessageBox("找到给定的函数"); return ; } pThunk++; } pImport++; }
当然以上仅仅是找到相应的函数。要用我们期望的函数代替它,还需要我们自己定义函数。
此处有一点要特别注意,当我们对某函数进行拦截时,要替换的函数必须与被替换的函数的原型完全相同。
比如此时我们要对MessageBoxA进行拦截,我们定义自己的函数的原型与MessageBoxA原型是完全一致的:
int WINAPI MyMessageBoxA(HWND hWnd,LPSTR str,LPSTR caption ,UINT type) { //定义函数的行为。 }
我们可以在函数中调用被拦截的函数,也可以不对拦截的函数进行调用而进行其他操作。
注意:在拦截后,原来的函数地址已经被我们自定义的函数的地址覆盖,如果之后需要调用被拦截的函数,就必须在覆盖之前保存被拦截函数的地址。如果我们没有保存而直接调用,调用的只是替换后的函数。
以下代码将对MessageBox的调用进行拦截,并用我们的自定义函数MyMessageBox替代。
g_addr用于存储拦截之前MessageBox的地址
Typedef int (WINAPI *PFNMESSAGEBOX(HWND,LPSTR,LPSTR,UINT);//定义函数指针。 PFNMESSAGEBOX g_addr=(PFNMESSAGEBOX)MessageBoxA;//存储MessageBoxA的地址。 //自定义的函数。 int WINAPI MyMessage(HWND hWnd,LPSTR a,LPSTR b,UINT type) { return ((PFNMESSAGEBOX)g_addr)(hWnd,a,"替换后的函数!?",MB_YESNO); } //对 MessageBox进行拦截,用MyMessageBox进行替换。 Void ReplaceOneFunc(PCSTR ModuleName,PFNMESSAGEBOX pfnOld,PFNMESSAGEBOX pfnNew) { pfnOld=(PFNMESSAGEBOX)MessageBoxA; pfnNew=(PFNMESSAGEBOX)MyMessageBox; HMODULE hModule=GetModuleHandle(NULL); ULONG size; PIMAGE_IMPORT_DESCRIPTOR pImport=(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData (hModule,true,IMAGE_DIRECTORY_ENTRY_IMPORT,&size); UpdateData(); while(pImport->FirstThunk) { char *ModuleName=(char*)((BYTE*)hModule+pImport->Name); PIMAGE_THUNK_DATA pThunk=(PIMAGE_THUNK_DATA)((BYTE*)hModule+pImport->FirstThunk); while(pThunk->u1.Function) { PFNMESSAGEBOX *ppfn=(PFNMESSAGEBOX*)&(pThunk->u1.Function); if(*ppfn==pfnOld) { MessageBox("找到函数!恭喜你!"); SIZE_T num; WriteProcessMemory(GetCurrentProcess(),ppfn,&pfnNew,4,&num); MessageBox(NULL,"现在调用的是替换后的API!","",MB_OK); ((PFNMESSAGEBOX)g_addr)(NULL,"哈哈,现在终于轮到你了","",MB_OK); return ; } pThunk++; } pImport++; } MessageBox("没有找到该函数!"); }
WriteProcessMemory(GetCurrentProcess(),ppfn,&pfnNew,4,&num);
此函数用于修改内存。实现用MyMessagebox的地址替换MessageBox的地址。关于此函数的功能请参考MSDN。
((PFNMESSAGEBOX)g_addr)(NULL,"哈哈,现在终于轮到你了","",MB_OK);
此句用于调用在拦截之前MessageBox的地址。此处的调用是调用的MessageBox函数,而不是MyMessageBox函数。
至此关于修改导入段拦截API介绍完毕。