咱们知道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