1,3 → 1,41 |
; Processings of PE format. |
; Works in conjunction with modules.inc for non-PE-specific code. |
|
; PE-specific part of init_module_struct. |
; Fills fields of MODULE struct from PE image. |
macro init_module_struct_pe_specific |
{ |
; We need a module timestamp for bound imports. |
; In a full PE, there are two timestamps, one in the header |
; and one in the export table; existing tools use the first one. |
; A stripped PE header has no timestamp, so read the export table; |
; the stripper should write the correct value there. |
cmp byte [esi], 'M' |
jz .parse_mz |
cmp [esi+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_EXPORT |
jbe @f |
mov edx, [esi+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY+IMAGE_DATA_DIRECTORY.VirtualAddress] |
@@: |
mov [eax+MODULE.timestamp], edx |
mov edx, esi |
sub edx, [esi+STRIPPED_PE_HEADER.ImageBase] |
mov [eax+MODULE.basedelta], edx |
mov edx, [esi+STRIPPED_PE_HEADER.SizeOfImage] |
mov [eax+MODULE.size], edx |
ret |
.parse_mz: |
mov ecx, [esi+3Ch] |
add ecx, esi |
mov edx, [ecx+IMAGE_NT_HEADERS.FileHeader.TimeDateStamp] |
mov [eax+MODULE.timestamp], edx |
mov edx, esi |
sub edx, [ecx+IMAGE_NT_HEADERS.OptionalHeader.ImageBase] |
mov [eax+MODULE.basedelta], edx |
mov edx, [ecx+IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage] |
mov [eax+MODULE.size], edx |
ret |
} |
|
; Check whether PE module has been loaded at preferred address. |
; If not, relocate the module. |
; |
4,7 → 42,7 |
; in: esi = PE base address |
; in: [esp+4] = module name for debug print |
; out: CF=1 - fail |
proc fixup_pe_relocations uses edi ebp |
proc fixup_pe_relocations c uses ebp, modulename |
; 1. Fetch some data from PE header or stripped PE header. |
; We need: |
; * ImageBase - preferred address, compare with esi = actual load address; |
19,7 → 57,7 |
; In the first case, IMAGE_FILE_RELOCS_STRIPPED is set, and this is an error. |
; In the second case, IMAGE_FILE_RELOCS_STRIPPED is not set; do nothing. |
mov ebp, esi |
cmp word [esi], 'MZ' |
cmp byte [esi], 'M' |
jz .parse_mz |
sub ebp, [esi+STRIPPED_PE_HEADER.ImageBase] |
jnz @f |
33,6 → 71,7 |
.norelocs: |
test dl, IMAGE_FILE_RELOCS_STRIPPED |
jz .nothing |
ccall loader_say_error, msg_noreloc1, [modulename], msg_noreloc2, 0 |
stc |
ret |
.parse_mz: |
40,25 → 79,29 |
add eax, esi |
sub ebp, [eax+IMAGE_NT_HEADERS.OptionalHeader.ImageBase] |
jz .nothing |
mov dl, byte [esi+IMAGE_NT_HEADERS.FileHeader.Characteristics] |
mov dl, byte [eax+IMAGE_NT_HEADERS.FileHeader.Characteristics] |
cmp [eax+IMAGE_NT_HEADERS.OptionalHeader.NumberOfDirectories], IMAGE_DIRECTORY_ENTRY_BASERELOC |
jbe .norelocs |
add eax, IMAGE_NT_HEADERS.OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_BASERELOC*sizeof.IMAGE_DATA_DIRECTORY |
.common: |
cmp [eax+IMAGE_DATA_DIRECTORY.isize], 0 |
jz .norelocs |
mov edi, [eax+IMAGE_DATA_DIRECTORY.VirtualAddress] |
push -1 |
push -1 |
push [eax+IMAGE_DATA_DIRECTORY.isize] |
virtual at esp |
.sizeleft dd ? |
.next_page_original_access dd ? |
.next_page_addr dd ? |
end virtual |
add edi, esi |
cmp [.sizeleft], 0 |
jz .norelocs |
; 2. We need to relocate and we have the relocation table. |
; esi = PE base address |
; edi = pointer to current data of relocation table |
; 2a. Relocation table is organized into blocks describing every page. |
; End of table is defined from table size fetched from the header. |
; Loop 2b-2g over all blocks until no more data is left. |
; Loop 2b-2i over all blocks until no more data is left. |
.pageloop: |
; 2b. Load the header of the current block: address and size. |
; Advance total size. |
70,13 → 113,20 |
add edi, sizeof.IMAGE_BASE_RELOCATION |
sub ecx, sizeof.IMAGE_BASE_RELOCATION |
jbe .pagedone |
; 2c. We are going to modify data, so mprotect the current page to be writable. |
push esi |
fpo_delta = fpo_delta + 4 |
; 2c. Check whether we have mprotect-ed the current page at the previous step. |
; If so, go to 2e. |
cmp [.next_page_addr+fpo_delta], edx |
jz .mprotected_earlier |
; 2d. We are going to modify data, so mprotect the current page to be writable. |
; Save the old protection, we will restore it after the block is processed. |
; Ignore any error. |
; Go to 2f after. |
PROT_READ = 1 |
PROT_WRITE = 2 |
PROT_EXEC = 4 |
push esi ecx |
push ecx |
mov eax, 68 |
mov ebx, 30 |
mov ecx, PROT_READ+PROT_WRITE |
84,12 → 134,20 |
mov esi, 0x1000 |
call FS_SYSCALL_PTR |
pop ecx |
jmp .mprotected |
; 2e. We have already mprotect-ed the current page, |
; move corresponding variables. |
.mprotected_earlier: |
mov [.next_page_addr+fpo_delta], -1 |
mov eax, [.next_page_original_access+fpo_delta] |
.mprotected: |
push eax |
; 2d. Block data is an array of word values. Repeat 2e for every of those. |
fpo_delta = fpo_delta + 4 |
; 2g. Block data is an array of word values. Repeat 2h for every of those. |
.relocloop: |
sub ecx, 2 |
jb .relocdone |
; 2e. Every value consists of a 4-bit type and 12-bit offset in the page. |
; 2h. Every value consists of a 4-bit type and 12-bit offset in the page. |
; x86 uses two types: 0 = no data (used for padding), 3 = 32-bit relative. |
movzx eax, word [edi] |
add edi, 2 |
99,11 → 157,31 |
jz .relocloop |
cmp al, IMAGE_REL_BASED_HIGHLOW |
jnz .badreloc |
; If the target dword intersects page boundary, |
; we need to mprotect the next page too. |
cmp ebx, 0xFFC |
jbe .no_mprotect_next |
push ebx ecx edx |
fpo_delta = fpo_delta + 12 |
lea eax, [.next_page_original_access+fpo_delta] |
call .restore_old_access |
mov eax, 68 |
mov ebx, 30 |
mov ecx, PROT_READ+PROT_WRITE |
mov esi, 0x1000 |
add edx, esi |
call FS_SYSCALL_PTR |
mov [.next_page_original_access+fpo_delta], eax |
mov [.next_page_addr+fpo_delta], edx |
pop edx ecx ebx |
fpo_delta = fpo_delta - 12 |
.no_mprotect_next: |
add [edx+ebx], ebp |
jmp .relocloop |
.relocdone: |
; 2f. Restore memory protection changed in 2c. |
; 2i. Restore memory protection changed in 2d. |
pop ecx |
fpo_delta = fpo_delta - 4 |
cmp ecx, -1 |
jz @f |
mov eax, 68 |
112,17 → 190,21 |
call FS_SYSCALL_PTR |
@@: |
pop esi |
fpo_delta = fpo_delta - 4 |
.pagedone: |
cmp [.sizeleft], 0 |
cmp [.sizeleft+fpo_delta], 0 |
jnz .pageloop |
pop eax ; pop .sizeleft |
lea eax, [.next_page_original_access+fpo_delta] |
call .restore_old_access |
add esp, 12 |
; 3. For performance reasons, relocation should be avoided |
; by choosing an appropriate preferred address. |
; If we have actually relocated something, yell to the debug board, |
; so the programmer can notice that. |
; It's a warning, not an error, so don't call loader_say_error. |
mov ecx, msg_relocated1 |
call sys_msg_board_str |
mov ecx, [esp+4] |
mov ecx, [modulename] |
call sys_msg_board_str |
mov ecx, msg_relocated2 |
call sys_msg_board_str |
129,13 → 211,921 |
clc |
ret |
.badreloc: |
pop eax |
mov ecx, msg_bad_relocation1 |
call sys_msg_board_str |
mov ecx, [esp+4] |
call sys_msg_board_str |
mov ecx, msg_newline |
call sys_msg_board_str |
pop ecx |
pop esi |
add esp, 12 |
ccall loader_say_error, msg_bad_relocation, [modulename], 0 |
stc |
ret |
|
.restore_old_access: |
cmp dword [eax+4], -1 |
jz @f |
mov ecx, [eax] |
mov edx, [eax+4] |
mov eax, 68 |
mov ebx, 30 |
mov esi, 0x1000 |
call FS_SYSCALL_PTR |
@@: |
retn |
endp |
|
; Resolves static dependencies in the given PE module. |
; Recursively loads and initializes all dependencies. |
; in: esi -> MODULE struct |
; out: eax=0 - success, eax=-1 - error |
; modules_mutex should be locked |
proc resolve_pe_imports |
locals |
export_base dd ? |
export_ptr dd ? |
export_size dd ? |
import_module dd ? |
import_dir dd ? |
import_descriptor dd ? |
bound_import_dir dd ? |
bound_import_cur_module dd ? |
relocated_bound_modules_count dd ? |
relocated_bound_modules_ptr dd ? |
cur_page dd -0x1000 ; the page at 0xFFFFF000 is never allocated |
cur_page_old_access dd ? |
next_page dd -1 |
next_page_old_access dd ? |
endl |
|
; General case of resolving imports against one module that is already loaded: |
; binding either does not exist or has mismatched timestamp, |
; so we need to walk through all imported symbols and resolve each one. |
; in: ebp -> IMAGE_IMPORT_DESCRIPTOR |
macro resolve_import_from_module fail_action |
{ |
local .label1, .loop, .done |
; common preparation that doesn't need to be repeated per each symbol |
mov eax, [import_module] |
mov eax, [eax+MODULE.base] |
call prepare_import_from_module |
; There are two arrays of dwords pointed to by FirstThunk and OriginalFirstThunk. |
; Target array is FirstThunk: addresses of imported symbols should be written |
; there, that is where the program expects to find them. |
; Source array can be either FirstThunk or OriginalFirstThunk. |
; Normally, FirstThunk and OriginalFirstThunk in a just-compiled binary |
; point to two identical copies of the same array. |
; Binding of the binary rewrites FirstThunk array with actual addresses, |
; but keeps OriginalFirstThunk as is. |
; If OriginalFirstThunk and FirstThunk are both present, use OriginalFirstThunk |
; as source array. |
; However, a compiler is allowed to generate a binary without OriginalFirstThunk; |
; it is impossible to bind such a binary, but it is still valid. |
; If OriginalFirstThunk is absent, use FirstThunk as source array. |
mov ebx, [ebp+IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk] |
mov ebp, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
test ebx, ebx |
jnz .label1 |
mov ebx, ebp |
.label1: |
; FirstThunk and OriginalFirstThunk are RVAs. |
add ebx, [esi+MODULE.base] |
add ebp, [esi+MODULE.base] |
; Source array is terminated with zero dword. |
.loop: |
cmp dword [ebx], 0 |
jz .done |
mov ecx, [ebx] |
get_address_for_thunk ; should preserve esi,edi,ebp |
test eax, eax |
jz fail_action |
mov edi, eax |
mov edx, ebp |
call .ensure_writable ; should preserve edx,ebx,esi,ebp |
mov [edx], edi |
add ebx, 4 |
add ebp, 4 |
jmp .loop |
.done: |
} |
|
; Resolve one imported symbol. |
; in: ecx = ordinal or RVA of thunk |
; out: eax = address of exported function |
macro get_address_for_thunk |
{ |
local .ordinal, .common |
; Ordinal imports have bit 31 set, name imports have bit 31 clear. |
btr ecx, 31 |
jc .ordinal |
; Thunk for name import is RVA of IMAGE_IMPORT_BY_NAME structure. |
add ecx, [esi+MODULE.base] |
movzx edx, [ecx+IMAGE_IMPORT_BY_NAME.Hint] |
add ecx, IMAGE_IMPORT_BY_NAME.Name |
call get_exported_function_by_name |
jmp .common |
.ordinal: |
; Thunk for ordinal import is just an ordinal, |
; bit 31 has been cleared by btr instruction. |
call get_exported_function_by_ordinal |
.common: |
} |
|
; We have four main variants: |
; normal unbound import, old-style bound import, new-style bound import, |
; no import. |
; * Normal unbound import: |
; we have an array of import descriptors, one per imported module, |
; pointed to by import directory. |
; We should loop over all descriptors and apply resolve_import_from_module |
; for each one. |
; * Old-style bound import: |
; we have the same array of import descriptors, but timestamp field is set up. |
; We should do the same loop, but we can do a lightweight processing |
; of modules with correct timestamp. In the best case, "lightweight processing" |
; means just skipping them, but corrections arise for relocated modules |
; and forwarded exports. |
; * New-style bound import: |
; we have two parallel arrays of import descriptors and bound descriptors, |
; pointed to by two directories. Timestamp field has a special value -1 |
; in import descriptors, real timestamps are in bound descriptors. |
; There can be different strategies; we loop over bound descriptors |
; and scan for corresponding import descriptors only if needed, |
; this accelerates the fast path where all timestamps are correct and |
; dependencies are not relocated. |
; * No import: not really different from normal import with no descriptors. |
; There are two large parts in this function: |
; step 2 handles unbound and old-style bound import, where we loop over import descriptors; |
; step 3 handles new-style bound import, where we loop over bound descriptors. |
; 1. Fetch addresses of two directories. We are not interested in their sizes. |
; ebp = import RVA |
; ebx = bound import RVA |
xor ebx, ebx |
xor ebp, ebp |
; PE and stripped PE have different places for directories. |
mov eax, [esi+MODULE.base] |
cmp byte [eax], 'M' |
jz .parse_mz |
cmp [eax+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_IMPORT |
jbe .common |
mov ebp, [eax+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_IMPORT*sizeof.IMAGE_DATA_DIRECTORY] |
cmp [eax+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_BOUND_IMPORT |
jbe .common |
mov ebx, [eax+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_BOUND_IMPORT*sizeof.IMAGE_DATA_DIRECTORY] |
jmp .common |
.parse_mz: |
add eax, [eax+3Ch] |
cmp [eax+IMAGE_NT_HEADERS.OptionalHeader.NumberOfDirectories], IMAGE_DIRECTORY_ENTRY_IMPORT |
jbe .common |
mov ebp, [eax+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_IMPORT*sizeof.IMAGE_DATA_DIRECTORY] |
cmp [eax+IMAGE_NT_HEADERS.OptionalHeader.NumberOfDirectories], IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT |
jbe .common |
mov ebx, [eax+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT*sizeof.IMAGE_DATA_DIRECTORY] |
.common: |
mov [import_dir], ebp |
; If bound import is present, go to 3. |
; If both directories are absent, no import - nothing to do. |
; Otherwise, advance to 2. |
test ebx, ebx |
jnz .bound_import |
test ebp, ebp |
jz .done |
; 2. Unbound import or old-style bound import. |
; Repeat 2a-2h for all descriptors in the directory. |
add ebp, [esi+MODULE.base] ; directories contain RVA |
.normal_import_loop: |
; 2a. Check whether this descriptor is an end mark with zero fields. |
; Look at Name field. |
mov edi, [ebp+IMAGE_IMPORT_DESCRIPTOR.Name] |
test edi, edi |
jz .done |
; 2b. Load the target module. |
add edi, [esi+MODULE.base] ; Name field is RVA |
call load_imported_module ; should preserve esi,ebp |
test eax, eax |
jz .failed |
mov [import_module], eax |
; 2c. Check whether the descriptor has a non-stale old-style binding. |
; Zero timestamp means "not bound". |
; Mismatched timestamp means "stale binding". |
; In both cases, go to 2g. |
mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.TimeDateStamp] |
test edx, edx |
jz .resolve_normal_import |
cmp edx, [eax+MODULE.timestamp] |
jnz .resolve_normal_import |
; 2d. The descriptor has a non-stale old-style binding. |
; There are two cases when we still need to do something: |
; * if the target module has been relocated, we need to add |
; relocation delta to all addresses; |
; * if some exports are forwarded, old-style binding cannot bind them: |
; there is only one timestamp field, we can't verify timestamps |
; of forward targets. |
; Thunks for forwarded exports contain index of next forwarded export |
; instead of target address, making a single-linked list terminated by -1. |
; ForwarderChain is the head of the list. |
; If both problems are present, we resort to 2g as if binding is stale, |
; it shouldn't be encountered normally anyway: relocations should be avoided, |
; and forwarded exports should be new-style bound. |
; If the target module is not relocated, go to 2f. |
; If the target module is relocated and there are no forwarded exports, |
; advance to 2e. |
cmp [eax+MODULE.basedelta], 0 |
jz .normal_import_check_forwarders |
cmp [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain], -1 |
jnz .resolve_normal_import |
; 2e. Binding is correct, but we need to add MODULE.basedelta |
; to all imported addresses in FirstThunk array. |
; For consistency with generic-case resolve_import_from_module, |
; check for end of thunks by looking at OriginalFirstThunk array. |
; After that, go to 2h. |
mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
add edx, [esi+MODULE.base] |
mov ebx, [ebp+IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk] |
add ebx, [esi+MODULE.base] |
mov edi, [eax+MODULE.basedelta] |
.normal_import_add_delta: |
cmp dword [ebx], 0 |
jz .normal_import_next |
call .ensure_writable ; should preserve esi,edi,ebp,ebx,edx |
add dword [edx], edi |
add edx, 4 |
add ebx, 4 |
jmp .normal_import_add_delta |
.normal_import_check_forwarders: |
; 2f. The target module is not relocated. |
; Exports that are not forwarded are correct. |
; Go through ForwarderChain list and resolve all exports from it. |
; After that, go to 2h. |
mov edi, [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain] |
cmp edi, -1 |
jz .normal_import_next ; don't prepare_import_from_module for empty list |
mov eax, [import_module] |
mov eax, [eax+MODULE.base] |
call prepare_import_from_module |
.normal_import_forward_chain: |
mov ebx, [ebp+IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk] |
add ebx, [esi+MODULE.base] |
mov ecx, [ebx+edi*4] |
get_address_for_thunk ; should preserve esi,edi,ebp |
test eax, eax |
jz .failed |
mov ebx, eax |
mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
add edx, [esi+MODULE.base] |
lea edx, [edx+edi*4] |
call .ensure_writable ; should preserve edx,ebx,esi,ebp |
mov edi, [edx] ; next forwarded export |
mov [edx], ebx ; store the address |
cmp edi, -1 |
jnz .normal_import_forward_chain |
jmp .normal_import_next |
.resolve_normal_import: |
; 2g. Run generic-case resolver. |
mov [import_descriptor], ebp |
resolve_import_from_module .failed ; should preserve esi |
mov ebp, [import_descriptor] |
.normal_import_next: |
; 2h. Advance to next descriptor and continue the loop. |
add ebp, sizeof.IMAGE_IMPORT_DESCRIPTOR |
jmp .normal_import_loop |
.bound_import: |
; 3. New-style bound import. |
; Repeat 3a-3o for all descriptors in bound import directory. |
mov [bound_import_dir], ebx |
add ebx, [esi+MODULE.base] |
.bound_import_loop: |
; 3a. Check whether this descriptor is an end mark with zero fields. |
movzx edi, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName] |
mov [bound_import_cur_module], edi |
test edi, edi |
jz .done |
; Bound import descriptors come in groups. |
; The first descriptor in each group corresponds to the main imported module. |
; If some exports from the module are forwarded, additional descriptors |
; are created for modules where those exports are forwarded to. |
; Number of additional descriptors is given by one field in the first descriptor. |
; 3b. Prepare for loop at 3c-3f with loading targets of all exports. |
; This includes the target module and all modules in chains of forwarded exports. |
movzx ebp, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs] |
mov [relocated_bound_modules_count], 0 |
mov [relocated_bound_modules_ptr], 0 |
mov [import_module], 0 |
.bound_import_forwarder_loop: |
; 3c. Load a referenced module. |
; Names in bound import descriptors are relative to bound import directory, |
; not RVAs. |
add edi, [bound_import_dir] |
call load_imported_module ; should preserve ebx,esi,ebp |
test eax, eax |
jz .bound_import_failed |
; The target module is first in the list. |
cmp [import_module], 0 |
jnz @f |
mov [import_module], eax |
@@: |
; 3d. Check whether timestamp in the descriptor matches module timestamp. |
; If not, go to 3h which after some preparations will resort to generic-case |
; resolve_import_from_module; in this case, we stop processing the group, |
; resolve_import_from_module will take care about additional modules anyway. |
mov edx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.TimeDateStamp] |
test edx, edx |
jz .bound_import_wrong_timestamp |
cmp edx, [eax+MODULE.timestamp] |
jnz .bound_import_wrong_timestamp |
; 3e. Collect all referenced modules that have been relocated. |
cmp [eax+MODULE.basedelta], 0 |
jz .bound_import_forwarder_next |
mov edi, eax |
; We don't want to reallocate too often, since reallocation |
; may involve copying our data to a new place. |
; We always reserve space that is a power of two; in this way, |
; the wasted space is never greater than the used space, |
; and total time of copying the data is O(number of modules). |
mov eax, [relocated_bound_modules_ptr] |
mov edx, [relocated_bound_modules_count] |
; X is a power of two or zero if and only if (X and (X - 1)) is zero |
lea ecx, [edx-1] |
test ecx, edx |
jnz .bound_import_norealloc |
; if the current size is zero, allocate 1 item, |
; otherwise double number of items. |
; Item size is 4 bytes. |
lea edx, [edx*8] |
test edx, edx |
jnz @f |
mov edx, 4 |
@@: |
stdcall realloc, [relocated_bound_modules_ptr], edx |
test eax, eax |
jz .bound_import_failed |
mov [relocated_bound_modules_ptr], eax |
.bound_import_norealloc: |
mov edx, [relocated_bound_modules_count] |
inc [relocated_bound_modules_count] |
mov [eax+edx*4], edi |
.bound_import_forwarder_next: |
; 3f. Advance to the next descriptor in the group. |
add ebx, sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR |
movzx edi, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName] |
dec ebp |
jns .bound_import_forwarder_loop |
; 3g. All timestamps are correct. |
; If all targets are not relocated, then we have nothing to do |
; with exports from the current module, so continue loop at 3a; |
; ebx already points to the next descriptor. |
; Otherwise, go to 3i. |
cmp [relocated_bound_modules_count], 0 |
jz .bound_import_loop |
jmp .bound_import_fix |
.bound_import_wrong_timestamp: |
; 3h. We have aborted the loop over the group; |
; advance ebx so that it points to the first descriptor of the next group, |
; make a mark so that 3l will know that we need to reimport everything. |
; We don't need [relocated_bound_modules_count] in this case anymore, |
; use zero value as a mark. |
lea ebx, [ebx+(ebp+1)*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR] |
mov [relocated_bound_modules_count], 0 |
.bound_import_fix: |
; 3i. We need to do something with exported addresses. |
; Find corresponding import descriptors; there can be more than one. |
; Repeat 3j-3n for all import descriptors. |
mov ebp, [import_dir] |
add ebp, [esi+MODULE.base] |
.look_related_descriptors: |
; 3j. Check whether we have reached end of import table. |
; If so, go to 3o. |
mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.Name] |
test edx, edx |
jz .bound_import_next |
; 3k. Check whether the current import descriptor matches the current |
; bound import descriptor. Check Name fields. |
; If so, advance to 3l. |
; Otherwise, advance to the next import descriptor and return to 3j. |
add edx, [esi+MODULE.base] |
mov edi, [bound_import_cur_module] |
@@: |
mov al, [edx] |
cmp [edi], al |
jnz .next_related_descriptor |
test al, al |
jz .found_related_descriptor |
inc edx |
inc edi |
jmp @b |
.next_related_descriptor_restore: |
mov ebp, [import_descriptor] |
.next_related_descriptor: |
add ebp, sizeof.IMAGE_IMPORT_DESCRIPTOR |
jmp .look_related_descriptors |
.found_related_descriptor: |
; 3l. Check what we should do: |
; advance to 3m, if we need to reimport everything, |
; go to 3n, if we just need to relocate something. |
mov [import_descriptor], ebp |
cmp [relocated_bound_modules_count], 0 |
jnz .bound_import_add_delta |
; 3m. Apply resolve_import_from_module and return to 3j. |
resolve_import_from_module .bound_import_failed ; should preserve ebx,esi |
jmp .next_related_descriptor_restore |
.bound_import_add_delta: |
; 3n. Loop over all imported symbols. |
; For every imported symbol, check whether it fits within one of relocated |
; modules, and if so, apply relocation to it. |
; For consistency with generic-case resolve_import_from_module, |
; determine end of thunks from OriginalFirstThunk array. |
mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk] |
add edx, [esi+MODULE.base] |
mov ebx, [ebp+IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk] |
add ebx, [esi+MODULE.base] |
.bound_import_add_delta_loop: |
cmp dword [ebx], 0 |
jz .next_related_descriptor_restore |
mov ecx, [relocated_bound_modules_ptr] |
mov ebp, [relocated_bound_modules_count] |
push esi |
.find_delta_module: |
mov esi, [ecx] |
mov eax, [edx] |
sub eax, [esi+MODULE.base] |
add eax, [esi+MODULE.basedelta] |
cmp eax, [esi+MODULE.size] |
jb .found_delta_module |
add ecx, 4 |
dec ebp |
jnz .find_delta_module |
pop esi |
.bound_import_add_delta_next: |
add ebx, 4 |
add edx, 4 |
jmp .bound_import_add_delta_loop |
.found_delta_module: |
mov ebp, [esi+MODULE.basedelta] |
pop esi |
call .ensure_writable ; should preserve esi,ebp,ebx,edx |
add [edx], ebp |
jmp .bound_import_add_delta_next |
.bound_import_next: |
; 3o. Free the data we might have allocated and return to 3a. |
cmp [relocated_bound_modules_ptr], 0 |
jz .bound_import_loop |
stdcall free, [relocated_bound_modules_ptr] |
jmp .bound_import_loop |
.done: |
call .restore_protection |
xor eax, eax |
ret |
.bound_import_failed: |
cmp [relocated_bound_modules_ptr], 0 |
jz .failed |
stdcall free, [relocated_bound_modules_ptr] |
.failed: |
call .restore_protection |
xor eax, eax |
dec eax |
ret |
|
; Local helper functions. |
fpo_delta = fpo_delta + 4 |
; Import table may reside in read-only pages. |
; We should mprotect any page where we are going to write to. |
; Things get interesting when one thunk spans two pages. |
; in: edx = address of dword to make writable |
.ensure_writable: |
; 1. Fast path: if we have already mprotect-ed one page and |
; the requested dword is in the same page, do nothing. |
mov eax, edx |
sub eax, [cur_page] |
cmp eax, 0x1000 - 4 |
ja .cur_page_not_sufficient |
.ensure_writable.nothing: |
retn |
.cur_page_not_sufficient: |
; 2. If the requested dword begins in the current page |
; and ends in the next page, mprotect the next page and return. |
push ebx esi edx |
fpo_delta = fpo_delta + 12 |
cmp eax, 0x1000 |
jae .wrong_cur_page |
cmp [next_page], -1 |
jnz @f |
mov eax, 68 |
mov ebx, 30 |
mov ecx, PROT_READ+PROT_WRITE |
mov edx, [cur_page] |
mov esi, 0x1000 |
add edx, esi |
mov [next_page], edx |
call FS_SYSCALL_PTR |
mov [next_page_old_access], eax |
@@: |
pop edx esi ebx |
retn |
.wrong_cur_page: |
; The requested dword does not intersect with the current page. |
; 3. Restore the protection of the current page, |
; it is unlikely to be used again. |
cmp [cur_page], -0x1000 |
jz @f |
mov eax, 68 |
mov ebx, 30 |
mov ecx, [cur_page_old_access] |
mov edx, [cur_page] |
mov esi, 0x1000 |
call FS_SYSCALL_PTR |
@@: |
; 4. If the next page has been mprotect-ed too, |
; switch to it as the current page and restart the function. |
cmp [next_page], -1 |
jz @f |
mov eax, [next_page] |
mov [cur_page], eax |
mov eax, [next_page_old_access] |
mov [cur_page_old_access], eax |
mov [next_page], -1 |
pop edx esi ebx |
jmp .ensure_writable |
@@: |
; 5. This is the entirely new page to mprotect. |
mov edx, [esp] |
and edx, not 0xFFF |
mov eax, 68 |
mov ebx, 30 |
mov ecx, PROT_READ+PROT_WRITE |
mov [cur_page], edx |
mov esi, 0x1000 |
call FS_SYSCALL_PTR |
mov [cur_page_old_access], eax |
pop edx esi ebx |
fpo_delta = fpo_delta - 12 |
retn |
|
; Called at end of processing, |
; restores protection of pages that we have mprotect-ed for write. |
.restore_protection: |
push esi |
fpo_delta = fpo_delta + 4 |
cmp [next_page], -1 |
jz @f |
mov eax, 68 |
mov ebx, 30 |
mov ecx, [next_page_old_access] |
mov edx, [next_page] |
mov esi, 0x1000 |
call FS_SYSCALL_PTR |
@@: |
cmp [cur_page], -0x1000 |
jz @f |
mov eax, 68 |
mov ebx, 30 |
mov ecx, [cur_page_old_access] |
mov edx, [cur_page] |
mov esi, 0x1000 |
call FS_SYSCALL_PTR |
@@: |
pop esi |
fpo_delta = fpo_delta - 4 |
retn |
endp |
|
; Part of resolving symbol from a module that is the same for all symbols. |
; resolve_pe_imports calls it only once per module. |
; Fetches export directory from the module. |
; Non-standard calling convention: saves results to first 2 dwords on the stack. |
; in: eax = module base |
proc prepare_import_from_module c, export_base, export_ptr, export_size |
; The implementation is straightforward. |
mov [export_base], eax |
cmp byte [eax], 'M' |
jz .parse_mz |
cmp [eax+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_EXPORT |
jbe .noexport |
mov edx, [eax+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY+IMAGE_DATA_DIRECTORY.VirtualAddress] |
add edx, eax |
mov [export_ptr], edx |
mov edx, [eax+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY+IMAGE_DATA_DIRECTORY.isize] |
mov [export_size], edx |
ret |
.parse_mz: |
mov ecx, [eax+3Ch] |
add ecx, eax |
cmp [ecx+IMAGE_NT_HEADERS.OptionalHeader.NumberOfDirectories], IMAGE_DIRECTORY_ENTRY_EXPORT |
jbe .noexport |
mov edx, [ecx+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory.VirtualAddress+IMAGE_DIRECTORY_ENTRY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY] |
add edx, eax |
mov [export_ptr], edx |
mov edx, [ecx+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory.isize+IMAGE_DIRECTORY_ENTRY_EXPORT*sizeof.IMAGE_DATA_DIRECTORY] |
mov [export_size], edx |
ret |
.noexport: |
mov [export_ptr], 0 |
mov [export_size], 0 |
ret |
endp |
|
; PE format supports export by name and by ordinal. |
; Any exported symbol always have an ordinal. |
; It may have a name, it may have no name. |
; A symbol can even have multiple names, usually this happens |
; when several functions with the same body like 'ret' are merged. |
; |
; Addresses of all exported symbols are contained in one array AddressOfFunctions. |
; Ordinal of a symbol is an index in this array + Base. |
; Base is defined in export directory, usually it equals 1. |
; |
; Export by name is more complicated. There are two parallel arrays |
; AddressOfNames and AddressOfNameOrdinals with the same length. |
; This length can be less or greater than length of AddressOfFunctions. |
; AddressOfNames is a sorted array with all exported names. |
; AddressOfNameOrdinals, contrary to the title, gives index in AddressOfFunctions. |
; Looking up a name means |
; * scanning AddressOfNames array to find the index of the corresponding name |
; * looking in AddressOfNameOrdinals at the index found above to get another index; |
; index in AddressOfNames/AddressOfNameOrdinals has no other meaning |
; * finally, looking in AddressOfFunctions with that second index. |
|
; Resolve symbol from a module by name. |
; prepare_import_from_module should be called beforehand. |
; in: ecx -> name, edx = hint for lookup in name table |
; out: eax = exported address or NULL |
; if [module] is zero, modules_mutex should be unlocked |
; if [module] is nonzero, modules_mutex should be locked |
proc get_exported_function_by_name c uses ebx esi edi, export_base, export_ptr, export_size, module |
locals |
forward_export_base dd ? |
forward_export_ptr dd ? |
forward_export_size dd ? |
forward_export_module dd ? |
endl |
; 1. Find length of the name, including terminating zero. |
mov esi, ecx |
@@: |
inc ecx |
cmp byte [ecx-1], 0 |
jnz @b |
sub ecx, esi |
; 2. Validate that export directory is present at all. |
mov eax, [export_ptr] |
test eax, eax |
jz .export_name_not_found |
; 3. Check whether the hint is correct. |
; The hint is a zero-based index in name table. |
; Theoretically, zero is a valid hint. |
; Unfortunately, in practice everyone uses zero if the hint is unknown, |
; which is a quite typical situation, so treating zero as a valid hint |
; would waste processor cycles much more often than save. |
; So only check the hint if it is between 1 and NumberOfNames-1 inclusive. |
; 3a. Validate the hint. |
mov ebx, [eax+IMAGE_EXPORT_DIRECTORY.AddressOfNames] |
add ebx, [export_base] |
cmp edx, [eax+IMAGE_EXPORT_DIRECTORY.NumberOfNames] |
jae .ignore_hint |
test edx, edx |
jz .ignore_hint |
; 3b. Check the hinted name. |
; If it matches, go to 5. If not, we're out of luck, use normal lookup. |
mov edi, [ebx+edx*4] |
add edi, [export_base] |
push ecx esi |
repz cmpsb |
pop esi ecx |
jz .found |
.ignore_hint: |
; 4. Binary search over name table. |
; Export names are sorted with respect to repz cmpsb. |
; edi <= (the target index) < edx |
xor edi, edi |
mov edx, [eax+IMAGE_EXPORT_DIRECTORY.NumberOfNames] |
.export_name_search.loop: |
; if there are no indexes between edi and edx, name is invalid |
cmp edi, edx |
jae .export_name_not_found |
; try the index in the middle of current range |
lea eax, [edi+edx] |
shr eax, 1 |
; compare |
push ecx esi edi |
fpo_delta = fpo_delta + 12 |
mov edi, [ebx+eax*4] |
add edi, [export_base] |
repz cmpsb |
pop edi esi ecx |
fpo_delta = fpo_delta - 12 |
; exact match -> found, go to 5 |
; string at esi = target, string at edi = current attempt |
; (string at esi) < (string at edi) -> current index is too high, update upper range |
; (string at esi) > (string at edi) -> current index is too low, update lower range |
jz .found |
jb @f |
lea edi, [eax+1] |
jmp .export_name_search.loop |
@@: |
mov edx, eax |
jmp .export_name_search.loop |
; Generic error handler. |
.export_name_not_found: |
mov ebx, esi |
mov esi, [module] |
test esi, esi |
jnz @f |
mutex_lock modules_mutex |
mov ecx, [export_base] |
call find_module_by_addr |
mutex_unlock modules_mutex |
@@: |
mov eax, msg_unknown |
test esi, esi |
jz @f |
mov eax, [esi+MODULE.filename] |
@@: |
ccall loader_say_error, msg_export_name_not_found, ebx, msg_export_not_found, eax, 0 |
.return0: |
xor eax, eax |
ret |
.found: |
; 5. We have found an index in AddressOfNames/AddressOfNameOrdinals arrays, |
; convert it to index in AddressOfFunctions array. |
mov edx, [export_ptr] |
mov ebx, [edx+IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals] |
add ebx, [export_base] |
movzx eax, word [ebx+eax*2] |
; 6. Fetch the exported address from AddressOfFunctions array. |
cmp eax, [edx+IMAGE_EXPORT_DIRECTORY.NumberOfFunctions] |
jae .export_name_not_found |
mov ebx, [edx+IMAGE_EXPORT_DIRECTORY.AddressOfFunctions] |
add ebx, [export_base] |
mov eax, [ebx+eax*4] |
test eax, eax |
jz .export_name_not_found |
.check_forwarded: |
; This part of code is also used by get_exported_function_by_ordinal. |
; 7. Check whether the address is inside the export directory. |
; If not, we are done. |
add eax, [export_base] |
mov esi, eax |
sub esi, edx |
cmp esi, [export_size] |
jb .export_is_forwarded |
ret |
.export_is_forwarded: |
; The export is forwarded to another module. |
; The address we have got points to the string "<module>.<function>" |
; 8. Get the target module name. It is everything before the first dot, |
; minus DLL extension. |
; 8a. Find the dot. |
mov ebx, eax |
@@: |
inc eax |
cmp byte [eax-1], '.' |
jz .dot_found |
cmp byte [eax-1], 0 |
jnz @b |
jmp .export_name_not_found |
.dot_found: |
; 8b. Allocate the memory. |
sub eax, ebx |
mov edi, eax |
add eax, 4 ; dll + terminating zero |
stdcall malloc, eax |
test eax, eax |
jz .return0 |
; 8c. Copy module name. |
mov esi, ebx |
mov ecx, edi |
mov edi, eax |
rep movsb |
mov dword [edi], 'dll' |
mov ebx, esi ; save pointer to <function> |
mov edi, eax ; module name |
; 9. Load the target module. |
; 9a. Get the pointer to MODULE struct for ourselves. |
mov esi, [module] |
test esi, esi |
jnz @f |
mutex_lock modules_mutex |
mov ecx, [export_base] |
call find_module_by_addr |
test esi, esi |
jz .load_forwarded_failed |
@@: |
; 9b. Call the worker. |
call load_imported_module |
test eax, eax |
jz .load_forwarded_failed |
mov esi, eax |
; 9c. We don't need module name anymore, free the memory allocated at 8b. |
stdcall free, edi |
; 10. Resolve the forwarded export recursively. |
; 10a. Prepare for importing. |
mov [forward_export_module], esi |
mov eax, [esi+MODULE.base] |
call prepare_import_from_module |
; 10b. Check whether we are importing by ordinal or by name. |
; Forwarded export by ordinal has ebx -> "#<ordinal>". |
cmp byte [ebx], '#' |
jnz .no_ordinal |
lea edx, [ebx+1] |
xor ecx, ecx ; ordinal |
@@: |
movzx eax, byte [edx] |
sub eax, '0' |
cmp eax, 10 |
jae .no_ordinal |
lea ecx, [ecx*5] |
lea ecx, [ecx*2+eax] |
inc edx |
cmp byte [edx], 0 |
jnz @b |
; 10c. We are importing by ordinal. Call the worker. |
call get_exported_function_by_ordinal |
jmp @f |
ret |
.no_ordinal: |
; 10d. We are importing by name. Call the worker. |
mov ecx, ebx |
or edx, -1 |
call get_exported_function_by_name |
@@: |
cmp [module], 0 |
jnz @f |
push eax |
mutex_unlock modules_mutex |
pop eax |
@@: |
ret |
.load_forwarded_failed: |
cmp [module], 0 |
jnz @f |
mutex_unlock modules_mutex |
@@: |
stdcall free, edi |
xor eax, eax |
ret |
endp |
|
; Resolve symbol from a module by name. |
; prepare_import_from_module should be called beforehand. |
; in: ecx = ordinal |
; out: eax = exported address or NULL |
; if [module] is zero, modules_mutex should be unlocked |
; if [module] is nonzero, modules_mutex should be locked |
proc get_exported_function_by_ordinal c uses ebx esi edi, export_base, export_ptr, export_size, module |
locals |
forward_export_base dd ? |
forward_export_ptr dd ? |
forward_export_size dd ? |
forward_export_module dd ? |
endl |
; 1. Validate that export directory is present at all. |
mov edx, [export_ptr] |
test edx, edx |
jz .export_ordinal_not_found |
; 2. Convert ordinal to index in AddressOfFunctions array. |
mov eax, ecx ; keep ecx for error message |
sub eax, [edx+IMAGE_EXPORT_DIRECTORY.Base] |
; 3. Validate the index. |
cmp eax, [edx+IMAGE_EXPORT_DIRECTORY.NumberOfFunctions] |
jae .export_ordinal_not_found |
; 4. Fetch the exported address from AddressOfFunctions array. |
; On success, continue to check for forwarded exports in get_exported_function_by_name. |
mov ebx, [edx+IMAGE_EXPORT_DIRECTORY.AddressOfFunctions] |
add ebx, [export_base] |
mov eax, [ebx+eax*4] |
test eax, eax |
jnz get_exported_function_by_name.check_forwarded |
; Generic error handler. |
.export_ordinal_not_found: |
sub esp, 16 |
fpo_delta = fpo_delta + 16 |
; Convert ordinal to string. |
lea edi, [esp+15] |
mov byte [edi], 0 |
@@: |
mov eax, 0xCCCCCCCD |
mul ecx |
shr edx, 3 ; edx = quotient of ecx / 10 |
lea eax, [edx*5] |
add eax, eax |
sub ecx, eax ; ecx = remainder of ecx % 10 |
add cl, '0' |
dec edi |
mov byte [edi], cl |
mov ecx, edx |
test edx, edx |
jnz @b |
; Get module name. |
mov esi, [module] |
test esi, esi |
jnz @f |
mutex_lock modules_mutex |
mov ecx, [export_base] |
call find_module_by_addr |
mutex_unlock modules_mutex |
@@: |
mov eax, msg_unknown |
test esi, esi |
jz @f |
mov eax, [esi+MODULE.filename] |
@@: |
ccall loader_say_error, msg_export_ordinal_not_found, edi, msg_export_not_found, eax, 0 |
add esp, 16 |
fpo_delta = fpo_delta - 16 |
xor eax, eax |
ret |
endp |