Rev 6767 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
6767 | clevermous | 1 | ; Processings of PE format. |
2 | ; Works in conjunction with modules.inc for non-PE-specific code. |
||
3 | |||
4 | ; PE-specific part of init_module_struct. |
||
5 | ; Fills fields of MODULE struct from PE image. |
||
6 | macro init_module_struct_pe_specific |
||
7 | { |
||
8 | ; We need a module timestamp for bound imports. |
||
9 | ; In a full PE, there are two timestamps, one in the header |
||
10 | ; and one in the export table; existing tools use the first one. |
||
11 | ; A stripped PE header has no timestamp, so read the export table; |
||
12 | ; the stripper should write the correct value there. |
||
13 | cmp byte [esi], 'M' |
||
14 | jz .parse_mz |
||
15 | cmp [esi+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_EXPORT |
||
16 | jbe @f |
||
17 | mov edx, [esi+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY+IMAGE_DATA_DIRECTORY.VirtualAddress] |
||
6810 | clevermous | 18 | mov edx, [esi+edx+IMAGE_EXPORT_DIRECTORY.TimeDateStamp] |
6767 | clevermous | 19 | @@: |
20 | mov [eax+MODULE.timestamp], edx |
||
21 | mov edx, esi |
||
22 | sub edx, [esi+STRIPPED_PE_HEADER.ImageBase] |
||
23 | mov [eax+MODULE.basedelta], edx |
||
24 | mov edx, [esi+STRIPPED_PE_HEADER.SizeOfImage] |
||
25 | mov [eax+MODULE.size], edx |
||
26 | ret |
||
27 | .parse_mz: |
||
28 | mov ecx, [esi+3Ch] |
||
29 | add ecx, esi |
||
30 | mov edx, [ecx+IMAGE_NT_HEADERS.FileHeader.TimeDateStamp] |
||
31 | mov [eax+MODULE.timestamp], edx |
||
32 | mov edx, esi |
||
33 | sub edx, [ecx+IMAGE_NT_HEADERS.OptionalHeader.ImageBase] |
||
34 | mov [eax+MODULE.basedelta], edx |
||
35 | mov edx, [ecx+IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage] |
||
36 | mov [eax+MODULE.size], edx |
||
37 | ret |
||
38 | } |
||
39 | |||
6614 | clevermous | 40 | ; Check whether PE module has been loaded at preferred address. |
41 | ; If not, relocate the module. |
||
42 | ; |
||
43 | ; in: esi = PE base address |
||
44 | ; in: [esp+4] = module name for debug print |
||
45 | ; out: CF=1 - fail |
||
6767 | clevermous | 46 | proc fixup_pe_relocations c uses ebp, modulename |
6614 | clevermous | 47 | ; 1. Fetch some data from PE header or stripped PE header. |
48 | ; We need: |
||
49 | ; * ImageBase - preferred address, compare with esi = actual load address; |
||
50 | ; ebp will keep the delta |
||
51 | ; * RVA and size of fixups directory |
||
52 | ; * flag IMAGE_FILE_RELOCS_STRIPPED from Characteristics |
||
53 | ; If the actual address equals the preferred address, do nothing. |
||
54 | ; If fixups directory is present, proceed to 2. |
||
55 | ; If there is no fixups directory, there are two options: |
||
56 | ; * either the directory has not been created |
||
57 | ; * or the module has no fixups (data-only module, for example). |
||
58 | ; In the first case, IMAGE_FILE_RELOCS_STRIPPED is set, and this is an error. |
||
59 | ; In the second case, IMAGE_FILE_RELOCS_STRIPPED is not set; do nothing. |
||
60 | mov ebp, esi |
||
6767 | clevermous | 61 | cmp byte [esi], 'M' |
6614 | clevermous | 62 | jz .parse_mz |
63 | sub ebp, [esi+STRIPPED_PE_HEADER.ImageBase] |
||
64 | jnz @f |
||
65 | .nothing: |
||
66 | ret |
||
67 | @@: |
||
68 | mov dl, byte [esi+STRIPPED_PE_HEADER.Characteristics] |
||
69 | lea eax, [esi+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_BASERELOC*sizeof.IMAGE_DATA_DIRECTORY] |
||
70 | cmp [esi+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_BASERELOC |
||
71 | ja .common |
||
72 | .norelocs: |
||
73 | test dl, IMAGE_FILE_RELOCS_STRIPPED |
||
74 | jz .nothing |
||
6767 | clevermous | 75 | ccall loader_say_error, msg_noreloc1, [modulename], msg_noreloc2, 0 |
6614 | clevermous | 76 | stc |
77 | ret |
||
78 | .parse_mz: |
||
79 | mov eax, [esi+3Ch] |
||
80 | add eax, esi |
||
81 | sub ebp, [eax+IMAGE_NT_HEADERS.OptionalHeader.ImageBase] |
||
82 | jz .nothing |
||
6767 | clevermous | 83 | mov dl, byte [eax+IMAGE_NT_HEADERS.FileHeader.Characteristics] |
6614 | clevermous | 84 | cmp [eax+IMAGE_NT_HEADERS.OptionalHeader.NumberOfDirectories], IMAGE_DIRECTORY_ENTRY_BASERELOC |
85 | jbe .norelocs |
||
86 | add eax, IMAGE_NT_HEADERS.OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_BASERELOC*sizeof.IMAGE_DATA_DIRECTORY |
||
87 | .common: |
||
6767 | clevermous | 88 | cmp [eax+IMAGE_DATA_DIRECTORY.isize], 0 |
89 | jz .norelocs |
||
6614 | clevermous | 90 | mov edi, [eax+IMAGE_DATA_DIRECTORY.VirtualAddress] |
6767 | clevermous | 91 | push -1 |
92 | push -1 |
||
6614 | clevermous | 93 | push [eax+IMAGE_DATA_DIRECTORY.isize] |
94 | virtual at esp |
||
95 | .sizeleft dd ? |
||
6767 | clevermous | 96 | .next_page_original_access dd ? |
97 | .next_page_addr dd ? |
||
6614 | clevermous | 98 | end virtual |
99 | add edi, esi |
||
100 | ; 2. We need to relocate and we have the relocation table. |
||
101 | ; esi = PE base address |
||
102 | ; edi = pointer to current data of relocation table |
||
103 | ; 2a. Relocation table is organized into blocks describing every page. |
||
104 | ; End of table is defined from table size fetched from the header. |
||
6767 | clevermous | 105 | ; Loop 2b-2i over all blocks until no more data is left. |
6614 | clevermous | 106 | .pageloop: |
107 | ; 2b. Load the header of the current block: address and size. |
||
108 | ; Advance total size. |
||
109 | ; Size in the block includes size of the header, subtract it. |
||
110 | ; If there is no data in this block, go to 2g. |
||
111 | mov edx, [edi+IMAGE_BASE_RELOCATION.VirtualAddress] |
||
112 | mov ecx, [edi+IMAGE_BASE_RELOCATION.SizeOfBlock] |
||
113 | sub [.sizeleft], ecx |
||
114 | add edi, sizeof.IMAGE_BASE_RELOCATION |
||
115 | sub ecx, sizeof.IMAGE_BASE_RELOCATION |
||
116 | jbe .pagedone |
||
6767 | clevermous | 117 | push esi |
118 | fpo_delta = fpo_delta + 4 |
||
119 | ; 2c. Check whether we have mprotect-ed the current page at the previous step. |
||
120 | ; If so, go to 2e. |
||
6810 | clevermous | 121 | add edx, esi |
6767 | clevermous | 122 | cmp [.next_page_addr+fpo_delta], edx |
123 | jz .mprotected_earlier |
||
124 | ; 2d. We are going to modify data, so mprotect the current page to be writable. |
||
6614 | clevermous | 125 | ; Save the old protection, we will restore it after the block is processed. |
126 | ; Ignore any error. |
||
6767 | clevermous | 127 | ; Go to 2f after. |
6614 | clevermous | 128 | PROT_READ = 1 |
129 | PROT_WRITE = 2 |
||
130 | PROT_EXEC = 4 |
||
6767 | clevermous | 131 | push ecx |
6614 | clevermous | 132 | mov eax, 68 |
133 | mov ebx, 30 |
||
134 | mov ecx, PROT_READ+PROT_WRITE |
||
135 | mov esi, 0x1000 |
||
136 | call FS_SYSCALL_PTR |
||
137 | pop ecx |
||
6767 | clevermous | 138 | jmp .mprotected |
139 | ; 2e. We have already mprotect-ed the current page, |
||
140 | ; move corresponding variables. |
||
141 | .mprotected_earlier: |
||
142 | mov [.next_page_addr+fpo_delta], -1 |
||
143 | mov eax, [.next_page_original_access+fpo_delta] |
||
144 | .mprotected: |
||
6614 | clevermous | 145 | push eax |
6767 | clevermous | 146 | fpo_delta = fpo_delta + 4 |
147 | ; 2g. Block data is an array of word values. Repeat 2h for every of those. |
||
6614 | clevermous | 148 | .relocloop: |
149 | sub ecx, 2 |
||
150 | jb .relocdone |
||
6767 | clevermous | 151 | ; 2h. Every value consists of a 4-bit type and 12-bit offset in the page. |
6614 | clevermous | 152 | ; x86 uses two types: 0 = no data (used for padding), 3 = 32-bit relative. |
153 | movzx eax, word [edi] |
||
154 | add edi, 2 |
||
155 | mov ebx, eax |
||
156 | and ebx, 0xFFF |
||
157 | shr eax, 12 |
||
158 | jz .relocloop |
||
159 | cmp al, IMAGE_REL_BASED_HIGHLOW |
||
160 | jnz .badreloc |
||
6767 | clevermous | 161 | ; If the target dword intersects page boundary, |
162 | ; we need to mprotect the next page too. |
||
163 | cmp ebx, 0xFFC |
||
164 | jbe .no_mprotect_next |
||
165 | push ebx ecx edx |
||
166 | fpo_delta = fpo_delta + 12 |
||
167 | lea eax, [.next_page_original_access+fpo_delta] |
||
168 | call .restore_old_access |
||
169 | mov eax, 68 |
||
170 | mov ebx, 30 |
||
171 | mov ecx, PROT_READ+PROT_WRITE |
||
172 | mov esi, 0x1000 |
||
173 | add edx, esi |
||
174 | call FS_SYSCALL_PTR |
||
175 | mov [.next_page_original_access+fpo_delta], eax |
||
176 | mov [.next_page_addr+fpo_delta], edx |
||
177 | pop edx ecx ebx |
||
178 | fpo_delta = fpo_delta - 12 |
||
179 | .no_mprotect_next: |
||
6614 | clevermous | 180 | add [edx+ebx], ebp |
181 | jmp .relocloop |
||
182 | .relocdone: |
||
6767 | clevermous | 183 | ; 2i. Restore memory protection changed in 2d. |
6614 | clevermous | 184 | pop ecx |
6767 | clevermous | 185 | fpo_delta = fpo_delta - 4 |
6614 | clevermous | 186 | mov eax, 68 |
187 | mov ebx, 30 |
||
188 | mov esi, 0x1000 |
||
189 | call FS_SYSCALL_PTR |
||
190 | pop esi |
||
6767 | clevermous | 191 | fpo_delta = fpo_delta - 4 |
6614 | clevermous | 192 | .pagedone: |
6767 | clevermous | 193 | cmp [.sizeleft+fpo_delta], 0 |
6614 | clevermous | 194 | jnz .pageloop |
6767 | clevermous | 195 | lea eax, [.next_page_original_access+fpo_delta] |
196 | call .restore_old_access |
||
197 | add esp, 12 |
||
6614 | clevermous | 198 | ; 3. For performance reasons, relocation should be avoided |
199 | ; by choosing an appropriate preferred address. |
||
200 | ; If we have actually relocated something, yell to the debug board, |
||
201 | ; so the programmer can notice that. |
||
6767 | clevermous | 202 | ; It's a warning, not an error, so don't call loader_say_error. |
6614 | clevermous | 203 | mov ecx, msg_relocated1 |
204 | call sys_msg_board_str |
||
6767 | clevermous | 205 | mov ecx, [modulename] |
6614 | clevermous | 206 | call sys_msg_board_str |
207 | mov ecx, msg_relocated2 |
||
208 | call sys_msg_board_str |
||
209 | clc |
||
210 | ret |
||
211 | .badreloc: |
||
6767 | clevermous | 212 | pop ecx |
213 | pop esi |
||
214 | add esp, 12 |
||
215 | ccall loader_say_error, msg_bad_relocation, [modulename], 0 |
||
6614 | clevermous | 216 | stc |
217 | ret |
||
6767 | clevermous | 218 | |
219 | .restore_old_access: |
||
220 | cmp dword [eax+4], -1 |
||
221 | jz @f |
||
222 | mov ecx, [eax] |
||
223 | mov edx, [eax+4] |
||
224 | mov eax, 68 |
||
225 | mov ebx, 30 |
||
226 | mov esi, 0x1000 |
||
227 | call FS_SYSCALL_PTR |
||
228 | @@: |
||
229 | retn |
||
6614 | clevermous | 230 | endp |
6767 | clevermous | 231 | |
232 | ; Resolves static dependencies in the given PE module. |
||
233 | ; Recursively loads and initializes all dependencies. |
||
234 | ; in: esi -> MODULE struct |
||
235 | ; out: eax=0 - success, eax=-1 - error |
||
236 | ; modules_mutex should be locked |
||
237 | proc resolve_pe_imports |
||
238 | locals |
||
239 | export_base dd ? |
||
240 | export_ptr dd ? |
||
241 | export_size dd ? |
||
242 | import_module dd ? |
||
243 | import_descriptor dd ? |
||
6810 | clevermous | 244 | next_forwarder dd ? |
245 | bound_import_descriptor dd ? |
||
6767 | clevermous | 246 | bound_import_dir dd ? |
6810 | clevermous | 247 | bound_modules_count dd ? |
248 | bound_modules_ptr dd ? |
||
249 | bound_module dd ? |
||
250 | bound_modules_left dd ? |
||
6767 | clevermous | 251 | cur_page dd -0x1000 ; the page at 0xFFFFF000 is never allocated |
252 | cur_page_old_access dd ? |
||
253 | next_page dd -1 |
||
254 | next_page_old_access dd ? |
||
255 | endl |
||
256 | |||
257 | ; General case of resolving imports against one module that is already loaded: |
||
258 | ; binding either does not exist or has mismatched timestamp, |
||
259 | ; so we need to walk through all imported symbols and resolve each one. |
||
260 | ; in: ebp -> IMAGE_IMPORT_DESCRIPTOR |
||
261 | macro resolve_import_from_module fail_action |
||
262 | { |
||
263 | local .label1, .loop, .done |
||
264 | ; common preparation that doesn't need to be repeated per each symbol |
||
265 | mov eax, [import_module] |
||
266 | mov eax, [eax+MODULE.base] |
||
267 | call prepare_import_from_module |
||
268 | ; There are two arrays of dwords pointed to by FirstThunk and OriginalFirstThunk. |
||
269 | ; Target array is FirstThunk: addresses of imported symbols should be written |
||
270 | ; there, that is where the program expects to find them. |
||
271 | ; Source array can be either FirstThunk or OriginalFirstThunk. |
||
272 | ; Normally, FirstThunk and OriginalFirstThunk in a just-compiled binary |
||
273 | ; point to two identical copies of the same array. |
||
274 | ; Binding of the binary rewrites FirstThunk array with actual addresses, |
||
275 | ; but keeps OriginalFirstThunk as is. |
||
276 | ; If OriginalFirstThunk and FirstThunk are both present, use OriginalFirstThunk |
||
277 | ; as source array. |
||
278 | ; However, a compiler is allowed to generate a binary without OriginalFirstThunk; |
||
279 | ; it is impossible to bind such a binary, but it is still valid. |
||
280 | ; If OriginalFirstThunk is absent, use FirstThunk as source array. |
||
281 | mov ebx, [ebp+IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk] |
||
282 | mov ebp, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
||
283 | test ebx, ebx |
||
6810 | clevermous | 284 | jnz @f |
6767 | clevermous | 285 | mov ebx, ebp |
6810 | clevermous | 286 | @@: |
6767 | clevermous | 287 | ; FirstThunk and OriginalFirstThunk are RVAs. |
288 | add ebx, [esi+MODULE.base] |
||
289 | add ebp, [esi+MODULE.base] |
||
290 | ; Source array is terminated with zero dword. |
||
291 | .loop: |
||
292 | cmp dword [ebx], 0 |
||
293 | jz .done |
||
294 | mov ecx, [ebx] |
||
295 | get_address_for_thunk ; should preserve esi,edi,ebp |
||
296 | test eax, eax |
||
297 | jz fail_action |
||
298 | mov edi, eax |
||
299 | mov edx, ebp |
||
300 | call .ensure_writable ; should preserve edx,ebx,esi,ebp |
||
301 | mov [edx], edi |
||
302 | add ebx, 4 |
||
303 | add ebp, 4 |
||
304 | jmp .loop |
||
305 | .done: |
||
306 | } |
||
307 | |||
308 | ; Resolve one imported symbol. |
||
309 | ; in: ecx = ordinal or RVA of thunk |
||
310 | ; out: eax = address of exported function |
||
311 | macro get_address_for_thunk |
||
312 | { |
||
313 | local .ordinal, .common |
||
314 | ; Ordinal imports have bit 31 set, name imports have bit 31 clear. |
||
315 | btr ecx, 31 |
||
316 | jc .ordinal |
||
317 | ; Thunk for name import is RVA of IMAGE_IMPORT_BY_NAME structure. |
||
318 | add ecx, [esi+MODULE.base] |
||
319 | movzx edx, [ecx+IMAGE_IMPORT_BY_NAME.Hint] |
||
320 | add ecx, IMAGE_IMPORT_BY_NAME.Name |
||
321 | call get_exported_function_by_name |
||
322 | jmp .common |
||
323 | .ordinal: |
||
324 | ; Thunk for ordinal import is just an ordinal, |
||
325 | ; bit 31 has been cleared by btr instruction. |
||
326 | call get_exported_function_by_ordinal |
||
327 | .common: |
||
328 | } |
||
329 | |||
330 | ; We have four main variants: |
||
331 | ; normal unbound import, old-style bound import, new-style bound import, |
||
332 | ; no import. |
||
333 | ; * Normal unbound import: |
||
334 | ; we have an array of import descriptors, one per imported module, |
||
335 | ; pointed to by import directory. |
||
336 | ; We should loop over all descriptors and apply resolve_import_from_module |
||
337 | ; for each one. |
||
338 | ; * Old-style bound import: |
||
339 | ; we have the same array of import descriptors, but timestamp field is set up. |
||
340 | ; We should do the same loop, but we can do a lightweight processing |
||
341 | ; of modules with correct timestamp. In the best case, "lightweight processing" |
||
342 | ; means just skipping them, but corrections arise for relocated modules |
||
343 | ; and forwarded exports. |
||
344 | ; * New-style bound import: |
||
345 | ; we have two parallel arrays of import descriptors and bound descriptors, |
||
346 | ; pointed to by two directories. Timestamp field has a special value -1 |
||
347 | ; in import descriptors, real timestamps are in bound descriptors. |
||
348 | ; * No import: not really different from normal import with no descriptors. |
||
6810 | clevermous | 349 | ; Forwarded exports are the part where binding goes really interesting. |
350 | ; The address of forwarded export can change with any library in the chain. |
||
351 | ; In old-style bound import, a descriptor can list only the library itself, |
||
352 | ; so it is impossible to bind forwarded exports at all; thunks that point to |
||
353 | ; forwarded exports are linked in the list starting from ForwarderChain in import descriptor. |
||
354 | ; New-style bound import exists exactly to address this problem; it allows to |
||
355 | ; list several related libraries for one imported module. |
||
356 | ; However, even with new-style bound import, some forwarded exports can |
||
357 | ; still remain unbound: binding tool can fail to load dependent libraries |
||
358 | ; during binding time; the tool from MS SDK for unknown reason just refuses to |
||
359 | ; bind forwarded exports except for built-in white list if subsystem version is |
||
360 | ; < 6.0. So even with new-style bound import, ForwarderChain still can be non-empty. |
||
361 | ; Thus, we always need to look at old-style import descriptor. |
||
6767 | clevermous | 362 | ; 1. Fetch addresses of two directories. We are not interested in their sizes. |
363 | ; ebp = import RVA |
||
364 | ; ebx = bound import RVA |
||
365 | xor ebx, ebx |
||
366 | xor ebp, ebp |
||
367 | ; PE and stripped PE have different places for directories. |
||
368 | mov eax, [esi+MODULE.base] |
||
369 | cmp byte [eax], 'M' |
||
370 | jz .parse_mz |
||
371 | cmp [eax+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_IMPORT |
||
372 | jbe .common |
||
373 | mov ebp, [eax+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_IMPORT*sizeof.IMAGE_DATA_DIRECTORY] |
||
374 | cmp [eax+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_BOUND_IMPORT |
||
375 | jbe .common |
||
376 | mov ebx, [eax+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_BOUND_IMPORT*sizeof.IMAGE_DATA_DIRECTORY] |
||
377 | jmp .common |
||
378 | .parse_mz: |
||
379 | add eax, [eax+3Ch] |
||
380 | cmp [eax+IMAGE_NT_HEADERS.OptionalHeader.NumberOfDirectories], IMAGE_DIRECTORY_ENTRY_IMPORT |
||
381 | jbe .common |
||
382 | mov ebp, [eax+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_IMPORT*sizeof.IMAGE_DATA_DIRECTORY] |
||
383 | cmp [eax+IMAGE_NT_HEADERS.OptionalHeader.NumberOfDirectories], IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT |
||
384 | jbe .common |
||
385 | mov ebx, [eax+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT*sizeof.IMAGE_DATA_DIRECTORY] |
||
386 | .common: |
||
6810 | clevermous | 387 | ; If import directory is not present, no import - nothing to do. |
388 | ; Ignore bound import directory in this case. |
||
6767 | clevermous | 389 | test ebp, ebp |
390 | jz .done |
||
391 | add ebp, [esi+MODULE.base] ; directories contain RVA |
||
6810 | clevermous | 392 | add ebx, [esi+MODULE.base] ; directories contain RVA |
393 | mov [bound_import_dir], ebx |
||
394 | mov [bound_import_descriptor], ebx |
||
395 | ; Repeat remaining steps for all descriptors in the directory. |
||
396 | .descriptor_loop: |
||
397 | ; 2. Check whether this descriptor is an end mark with zero fields. |
||
6767 | clevermous | 398 | ; Look at Name field. |
399 | mov edi, [ebp+IMAGE_IMPORT_DESCRIPTOR.Name] |
||
400 | test edi, edi |
||
401 | jz .done |
||
6810 | clevermous | 402 | ; 3. Load the target module. |
6767 | clevermous | 403 | add edi, [esi+MODULE.base] ; Name field is RVA |
404 | call load_imported_module ; should preserve esi,ebp |
||
405 | test eax, eax |
||
406 | jz .failed |
||
407 | mov [import_module], eax |
||
6810 | clevermous | 408 | ; 4. Check whether the descriptor has a non-stale old-style binding. |
6767 | clevermous | 409 | ; Zero timestamp means "not bound". |
6810 | clevermous | 410 | ; Timestamp 0xFFFFFFFF means "new-style binding". |
6767 | clevermous | 411 | ; Mismatched timestamp means "stale binding". |
6810 | clevermous | 412 | ; In first and third cases, go to 9. |
413 | ; In second case, go to 10 for further checks. |
||
6767 | clevermous | 414 | mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.TimeDateStamp] |
415 | test edx, edx |
||
6810 | clevermous | 416 | jz .resolve_generic |
417 | cmp edx, -1 |
||
418 | jz .new_binding |
||
6767 | clevermous | 419 | cmp edx, [eax+MODULE.timestamp] |
6810 | clevermous | 420 | jnz .resolve_generic |
421 | ; 5. The descriptor has a non-stale old-style binding. |
||
6767 | clevermous | 422 | ; There are two cases when we still need to do something: |
423 | ; * if the target module has been relocated, we need to add |
||
424 | ; relocation delta to all addresses; |
||
6810 | clevermous | 425 | ; * if some exports are forwarded, we need to resolve them. |
6767 | clevermous | 426 | ; Thunks for forwarded exports contain index of next forwarded export |
427 | ; instead of target address, making a single-linked list terminated by -1. |
||
428 | ; ForwarderChain is the head of the list. |
||
6810 | clevermous | 429 | ; Check for the first problem first; the corresponding code can handle both. |
430 | ; If the target module is relocated, go to 8. |
||
431 | ; If the target module is not relocated, but has forwarded exports, go to 7. |
||
432 | ; Otherwise, advance to 6. |
||
433 | .old_binding: |
||
6767 | clevermous | 434 | cmp [eax+MODULE.basedelta], 0 |
6810 | clevermous | 435 | jnz .old_binding_relocate |
436 | .check_forwarder_chain: |
||
6767 | clevermous | 437 | cmp [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain], -1 |
6810 | clevermous | 438 | jnz .resolve_forward_chain |
439 | .next_descriptor: |
||
440 | ; 6. Advance to next descriptor and continue the loop. |
||
441 | add ebp, sizeof.IMAGE_IMPORT_DESCRIPTOR |
||
442 | jmp .descriptor_loop |
||
443 | .resolve_forward_chain: |
||
444 | ; 7. Resolve all thunks from ForwarderChain list. |
||
6767 | clevermous | 445 | mov eax, [import_module] |
446 | mov eax, [eax+MODULE.base] |
||
447 | call prepare_import_from_module |
||
6810 | clevermous | 448 | mov edi, [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain] |
449 | .resolve_forward_chain_loop: |
||
6767 | clevermous | 450 | mov ebx, [ebp+IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk] |
451 | add ebx, [esi+MODULE.base] |
||
452 | mov ecx, [ebx+edi*4] |
||
453 | get_address_for_thunk ; should preserve esi,edi,ebp |
||
454 | test eax, eax |
||
455 | jz .failed |
||
456 | mov ebx, eax |
||
457 | mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
||
458 | add edx, [esi+MODULE.base] |
||
459 | lea edx, [edx+edi*4] |
||
460 | call .ensure_writable ; should preserve edx,ebx,esi,ebp |
||
461 | mov edi, [edx] ; next forwarded export |
||
462 | mov [edx], ebx ; store the address |
||
463 | cmp edi, -1 |
||
6810 | clevermous | 464 | jnz .resolve_forward_chain_loop |
465 | ; After resolving, we are done with this import descriptor, go to 6. |
||
466 | jmp .next_descriptor |
||
467 | .done: |
||
468 | call .restore_protection |
||
469 | xor eax, eax |
||
470 | ret |
||
471 | .old_binding_relocate: |
||
472 | ; 8. The descriptor has a non-stale old-style binding, |
||
473 | ; but the target module is relocated, so we need to add MODULE.basedelta to |
||
474 | ; all addresses. Also, there can be forwarded exports. |
||
475 | ; For consistency with generic-case resolve_import_from_module, |
||
476 | ; check for end of thunks by looking at OriginalFirstThunk array. |
||
477 | ; Note: we assume here that ForwarderChain list is ordered. |
||
478 | ; After that, go to 6. |
||
479 | mov edi, [eax+MODULE.basedelta] |
||
480 | cmp [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain], -1 |
||
481 | jz @f |
||
482 | mov eax, [import_module] |
||
483 | mov eax, [eax+MODULE.base] |
||
484 | call prepare_import_from_module |
||
485 | @@: |
||
486 | mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
||
487 | add edx, [esi+MODULE.base] |
||
488 | mov ebx, [ebp+IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk] |
||
489 | add ebx, [esi+MODULE.base] |
||
490 | mov eax, [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain] |
||
491 | lea eax, [edx+eax*4] |
||
492 | mov [next_forwarder], eax |
||
493 | .old_binding_relocate_loop: |
||
494 | cmp dword [ebx], 0 |
||
495 | jz .next_descriptor |
||
496 | call .ensure_writable ; should preserve esi,edi,ebp,ebx,edx |
||
497 | cmp edx, [next_forwarder] |
||
498 | jz .old_binding_relocate_forwarder |
||
499 | add dword [edx], edi |
||
500 | .old_binding_relocate_next: |
||
501 | add edx, 4 |
||
502 | add ebx, 4 |
||
503 | jmp .old_binding_relocate_loop |
||
504 | .old_binding_relocate_forwarder: |
||
505 | mov ecx, [ebx] |
||
506 | get_address_for_thunk |
||
507 | test eax, eax |
||
508 | jz .failed |
||
509 | mov edx, [next_forwarder] |
||
510 | mov ecx, [edx] |
||
511 | mov [edx], eax |
||
512 | mov eax, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
||
513 | add eax, [esi+MODULE.base] |
||
514 | lea eax, [eax+ecx*4] |
||
515 | mov [next_forwarder], eax |
||
516 | jmp .old_binding_relocate_next |
||
517 | .resolve_generic: |
||
518 | ; 9. Run generic-case resolver. |
||
6767 | clevermous | 519 | mov [import_descriptor], ebp |
520 | resolve_import_from_module .failed ; should preserve esi |
||
521 | mov ebp, [import_descriptor] |
||
6810 | clevermous | 522 | ; After that, go to 6. |
523 | jmp .next_descriptor |
||
524 | .new_binding: |
||
525 | ; New-style bound import. |
||
526 | ; 10. Locate the new-style descriptor corresponding to the current |
||
527 | ; import descriptor. |
||
528 | ; 10a. Check the current new-style descriptor: the one that follows |
||
529 | ; previous one, if there was the previous one, or the first one. |
||
530 | ; In most cases, new-style descriptors are in the same order as |
||
531 | ; import descriptors, so full loop in 10b can be avoided. |
||
532 | mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.Name] |
||
533 | add edx, [esi+MODULE.base] |
||
534 | mov ebx, [bound_import_descriptor] |
||
6767 | clevermous | 535 | movzx edi, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName] |
536 | test edi, edi |
||
6810 | clevermous | 537 | jz .look_new_binding_hard |
538 | add edi, [bound_import_dir] |
||
539 | xor ecx, ecx |
||
540 | @@: |
||
541 | mov al, [edx+ecx] |
||
542 | cmp al, [edi+ecx] |
||
543 | jnz .look_new_binding_hard |
||
544 | test al, al |
||
545 | jz .new_binding_found |
||
546 | inc ecx |
||
547 | jmp @b |
||
548 | .look_new_binding_hard: |
||
549 | ; 10b. We are out of luck with the current new-style descriptor, |
||
550 | ; so loop over all of them looking for the matching one. |
||
551 | mov ebx, [bound_import_dir] |
||
552 | .look_new_binding_loop: |
||
553 | movzx edi, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName] |
||
554 | add edi, [bound_import_dir] |
||
555 | xor ecx, ecx |
||
556 | @@: |
||
557 | mov al, [edx+ecx] |
||
558 | cmp al, [edi+ecx] |
||
559 | jnz .look_new_binding_next |
||
560 | test al, al |
||
561 | jz .new_binding_found |
||
562 | inc ecx |
||
563 | jmp @b |
||
564 | .look_new_binding_next: |
||
565 | movzx ecx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs] |
||
566 | lea ebx, [ebx+(ecx+1)*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR] |
||
567 | jmp .look_new_binding_loop |
||
568 | .new_binding_found: |
||
569 | ; 10c. Store the next descriptor for subsequent scans. |
||
570 | movzx ecx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs] |
||
571 | lea eax, [ebx+(ecx+1)*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR] |
||
572 | mov [bound_import_descriptor], eax |
||
6767 | clevermous | 573 | ; Bound import descriptors come in groups. |
574 | ; The first descriptor in each group corresponds to the main imported module. |
||
575 | ; If some exports from the module are forwarded, additional descriptors |
||
576 | ; are created for modules where those exports are forwarded to. |
||
577 | ; Number of additional descriptors is given by one field in the first descriptor. |
||
6810 | clevermous | 578 | ; 11. We have already loaded the main module, validate its timestamp. |
579 | ; If timestamp does not match, go to 9 to run generic-case resolver. |
||
580 | mov eax, [import_module] |
||
581 | mov edx, [eax+MODULE.timestamp] |
||
582 | cmp edx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.TimeDateStamp] |
||
583 | jnz .resolve_generic |
||
584 | ; 12. If there are no additional libraries, |
||
585 | ; the situation is exactly same as old-style binding, so go to 5. |
||
586 | test ecx, ecx |
||
587 | jz .old_binding |
||
588 | ; 13. Load additional libraries and validate their timestamps. |
||
589 | ; If at least one timestamp is invalid, resort to generic-case resolving. |
||
590 | ; 13a. Allocate memory for all bound modules, including the main module. |
||
591 | lea ecx, [(ecx+1)*4] |
||
592 | stdcall malloc, ecx |
||
593 | test eax, eax |
||
594 | jz .failed |
||
595 | mov [bound_modules_ptr], eax |
||
596 | ; 13b. Store the main module. |
||
597 | mov edx, [import_module] |
||
598 | mov [eax], edx |
||
599 | xor ecx, ecx |
||
600 | ; 13c. Loop over all additional descriptors. |
||
601 | .newstyle_load_loop: |
||
602 | inc ecx |
||
603 | mov [bound_modules_count], ecx |
||
604 | movzx edi, [ebx+ecx*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR+IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName] |
||
6767 | clevermous | 605 | add edi, [bound_import_dir] |
606 | call load_imported_module ; should preserve ebx,esi,ebp |
||
607 | test eax, eax |
||
6810 | clevermous | 608 | jz .newstyle_failed |
609 | mov ecx, [bound_modules_count] |
||
610 | mov edx, [ebx+ecx*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR+IMAGE_BOUND_IMPORT_DESCRIPTOR.TimeDateStamp] |
||
611 | cmp [eax+MODULE.timestamp], edx |
||
612 | jnz .newstyle_stale |
||
613 | mov edx, [bound_modules_ptr] |
||
614 | mov [edx+ecx*4], eax |
||
615 | cmp cx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs] |
||
616 | jb .newstyle_load_loop |
||
617 | inc ecx |
||
618 | mov [bound_modules_count], ecx |
||
619 | ; New-style binding has correct timestamp. |
||
620 | ; There still can be same two problems as in step 5 with old-style binding. |
||
621 | ; 14. Check whether at least one module is relocated. If so, go to 16. |
||
622 | .newstyle_check_reloc: |
||
623 | mov eax, [edx] |
||
6767 | clevermous | 624 | cmp [eax+MODULE.basedelta], 0 |
6810 | clevermous | 625 | jnz .newstyle_need_reloc |
626 | add edx, 4 |
||
627 | dec ecx |
||
628 | jnz .newstyle_check_reloc |
||
629 | ; 15. Bound modules are not relocated. |
||
630 | ; The only remaining problem could be unbound forwarders. |
||
631 | ; Free memory allocated at 13a and let steps 6 and 7 do their work. |
||
632 | stdcall free, [bound_modules_ptr] |
||
633 | jmp .check_forwarder_chain |
||
634 | .newstyle_stale: |
||
635 | stdcall free, [bound_modules_ptr] |
||
636 | jmp .resolve_generic |
||
637 | ; 16. The descriptor has a non-stale new-style binding, |
||
638 | ; but at least one of target modules is relocated, so we need to add |
||
639 | ; MODULE.basedelta to addresses from relocated modules. |
||
640 | ; Also, there can be forwarded exports. |
||
6767 | clevermous | 641 | ; For consistency with generic-case resolve_import_from_module, |
6810 | clevermous | 642 | ; check for end of thunks by looking at OriginalFirstThunk array. |
643 | ; Note: we assume here that ForwarderChain list is ordered. |
||
644 | ; After that, go to 6. |
||
645 | .newstyle_need_reloc: |
||
646 | mov eax, [import_module] |
||
647 | mov eax, [eax+MODULE.base] |
||
648 | call prepare_import_from_module |
||
6767 | clevermous | 649 | mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
650 | add edx, [esi+MODULE.base] |
||
651 | mov ebx, [ebp+IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk] |
||
652 | add ebx, [esi+MODULE.base] |
||
6810 | clevermous | 653 | mov eax, [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain] |
654 | lea eax, [edx+eax*4] |
||
655 | mov [next_forwarder], eax |
||
656 | .new_binding_relocate_loop: |
||
6767 | clevermous | 657 | cmp dword [ebx], 0 |
6810 | clevermous | 658 | jz .new_binding_relocate_done |
659 | cmp edx, [next_forwarder] |
||
660 | jz .new_binding_resolve_thunk |
||
661 | mov [bound_module], 0 |
||
662 | mov eax, [bound_modules_count] |
||
663 | mov [bound_modules_left], eax |
||
664 | mov edi, [bound_modules_ptr] |
||
665 | ; There should be at least one module containing address [edx]. |
||
666 | ; There can be more than one if preferred address ranges for two modules intersect; |
||
667 | ; in this case, we are forced to resolve address from scratch. |
||
668 | .new_binding_lookup_module: |
||
669 | mov ecx, [edx] |
||
670 | mov eax, [edi] |
||
671 | sub ecx, [eax+MODULE.base] |
||
672 | add ecx, [eax+MODULE.basedelta] |
||
673 | cmp ecx, [eax+MODULE.size] |
||
674 | jae @f |
||
675 | cmp [bound_module], 0 |
||
676 | jnz .new_binding_resolve_thunk |
||
677 | mov [bound_module], eax |
||
678 | @@: |
||
679 | add edi, 4 |
||
680 | dec [bound_modules_left] |
||
681 | jnz .new_binding_lookup_module |
||
682 | mov edi, [bound_module] |
||
683 | mov edi, [edi+MODULE.basedelta] |
||
684 | test edi, edi |
||
685 | jz .new_binding_relocate_next |
||
686 | call .ensure_writable ; should preserve esi,edi,ebp,ebx,edx |
||
687 | add dword [edx], edi |
||
688 | .new_binding_relocate_next: |
||
689 | add edx, 4 |
||
6767 | clevermous | 690 | add ebx, 4 |
6810 | clevermous | 691 | jmp .new_binding_relocate_loop |
692 | .new_binding_resolve_thunk: |
||
693 | mov [bound_modules_left], edx |
||
694 | call .ensure_writable |
||
695 | mov ecx, [ebx] |
||
696 | get_address_for_thunk |
||
697 | test eax, eax |
||
698 | jz .newstyle_failed |
||
699 | mov edx, [bound_modules_left] |
||
700 | mov ecx, [edx] |
||
701 | mov [edx], eax |
||
702 | cmp edx, [next_forwarder] |
||
703 | jnz .new_binding_relocate_next |
||
704 | mov eax, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
||
705 | add eax, [esi+MODULE.base] |
||
706 | lea eax, [eax+ecx*4] |
||
707 | mov [next_forwarder], eax |
||
708 | jmp .new_binding_relocate_next |
||
709 | .new_binding_relocate_done: |
||
710 | stdcall free, [bound_modules_ptr] |
||
711 | jmp .next_descriptor |
||
712 | .newstyle_failed: |
||
713 | stdcall free, [bound_modules_ptr] |
||
6767 | clevermous | 714 | .failed: |
715 | call .restore_protection |
||
716 | xor eax, eax |
||
717 | dec eax |
||
718 | ret |
||
719 | |||
720 | ; Local helper functions. |
||
721 | fpo_delta = fpo_delta + 4 |
||
722 | ; Import table may reside in read-only pages. |
||
723 | ; We should mprotect any page where we are going to write to. |
||
724 | ; Things get interesting when one thunk spans two pages. |
||
725 | ; in: edx = address of dword to make writable |
||
726 | .ensure_writable: |
||
727 | ; 1. Fast path: if we have already mprotect-ed one page and |
||
728 | ; the requested dword is in the same page, do nothing. |
||
729 | mov eax, edx |
||
730 | sub eax, [cur_page] |
||
731 | cmp eax, 0x1000 - 4 |
||
732 | ja .cur_page_not_sufficient |
||
733 | .ensure_writable.nothing: |
||
734 | retn |
||
735 | .cur_page_not_sufficient: |
||
736 | ; 2. If the requested dword begins in the current page |
||
737 | ; and ends in the next page, mprotect the next page and return. |
||
738 | push ebx esi edx |
||
739 | fpo_delta = fpo_delta + 12 |
||
740 | cmp eax, 0x1000 |
||
741 | jae .wrong_cur_page |
||
742 | cmp [next_page], -1 |
||
743 | jnz @f |
||
744 | mov eax, 68 |
||
745 | mov ebx, 30 |
||
746 | mov ecx, PROT_READ+PROT_WRITE |
||
747 | mov edx, [cur_page] |
||
748 | mov esi, 0x1000 |
||
749 | add edx, esi |
||
750 | mov [next_page], edx |
||
751 | call FS_SYSCALL_PTR |
||
752 | mov [next_page_old_access], eax |
||
753 | @@: |
||
754 | pop edx esi ebx |
||
755 | retn |
||
756 | .wrong_cur_page: |
||
757 | ; The requested dword does not intersect with the current page. |
||
758 | ; 3. Restore the protection of the current page, |
||
759 | ; it is unlikely to be used again. |
||
760 | cmp [cur_page], -0x1000 |
||
761 | jz @f |
||
762 | mov eax, 68 |
||
763 | mov ebx, 30 |
||
764 | mov ecx, [cur_page_old_access] |
||
765 | mov edx, [cur_page] |
||
766 | mov esi, 0x1000 |
||
767 | call FS_SYSCALL_PTR |
||
768 | @@: |
||
769 | ; 4. If the next page has been mprotect-ed too, |
||
770 | ; switch to it as the current page and restart the function. |
||
771 | cmp [next_page], -1 |
||
772 | jz @f |
||
773 | mov eax, [next_page] |
||
774 | mov [cur_page], eax |
||
775 | mov eax, [next_page_old_access] |
||
776 | mov [cur_page_old_access], eax |
||
777 | mov [next_page], -1 |
||
778 | pop edx esi ebx |
||
779 | jmp .ensure_writable |
||
780 | @@: |
||
781 | ; 5. This is the entirely new page to mprotect. |
||
782 | mov edx, [esp] |
||
783 | and edx, not 0xFFF |
||
784 | mov eax, 68 |
||
785 | mov ebx, 30 |
||
786 | mov ecx, PROT_READ+PROT_WRITE |
||
787 | mov [cur_page], edx |
||
788 | mov esi, 0x1000 |
||
789 | call FS_SYSCALL_PTR |
||
790 | mov [cur_page_old_access], eax |
||
791 | pop edx esi ebx |
||
792 | fpo_delta = fpo_delta - 12 |
||
793 | retn |
||
794 | |||
795 | ; Called at end of processing, |
||
796 | ; restores protection of pages that we have mprotect-ed for write. |
||
797 | .restore_protection: |
||
798 | push esi |
||
799 | fpo_delta = fpo_delta + 4 |
||
800 | cmp [next_page], -1 |
||
801 | jz @f |
||
802 | mov eax, 68 |
||
803 | mov ebx, 30 |
||
804 | mov ecx, [next_page_old_access] |
||
805 | mov edx, [next_page] |
||
806 | mov esi, 0x1000 |
||
807 | call FS_SYSCALL_PTR |
||
808 | @@: |
||
809 | cmp [cur_page], -0x1000 |
||
810 | jz @f |
||
811 | mov eax, 68 |
||
812 | mov ebx, 30 |
||
813 | mov ecx, [cur_page_old_access] |
||
814 | mov edx, [cur_page] |
||
815 | mov esi, 0x1000 |
||
816 | call FS_SYSCALL_PTR |
||
817 | @@: |
||
818 | pop esi |
||
819 | fpo_delta = fpo_delta - 4 |
||
820 | retn |
||
821 | endp |
||
822 | |||
823 | ; Part of resolving symbol from a module that is the same for all symbols. |
||
824 | ; resolve_pe_imports calls it only once per module. |
||
825 | ; Fetches export directory from the module. |
||
826 | ; Non-standard calling convention: saves results to first 2 dwords on the stack. |
||
827 | ; in: eax = module base |
||
828 | proc prepare_import_from_module c, export_base, export_ptr, export_size |
||
829 | ; The implementation is straightforward. |
||
830 | mov [export_base], eax |
||
831 | cmp byte [eax], 'M' |
||
832 | jz .parse_mz |
||
833 | cmp [eax+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_EXPORT |
||
834 | jbe .noexport |
||
835 | mov edx, [eax+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY+IMAGE_DATA_DIRECTORY.VirtualAddress] |
||
6810 | clevermous | 836 | test edx, edx |
837 | jz .noexport |
||
6767 | clevermous | 838 | add edx, eax |
839 | mov [export_ptr], edx |
||
840 | mov edx, [eax+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY+IMAGE_DATA_DIRECTORY.isize] |
||
841 | mov [export_size], edx |
||
842 | ret |
||
843 | .parse_mz: |
||
844 | mov ecx, [eax+3Ch] |
||
845 | add ecx, eax |
||
846 | cmp [ecx+IMAGE_NT_HEADERS.OptionalHeader.NumberOfDirectories], IMAGE_DIRECTORY_ENTRY_EXPORT |
||
847 | jbe .noexport |
||
848 | mov edx, [ecx+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory.VirtualAddress+IMAGE_DIRECTORY_ENTRY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY] |
||
6810 | clevermous | 849 | test edx, edx |
850 | jz .noexport |
||
6767 | clevermous | 851 | add edx, eax |
852 | mov [export_ptr], edx |
||
853 | mov edx, [ecx+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory.isize+IMAGE_DIRECTORY_ENTRY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY] |
||
854 | mov [export_size], edx |
||
855 | ret |
||
856 | .noexport: |
||
857 | mov [export_ptr], 0 |
||
858 | mov [export_size], 0 |
||
859 | ret |
||
860 | endp |
||
861 | |||
862 | ; PE format supports export by name and by ordinal. |
||
863 | ; Any exported symbol always have an ordinal. |
||
864 | ; It may have a name, it may have no name. |
||
865 | ; A symbol can even have multiple names, usually this happens |
||
866 | ; when several functions with the same body like 'ret' are merged. |
||
867 | ; |
||
868 | ; Addresses of all exported symbols are contained in one array AddressOfFunctions. |
||
869 | ; Ordinal of a symbol is an index in this array + Base. |
||
870 | ; Base is defined in export directory, usually it equals 1. |
||
871 | ; |
||
872 | ; Export by name is more complicated. There are two parallel arrays |
||
873 | ; AddressOfNames and AddressOfNameOrdinals with the same length. |
||
874 | ; This length can be less or greater than length of AddressOfFunctions. |
||
875 | ; AddressOfNames is a sorted array with all exported names. |
||
876 | ; AddressOfNameOrdinals, contrary to the title, gives index in AddressOfFunctions. |
||
877 | ; Looking up a name means |
||
878 | ; * scanning AddressOfNames array to find the index of the corresponding name |
||
879 | ; * looking in AddressOfNameOrdinals at the index found above to get another index; |
||
880 | ; index in AddressOfNames/AddressOfNameOrdinals has no other meaning |
||
881 | ; * finally, looking in AddressOfFunctions with that second index. |
||
882 | |||
883 | ; Resolve symbol from a module by name. |
||
884 | ; prepare_import_from_module should be called beforehand. |
||
885 | ; in: ecx -> name, edx = hint for lookup in name table |
||
886 | ; out: eax = exported address or NULL |
||
887 | ; if [module] is zero, modules_mutex should be unlocked |
||
888 | ; if [module] is nonzero, modules_mutex should be locked |
||
889 | proc get_exported_function_by_name c uses ebx esi edi, export_base, export_ptr, export_size, module |
||
890 | locals |
||
891 | forward_export_base dd ? |
||
892 | forward_export_ptr dd ? |
||
893 | forward_export_size dd ? |
||
894 | forward_export_module dd ? |
||
895 | endl |
||
896 | ; 1. Find length of the name, including terminating zero. |
||
897 | mov esi, ecx |
||
898 | @@: |
||
899 | inc ecx |
||
900 | cmp byte [ecx-1], 0 |
||
901 | jnz @b |
||
902 | sub ecx, esi |
||
903 | ; 2. Validate that export directory is present at all. |
||
904 | mov eax, [export_ptr] |
||
905 | test eax, eax |
||
906 | jz .export_name_not_found |
||
907 | ; 3. Check whether the hint is correct. |
||
908 | ; The hint is a zero-based index in name table. |
||
909 | ; Theoretically, zero is a valid hint. |
||
910 | ; Unfortunately, in practice everyone uses zero if the hint is unknown, |
||
911 | ; which is a quite typical situation, so treating zero as a valid hint |
||
912 | ; would waste processor cycles much more often than save. |
||
913 | ; So only check the hint if it is between 1 and NumberOfNames-1 inclusive. |
||
914 | ; 3a. Validate the hint. |
||
915 | mov ebx, [eax+IMAGE_EXPORT_DIRECTORY.AddressOfNames] |
||
916 | add ebx, [export_base] |
||
917 | cmp edx, [eax+IMAGE_EXPORT_DIRECTORY.NumberOfNames] |
||
918 | jae .ignore_hint |
||
919 | test edx, edx |
||
920 | jz .ignore_hint |
||
921 | ; 3b. Check the hinted name. |
||
922 | ; If it matches, go to 5. If not, we're out of luck, use normal lookup. |
||
923 | mov edi, [ebx+edx*4] |
||
924 | add edi, [export_base] |
||
925 | push ecx esi |
||
926 | repz cmpsb |
||
927 | pop esi ecx |
||
6810 | clevermous | 928 | jz .hint_ok |
6767 | clevermous | 929 | .ignore_hint: |
930 | ; 4. Binary search over name table. |
||
931 | ; Export names are sorted with respect to repz cmpsb. |
||
932 | ; edi <= (the target index) < edx |
||
933 | xor edi, edi |
||
934 | mov edx, [eax+IMAGE_EXPORT_DIRECTORY.NumberOfNames] |
||
935 | .export_name_search.loop: |
||
936 | ; if there are no indexes between edi and edx, name is invalid |
||
937 | cmp edi, edx |
||
938 | jae .export_name_not_found |
||
939 | ; try the index in the middle of current range |
||
940 | lea eax, [edi+edx] |
||
941 | shr eax, 1 |
||
942 | ; compare |
||
943 | push ecx esi edi |
||
944 | fpo_delta = fpo_delta + 12 |
||
945 | mov edi, [ebx+eax*4] |
||
946 | add edi, [export_base] |
||
947 | repz cmpsb |
||
948 | pop edi esi ecx |
||
949 | fpo_delta = fpo_delta - 12 |
||
950 | ; exact match -> found, go to 5 |
||
951 | ; string at esi = target, string at edi = current attempt |
||
952 | ; (string at esi) < (string at edi) -> current index is too high, update upper range |
||
953 | ; (string at esi) > (string at edi) -> current index is too low, update lower range |
||
954 | jz .found |
||
955 | jb @f |
||
956 | lea edi, [eax+1] |
||
957 | jmp .export_name_search.loop |
||
958 | @@: |
||
959 | mov edx, eax |
||
960 | jmp .export_name_search.loop |
||
961 | ; Generic error handler. |
||
962 | .export_name_not_found: |
||
963 | mov ebx, esi |
||
6810 | clevermous | 964 | call .get_module_name |
6767 | clevermous | 965 | ccall loader_say_error, msg_export_name_not_found, ebx, msg_export_not_found, eax, 0 |
966 | .return0: |
||
967 | xor eax, eax |
||
968 | ret |
||
6810 | clevermous | 969 | .hint_ok: |
970 | mov eax, edx |
||
6767 | clevermous | 971 | .found: |
972 | ; 5. We have found an index in AddressOfNames/AddressOfNameOrdinals arrays, |
||
973 | ; convert it to index in AddressOfFunctions array. |
||
974 | mov edx, [export_ptr] |
||
975 | mov ebx, [edx+IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals] |
||
976 | add ebx, [export_base] |
||
977 | movzx eax, word [ebx+eax*2] |
||
978 | ; 6. Fetch the exported address from AddressOfFunctions array. |
||
979 | cmp eax, [edx+IMAGE_EXPORT_DIRECTORY.NumberOfFunctions] |
||
980 | jae .export_name_not_found |
||
981 | mov ebx, [edx+IMAGE_EXPORT_DIRECTORY.AddressOfFunctions] |
||
982 | add ebx, [export_base] |
||
983 | mov eax, [ebx+eax*4] |
||
984 | test eax, eax |
||
985 | jz .export_name_not_found |
||
986 | .check_forwarded: |
||
987 | ; This part of code is also used by get_exported_function_by_ordinal. |
||
988 | ; 7. Check whether the address is inside the export directory. |
||
989 | ; If not, we are done. |
||
990 | add eax, [export_base] |
||
6810 | clevermous | 991 | mov ebx, eax |
992 | sub ebx, edx |
||
993 | cmp ebx, [export_size] |
||
6767 | clevermous | 994 | jb .export_is_forwarded |
995 | ret |
||
996 | .export_is_forwarded: |
||
997 | ; The export is forwarded to another module. |
||
998 | ; The address we have got points to the string " |
||
999 | ; 8. Get the target module name. It is everything before the first dot, |
||
1000 | ; minus DLL extension. |
||
1001 | ; 8a. Find the dot. |
||
1002 | mov ebx, eax |
||
1003 | @@: |
||
1004 | inc eax |
||
1005 | cmp byte [eax-1], '.' |
||
1006 | jz .dot_found |
||
1007 | cmp byte [eax-1], 0 |
||
1008 | jnz @b |
||
6810 | clevermous | 1009 | call .get_module_name |
1010 | ccall loader_say_error, msg_invalid_forwarder, eax, 0 |
||
1011 | xor eax, eax |
||
1012 | ret |
||
6767 | clevermous | 1013 | .dot_found: |
1014 | ; 8b. Allocate the memory. |
||
1015 | sub eax, ebx |
||
1016 | mov edi, eax |
||
1017 | add eax, 4 ; dll + terminating zero |
||
1018 | stdcall malloc, eax |
||
1019 | test eax, eax |
||
1020 | jz .return0 |
||
1021 | ; 8c. Copy module name. |
||
1022 | mov esi, ebx |
||
1023 | mov ecx, edi |
||
1024 | mov edi, eax |
||
1025 | rep movsb |
||
1026 | mov dword [edi], 'dll' |
||
1027 | mov ebx, esi ; save pointer to |
||
1028 | mov edi, eax ; module name |
||
1029 | ; 9. Load the target module. |
||
1030 | ; 9a. Get the pointer to MODULE struct for ourselves. |
||
1031 | mov esi, [module] |
||
1032 | test esi, esi |
||
1033 | jnz @f |
||
1034 | mutex_lock modules_mutex |
||
1035 | mov ecx, [export_base] |
||
1036 | call find_module_by_addr |
||
1037 | test esi, esi |
||
1038 | jz .load_forwarded_failed |
||
1039 | @@: |
||
1040 | ; 9b. Call the worker. |
||
1041 | call load_imported_module |
||
1042 | test eax, eax |
||
1043 | jz .load_forwarded_failed |
||
1044 | mov esi, eax |
||
1045 | ; 9c. We don't need module name anymore, free the memory allocated at 8b. |
||
1046 | stdcall free, edi |
||
1047 | ; 10. Resolve the forwarded export recursively. |
||
1048 | ; 10a. Prepare for importing. |
||
1049 | mov [forward_export_module], esi |
||
1050 | mov eax, [esi+MODULE.base] |
||
1051 | call prepare_import_from_module |
||
1052 | ; 10b. Check whether we are importing by ordinal or by name. |
||
1053 | ; Forwarded export by ordinal has ebx -> "# |
||
1054 | cmp byte [ebx], '#' |
||
1055 | jnz .no_ordinal |
||
1056 | lea edx, [ebx+1] |
||
1057 | xor ecx, ecx ; ordinal |
||
1058 | @@: |
||
1059 | movzx eax, byte [edx] |
||
1060 | sub eax, '0' |
||
1061 | cmp eax, 10 |
||
1062 | jae .no_ordinal |
||
1063 | lea ecx, [ecx*5] |
||
1064 | lea ecx, [ecx*2+eax] |
||
1065 | inc edx |
||
1066 | cmp byte [edx], 0 |
||
1067 | jnz @b |
||
1068 | ; 10c. We are importing by ordinal. Call the worker. |
||
1069 | call get_exported_function_by_ordinal |
||
1070 | jmp @f |
||
1071 | ret |
||
1072 | .no_ordinal: |
||
1073 | ; 10d. We are importing by name. Call the worker. |
||
1074 | mov ecx, ebx |
||
1075 | or edx, -1 |
||
1076 | call get_exported_function_by_name |
||
1077 | @@: |
||
1078 | cmp [module], 0 |
||
1079 | jnz @f |
||
1080 | push eax |
||
1081 | mutex_unlock modules_mutex |
||
1082 | pop eax |
||
1083 | @@: |
||
1084 | ret |
||
1085 | .load_forwarded_failed: |
||
1086 | cmp [module], 0 |
||
1087 | jnz @f |
||
1088 | mutex_unlock modules_mutex |
||
1089 | @@: |
||
1090 | stdcall free, edi |
||
1091 | xor eax, eax |
||
1092 | ret |
||
6810 | clevermous | 1093 | |
1094 | fpo_delta = fpo_delta + 4 |
||
1095 | .get_module_name: |
||
1096 | mov esi, [module] |
||
1097 | test esi, esi |
||
1098 | jnz @f |
||
1099 | mutex_lock modules_mutex |
||
1100 | mov ecx, [export_base] |
||
1101 | call find_module_by_addr |
||
1102 | mutex_unlock modules_mutex |
||
1103 | @@: |
||
1104 | mov eax, msg_unknown |
||
1105 | test esi, esi |
||
1106 | jz @f |
||
1107 | mov eax, [esi+MODULE.filename] |
||
1108 | @@: |
||
1109 | retn |
||
6767 | clevermous | 1110 | endp |
1111 | |||
1112 | ; Resolve symbol from a module by name. |
||
1113 | ; prepare_import_from_module should be called beforehand. |
||
1114 | ; in: ecx = ordinal |
||
1115 | ; out: eax = exported address or NULL |
||
1116 | ; if [module] is zero, modules_mutex should be unlocked |
||
1117 | ; if [module] is nonzero, modules_mutex should be locked |
||
1118 | proc get_exported_function_by_ordinal c uses ebx esi edi, export_base, export_ptr, export_size, module |
||
1119 | locals |
||
1120 | forward_export_base dd ? |
||
1121 | forward_export_ptr dd ? |
||
1122 | forward_export_size dd ? |
||
1123 | forward_export_module dd ? |
||
1124 | endl |
||
1125 | ; 1. Validate that export directory is present at all. |
||
1126 | mov edx, [export_ptr] |
||
1127 | test edx, edx |
||
1128 | jz .export_ordinal_not_found |
||
1129 | ; 2. Convert ordinal to index in AddressOfFunctions array. |
||
1130 | mov eax, ecx ; keep ecx for error message |
||
1131 | sub eax, [edx+IMAGE_EXPORT_DIRECTORY.Base] |
||
1132 | ; 3. Validate the index. |
||
1133 | cmp eax, [edx+IMAGE_EXPORT_DIRECTORY.NumberOfFunctions] |
||
1134 | jae .export_ordinal_not_found |
||
1135 | ; 4. Fetch the exported address from AddressOfFunctions array. |
||
1136 | ; On success, continue to check for forwarded exports in get_exported_function_by_name. |
||
1137 | mov ebx, [edx+IMAGE_EXPORT_DIRECTORY.AddressOfFunctions] |
||
1138 | add ebx, [export_base] |
||
1139 | mov eax, [ebx+eax*4] |
||
1140 | test eax, eax |
||
1141 | jnz get_exported_function_by_name.check_forwarded |
||
1142 | ; Generic error handler. |
||
1143 | .export_ordinal_not_found: |
||
1144 | sub esp, 16 |
||
1145 | fpo_delta = fpo_delta + 16 |
||
1146 | ; Convert ordinal to string. |
||
1147 | lea edi, [esp+15] |
||
1148 | mov byte [edi], 0 |
||
1149 | @@: |
||
1150 | mov eax, 0xCCCCCCCD |
||
1151 | mul ecx |
||
1152 | shr edx, 3 ; edx = quotient of ecx / 10 |
||
1153 | lea eax, [edx*5] |
||
1154 | add eax, eax |
||
1155 | sub ecx, eax ; ecx = remainder of ecx % 10 |
||
1156 | add cl, '0' |
||
1157 | dec edi |
||
1158 | mov byte [edi], cl |
||
1159 | mov ecx, edx |
||
1160 | test edx, edx |
||
1161 | jnz @b |
||
1162 | ; Get module name. |
||
1163 | mov esi, [module] |
||
1164 | test esi, esi |
||
1165 | jnz @f |
||
1166 | mutex_lock modules_mutex |
||
1167 | mov ecx, [export_base] |
||
1168 | call find_module_by_addr |
||
1169 | mutex_unlock modules_mutex |
||
1170 | @@: |
||
1171 | mov eax, msg_unknown |
||
1172 | test esi, esi |
||
1173 | jz @f |
||
1174 | mov eax, [esi+MODULE.filename] |
||
1175 | @@: |
||
1176 | ccall loader_say_error, msg_export_ordinal_not_found, edi, msg_export_not_found, eax, 0 |
||
1177 | add esp, 16 |
||
1178 | fpo_delta = fpo_delta - 16 |
||
1179 | xor eax, eax |
||
1180 | ret |
||
1181 | endp>>=>> |