当前位置: 首页 > 编程技术 > 正文

获得程序导入表函数名和导入表地址

得到导入表的函数的名称和导入表函数的地址在编写HookAPI时,较常用到

一般来说,分为下面的几步:

1.获取程序载入的ImageBase

2.获取DOS头

3.获取PE头

4.根据获得的PE头得到IMAGE_OPTIONAL_HEADER

5.得到IMAGE_OPTIONAL_HEADER后,得到导入表的地址

6.通过导入表的成员OriginalFirstThunk得到导入表的函数的名称

7.通过导入表的程序FirstThunk得到导入表的函数的地址

1获取ImageBase

通常使用GetMoudleHandle函数获取:

	/*
	   得到文件载入内存后的基址
	*/
	HMODULE hMod = GetModuleHandle(NULL);

2.获取DOS头的地址

因为在文件中,DOS头相对于文件的偏移为0,那么在载入内存后,ImageBase的地址就是DOS的起始地址了:

	/*
	  得到DOS头的地址,DOS头的RVA偏移地址为0,因此ImageBase就是DOS头的地址
	*/
	IMAGE_DOS_HEADER * pDosHeader = (IMAGE_DOS_HEADER *)hMod;

3.得到PE文件头的起始地址:

pe文件头的起始地址,存放在IMAGE_DOS_HEADER结构体的e_lfanew中:

pDosHeader->e_lfanew

4.得到IMAGE_OPTION_HEADER的起始地址:

	/*
	  根据DOS地址,得到PE文件头的VA地址,然后加上OPTION头相对于PE文件头的偏移再加上ImageBase
	  就得到了OPTION头的内存地址
	*/
	IMAGE_OPTIONAL_HEADER * pOptHeader = (IMAGE_OPTIONAL_HEADER *)(
		                                    (BYTE *)hMod                  /* 基址(ImageBase) */
		                                   + pDosHeader->e_lfanew  /* IMAGE_NT_HEADER相对偏移地址 */
										   + 24                   /* 24字节后是IMAGE_OPTIONAL_HEADER的相对偏移地址 */
	                                     );

5.得到导入表地址:

    /*
	  根据得到OPTION头的地址,得到导入表的RVA(导入表的第一个地址就是第一个DLL的地址)
	  然后加上ImageBase就是导入表的VA了
	*/
	IMAGE_IMPORT_DESCRIPTOR * pImportDesc = (IMAGE_IMPORT_DESCRIPTOR * )(
		                                         (BYTE *)hMod
		                                         + pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
											 ) ;

得到DLL模块的名称:

		/*
		  得到模块的名称名称,不要忘记一定要加上ImageBase值,因为得到地址都是RVA相对偏移地址
		*/
		char * chDllName = (char *) (
			 (BYTE *)hMod + pImportDesc->Name
			);

6.得到DLL模块中的导入函数的名称:

在得到了导入表的首地址后,要想获得导入表的名称,必须获得IMAGE_THUNK_DATA结构体的首地址, 在MSDN中,IMAGE_IMPORT_DESCRIPTOR的定义如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union{
	DWORD Characteristics;
	DWORD    OriginalFirstThunk; //hint/name(函数序号/名称)表的偏移量,记录了导入函数的名称
}
	DWORD    TimeDateStamp;
	DWORD     ForwarderChain;
	DWORD     Name;
	DWORD      FirstThunk;     //IAT(Import Address Table,导入地址表)的偏移量,记录了导入函数的地址

}IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

在IMAGE_IMPORT_DESCRIPTOR的结构体成员OriginalFirstThunk成员FirstThunk返回结果都是IMAGE_THUNK_DATA结构体的指针。

但是他们之间不同的是:

     OriginalFirstThunk返回的IMAGE_THUNK_DATA的首地址,IMAGE_THUNK_DATA结构体中得到的导入函数的名称相关信息,具体的名称在IMAGE_THUNK_DATA.u1.AddressOfData中

     FirstThunk返回的IMAGE_THUNK_DATA的首地址,IMAGE_THUNK_DATA结构体中得到的导入函数的地址相关信息,具体的名称在IMAGE_THUNK_DATA.u1.Function中 总的来说,

更加通俗一点,:

要想获得导入函数的名称信息,使用IMAGE_IMPORT_DESCRIPTOR的OriginalFirstThunk成员

要想获得导入函数的地址信息,使用IMAGE_IMPORT_DESCRIPTOR的FirstThunk成员

得到导入函数的名称:

		/*
		   根据得到DLL模块的首地址,得到DLL模块中的函数地址信息数组的首地址
		*/
		IMAGE_THUNK_DATA * pThunkData = (IMAGE_THUNK_DATA * )((BYTE*)hMod + pImportDesc->OriginalFirstThunk);
			/*
			  通过得到的函数地址信息数组,取得函数的名称,Thunk数据的前两个字节是hint信息,既函数的序号
			  后面的才是函数的名称
			*/
			char * chFunName = (char *)(
				 (BYTE *)hMod + (DWORD)pThunkData->u1.AddressOfData +2
				);
                        cout<<"  从此DLL导入的函数:"<<chFunName;

7.得到导入函数的地址:

                        int n = 0;
			PDWORD lpFunAddress = (DWORD *)(
				      (BYTE *)hMod
					+ pImportDesc->FirstThunk /* DLL模块的首地址,也是第一个函数的首地址 */

				) +n ;                       /* 第n个函数 */

实现获取应用程序的全部DLL模块名称和导入函数名称和导入函数地址的完整代码如下:

#include
#include
using namespace std;
void main(int argc,char argv[])
{
	/*
	   得到文件载入内存后的基址
	*/
	HMODULE hMod = GetModuleHandle(NULL);  

	/*
	  得到DOS头的地址,DOS头的RVA偏移地址为0,因此ImageBase就是DOS头的地址
	*/
	IMAGE_DOS_HEADER * pDosHeader = (IMAGE_DOS_HEADER *)hMod;  

	/*
	  根据DOS地址,得到PE文件头的VA地址,然后加上OPTION头相对于PE文件头的偏移再加上ImageBase
	  就得到了OPTION头的内存地址
	*/
	IMAGE_OPTIONAL_HEADER * pOptHeader = (IMAGE_OPTIONAL_HEADER *)(
		                                    (BYTE *)hMod                  /* 基址(ImageBase) */
		                                   + pDosHeader->e_lfanew  /* IMAGE_NT_HEADER相对偏移地址 */
										   + 24                   /* 24字节后是IMAGE_OPTIONAL_HEADER的相对偏移地址 */
	                                     );

    /*
	  根据得到OPTION头的地址,得到导入表的RVA(导入表的第一个地址就是第一个DLL的地址)
	  然后加上ImageBase就是导入表的VA了
	*/
	IMAGE_IMPORT_DESCRIPTOR * pImportDesc = (IMAGE_IMPORT_DESCRIPTOR * )(
		                                         (BYTE *)hMod
		                                         + pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
											 ) ;

	/*
	  根据得到的导入表的首地址,得到各个DLL模块的地址(FirstTunk值)
	*/
	while(pImportDesc->FirstThunk){

		/*
		  得到模块的名称名称,不要忘记一定要加上ImageBase值,因为得到地址都是RVA相对偏移地址
		*/
		char * chDllName = (char *) (
			 (BYTE *)hMod + pImportDesc->Name
			);
                cout<<"DLL模块的名称为: "<<chDllName<<endl; 
                /*
		   根据得到DLL模块的首地址,得到DLL模块中的函数地址信息数组的首地址
		*/
		IMAGE_THUNK_DATA * pThunkData = (IMAGE_THUNK_DATA * )((BYTE*)hMod + pImportDesc->OriginalFirstThunk);

		int n = 0;
		while(pThunkData->u1.Function){ /* 判断DLL模块中的函数地址信息数组是否有函数 */

			/*
			  通过得到的函数地址信息数组,取得函数的名称,Thunk数据的前两个字节是hint信息,既函数的序号
			  后面的才是函数的名称
			*/
			char * chFunName = (char *)(
				 (BYTE *)hMod + (DWORD)pThunkData->u1.AddressOfData +2
				);
                        cout<<"  从此DLL导入的函数:"<<chFunName;
			/* DLL模块中的函数地址信息数组DLL模块中的函数地址信息数组
			    得到第n个函数的地址
			*/
			PDWORD lpFunAddress = (DWORD *)(
				      (BYTE *)hMod
					+ pImportDesc->FirstThunk /* DLL模块的首地址,也是第一个函数的首地址 */

				) +n ;                       /* 第n个函数 */
                         cout<<"\t\t函数地址:"<<lpFunAddress<<endl;

			/*
			   指向下一个函数信息数组和下一个函数
			*/
			n++,pThunkData++;

		}

		/*
		  指针移动到下一个DLL模块
		*/
		pImportDesc++;

	}

}


本文固定链接: http://kuaile.in/archives/1142 | 蒲公英的博客

该日志由 蒲公英 于2012年08月03日发表在 编程技术 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: 获得程序导入表函数名和导入表地址 | 蒲公英的博客
关键字: ,

获得程序导入表函数名和导入表地址:等您坐沙发呢!

发表评论


You must enable javascript to see captcha here!

快捷键:Ctrl+Enter