6198f1e4c9
git-svn-id: svn://kolibrios.org@6810 a494cfbc-eb01-0410-851d-a64ba20cac60
1182 lines
44 KiB
PHP
1182 lines
44 KiB
PHP
; 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 edx, [esi+edx+IMAGE_EXPORT_DIRECTORY.TimeDateStamp]
|
|
@@:
|
|
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.
|
|
;
|
|
; in: esi = PE base address
|
|
; in: [esp+4] = module name for debug print
|
|
; out: CF=1 - fail
|
|
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;
|
|
; ebp will keep the delta
|
|
; * RVA and size of fixups directory
|
|
; * flag IMAGE_FILE_RELOCS_STRIPPED from Characteristics
|
|
; If the actual address equals the preferred address, do nothing.
|
|
; If fixups directory is present, proceed to 2.
|
|
; If there is no fixups directory, there are two options:
|
|
; * either the directory has not been created
|
|
; * or the module has no fixups (data-only module, for example).
|
|
; 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 byte [esi], 'M'
|
|
jz .parse_mz
|
|
sub ebp, [esi+STRIPPED_PE_HEADER.ImageBase]
|
|
jnz @f
|
|
.nothing:
|
|
ret
|
|
@@:
|
|
mov dl, byte [esi+STRIPPED_PE_HEADER.Characteristics]
|
|
lea eax, [esi+sizeof.STRIPPED_PE_HEADER+SPE_DIRECTORY_BASERELOC*sizeof.IMAGE_DATA_DIRECTORY]
|
|
cmp [esi+STRIPPED_PE_HEADER.NumberOfRvaAndSizes], SPE_DIRECTORY_BASERELOC
|
|
ja .common
|
|
.norelocs:
|
|
test dl, IMAGE_FILE_RELOCS_STRIPPED
|
|
jz .nothing
|
|
ccall loader_say_error, msg_noreloc1, [modulename], msg_noreloc2, 0
|
|
stc
|
|
ret
|
|
.parse_mz:
|
|
mov eax, [esi+3Ch]
|
|
add eax, esi
|
|
sub ebp, [eax+IMAGE_NT_HEADERS.OptionalHeader.ImageBase]
|
|
jz .nothing
|
|
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
|
|
; 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-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.
|
|
; Size in the block includes size of the header, subtract it.
|
|
; If there is no data in this block, go to 2g.
|
|
mov edx, [edi+IMAGE_BASE_RELOCATION.VirtualAddress]
|
|
mov ecx, [edi+IMAGE_BASE_RELOCATION.SizeOfBlock]
|
|
sub [.sizeleft], ecx
|
|
add edi, sizeof.IMAGE_BASE_RELOCATION
|
|
sub ecx, sizeof.IMAGE_BASE_RELOCATION
|
|
jbe .pagedone
|
|
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.
|
|
add edx, esi
|
|
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 ecx
|
|
mov eax, 68
|
|
mov ebx, 30
|
|
mov ecx, PROT_READ+PROT_WRITE
|
|
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
|
|
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
|
|
; 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
|
|
mov ebx, eax
|
|
and ebx, 0xFFF
|
|
shr eax, 12
|
|
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:
|
|
; 2i. Restore memory protection changed in 2d.
|
|
pop ecx
|
|
fpo_delta = fpo_delta - 4
|
|
mov eax, 68
|
|
mov ebx, 30
|
|
mov esi, 0x1000
|
|
call FS_SYSCALL_PTR
|
|
pop esi
|
|
fpo_delta = fpo_delta - 4
|
|
.pagedone:
|
|
cmp [.sizeleft+fpo_delta], 0
|
|
jnz .pageloop
|
|
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, [modulename]
|
|
call sys_msg_board_str
|
|
mov ecx, msg_relocated2
|
|
call sys_msg_board_str
|
|
clc
|
|
ret
|
|
.badreloc:
|
|
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_descriptor dd ?
|
|
next_forwarder dd ?
|
|
bound_import_descriptor dd ?
|
|
bound_import_dir dd ?
|
|
bound_modules_count dd ?
|
|
bound_modules_ptr dd ?
|
|
bound_module dd ?
|
|
bound_modules_left 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 @f
|
|
mov ebx, ebp
|
|
@@:
|
|
; 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.
|
|
; * No import: not really different from normal import with no descriptors.
|
|
; Forwarded exports are the part where binding goes really interesting.
|
|
; The address of forwarded export can change with any library in the chain.
|
|
; In old-style bound import, a descriptor can list only the library itself,
|
|
; so it is impossible to bind forwarded exports at all; thunks that point to
|
|
; forwarded exports are linked in the list starting from ForwarderChain in import descriptor.
|
|
; New-style bound import exists exactly to address this problem; it allows to
|
|
; list several related libraries for one imported module.
|
|
; However, even with new-style bound import, some forwarded exports can
|
|
; still remain unbound: binding tool can fail to load dependent libraries
|
|
; during binding time; the tool from MS SDK for unknown reason just refuses to
|
|
; bind forwarded exports except for built-in white list if subsystem version is
|
|
; < 6.0. So even with new-style bound import, ForwarderChain still can be non-empty.
|
|
; Thus, we always need to look at old-style import descriptor.
|
|
; 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:
|
|
; If import directory is not present, no import - nothing to do.
|
|
; Ignore bound import directory in this case.
|
|
test ebp, ebp
|
|
jz .done
|
|
add ebp, [esi+MODULE.base] ; directories contain RVA
|
|
add ebx, [esi+MODULE.base] ; directories contain RVA
|
|
mov [bound_import_dir], ebx
|
|
mov [bound_import_descriptor], ebx
|
|
; Repeat remaining steps for all descriptors in the directory.
|
|
.descriptor_loop:
|
|
; 2. 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
|
|
; 3. 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
|
|
; 4. Check whether the descriptor has a non-stale old-style binding.
|
|
; Zero timestamp means "not bound".
|
|
; Timestamp 0xFFFFFFFF means "new-style binding".
|
|
; Mismatched timestamp means "stale binding".
|
|
; In first and third cases, go to 9.
|
|
; In second case, go to 10 for further checks.
|
|
mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.TimeDateStamp]
|
|
test edx, edx
|
|
jz .resolve_generic
|
|
cmp edx, -1
|
|
jz .new_binding
|
|
cmp edx, [eax+MODULE.timestamp]
|
|
jnz .resolve_generic
|
|
; 5. 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, we need to resolve them.
|
|
; 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.
|
|
; Check for the first problem first; the corresponding code can handle both.
|
|
; If the target module is relocated, go to 8.
|
|
; If the target module is not relocated, but has forwarded exports, go to 7.
|
|
; Otherwise, advance to 6.
|
|
.old_binding:
|
|
cmp [eax+MODULE.basedelta], 0
|
|
jnz .old_binding_relocate
|
|
.check_forwarder_chain:
|
|
cmp [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain], -1
|
|
jnz .resolve_forward_chain
|
|
.next_descriptor:
|
|
; 6. Advance to next descriptor and continue the loop.
|
|
add ebp, sizeof.IMAGE_IMPORT_DESCRIPTOR
|
|
jmp .descriptor_loop
|
|
.resolve_forward_chain:
|
|
; 7. Resolve all thunks from ForwarderChain list.
|
|
mov eax, [import_module]
|
|
mov eax, [eax+MODULE.base]
|
|
call prepare_import_from_module
|
|
mov edi, [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain]
|
|
.resolve_forward_chain_loop:
|
|
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 .resolve_forward_chain_loop
|
|
; After resolving, we are done with this import descriptor, go to 6.
|
|
jmp .next_descriptor
|
|
.done:
|
|
call .restore_protection
|
|
xor eax, eax
|
|
ret
|
|
.old_binding_relocate:
|
|
; 8. The descriptor has a non-stale old-style binding,
|
|
; but the target module is relocated, so we need to add MODULE.basedelta to
|
|
; all addresses. Also, there can be forwarded exports.
|
|
; For consistency with generic-case resolve_import_from_module,
|
|
; check for end of thunks by looking at OriginalFirstThunk array.
|
|
; Note: we assume here that ForwarderChain list is ordered.
|
|
; After that, go to 6.
|
|
mov edi, [eax+MODULE.basedelta]
|
|
cmp [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain], -1
|
|
jz @f
|
|
mov eax, [import_module]
|
|
mov eax, [eax+MODULE.base]
|
|
call prepare_import_from_module
|
|
@@:
|
|
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 eax, [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain]
|
|
lea eax, [edx+eax*4]
|
|
mov [next_forwarder], eax
|
|
.old_binding_relocate_loop:
|
|
cmp dword [ebx], 0
|
|
jz .next_descriptor
|
|
call .ensure_writable ; should preserve esi,edi,ebp,ebx,edx
|
|
cmp edx, [next_forwarder]
|
|
jz .old_binding_relocate_forwarder
|
|
add dword [edx], edi
|
|
.old_binding_relocate_next:
|
|
add edx, 4
|
|
add ebx, 4
|
|
jmp .old_binding_relocate_loop
|
|
.old_binding_relocate_forwarder:
|
|
mov ecx, [ebx]
|
|
get_address_for_thunk
|
|
test eax, eax
|
|
jz .failed
|
|
mov edx, [next_forwarder]
|
|
mov ecx, [edx]
|
|
mov [edx], eax
|
|
mov eax, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk]
|
|
add eax, [esi+MODULE.base]
|
|
lea eax, [eax+ecx*4]
|
|
mov [next_forwarder], eax
|
|
jmp .old_binding_relocate_next
|
|
.resolve_generic:
|
|
; 9. Run generic-case resolver.
|
|
mov [import_descriptor], ebp
|
|
resolve_import_from_module .failed ; should preserve esi
|
|
mov ebp, [import_descriptor]
|
|
; After that, go to 6.
|
|
jmp .next_descriptor
|
|
.new_binding:
|
|
; New-style bound import.
|
|
; 10. Locate the new-style descriptor corresponding to the current
|
|
; import descriptor.
|
|
; 10a. Check the current new-style descriptor: the one that follows
|
|
; previous one, if there was the previous one, or the first one.
|
|
; In most cases, new-style descriptors are in the same order as
|
|
; import descriptors, so full loop in 10b can be avoided.
|
|
mov edx, [ebp+IMAGE_IMPORT_DESCRIPTOR.Name]
|
|
add edx, [esi+MODULE.base]
|
|
mov ebx, [bound_import_descriptor]
|
|
movzx edi, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName]
|
|
test edi, edi
|
|
jz .look_new_binding_hard
|
|
add edi, [bound_import_dir]
|
|
xor ecx, ecx
|
|
@@:
|
|
mov al, [edx+ecx]
|
|
cmp al, [edi+ecx]
|
|
jnz .look_new_binding_hard
|
|
test al, al
|
|
jz .new_binding_found
|
|
inc ecx
|
|
jmp @b
|
|
.look_new_binding_hard:
|
|
; 10b. We are out of luck with the current new-style descriptor,
|
|
; so loop over all of them looking for the matching one.
|
|
mov ebx, [bound_import_dir]
|
|
.look_new_binding_loop:
|
|
movzx edi, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName]
|
|
add edi, [bound_import_dir]
|
|
xor ecx, ecx
|
|
@@:
|
|
mov al, [edx+ecx]
|
|
cmp al, [edi+ecx]
|
|
jnz .look_new_binding_next
|
|
test al, al
|
|
jz .new_binding_found
|
|
inc ecx
|
|
jmp @b
|
|
.look_new_binding_next:
|
|
movzx ecx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs]
|
|
lea ebx, [ebx+(ecx+1)*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR]
|
|
jmp .look_new_binding_loop
|
|
.new_binding_found:
|
|
; 10c. Store the next descriptor for subsequent scans.
|
|
movzx ecx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs]
|
|
lea eax, [ebx+(ecx+1)*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR]
|
|
mov [bound_import_descriptor], eax
|
|
; 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.
|
|
; 11. We have already loaded the main module, validate its timestamp.
|
|
; If timestamp does not match, go to 9 to run generic-case resolver.
|
|
mov eax, [import_module]
|
|
mov edx, [eax+MODULE.timestamp]
|
|
cmp edx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.TimeDateStamp]
|
|
jnz .resolve_generic
|
|
; 12. If there are no additional libraries,
|
|
; the situation is exactly same as old-style binding, so go to 5.
|
|
test ecx, ecx
|
|
jz .old_binding
|
|
; 13. Load additional libraries and validate their timestamps.
|
|
; If at least one timestamp is invalid, resort to generic-case resolving.
|
|
; 13a. Allocate memory for all bound modules, including the main module.
|
|
lea ecx, [(ecx+1)*4]
|
|
stdcall malloc, ecx
|
|
test eax, eax
|
|
jz .failed
|
|
mov [bound_modules_ptr], eax
|
|
; 13b. Store the main module.
|
|
mov edx, [import_module]
|
|
mov [eax], edx
|
|
xor ecx, ecx
|
|
; 13c. Loop over all additional descriptors.
|
|
.newstyle_load_loop:
|
|
inc ecx
|
|
mov [bound_modules_count], ecx
|
|
movzx edi, [ebx+ecx*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR+IMAGE_BOUND_IMPORT_DESCRIPTOR.OffsetModuleName]
|
|
add edi, [bound_import_dir]
|
|
call load_imported_module ; should preserve ebx,esi,ebp
|
|
test eax, eax
|
|
jz .newstyle_failed
|
|
mov ecx, [bound_modules_count]
|
|
mov edx, [ebx+ecx*sizeof.IMAGE_BOUND_IMPORT_DESCRIPTOR+IMAGE_BOUND_IMPORT_DESCRIPTOR.TimeDateStamp]
|
|
cmp [eax+MODULE.timestamp], edx
|
|
jnz .newstyle_stale
|
|
mov edx, [bound_modules_ptr]
|
|
mov [edx+ecx*4], eax
|
|
cmp cx, [ebx+IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs]
|
|
jb .newstyle_load_loop
|
|
inc ecx
|
|
mov [bound_modules_count], ecx
|
|
; New-style binding has correct timestamp.
|
|
; There still can be same two problems as in step 5 with old-style binding.
|
|
; 14. Check whether at least one module is relocated. If so, go to 16.
|
|
.newstyle_check_reloc:
|
|
mov eax, [edx]
|
|
cmp [eax+MODULE.basedelta], 0
|
|
jnz .newstyle_need_reloc
|
|
add edx, 4
|
|
dec ecx
|
|
jnz .newstyle_check_reloc
|
|
; 15. Bound modules are not relocated.
|
|
; The only remaining problem could be unbound forwarders.
|
|
; Free memory allocated at 13a and let steps 6 and 7 do their work.
|
|
stdcall free, [bound_modules_ptr]
|
|
jmp .check_forwarder_chain
|
|
.newstyle_stale:
|
|
stdcall free, [bound_modules_ptr]
|
|
jmp .resolve_generic
|
|
; 16. The descriptor has a non-stale new-style binding,
|
|
; but at least one of target modules is relocated, so we need to add
|
|
; MODULE.basedelta to addresses from relocated modules.
|
|
; Also, there can be forwarded exports.
|
|
; For consistency with generic-case resolve_import_from_module,
|
|
; check for end of thunks by looking at OriginalFirstThunk array.
|
|
; Note: we assume here that ForwarderChain list is ordered.
|
|
; After that, go to 6.
|
|
.newstyle_need_reloc:
|
|
mov eax, [import_module]
|
|
mov eax, [eax+MODULE.base]
|
|
call prepare_import_from_module
|
|
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 eax, [ebp+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain]
|
|
lea eax, [edx+eax*4]
|
|
mov [next_forwarder], eax
|
|
.new_binding_relocate_loop:
|
|
cmp dword [ebx], 0
|
|
jz .new_binding_relocate_done
|
|
cmp edx, [next_forwarder]
|
|
jz .new_binding_resolve_thunk
|
|
mov [bound_module], 0
|
|
mov eax, [bound_modules_count]
|
|
mov [bound_modules_left], eax
|
|
mov edi, [bound_modules_ptr]
|
|
; There should be at least one module containing address [edx].
|
|
; There can be more than one if preferred address ranges for two modules intersect;
|
|
; in this case, we are forced to resolve address from scratch.
|
|
.new_binding_lookup_module:
|
|
mov ecx, [edx]
|
|
mov eax, [edi]
|
|
sub ecx, [eax+MODULE.base]
|
|
add ecx, [eax+MODULE.basedelta]
|
|
cmp ecx, [eax+MODULE.size]
|
|
jae @f
|
|
cmp [bound_module], 0
|
|
jnz .new_binding_resolve_thunk
|
|
mov [bound_module], eax
|
|
@@:
|
|
add edi, 4
|
|
dec [bound_modules_left]
|
|
jnz .new_binding_lookup_module
|
|
mov edi, [bound_module]
|
|
mov edi, [edi+MODULE.basedelta]
|
|
test edi, edi
|
|
jz .new_binding_relocate_next
|
|
call .ensure_writable ; should preserve esi,edi,ebp,ebx,edx
|
|
add dword [edx], edi
|
|
.new_binding_relocate_next:
|
|
add edx, 4
|
|
add ebx, 4
|
|
jmp .new_binding_relocate_loop
|
|
.new_binding_resolve_thunk:
|
|
mov [bound_modules_left], edx
|
|
call .ensure_writable
|
|
mov ecx, [ebx]
|
|
get_address_for_thunk
|
|
test eax, eax
|
|
jz .newstyle_failed
|
|
mov edx, [bound_modules_left]
|
|
mov ecx, [edx]
|
|
mov [edx], eax
|
|
cmp edx, [next_forwarder]
|
|
jnz .new_binding_relocate_next
|
|
mov eax, [ebp+IMAGE_IMPORT_DESCRIPTOR.FirstThunk]
|
|
add eax, [esi+MODULE.base]
|
|
lea eax, [eax+ecx*4]
|
|
mov [next_forwarder], eax
|
|
jmp .new_binding_relocate_next
|
|
.new_binding_relocate_done:
|
|
stdcall free, [bound_modules_ptr]
|
|
jmp .next_descriptor
|
|
.newstyle_failed:
|
|
stdcall free, [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]
|
|
test edx, edx
|
|
jz .noexport
|
|
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]
|
|
test edx, edx
|
|
jz .noexport
|
|
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 .hint_ok
|
|
.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
|
|
call .get_module_name
|
|
ccall loader_say_error, msg_export_name_not_found, ebx, msg_export_not_found, eax, 0
|
|
.return0:
|
|
xor eax, eax
|
|
ret
|
|
.hint_ok:
|
|
mov eax, edx
|
|
.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 ebx, eax
|
|
sub ebx, edx
|
|
cmp ebx, [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
|
|
call .get_module_name
|
|
ccall loader_say_error, msg_invalid_forwarder, eax, 0
|
|
xor eax, eax
|
|
ret
|
|
.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
|
|
|
|
fpo_delta = fpo_delta + 4
|
|
.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]
|
|
@@:
|
|
retn
|
|
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
|