实现GetProcAddress

标 题: 【原创】完美实现GetProcAddress
作 者: blueapplez
时 间: 2010-09-27,22:11:13
链 接: http://bbs.pediy.com/showthread.php?t=121226

// 本文系转载,代码中的部分注释是我学习中写的,通过作者的探索,让我了解了更多的关于GetProcAddress函数的细节,再次对前人的探索以及无私的共享表示感谢!

 

我们知道kernel32.dll里有一个GetProcAddress函数,可以找到模块中的函数地址,函数原型是这样的:

[cpp]  view plain copy
  1. WINBASEAPI  
  2. FARPROC  
  3. WINAPI  
  4. GetProcAddress(  
  5.     IN HMODULE hModule,  
  6.     IN LPCSTR lpProcName  
  7.     );  
  8. hModule 是模块的句柄,说白了就是内存中dll模块的首地址  
  9. loProcName 一般指函数名称的字符串地址,也可能是指序号,如何区分呢?  
  10. 我们这样  
  11.   if (((DWORD)lpProcName& 0xFFFF0000) == 0)  
  12.   {  
  13.     //这里是序号导出的;  
  14.   }  
  15.   {  
  16.                //这里是函数名称导出的  
  17.   }  

 

最终真找到匹配的函数地址,然后返回就ok了,但是前提是你需要了解PE的导出表
下面简单说一下,首先从PE里找到下面这个结构,如果不知道如何找的话,建议在坛子里搜索一下PE结构解析的文章,找到这个结构应该

还是很简单的

[cpp]  view plain copy
  1. typedef struct _IMAGE_EXPORT_DIRECTORY {  
  2.     DWORD   Characteristics;  
  3.     DWORD   TimeDateStamp;  
  4.     WORD    MajorVersion;  
  5.     WORD    MinorVersion;  
  6.     DWORD   Name;  
  7.     DWORD   Base;  
  8.     DWORD   NumberOfFunctions;  
  9.     DWORD   NumberOfNames;  
  10.     DWORD   AddressOfFunctions; // RVA from base of image  
  11.     DWORD   AddressOfNames;     // RVA from base of image  
  12.     DWORD   AddressOfNameOrdinals;// RVA from base of image  
  13. } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;  

具体什么意思呢?  
虽然, kanxue老大在这里写的很漂亮了都
http://www.pediy.com/tutorial/chap8/Chap8-1-7.htm
我还是打算啰嗦一下
Base  函数以序号导出的时候的序号基数,从这个数开始递增
NumberOfFunctions 本dll一共有多少个导出函数,不管是以序号还是以函数名导出
NumberOfFunctions 本dll中以能够以函数名称导出的函数个数(注意,说一下,其实函数里的每一个函数都能通过序号导出,但是为了

兼容性等等,也给一些函数提供用函数名称来导出)
AddressOfFunctions  指向一个DWORD数组首地址,共有NumberOfFunctions 个元素,每一个元素都是一个函数地址
AddressOfNames 指向一个DWORD数组首地址,共有NumberOfNames个元素,每一个元素都是一个字符串(函数名字符串)首地址
AddressOfNameOrdinals指向一个WORD数组首地址,共有NumberOfNames个元素,每一个元素都是一个函数序号
我们说的最后俩数组,其实是一种一一对应的关系,假如分别叫 dwNames[] 和 dwNamesOrdinals[],
假如dwNames[5]的值(这个指是一个地址,前面都说了)指向的字符串等于“GetValue”,那么dwNamesOrdinals[5]的值(这个指是一个

序号,前面都说了),就是GetValue导出函数的序号啦,那么怎样找到地址呢?
这时候就需要用到第一个数组了,假如名字叫dwFuncAddress[], GetValue的导出地址就是
dwFuncAddress[dwNamesOrdinals[5]] + 模块基址
好了,啰嗦了这么多,看代码:

代码:

[c-sharp]  view plain copy
  1. DWORD MyGetProcAddress(  
  2.              HMODULE hModule,    // handle to DLL module  
  3.              LPCSTR lpProcName   // function name  
  4.              )  
  5. {  
  6.       
  7.     int i=0;  
  8.     PIMAGE_DOS_HEADER pImageDosHeader = NULL;  
  9.     PIMAGE_NT_HEADERS pImageNtHeader = NULL;  
  10.     PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL;  
  11.       
  12.     pImageDosHeader=(PIMAGE_DOS_HEADER)hModule;  
  13.     pImageNtHeader=(PIMAGE_NT_HEADERS)((DWORD)hModule+pImageDosHeader->e_lfanew);  
  14.     pImageExportDirectory=(PIMAGE_EXPORT_DIRECTORY)((DWORD)hModule+pImageNtHeader->OptionalHeader.DataDirectory  
  15.   
  16. [IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);  
  17.       
  18.     DWORD *pAddressOfFunction = (DWORD*)(pImageExportDirectory->AddressOfFunctions + (DWORD)hModule);  
  19.     DWORD *pAddressOfNames = (DWORD*)(pImageExportDirectory->AddressOfNames + (DWORD)hModule);  
  20.     DWORD dwNumberOfNames = (DWORD)(pImageExportDirectory->NumberOfNames);  
  21.     DWORD dwBase = (DWORD)(pImageExportDirectory->Base);  
  22.       
  23.     WORD *pAddressOfNameOrdinals = (WORD*)(pImageExportDirectory->AddressOfNameOrdinals + (DWORD)hModule);  
  24.       
  25.         //这个是查一下是按照什么方式(函数名称or函数序号)来查函数地址的   
  26.     DWORD dwName = (DWORD)lpProcName;  
  27.     if ((dwName & 0xFFFF0000) == 0)//这个是通过以序号的方式来查函数地址的  
  28.     {  
  29.         if (dwName < dwBase || dwName > dwBase + pImageExportDirectory->NumberOfFunctions - 1)  
  30.         {  
  31.             return 0;  
  32.         }  
  33.         return (pAddressOfFunction[dwName - dwBase] + (DWORD)hModule);  
  34.     }  
  35.     // 通过函数名导出的函数  
  36.     for (i=0; i < (int)dwNumberOfNames; i++)  
  37.     {  
  38.         char *strFunction = (char *)(pAddressOfNames[i] + (DWORD)hModule);  
  39.         if (lstrcmp(lpProcName, strFunction) == 0)  
  40.         {  
  41.             return (pAddressOfFunction[pAddressOfNameOrdinals[i]] + (DWORD)hModule);  
  42.         }  
  43.     }  
  44.     return 0;  
  45. }  

好了,测试一下,
        //我们写的函数返回的地址
  DWORD dw1 = MyGetProcAddress(LoadLibrary("user32.dll"), "MessageBoxA");
        //系统函数返回的地址
  DWORD dw2 = (DWORD)GetProcAddress(LoadLibrary("user32.dll"), "MessageBoxA");
哈,发现一样,突然非常有成就感!!
再试试序号查找
        //我们写的函数返回的地址
  DWORD dw1 = MyGetProcAddress(LoadLibrary("user32.dll"), (LPCSTR)0x110);
        //系统函数返回的地址
  DWORD dw2 = (DWORD)GetProcAddress(LoadLibrary("user32.dll"), (LPCSTR)0x110);
我们发现还是一样,成就感更大啦。。哈哈(其实kernel32.dll的0x110 是GetComputerNameExW的序号,自己可以用LordPE查一下)

突然有一天有人说 你的这个函数不行,然后给你举了个例子,于是你测试了一下,下面是例子
  DWORD a1 = (DWORD)MyGetProcAddress(LoadLibrary("kernel32.dll"), "HeapFree");
  DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"), "HeapFree");
于是 我们就苦思冥想,依然不得其解。。。
但是我发现a1表示的地址的内容是一个字符串 "NTDLL.RtlFreeHeap"似乎不能用巧合来说这个问题,难道是返回了这个字符串  还要我们

再Load一下Ntdll 然后再找一个RtlFreeHeap的地址吗?好了先试验一下 果然ntdll.dll中的 RtlFreeHeap的地址 和a2的值的一样的,
似乎印证了什么东西,
好吧  OD拿来   开启逆向 kernel32.GetProcAddress 搞了一会头晕了,看不出头绪
我老大很有才,他去翻了翻win2000的源码  偶也   一目了然
Win2K 源码

代码:

[cpp]  view plain copy
  1. FARPROC  
  2. GetProcAddress(  
  3.     HMODULE hModule,  
  4.     LPCSTR lpProcName  
  5.     )  
  6.   
  7. /*++ 
  8.  
  9. Routine Description: 
  10.  
  11.     This function retrieves the memory address of the function whose 
  12.     name is pointed to by the lpProcName parameter.  The GetProcAddress 
  13.     function searches for the function in the module specified by the 
  14.     hModule parameter, or in the module associated with the current 
  15.     process if hModule is NULL.  The function must be an exported 
  16.     function; the module's definition file must contain an appropriate 
  17.     EXPORTS line for the function. 
  18.  
  19.     If the lpProcName parameter is an ordinal value and a function with 
  20.     the specified ordinal does not exist in the module, GetProcAddress 
  21.     can still return a non-NULL value.  In cases where the function may 
  22.     not exist, specify the function by name rather than ordinal value. 
  23.  
  24.     Only use GetProcAddress to retrieve addresses of exported functions 
  25.     that belong to library modules. 
  26.  
  27.     The spelling of the function name (pointed to by lpProcName) must be 
  28.     identical to the spelling as it appears in the source library's 
  29.     definition (.DEF) file.  The function can be renamed in the 
  30.     definition file.  Case sensitive matching is used??? 
  31.  
  32. Arguments: 
  33.  
  34.     hModule - Identifies the module whose executable file contains the 
  35.         function.  A value of NULL references the module handle 
  36.         associated with the image file that was used to create the 
  37.         current process. 
  38.  
  39.  
  40.     lpProcName - Points to the function name, or contains the ordinal 
  41.         value of the function.  If it is an ordinal value, the value 
  42.         must be in the low-order word and zero must be in the high-order 
  43.         word.  The string must be a null-terminated character string. 
  44.  
  45. Return Value: 
  46.  
  47.     The return value points to the function's entry point if the 
  48.     function is successful.  A return value of NULL indicates an error 
  49.     and extended error status is available using the GetLastError function. 
  50.  
  51.  
  52. --*/  
  53.   
  54. {  
  55.     NTSTATUS Status;  
  56.     PVOID ProcedureAddress;  
  57.     STRING ProcedureName;  
  58.   
  59.     //+ by blueapplez  
  60.     //这应该是按函数名称查找  
  61.     //+ by blueapplez  
  62.     if ( (ULONG_PTR)lpProcName > 0xffff )   
  63.     {  
  64.         RtlInitString(&ProcedureName,lpProcName);  
  65.         Status = LdrGetProcedureAddress(  
  66.                         BasepMapModuleHandle( hModule, FALSE ),  
  67.                         &ProcedureName,  
  68.                         0L,  
  69.                         &ProcedureAddress  
  70.                         );  
  71.     }  
  72.     //+ by blueapplez  
  73.     //这应该是按函数序号查找  
  74.     //+ by blueapplez  
  75.     else   
  76.     {  
  77.         Status = LdrGetProcedureAddress(  
  78.                         BasepMapModuleHandle( hModule, FALSE ),  
  79.                         NULL,  
  80.                         PtrToUlong((PVOID)lpProcName),  
  81.                         &ProcedureAddress  
  82.                         );  
  83.     }  
  84.     if ( !NT_SUCCESS(Status) )   
  85.     {  
  86.         BaseSetLastNTError(Status);  
  87.         return NULL;  
  88.     }  
  89.     else   
  90.     {  
  91.         if ( ProcedureAddress == BasepMapModuleHandle( hModule, FALSE ) )  
  92.         {  
  93.             if ( (ULONG_PTR)lpProcName > 0xffff )   
  94.             {  
  95.                 Status = STATUS_ENTRYPOINT_NOT_FOUND;  
  96.             }  
  97.             else   
  98.             {  
  99.                 Status = STATUS_ORDINAL_NOT_FOUND;  
  100.             }  
  101.             BaseSetLastNTError(Status);  
  102.             return NULL;  
  103.         }  
  104.         else   
  105.         {  
  106.             return (FARPROC)ProcedureAddress;  
  107.         }  
  108.       }  
  109. }  

LdrGetProcedureAddress 是什么呢?
继续看

代码:
NTSTATUS
LdrGetProcedureAddress (
    IN PVOID DllHandle,
    IN PANSI_STRING ProcedureName OPTIONAL,
    IN ULONG ProcedureNumber OPTIONAL,
    OUT PVOID *ProcedureAddress
    )
{

    return LdrpGetProcedureAddress(DllHandle,ProcedureName,ProcedureNumber,ProcedureAddress,TRUE);

}

 LdrpGetProcedureAddress 就太长了   先主要说一下 他里面的一个关键函数 LdrpSnapThunk的关键的一段,能不能看懂我就不管了 

反正我基本看不懂


代码:

[cpp]  view plain copy
  1. else   
  2.   {  
  3.              Addr = (PULONG)((ULONG_PTR)DllBase + (ULONG)ExportDirectory->AddressOfFunctions);  
  4.              Thunk->u1.Function = ((ULONG_PTR)DllBase + Addr[OrdinalNumber]);  
  5.   
  6.             //+ by blueapplez  
  7.             //这段是判断一下返回的地址是否越界  
  8.             //越界了 就返回原来的值  
  9.             //+ by blueapplez  
  10.              if (Thunk->u1.Function > (ULONG_PTR)ExportDirectory && Thunk->u1.Function < ((ULONG_PTR)ExportDirectory +   
  11.   
  12. ExportSize))   
  13.              {  
  14.                 UNICODE_STRING UnicodeString;  
  15.                 ANSI_STRING ForwardDllName;  
  16.                 PVOID ForwardDllHandle;  
  17.                 PUNICODE_STRING ForwardProcName;  
  18.                 ULONG ForwardProcOrdinal;  
  19.   
  20.             //+ by blueapplez  
  21.             //如果没有越界,就从那个地址中查找'.',找到了就再做一次Load Dll,和GetProcAddress  
  22.             //没有找到'.' 就返回原来的值  
  23.             //+ by blueapplez  
  24.                 ImportString = (PSZ)Thunk->u1.Function;  
  25.                 ForwardDllName.Buffer = ImportString,  
  26.                 ForwardDllName.Length = (USHORT)(strchr(ImportString, '.') - ImportString);  
  27.                 ForwardDllName.MaximumLength = ForwardDllName.Length;  
  28.                 st = RtlAnsiStringToUnicodeString(&UnicodeString, &ForwardDllName, TRUE);  
  29.   
  30.                 if (NT_SUCCESS(st))   
  31.         {  
  32.     #if defined (WX86)  
  33.                     if (Wx86ProcessInit)  
  34.              {  
  35.                         NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll = RtlImageNtHeader(DllBase)->FileHeader.Machine  
  36.                                                    == IMAGE_FILE_MACHINE_I386;  
  37.              }  
  38.         #endif  

好了 到此已经告一段落, 下面给出源码,有人会问,怎样做的dll才会出现查找导出表的地址的时候返回一个字符串呢? 其实就是在 

.def文件的EXPORTS后加一句就成(这个我老大试验了n久才搞定)加上 MsgBox = user32.MessageBoxA


代码:

[cpp]  view plain copy
  1. DWORD MyGetProcAddress(HMODULE hModule,LPCSTR lpProcName)  
  2. {  
  3.     int i=0;  
  4.     char *pRet = NULL;  
  5.     PIMAGE_DOS_HEADER pImageDosHeader = NULL;  
  6.     PIMAGE_NT_HEADERS pImageNtHeader = NULL;  
  7.     PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL;  
  8.       
  9.     pImageDosHeader=(PIMAGE_DOS_HEADER)hModule;  
  10.     pImageNtHeader=(PIMAGE_NT_HEADERS)((DWORD)hModule+pImageDosHeader->e_lfanew);  
  11.     pImageExportDirectory=(PIMAGE_EXPORT_DIRECTORY)((DWORD)hModule+pImageNtHeader->OptionalHeader.DataDirectory  
  12.   
  13. [IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);  
  14.       
  15.     DWORD dwExportRVA = pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;  
  16.     DWORD dwExportSize = pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;  
  17.       
  18.     DWORD *pAddressOfFunction = (DWORD*)(pImageExportDirectory->AddressOfFunctions + (DWORD)hModule);  
  19.     DWORD *pAddressOfNames = (DWORD*)(pImageExportDirectory->AddressOfNames + (DWORD)hModule);  
  20.     DWORD dwNumberOfNames = (DWORD)(pImageExportDirectory->NumberOfNames);  
  21.     DWORD dwBase = (DWORD)(pImageExportDirectory->Base);  
  22.       
  23.     WORD *pAddressOfNameOrdinals = (WORD*)(pImageExportDirectory->AddressOfNameOrdinals + (DWORD)hModule);  
  24.       
  25.     //这个是查一下是按照什么方式(函数名称or函数序号)来查函数地址的  
  26.     DWORD dwName = (DWORD)lpProcName;  
  27.     if ((dwName & 0xFFFF0000) == 0)//这个是通过以序号的方式来查函数地址的  
  28.     {       // (dwName & 0xFFFF0000) == 0  
  29.         // 取出函数的序号 ------>>>>>>>>>>>>>   IMAGE_ORDINAL(dwName)  
  30.         // #define IMAGE_ORDINAL(Ordinal)          IMAGE_ORDINAL32(Ordinal)  
  31.         // #define IMAGE_ORDINAL32(Ordinal)        (Ordinal & 0xffff)  
  32.         if (dwName < dwBase || dwName > dwBase + pImageExportDirectory->NumberOfFunctions - 1)  
  33.         {  
  34.             return 0;  
  35.         }  
  36.         pRet = (char *)(pAddressOfFunction[dwName - dwBase] + (DWORD)hModule);  
  37.   
  38.         return (DWORD)pRet;  
  39.     }  
  40.        
  41.     for (i=0; i < (int)dwNumberOfNames; i++)  
  42.     {  
  43.         char *strFunction = (char *)(pAddressOfNames[i] + (DWORD)hModule);  
  44.         if (strcmp(strFunction, (char *)lpProcName) == 0)  
  45.         {  
  46.             pRet = (char *)(pAddressOfFunction[pAddressOfNameOrdinals[i]] + (DWORD)hModule);  
  47.             //判断得到的地址有没有越界  
  48.             if ((DWORD)pRet < dwExportRVA + (DWORD)hModule || (DWORD)pRet > dwExportRVA + (DWORD)hModule +   
  49.   
  50. dwExportSize)  
  51.             {  
  52.                 return (DWORD)pRet;  
  53.             }  
  54.   
  55.             char pTempDll[100] = {0};  
  56.             char pTempFuction[100] = {0};  
  57.             lstrcpy(pTempDll, pRet);  
  58.             char *p = strchr(pTempDll, '.');  
  59.             if (!p)  
  60.             {  
  61.                 return (DWORD)pRet;  
  62.             }  
  63.             // *p = 0; 原来的  
  64.             *p = '/0'// 目的是截断字符串将 . 置换为 0 即 '/0'  
  65.             lstrcpy(pTempFuction, p+1);  
  66.             lstrcat(pTempDll, ".dll");  
  67.             HMODULE h = LoadLibrary(pTempDll);  
  68.             if (h == NULL)  
  69.             {  
  70.                 return (DWORD)pRet;  
  71.             }  
  72.             return MyGetProcAddress(h, pTempFuction);  
  73.         }  
  74.     }  
  75.     return 0;     
  76. }  


现在测试下
  DWORD a1 = (DWORD)MyGetProcAddress(LoadLibrary("kernel32.dll"), (LPCSTR)"HeapFree");
  DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"), (LPCSTR)"HeapFree");
发现值一样了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值