完美實現GetProcAddress

咱們知道kernel32.dll裏有一個GetProcAddress函數,能夠找到模塊中的函數地址,函數原型是這樣的:
WINBASEAPI
FARPROC
WINAPI
GetProcAddress(
    IN HMODULE hModule,
    IN LPCSTR lpProcName
    );
hModule 是模塊的句柄,說白了就是內存中dll模塊的首地址
loProcName 通常指函數名稱的字符串地址,也多是指序號,如何區分呢?
咱們這樣
        if (((DWORD)lpProcName& 0xFFFF0000) == 0)
        {
                //這裏是序號導出的;
        }
        {
               //這裏是函數名稱導出的
        }
最終真找到匹配的函數地址,而後返回就ok了,可是前提是你須要瞭解PE的導出表
下面簡單說一下,首先從PE裏找到下面這個結構,若是不知道如何找的話,建議在罈子裏搜索一下PE結構解析的文章,找到這個結構應該仍是很簡單的
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;
具體什麼意思呢?  
雖然, 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]] + 模塊基址
好了,囉嗦了這麼多,看代碼:數組

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



好了,測試一下,
        //咱們寫的函數返回的地址
        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 源碼app

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



LdrGetProcedureAddress 是什麼呢?
繼續看ide

1
2
3
4
5
6
7
8
9
10
11
12
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的關鍵的一段,能不能看懂我就無論了  反正我基本看不懂
 函數

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



好了 到此已經告一段落, 下面給出源碼,有人會問,怎樣作的dll纔會出現查找導出表的地址的時候返回一個字符串呢? 其實就是在  .def文件的EXPORTS後加一句就成(這個我老大試驗了n久才搞定)加上 MsgBox = user32.MessageBoxA
 測試

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
DWORD MyGetProcAddress(
                        HMODULE hModule,    // handle to DLL module
                        LPCSTR lpProcName   // function name
                        )
{
     int i=0;
     char *pRet = NULL;
     PIMAGE_DOS_HEADER pImageDosHeader = NULL;
     PIMAGE_NT_HEADERS pImageNtHeader = NULL;
     PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL;
     
     pImageDosHeader=(PIMAGE_DOS_HEADER)hModule;
     pImageNtHeader=(PIMAGE_NT_HEADERS)((DWORD)hModule+pImageDosHeader->e_lfanew);
     pImageExportDirectory=(PIMAGE_EXPORT_DIRECTORY)((DWORD)hModule+pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
 
     DWORD dwExportRVA = pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
     DWORD dwExportSize = pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
     
     DWORD *pAddressOfFunction = (DWORD*)(pImageExportDirectory->AddressOfFunctions + (DWORD)hModule);
     DWORD *pAddressOfNames = (DWORD*)(pImageExportDirectory->AddressOfNames + (DWORD)hModule);
     DWORD dwNumberOfNames = (DWORD)(pImageExportDirectory->NumberOfNames);
     DWORD dwBase = (DWORD)(pImageExportDirectory->Base);
     
     WORD *pAddressOfNameOrdinals = (WORD*)(pImageExportDirectory->AddressOfNameOrdinals + (DWORD)hModule);
     
     // 這個是查一下是按照什麼方式(函數名稱or函數序號)來查函數地址的
     DWORD dwName = (DWORD)lpProcName;
     if ((dwName & 0xFFFF0000) == 0)
     {
         goto xuhao;
     }
     
     for (i=0; i<(int)dwNumberOfNames; i++)
     {
         char *strFunction = (char *)(pAddressOfNames[i] + (DWORD)hModule);
         if (strcmp(strFunction, (char *)lpProcName) == 0)
         {
             pRet = (char *)(pAddressOfFunction[pAddressOfNameOrdinals[i]] + (DWORD)hModule);
             goto _exit11;
         }
     }
     // 這個是經過以序號的方式來查函數地址的
xuhao:
     if (dwName < dwBase || dwName > dwBase + pImageExportDirectory->NumberOfFunctions - 1)
     {
         return 0;
     }
     pRet = (char *)(pAddressOfFunction[dwName - dwBase] + (DWORD)hModule);
_exit11:
        // 判斷獲得的地址有沒有越界
     if ((DWORD)pRet<dwExportRVA+(DWORD)hModule || (DWORD)pRet > dwExportRVA+ (DWORD)hModule + dwExportSize)
     {
         return (DWORD)pRet;
     }
     char pTempDll[100] = {0};
     char pTempFuction[100] = {0};
     lstrcpy(pTempDll, pRet);
     char *p = strchr(pTempDll, '.' );
     if (!p)
     {
         return (DWORD)pRet;
     }
     *p = 0;
     lstrcpy(pTempFuction, p+1);
     lstrcat(pTempDll, ".dll" );
     HMODULE h = LoadLibrary(pTempDll);
     if (h == NULL)
     {
         return (DWORD)pRet;
     }
     return MyGetProcAddress(h, pTempFuction);
}



如今測試下
        DWORD a1 = (DWORD)MyGetProcAddress(LoadLibrary("kernel32.dll"), (LPCSTR)"HeapFree");
        DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"), (LPCSTR)"HeapFree");
發現值同樣了

ps.如發現問題  歡迎批評指正。code

相關文章
相關標籤/搜索