导入表的加密应该是壳对应用程序加密时最重要的部分了。首先我们先了解一下怎样寻找引入表,并列出其中的导入的函数与DLL命。其次,进行讲解如何加密引入表。最后,讲解在我们自定义的节表中恢复引入表并重定位函数地址。
1.寻找引入表
在IMAGE_NT_HEADERS结构中的OptionalHeader字段中有一组数据目录,数据目录数组的第二个元素就是引入表的目录索引。
数据目录的结构是这样的
IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress dd ?
isize dd ?
IMAGE_DATA_DIRECTORY ENDS
VirtualAddress 是此表的RVA,也就是相对于模块加载的偏移量,此地址指向一个由IMAGE_IMPORT_DESCRIPTOR结构组成的数组。
isize 是此表的大小
我们利用以下算法寻找引入表
1.从 DOS header 找到 PE header
2.从 数据目录中 读取 data directory 的地址。 第二个索引就为引入表的地址。
3.IMAGE_DATA_DIRECTORY的虚拟地址偏移转化为文件偏移
4.文件偏移加上文件映射基址就为引入表的地址
寻找到引入表后,接下来的工作就是展开引入表,此时的指针指向一个IMAGE_IMPORT_DESCRIPTOR结构的数组,我们可以认为一个 IMAGE_IMPORT_DESCRIPTOR结构就是一个DLL,如果你的程序引用了5个DLL那么你的程序用就有5个 IMAGE_IMPORT_DESCRIPTOR结构,并且这个数组以一个全0的IMPORT_IMPORT_DESCRIPTOR为结束。为了让读者好 理解在以下称IMAGE_IMPORT_DESCRIPTOR为DLL结构。
对于加密导入表最重要的有三个属性,Name1,OriginalFirstThunk,FirstThunk。
Name1也是一个偏移量,指向这个DLL的DLL字符串的内存偏移。
OriginalFirstThunk与FirstThunk两个字段也同属于指针类型的。并且在文件中都指向一个位置。当加载到内存时, OriginalFirstThunk还指向原来指向的地方,FirstThunk指向一个API函数的地址的数据。(由PE加载器帮助定位并修改的)
在文件中,OriginalFirstThunk指向一组地址的偏移,这个地址偏移被称为IMAGE_THUNK_DATA
这个偏移值指向一组IMAGE_IMPORT_BY_NAME结构的数组。这个结构读者可以认为是这个DLL文件中的API。有几个API代表有几个这样的结构。
IMAGE_IMPORT_BY_NAME STRUCT
Hint dw ?
Name1 db ?
IMAGE_IMPORT_BY_NAME ENDS
Hint:代表此API是DLL中的第几个函数
Name1:为此API的名字,最后以NULL结尾。我们加密API的名字就是加密Name1的字段。
另一个FirstThunk如同复制一样也一模一样的指向了与OrigFirstThunk一样的地方。但是当文件加载到内存中后,FirstThunk会指向函数的地址。这个转换由PE加载器加载。
列出引入表代码如下
代码:
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
|
ListIID proc pFilename : LPSTR ;; map file to memory LOCAL hFile : HANDLE LOCAL hMap : HANDLE LOCAL pMem : LPVOID LOCAL dwNTHeaderAddr : DWORD LOCAL szTmpBuf[MAX_PATH] : BYTE ;; open file invoke CreateFile, pFilename,\ GENERIC_WRITE + GENERIC_READ,\ FILE_SHARE_WRITE + FILE_SHARE_READ,\ NULL,\ OPEN_EXISTING,\ FILE_ATTRIBUTE_NORMAL,\ 0 .IF eax == INVALID_HANDLE_VALUE jmp OpenFileFailed .ENDIF mov hFile, eax invoke GetFileSize, hFile, NULL .IF eax == 0 invoke CloseHandle, hFile jmp GetFileSizeFailed .ENDIF ;; create memory map xor ebx, ebx invoke CreateFileMapping, hFile, ebx, PAGE_READWRITE, ebx, eax, ebx .IF eax == 0 invoke CloseHandle, hFile jmp CreateMapFailed .ENDIF mov hMap, eax ;; map file to memory invoke MapViewOfFile, hMap, FILE_MAP_WRITE+FILE_MAP_READ+FIL E_MAP_COPY, ebx, ebx, ebx .IF eax == 0 invoke CloseHandle, hMap invoke CloseHandle, hFile jmp MapFileFailed .ENDIF mov pMem, eax ;; check it's PE file or not ? xchg eax, esi assume esi : ptr IMAGE_DOS_HEADER .IF [esi].e_magic != 'ZM' invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile jmp InvalidPE .ENDIF add esi, [esi].e_lfanew assume esi : ptr IMAGE_NT_HEADERS .IF word ptr [esi].Signature != 'EP' invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile jmp InvalidPE .ENDIF mov dwNTHeaderAddr, esi ;; 寻找引入表 assume esi : ptr IMAGE_NT_HEADERS mov eax, dword ptr [esi].OptionalHeader.DataDirecto ry[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress ;; å°ååå移转æ¢ä¸ºæä»¶åç§»,åå 䏿 件头çååæ å°å°±çäºå¼å¥è¡¨å¨æä»¶ 的地址了 invoke RVA2Offset, pMem, eax xchg ebx, eax add ebx, pMem assume ebx : ptr IMAGE_IMPORT_DESCRIPTOR ;; è¿éç´å°ä¸ä¸ªå¨0çIMAGE_IMPORT_DESCRIPTOR 结构为结束 ;; 这里判断Name1是否为NULL ListIIDLoop: mov eax, dword ptr [ebx].Name1 test eax, eax jz EndListIIDLoop ;; æ¤æ¶eaxæåä¸ä¸ªDLLååçRVA,æä»¬å RVA转换为文件的偏移 invoke RVA2Offset, pMem, eax ;; æä»¶çåç§»å 䏿件çèµ·å§æéå° 为文件中的地址 add eax, pMem ;; 打印DLL名 invoke PrintLine, offset g_szOutFormat, offset g_szOutLine invoke PrintLine, offset g_szOutFormat, eax invoke PrintLine, offset g_szOutFormat, offset g_szOutLine ;; æ£æ¥OriginalFirstThunkæ¯å¦ä¸º0ã妿ä 0则使用FirstThunk mov edx, dword ptr [ebx].OriginalFirstThunk test edx, edx jnz UseOrignalFirstThunk mov edx, dword ptr [ebx].FirstThunk UseOrignalFirstThunk: ;; 转换RVA转换文件偏移 invoke RVA2Offset, pMem, edx add eax, pMem mov edx, eax DisplayApiName: ;; æ¥çedxçæé«ä½æ¯å¦æ¯1ç¡®å®æ¯å¦ä 序数引出 test dword ptr [edx], IMAGE_ORDINAL_FLAG32 jnz DisPlayOrd ;; è¿éedxæåIMAGE_IMPORT_BY_NAMEç»æç RVA,继续将它转换 mov eax, dword ptr [edx] invoke RVA2Offset, pMem, eax add eax, pMem assume eax : ptr IMAGE_IMPORT_BY_NAME ;; æå°APIå符串,å°Name1çå°åè®¾ç½®ç» eax寄存器 lea eax, [eax].Name1 invoke PrintLine, offset g_szOutFormat, eax jmp NextAPI DisPlayOrd: ;; 取出序数,低2个字节为序数 mov eax, dword ptr [edx] and eax, 0FFFFh invoke PrintLine, offset g_szOutOrdFormat, eax NextAPI: ;; 取下一个IMAGE_THUNK_DATA的值 add edx, 04h ;; 直到edx指向一个0 mov eax, dword ptr [edx] test eax, eax jnz DisplayApiName ;; 指向下一个DLL add ebx, sizeof IMAGE_IMPORT_DESCRIPTOR jmp ListIIDLoop EndListIIDLoop: LogicShellExit: ;; close handle & write it invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile assume ebx : nothing assume esi : nothing ret ;; ----- Show error message ----- OpenFileFailed: lea eax, g_szOpenFileFailed jmp ShowErr GetFileSizeFailed: lea eax, g_szGetFileSizeFailed jmp ShowErr CreateMapFailed: lea eax, g_szCreateMapFailed jmp ShowErr MapFileFailed: lea eax, g_szMapFileFailed jmp ShowErr InvalidPE: lea eax, g_szInvalidPE jmp ShowErr ShowErr: invoke MessageBox, NULL, eax, offset g_szErr, MB_ICONERROR jmp LogicShellExit ListIID endp
|
列出引入表不知道我讲的是否清楚,总之当从PE头结构中获取到引入表地址将它的RVA转换为FVA再加上文件映射基址就可以按照微软定义的引入表结构进行 加密与销毁。接下来我们要做的就是将引入表中每个DLL名字与API的名字都进行加密,后记录每个IMAGE_IMPORT_DESCRIPTOR中的 FirstThunk值。并记录到我们自己的定义的结构中。RVA2Offset函数是转换RVA到FVA。此函数在附件代码中定义。
前面的只是给加密引入表做个铺垫。加密引入表和列出引入表算法都一样。只不过将显示函数加密而已。这里直接将代码列出。加密字符串的算法也很简单只是异或 上99h而已。其次和列出不一样的地方是,加密引入表记录了引入表每个IMAGE_IMPORT_DESCRIPTOR的Name1, OrigFirstThunk,FirstThunk三个字段。然后在解密段中按此三个字段在解密引入表。并重定位API的地址表。
这里是我们自己的引入表结构。这个结构安置在解密体内
代码:
1
2
3
4
5
|
IID_PRIVATE_DATA struct Name1 dd 0 OriginalFirstThunk dd 0 FirstThunk dd 0 IID_PRIVATE_DATA ends
|
下面列出其加密引入表的代码
代码:
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
EnCryptIID proc uses ebx ecx edx esi edi, pMem : LPVOID, pCurrentIID : LPVOID ;; encrypt image import directory LOCAL pIID[IMAGE_IMPORT_TABLE_SIZE] : BYTE LOCAL pImportFVA : LPVOID LOCAL pImportRVA : DWORD LOCAL dwLoadLibraryThunkData : DWORD LOCAL dwGetProcAddressThunkData : DWORD ;; 将IID_PRIVATE_DATA结构清0 mov edi, pCurrentIID mov eax, sizeof IID_PRVATE_DATA mov ecx, MAX_IID_NUM imul ecx xchg eax, ecx xor eax, eax cld rep stosb
;; 定位引入表的位置 mov esi, pMem add esi, dword ptr [esi+03ch] assume esi : ptr IMAGE_NT_HEADERS mov ecx, dword ptr [esi].OptionalHeader.DataDirecto ry[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].isize test ecx, ecx jz ExitEnCryptIID mov esi, dword ptr [esi].OptionalHeader.DataDirecto ry[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress test esi, esi jz ExitEnCryptIID ;; change RVA to offset invoke RVA2Offset, pMem, esi add eax, pMem xchg eax, edi ; esi = Import Symbol Table on file ;; 清除原始的引入表 assume edi : ptr IMAGE_IMPORT_DESCRIPTOR mov esi, pCurrentIID assume esi : ptr IID_PRIVATE_DATA xor ebx, ebx ; ebx = max of IID ClearOrigIIDLoop: mov eax, dword ptr [edi].Name1 test eax, eax ; eax = dll name string RVA jz ExitClearOrigIIDLoop inc ebx test ebx, MAX_IID_NUM jnz ExitClearOrigIIDLoop ;; å¨ååå§IIDçä¿¡æ¯,ä¿å尿们èªå·±ç IID结构,供解密用 push dword ptr [edi].Name1 pop dword ptr [esi].Name1 push dword ptr [edi].OriginalFirstThunk pop dword ptr [esi].OriginalFirstThunk push dword ptr [edi].FirstThunk pop dword ptr [esi].FirstThunk ;; change Name1 RVA to offset invoke RVA2Offset, pMem, eax add eax, pMem
;; 加密DLL库文件的文件名, è¿ä¸ªå½æ°å°å 符串的每个字节异或上99h invoke EnCryptString, eax ;; å å¯å¼å¥è¡¨åæ¾ç¤ºå¼å¥è¡¨çæåµç¸å ,把显示换成加密即可 mov eax, dword ptr [edi].OriginalFirstThunk test eax, eax jnz UseOriginalFirstThunk mov eax, dword ptr [edi].FirstThunk UseOriginalFirstThunk: ;; change Api Name string RVA to offset invoke RVA2Offset, pMem, eax add eax, pMem xchg edx, eax ; edx = Api offset in file push esi EnCryptApiNameLoop: mov esi, dword ptr [edx] test esi, esi jz EndEnCryptApiNameLoop ;; judege IMAGE_ORDINAL_FLAG32 flags test esi, IMAGE_ORDINAL_FLAG32 jnz SkipEncrypt invoke RVA2Offset, pMem, esi test eax, eax jz SkipEncrypt add eax, pMem add eax, 02h ; skip HINT invoke EnCryptString, eax SkipEncrypt: add edx, 04h jmp EnCryptApiNameLoop EndEnCryptApiNameLoop: pop esi ; esi = the point of current own IID ;; 这里是毁坏原始IID push 0 pop dword ptr [edi].Name1 push 0 pop dword ptr [edi].OriginalFirstThunk push 0 pop dword ptr [edi].FirstThunk push 0 pop dword ptr [edi].TimeDateStamp push 0 pop dword ptr [edi].ForwarderChain ;; mov to next IID add edi, sizeof IMAGE_IMPORT_DESCRIPTOR add esi, sizeof IID_PRIVATE_DATA jmp ClearOrigIIDLoop ExitClearOrigIIDLoop:
;; clear the new IID lea edi, pIID mov ecx, IMAGE_IMPORT_TABLE_SIZE cld xor al, al rep stosb sub edi, IMAGE_IMPORT_TABLE_SIZE ; set edi back to start point ;; å¢å ä¸ä¸ªæä»¬èªå·±çIIDè,éåä¼å°æ 建的IID写入到此节中 invoke AddSection, pMem, NULL, IMAGE_IMPORT_TABLE_SIZE mov pImportFVA, eax assume eax : ptr IMAGE_SECTION_HEADER ;; 这里创建一个新的IID ;; æä»¬çIID表åªå¯¼å¥ä¸ä¸ªDLLé£å°±æ¯åºæ¬ çkernel32.dllå¼å¥ç彿°ä¹åªæä¸¤ä¸ªå½æ ;; LoadLibraryA,GetProcAddressä¸¤ä¸ªå½æ°ãå¨è§£ 密节中利用这两个最基本的函数获取 ;; 其他的API地址与DLL句柄 ;; 结构如下 ;; 构建新的IID按照以下结构填写即可 ;; make new IID ;; ----- Image Import Descriptor ----- ;; IMAGE_IMPORT_DESCRIPTOR ;; IMAGE_IMPORT_DESCRIPTOR(0) ;; IMAGE_THUNK_DATA ;; IMAGE_THUNK_DATA ;; IMAGE_THUNK_DATA(0) ;; kernel32.dll,0 ;; IMAGE_IMPORT_BY_NAME(LoadLibraryA) ;; IMAGE_IMPORT_BY_NAME(GetProcAddress) lea edx, pIID assume edx : ptr IMAGE_IMPORT_DESCRIPTOR mov ecx, sizeof IMAGE_IMPORT_DESCRIPTOR add ecx, sizeof IMAGE_IMPORT_DESCRIPTOR add ecx, sizeof IMAGE_THUNK_DATA add ecx, sizeof IMAGE_THUNK_DATA add ecx, sizeof IMAGE_THUNK_DATA mov eax, dword ptr [eax].VirtualAddress mov pImportRVA, eax add eax, ecx ; ecx = kernel32.dll string offset mov dword ptr [edx].Name1, eax ;; 拷贝kernel32.dll 字符串到 文件 mov esi, offset g_szKernelDll add edi, ecx CopyKernel32StrLoop: mov al, byte ptr [esi] test al, al jz EndCopyKernel32StrLoop mov byte ptr [edi], al inc esi inc edi jmp CopyKernel32StrLoop EndCopyKernel32StrLoop: mov byte ptr [edi], al inc edi ;; 设置LoadLibraryA的IMAGE_IMPORT_BY_NAME mov eax, edi sub eax, edx mov dwLoadLibraryThunkData, eax xor eax, eax mov word ptr [edi], ax add edi, 02h mov esi, offset g_szLoadLibrary CopyLoadLibraryLoop: mov al, byte ptr [esi] test al, al jz EndCopyLoadLibraryLoop mov byte ptr [edi], al inc esi inc edi jmp CopyLoadLibraryLoop EndCopyLoadLibraryLoop: mov byte ptr [edi], al inc edi ;; 设置GetProcAddress的IMAGE_IMPORT_BY_NAME结构 mov eax, edi sub eax, edx mov dwGetProcAddressThunkData, eax xor eax, eax mov word ptr [edi], ax add edi, 02h mov esi, offset g_szGetProcAddress CopyGetProcAddressLoop: mov al, byte ptr [esi] test al, al jz EndCopyGetProcAddressLoop mov byte ptr [edi], al inc esi inc edi jmp CopyGetProcAddressLoop EndCopyGetProcAddressLoop: mov byte ptr [edi], al inc edi ;; 设置IMAGE_IMPORT_DESCRIPTOR ;; æä»¬è¿é设置èªå·±çIMAGE_IMPORT_DESCRIPT OR结构,单使用FirstThunk字段 ;; 将OriginalFirstThunk设置为0 xor eax, eax mov dword ptr [edx].Characteristics, eax mov dword ptr [edx].TimeDateStamp, eax mov dword ptr [edx].ForwarderChain, eax push 0 pop dword ptr [edx].OriginalFirstThunk mov eax, pImportRVA add eax, sizeof IMAGE_IMPORT_DESCRIPTOR add eax, sizeof IMAGE_IMPORT_DESCRIPTOR mov dword ptr [edx].FirstThunk, eax sub eax, pImportRVA ; eax = 2 of IMAGE_IMPORT_DESCRIPTOR SIZE add edx, eax mov eax, dwLoadLibraryThunkData add eax, pImportRVA mov dword ptr [edx], eax add edx, 04h ; move to next point mov eax, dwGetProcAddressThunkData add eax, pImportRVA mov dword ptr [edx], eax ;; 拷贝新的IID到文件 lea esi, pIID mov edi, pImportFVA assume edi : ptr IMAGE_SECTION_HEADER mov edi, dword ptr [edi].PointerToRawData add edi, pMem mov ecx, IMAGE_IMPORT_TABLE_SIZE cld rep movsb ;; 修改IT表的虚拟地址和尺寸 ;; 设置è¿ä¸ªæ°æ®ç®å½ä»¥ä¾¿PEå è½½å¨å¯ä»¥å 找到IID表 mov esi, pMem add esi, dword ptr [esi+03ch] assume esi : ptr IMAGE_NT_HEADERS mov eax, pImportFVA assume eax : ptr IMAGE_SECTION_HEADER push dword ptr [eax].VirtualAddress pop dword ptr [esi].OptionalHeader.DataDirectory[IM AGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress push dword ptr [eax].Misc.VirtualSize pop dword ptr [esi].OptionalHeader.DataDirectory[IM AGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].isize assume edx : nothing assume eax : nothing assume esi : nothing assume edi : nothing xor eax, eax ExitEnCryptIID: ret EnCryptIID endp
|
最后要讲解的就是解密引入表了。解密引入表比起加密来要复杂一些。我们的具体思路是这样的。建立两个节,一个为新的引入表,一个为解密节,这个节也设置为起始的节。
解密引入表,两种解密的方式,一是直接解密后将获取的API地址填写入FirstThunk字段指向的区域,另一种方式是重定位引入地址表。
例如,在我们的程序中调用CreateFileA这个函数。call dword ptr [XXXX], 这个XXXX其实就是FirstThunk指 向的那片区域中的一个地址而已,XXXX指向的地址,如果没有经过我们重定位那么直接就是跟的这个API的地址,如果经过我们重定位将这个XXXX指向的 地址指到我们分配的内存中例如YYYY,那么最终将会转到YYYY指向,我们再在YYYY这处地址写入例如JMP ZZZZ样的跳转指令,其中ZZZZ代 表的是系统API地址到YYYY这个地址的偏移量。那么当程序调用API时最终通过我们设置的一篇代理代码将跳转到API内执行。
这里主要的是,我们只重定位系统自身的DLL,如果是第三方的DLL,我们直接将它的地址设置到其FirstThunk指向的区域就好了。在NT系统下,加载第三方DLL是在小于070000000h,大于077FFFFFFh,9x下为小于080000000h。
解密节的代码如下
代码:
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
|
ListIID proc pFilename : LPSTR ;; map file to memory LOCAL hFile : HANDLE LOCAL hMap : HANDLE LOCAL pMem : LPVOID LOCAL dwNTHeaderAddr : DWORD LOCAL szTmpBuf[MAX_PATH] : BYTE ;; open file invoke CreateFile, pFilename,\ GENERIC_WRITE + GENERIC_READ,\ FILE_SHARE_WRITE + FILE_SHARE_READ,\ NULL,\ OPEN_EXISTING,\ FILE_ATTRIBUTE_NORMAL,\ 0 .IF eax == INVALID_HANDLE_VALUE jmp OpenFileFailed .ENDIF mov hFile, eax invoke GetFileSize, hFile, NULL .IF eax == 0 invoke CloseHandle, hFile jmp GetFileSizeFailed .ENDIF ;; create memory map xor ebx, ebx invoke CreateFileMapping, hFile, ebx, PAGE_READWRITE, ebx, eax, ebx .IF eax == 0 invoke CloseHandle, hFile jmp CreateMapFailed .ENDIF mov hMap, eax ;; map file to memory invoke MapViewOfFile, hMap, FILE_MAP_WRITE+FILE_MAP_READ+FIL E_MAP_COPY, ebx, ebx, ebx .IF eax == 0 invoke CloseHandle, hMap invoke CloseHandle, hFile jmp MapFileFailed .ENDIF mov pMem, eax ;; check it's PE file or not ? xchg eax, esi assume esi : ptr IMAGE_DOS_HEADER .IF [esi].e_magic != 'ZM' invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile jmp InvalidPE .ENDIF add esi, [esi].e_lfanew assume esi : ptr IMAGE_NT_HEADERS .IF word ptr [esi].Signature != 'EP' invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile jmp InvalidPE .ENDIF mov dwNTHeaderAddr, esi ;; 寻找引入表 assume esi : ptr IMAGE_NT_HEADERS mov eax, dword ptr [esi].OptionalHeader.DataDirecto ry[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress ;; å°ååå移转æ¢ä¸ºæä»¶åç§»,åå 䏿 件头çååæ å°å°±çäºå¼å¥è¡¨å¨æä»¶ 的地址了 invoke RVA2Offset, pMem, eax xchg ebx, eax add ebx, pMem assume ebx : ptr IMAGE_IMPORT_DESCRIPTOR ;; è¿éç´å°ä¸ä¸ªå¨0çIMAGE_IMPORT_DESCRIPTOR 结构为结束 ;; 这里判断Name1是否为NULL ListIIDLoop: mov eax, dword ptr [ebx].Name1 test eax, eax jz EndListIIDLoop ;; æ¤æ¶eaxæåä¸ä¸ªDLLååçRVA,æä»¬å RVA转换为文件的偏移 invoke RVA2Offset, pMem, eax ;; æä»¶çåç§»å 䏿件çèµ·å§æéå° 为文件中的地址 add eax, pMem ;; 打印DLL名 invoke PrintLine, offset g_szOutFormat, offset g_szOutLine invoke PrintLine, offset g_szOutFormat, eax invoke PrintLine, offset g_szOutFormat, offset g_szOutLine ;; æ£æ¥OriginalFirstThunkæ¯å¦ä¸º0ã妿ä 0则使用FirstThunk mov edx, dword ptr [ebx].OriginalFirstThunk test edx, edx jnz UseOrignalFirstThunk mov edx, dword ptr [ebx].FirstThunk UseOrignalFirstThunk: ;; 转换RVA转换文件偏移 invoke RVA2Offset, pMem, edx add eax, pMem mov edx, eax DisplayApiName: ;; æ¥çedxçæé«ä½æ¯å¦æ¯1ç¡®å®æ¯å¦ä 序数引出 test dword ptr [edx], IMAGE_ORDINAL_FLAG32 jnz DisPlayOrd ;; è¿éedxæåIMAGE_IMPORT_BY_NAMEç»æç RVA,继续将它转换 mov eax, dword ptr [edx] invoke RVA2Offset, pMem, eax add eax, pMem assume eax : ptr IMAGE_IMPORT_BY_NAME ;; æå°APIå符串,å°Name1çå°åè®¾ç½®ç» eax寄存器 lea eax, [eax].Name1 invoke PrintLine, offset g_szOutFormat, eax jmp NextAPI DisPlayOrd: ;; 取出序数,低2个字节为序数 mov eax, dword ptr [edx] and eax, 0FFFFh invoke PrintLine, offset g_szOutOrdFormat, eax NextAPI: ;; 取下一个IMAGE_THUNK_DATA的值 add edx, 04h ;; 直到edx指向一个0 mov eax, dword ptr [edx] test eax, eax jnz DisplayApiName ;; 指向下一个DLL add ebx, sizeof IMAGE_IMPORT_DESCRIPTOR jmp ListIIDLoop EndListIIDLoop: LogicShellExit: ;; close handle & write it invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile assume ebx : nothing assume esi : nothing ret ;; ----- Show error message ----- OpenFileFailed: lea eax, g_szOpenFileFailed jmp ShowErr GetFileSizeFailed: lea eax, g_szGetFileSizeFailed jmp ShowErr CreateMapFailed: lea eax, g_szCreateMapFailed jmp ShowErr MapFileFailed: lea eax, g_szMapFileFailed jmp ShowErr InvalidPE: lea eax, g_szInvalidPE jmp ShowErr ShowErr: invoke MessageBox, NULL, eax, offset g_szErr, MB_ICONERROR jmp LogicShellExit ListIID endp
|
最后要注意的就是有时候FirstThunk指向的区域是不能写的,所以当我们做完全部工作后要将所有节的属性赋予它可写的权限。
代码:
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
|
ListIID proc pFilename : LPSTR ;; map file to memory LOCAL hFile : HANDLE LOCAL hMap : HANDLE LOCAL pMem : LPVOID LOCAL dwNTHeaderAddr : DWORD LOCAL szTmpBuf[MAX_PATH] : BYTE ;; open file invoke CreateFile, pFilename,\ GENERIC_WRITE + GENERIC_READ,\ FILE_SHARE_WRITE + FILE_SHARE_READ,\ NULL,\ OPEN_EXISTING,\ FILE_ATTRIBUTE_NORMAL,\ 0 .IF eax == INVALID_HANDLE_VALUE jmp OpenFileFailed .ENDIF mov hFile, eax invoke GetFileSize, hFile, NULL .IF eax == 0 invoke CloseHandle, hFile jmp GetFileSizeFailed .ENDIF ;; create memory map xor ebx, ebx invoke CreateFileMapping, hFile, ebx, PAGE_READWRITE, ebx, eax, ebx .IF eax == 0 invoke CloseHandle, hFile jmp CreateMapFailed  |
|