0%

C-13_动态获取API函数地址

动态获取API入口地址方法

通常想要动态获取Api函数的地址,需要调用LoadlibraryA() 和GetProcAddress()函数动态获取。LoadlibraryA()用来加载API对应的dll,GetProcAddress()用来获取API函数对应的入口地址。

LoadLibraryA()和GetProcAddress()都是在kernel32.dll下引出的函数,在kernel32引出表中,所以需要先找到kernel32地址才能再去找kernel32的引出表地址。

一般有三种方法去找kernel32的基地址

  • 暴力搜索内存
  • seh异常处理链表搜索
  • TEB(Thread Environment Block)搜索
暴力搜索法

https://www.cnblogs.com/witty/archive/2012/03/27/2419725.html

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
.386		
.model flat,stdcall
option casemap:none

include windows.inc

.data
lpKernel32 dd 0

.code
GetKernelBase proc uses esi edi dwKernel:dword

local kernelbase:dword
mov edi, dwKernel
and edi, 0ffff0000h ; 初始化页
.while TRUE
.if word ptr [edi] == IMAGE_DOS_SIGNATURE ;等于MZ?
mov esi, edi
add esi, [esi+IMAGE_DOS_HEADER.e_lfanew] ; esi+3ch
.if word ptr [esi] == IMAGE_NT_SIGNATURE ;等于PE?
mov kernelbase, edi
.break
.endif
.endif
sub edi, 010000h ;暴力搜索内存 64kb 即每次减少64kb
.break .if edi < 070000000h ;基地址小于70000000h退出
.endw

mov eax, kernelbase ; 存到寄存器中再返回
ret
GetKernelBase endp

main proc

invoke GetKernelBase, [esp]
mov lpKernel32, eax
ret

main endp
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
;找到导出表 找到导出表中 NumberOfNames字段(存放着导出函数的总个数)用这个个数构建一个循环
;在 AddressOfNames 执行函数名称地址表
;循环中将每一项定义的函数名与所要搜索的函数名进行比较
;如果没找到任何一个函数名符合 表示文件中没有该指定名称的函数
;如果找到相符合的函数名 那么就几下这个函数名在字符串地址表中的索引值,然后在AddressOfNameOrdinals指向的数组中使用同样的索引值
;使用该索引值 在AddressOfFunctions字段指向的函数入口地址表中获取的RVA即函数入口地址RVA

.386
.model flat,stdcall
option casemap:none

include windows.inc


.data
lpKernel32 dd 0
szUser32 db 'user32.dll',0
lpUser32 dd 0
szMessageBoxA db 'MessageBoxA',0
lpMessageBoxA dd 0
szLoadLibraryA db 'LoadLibraryA',0
lpLoadLibraryA dd 0
szExitProcess db 'ExitProcess',0
lpExitProcess dd 0
szGetProcAddress db 'GetProcAddress',0
lpGetProcAddress dd 0

hola db 'hola!'

.code

GetKernelBase proc uses esi edi dwKernel:dword

local kernelbase:dword
mov edi, dwKernel
and edi, 0ffff0000h ; 初始化页
.while TRUE
.if word ptr [edi] == IMAGE_DOS_SIGNATURE ;等于MZ?
mov esi, edi
add esi, [esi+IMAGE_DOS_HEADER.e_lfanew] ; [esi+3ch]
.if word ptr [esi] == IMAGE_NT_SIGNATURE ;等于PE?
mov kernelbase, edi
.break
.endif
.endif
sub edi, 010000h ;暴力搜索内存 64kb 即每次减少64kb
;kernel32.dll的块对齐值是00001000h, 并且一般DLL以1M为边界,通过10000h (64k) 作为跨度
.break .if edi < 070000000h ;基地址小于70000000h退出
.endw

mov eax, kernelbase ; 存到寄存器中再返回
ret
GetKernelBase endp

;hMoudle 基地址
GetApi proc hModule:dword, szApiname:dword
local apilen:dword ;local声明局部遍历
local apibase:dword

pushad
;计算api字符长度
mov esi, szApiname
mov edx, esi
countStr:
cmp byte ptr [esi], 0 ;字符串末尾是否为0
jz countStrOver
inc esi;还没结束继续
jmp countStr

countStrOver:
inc esi
sub esi, edx ;减去首地址得到长度
mov apilen, esi

;找到导出表地址
mov esi, hModule;拿到模块基地址 一般esi存放指针、地址
add esi, [esi+3ch]; +3ch定位到NT_HEADERS
assume esi:ptr IMAGE_NT_HEADERS ;

mov esi,[esi].OptionalHeader.DataDirectory.VirtualAddress;得到导出表RVA
add esi, hModule;加上基地址 定位到地址

assume esi:ptr IMAGE_EXPORT_DIRECTORY; 让esi 指向 Kernel32.dll 的导出表

mov ebx, [esi].AddressOfNames;
add ebx, hModule

xor edx, edx ;计数函数 先初始化
.repeat
push esi
mov edi, [ebx]
add edi, hModule; 指向AddressOfNames指向的数组
mov esi, szApiname
mov ecx, apilen
cld ;设置方向标志 正向
repz cmpsb ;开始遍历查找 rep(重复)z(相等) ds:esi和es:edi 所指向的两个字节相等(zf=1) 就继续比较 如果不相等就停止循环

.if ZERO? ; 找到了 ZERO?是否置位 相当于 if(zf==1)
pop esi ; 恢复esi esi指向导出表
jmp findOver
.endif
;如果没找到
pop esi ; 恢复esi esi指向导出表
add ebx,4 ;下一个函数名称 每个函数地址占4个字节
inc edx; 计数函数加1

.until edx >= [esi].NumberOfNames ;遍历完后没有找到 退出 NumberOfNames函数名称总数量
jmp _Exit


findOver:
sub ebx, hModule; 减去基地址 得到RVA
sub ebx, [esi].AddressOfNames;得到匹配函数字符串首地址 相当于数组起始地址的偏移量

;除以2,用这个偏移量+AddressOfNameOrdinals指向的数组首地址找到索引
shr ebx,1
add ebx,[esi].AddressOfNameOrdinals;加上算出来的偏移量找到和AddressOfNames对应的那一项
add ebx,hModule


movzx eax,word ptr [ebx];取出AddressOfNameOrdinals那一项存放的索引 word movzx高位拓展0
shl eax,2;乘以4 索引值x4个字节+
add eax,[esi].AddressOfFunctions;AddressOfFunctions指向函数RVA地址表起始地址,加上这个偏移量取出AddressOfNameOrdinals给出索引的那一项
add eax,hModule

;API's Address = ( API's Ordinal * 4 ) + AddressOfFunctions' VA + Kernel32 imagebase

mov eax,[eax];得到函数的RVA
add eax,hModule;加上kernel32基地址得到函数入口地址VA
mov apibase,eax

_Exit:
assume esi:nothing ;不再使用esi寄存器作为指针时 用assume esi:nothing取消定义 释放掉。
popad
mov eax, apibase
ret

GetApi endp

main proc

;得到kernel32基地址
invoke GetKernelBase, [esp]
mov lpKernel32, eax

;获取API
invoke GetApi, lpKernel32, addr szLoadLibraryA
mov lpLoadLibraryA, eax

invoke GetApi, lpKernel32, addr szGetProcAddress
mov lpGetProcAddress, eax

invoke GetApi,lpKernel32,addr szExitProcess
mov lpExitProcess,eax

;载入user32.dll
push offset szUser32
call [lpLoadLibraryA]
mov lpUser32,eax

;获取user32.dll中的MessageBoxA
push offset szMessageBoxA
push lpUser32
call [lpGetProcAddress]
mov lpMessageBoxA,eax

push MB_OK
push offset hola
push offset hola
push NULL
call [lpMessageBoxA]

push 0
call [lpExitProcess]

main endp
end

在VS2022下编译有问题 然后又换到了vs2017 成功通过找api地址的方式去调用MessageBoxA函数

SEH

待完善补充...

TEB搜索

此方法是通过TEB获得PEB结构地址,然后再获得PEB_LDR_DATA结构地址,然后遍历模块列表,查找kernel32.dll模块的基地址。

fs寄存器指向TEB结构,TEB+0X30偏移处指向PEB结构,PEB+0x0c偏移处指向PEB_LDR_DATA结构,PEB_LDR_DATA+0x1c偏移处存放着程序加载的动态链接库地址。 找到的第一个地址为ntdll.dll 第二个为kernel32.dll

使用dt _teb 查看teb结构 可以看到在0x30偏移处存储着PEB结构的地址,然后在查看peb结构

peb结构在0x0c地址处存储着 _PEB_LDR_DATA结构地址,然后查看_PEB_LDR_DATA结构

img

通过 _PEB_LDR_DATA 拿到保存模块信息结构体的链表 _LIST_ENTRT

1
2
3
4
5
6
7
8
9
10
lkd> dt _PEB_LDR_DATA
nt!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY
+0x014 InMemoryOrderModuleList : _LIST_ENTRY
+0x01c InInitializationOrderModuleList : _LIST_ENTRY
+0x024 EntryInProgress : Ptr32 Void
....

_LIST_ENTRY有三种不同方式排列的双向链表

img

Flink表示从前往后, Blink表示从后往前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void main() {

DWORD kernel32str = 0;
_asm {
mov eax, fs: [30h] //拿到peb
mov eax, dword ptr [eax + 0ch] //拿到_PEB_LDR_DATA
mov eax, dword ptr [eax + 0ch] //获取InLoadOrderModuleList地址

mov eax, [eax]
mov eax, [eax]

mov ebx, dword ptr [eax + 18h]
mov kernel32str, ebx

}

printf("%0x", kernel32str);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD kernel32str = 0;
_asm {

mov eax, fs:[30h] //peb
mov eax, dword ptr [eax + 0ch] //
mov esi, dword ptr [eax + 1ch] //InInitializationOrderLinks
//Get InInitializationOrderModuleList.Flink,
//此时eax指向的是ntdll模块的InInitializationOrderModuleList线性地址。
//所以我们获得它的下一个则是kernel32.dll

lodsd //可以优化
mov eax, dword ptr [eax + 8h] //
mov kernel32str, ebx
}
printf("%X", kernel32str);
img

References

https://www.cnblogs.com/witty/archive/2012/03/23/2413890.html

https://blog.csdn.net/qq_35426012/article/details/102711275

https://blog.csdn.net/whypp/article/details/5681172

https://blog.csdn.net/b_h_l/article/details/30506955

[笔记]Windows安全之《三》注入技术之 Shellcode注入 - 掘金

欢迎关注我的其它发布渠道

------------- 💖 🌞 本 文 结 束 😚 感 谢 您 的 阅 读 🌞 💖 -------------