Hope you like it :)

>>> EDIT: see end for better version!




; GetProcAddress using binary search algorithm
; by storm1x in 2003

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

GetProcAddr proc lpBase:DWORD, szFn:LPSTR

push ebp
push ebx
push edi
push esi

; check for a valid PE
mov eax,[esp+10h+4]
cmp word ptr [eax],IMAGE_DOS_SIGNATURE
jnz @fail
mov edx,[eax+3Ch]
mov esi,[esp+10h+8]
add edx,eax
cmp dword ptr [edx],IMAGE_NT_SIGNATURE
jnz @fail
mov edx,[edx+78h]
test edx,edx
jz @fail ; fail if no export table
add edx,eax ; edx <- IMAGE_EXPORT_TABLE (edt)

; check if getting by ordinal
cmp esi,0000ffffh
ja @F
mov ebx,eax
mov ecx,[edx+1Ch] ; ecx <- address table
add ecx,eax
mov eax,[edx+14h] ; eax <- no. of eat entries
; check if valid ordinal
cmp esi,eax
jae @fail
mov eax,esi
mov esi,[edx+10h] ; esi <- ordinal base
; ordinal base must be subtracted from given ordinals
; before indexing the export address table, but not
; from values in the ordinal table, which runs parallel
; to the name rva table
sub eax,esi ; subtract ordinal base
; index eat by ordinal
mov eax,[ecx+4*eax]
add eax,ebx
jmp @ok

@@: mov ebp,[edx+18h] ; ebp <- no. of names
mov ebx,[edx+20h]
push edx
add ebx,eax ; ebx <- name rva table
mov ecx,eax ; ecx <- image base
push ebx
; binary search loop
; ebp = num, esi = key, edi = name va, ebx = base, edx = mid
@bs_loop:
test ebp,ebp
jz @bs_notfound
; get middle and halve no. left
shr ebp,1
lea edx,[ebx+4*ebp]
; get name va from rva array
mov edi,[edx]
add edi,ecx
; compare [esi] and [edi]
push esi
mov al,[esi]
@@: cmp al,[edi]
jnz @F
inc esi
inc edi
test al,al
mov al,[esi]
jnz @B
@@: pop esi
; now flags set according to str difference
lea eax,[edx+4]
cmova ebx,eax
jnz @bs_loop

; result in edx
@bs_done:
; get corresponding ordinal
; first work out where we are in the name rva array
pop ebx ; ebx <- name rva base
sub edx,ebx ; edx <- name rva offset (in bytes)
shr edx,1 ; now in _words_
pop esi ; esi <- export table
mov ebx,[esp+10h+4]
mov ecx,[esi+1Ch] ; ecx <- address table
add ecx,ebx
mov eax,[esi+24h] ; eax <- ordinal table
add eax,ebx
; index ordinal table
movzx eax,word ptr [eax+edx]
; index eat by ordinal (ordinal in eax, eat in ecx)
; ordinal table entries already have ordinal base subtracted
mov eax,[ecx+4*eax]
add eax,ebx
jmp @ok
@bs_notfound:
add esp,8 ; we had saved the export and name table pointers
@fail:
xor eax,eax
@ok:
pop esi
pop edi
pop ebx
pop ebp
ret 8

GetProcAddr endp

OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF
Posted on 2004-02-07 15:09:11 by stormix
Doesn't normal GetProcAddress already use binary search? Also, just where do you handle redirected exports?
Posted on 2004-02-07 18:23:52 by f0dder
What are redirected exports?
Posted on 2004-02-07 20:52:31 by comrade
for example: kernel32!HeapAlloc -> NTDLL!RtlAllocateHeap
Posted on 2004-02-07 21:04:36 by f0dder
So?
Posted on 2004-02-07 21:20:05 by comrade
So, using GetProcAddress on kernel32!HeapAlloc, you would be calling code like "dec esi / push esp / inc esp" (etc) - that is, the text string NTDLL.RtlAllocateHeap - because the code doesn't handle those redirected (or 'forwarded' or whatever) exports.
Posted on 2004-02-07 21:59:18 by f0dder
I don't understand, ET only provide pointers to function. Where does it support redirection?
Posted on 2004-02-07 22:46:57 by comrade
Export forwarding is described in most texts on the PE format. If the exported address points inside the area marked in the data directory as the export table, then the RVA points to a forwarder string instead of the actual function. Such a forwarder string is in the format f0dder mentions, e.g. NTDLL.RtlAllocateHeap.

If you have a kernel32.dll from NT/2K/XP you can see that a number of exports are forwarded to ntdll.dll.
Posted on 2004-02-08 02:06:21 by Jibz
An easy way to determine forwarded functions:


dumpbin /exports \windows\system32\kernel32.dll


The attachment has a list of all the kernel32 APIs.
(Tested on Win XP Home)
Posted on 2004-02-08 04:16:31 by Vortex
ordinal check should also be against 080000000h
Posted on 2004-02-08 07:40:24 by evlncrn8
Thank you, I didn't know about forwarded exports. I will add support for them.

re the 80000000h thing, according to msdn http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/getprocaddress.asp "If this parameter is an ordinal value, it must be in the low-order word; the high-order word must be zero."
Posted on 2004-02-08 17:19:59 by stormix
Ok here is the new version :)

I don't know if normal GetProcAddress uses binary search; anyone know? It calls alot of other functions so i guess this is faster..



; fast GetProcAddress using binary search algorithm
; by storm1x in 2004

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

GetProcAddr proc lpBase:DWORD, szFn:LPSTR

push ebp
push ebx
push edi
push esi

; check for a valid PE
mov eax,[esp+10h+4]
cmp word ptr [eax],IMAGE_DOS_SIGNATURE
jnz @fail
mov edx,[eax+3Ch]
mov esi,[esp+10h+8]
add edx,eax
cmp dword ptr [edx],IMAGE_NT_SIGNATURE
jnz @fail
mov ecx,[edx+7Ch] ; export table length
mov edx,[edx+78h]
test edx,edx
jz @fail ; fail if no export table
add edx,eax ; edx <- IMAGE_EXPORT_TABLE (edt)
mov [esp-32],ecx ; save export table length
mov [esp-28],edx ; save export table va

; check if getting by ordinal
cmp esi,0000ffffh
ja @F
mov ebx,eax
mov ecx,[edx+1Ch] ; ecx <- address table
add ecx,eax
mov eax,[edx+14h] ; eax <- no. of eat entries
; check if valid ordinal
cmp esi,eax
jae @fail
mov eax,esi
mov esi,[edx+10h] ; esi <- ordinal base
; ordinal base must be subtracted from given ordinals
; before indexing the export address table, but not
; from values in the ordinal table, which runs parallel
; to the name rva table
sub eax,esi ; subtract ordinal base
; index eat by ordinal
mov eax,[ecx+4*eax]
add eax,ebx
jmp @ok

@@: mov ebp,[edx+18h] ; ebp <- no. of names
mov ebx,[edx+20h]
push edx
add ebx,eax ; ebx <- name rva table
mov ecx,eax ; ecx <- image base
push ebx
; binary search loop
; ebp = num, esi = key, edi = name va, ebx = base, edx = mid
@bs_loop:
test ebp,ebp
jz @bs_notfound
; get middle and halve no. left
shr ebp,1
lea edx,[ebx+4*ebp]
; get name va from rva array
mov edi,[edx]
add edi,ecx
; compare [esi] and [edi]
push esi
mov al,[esi]
@@: cmp al,[edi]
jnz @F
inc esi
inc edi
test al,al
mov al,[esi]
jnz @B
@@: pop esi
; now flags set according to str difference
lea eax,[edx+4]
cmova ebx,eax
jnz @bs_loop

; result in edx
@bs_done:
; get corresponding ordinal
; first work out where we are in the name rva array
pop ebx ; ebx <- name rva base
sub edx,ebx ; edx <- name rva offset (in bytes)
shr edx,1 ; now in _words_
pop esi ; esi <- export table
mov ebx,[esp+10h+4]
mov ecx,[esi+1Ch] ; ecx <- address table
add ecx,ebx
mov eax,[esi+24h] ; eax <- ordinal table
add eax,ebx
; index ordinal table
movzx eax,word ptr [eax+edx]
; index eat by ordinal (ordinal in eax, eat in ecx)
; ordinal table entries already have ordinal base subtracted
mov eax,[ecx+4*eax]
add eax,ebx
; check if forwarder rva (e.g. "MYDLL.expfunc" or "MYDLL.#27")
mov ecx,[esp-32] ; ecx <- export table len
mov edx,[esp-28] ; edx <- export table va
add ecx,edx ; ecx <- export table end va

; If the address specified is not within the export section
; (as defined by the address and length indicated in the
; Optional Header), the field is an export RVA else a forwarder

cmp eax,edx
jb @ok
cmp eax,ecx
ja @ok

; so we have a forwarder..
mov esi,eax
@@: mov dl,[esi]
inc esi
cmp dl,'.'
jnz @B

neg esi
lea ecx,[esi+eax] ; ecx <- -(name len)
add ecx,1 ; account for '.'
neg esi

mov ebp,esp ; save stack
lea esp,[esp+ecx-5] ; allocate len+5 on stack

mov edi,esp

; is it forwarded to an ordinal?
cmp byte ptr [esi],'#'
jnz @notord
push esi ; save this as it marks end of dll name
inc esi
; accumulating in edx
xor ecx,ecx
xor edx,edx
@@: mov cl,[esi]
inc esi
test cl,cl
jz @toi_ok
; mul edx by 10
mov ebx,edx
shl ebx,1
shl edx,3
add edx,ebx
sub cl,'0'
add edx,ecx
jmp @B
@toi_ok:
pop esi
; push ordinal
push edx
jmp @ffn_done
@notord:
; push fun name
push esi
@ffn_done:

; copy dll name
push edi
@@: mov dl,[eax]
mov [edi],dl
inc eax
inc edi
cmp eax,esi
jb @B
mov dword ptr [edi],'LLD'

call GetModuleHandle

push eax
call GetProcAddr ; call self

mov esp,ebp ; restore stack

jmp @ok
@bs_notfound:
add esp,8 ; we had saved the export and name table pointers
@fail:
xor eax,eax
@ok:
pop esi
pop edi
pop ebx
pop ebp
ret 8

GetProcAddr endp

OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF
Posted on 2004-02-09 08:03:11 by stormix

I don't know if normal GetProcAddress uses binary search; anyone know?

It should - technical articles mention this, besides afaik DLL exports are required to be sorted by name, for this very reason.

Btw, how much does the speed of imports fixing really matter? When executables seem to load slowly under win32, it has almost always been because of a lot of runtime code before showing main windows (some delphi and bcb stuff), or because of delphi/bcb or other huge exes where the author has been stupid enough to use an EXE packer.

But I'm wondering whether import resolving speed might matter after all, because on linux, GUI applications seem to load terribly slow, and I can't believe that IDE performance or memory strategies would be THAT bad with a 2.6 kernel - but they might have a really retarded symbol searching strategy? Still seems pretty silly though =)
Posted on 2004-02-09 09:36:03 by f0dder
Stormix,
it is just an idea (not tested):



@@:mov ebp,[edx+18h]; ebp <- no. of names
[B]inc ebp[/B]
;
;
;
; binary search loop
; ebp = num, esi = key, edi = name va, ebx = base, edx = mid

@Next:
cmova ebx,edx ;eax
shr ebp,1
jz @bs_notfound
mov esi, [esp+24] ; esi->[esp+24]->szFn:LPSTR ???
mov edi, ecx ; rather then push esi / pop esi
lea edx, [ebx+4*ebp[B]-4[/B]]
mov al, [esi]
add edi, [edx]
@Compare:
cmp al, [edi]
jnz @Next
add esi, 1
add edi, 1
test al, al
mov al, [esi]
jnz @Compare
; result in edx
@bs_done:
; get corresponding ordinal
;
;

Regards,
Lingo
Posted on 2004-02-09 18:28:56 by lingo12
Lingo, thank you for your ideas :) I used them and it is faster now :D The only one that wasn't was not having the test ebp,ebp but having a jz after the shr (that was what you meant right?), this was slower for some reason.

Also the last one doesn't handle forwards if you searched by ordinal- oops! This does.

Anyways heres the new one:



; fast GetProcAddress using binary search algorithm
; by storm1x in 2004
; thanks to lingo12

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

GetProcAddr proc lpBase:DWORD, szFn:LPSTR

push ebp
push ebx
push edi
push esi

; check for a valid PE
mov eax,[esp+10h+4]
cmp word ptr [eax],IMAGE_DOS_SIGNATURE
jnz @fail
mov edx,[eax+3Ch]
mov esi,[esp+10h+8]
add edx,eax
cmp dword ptr [edx],IMAGE_NT_SIGNATURE
jnz @fail
mov ecx,[edx+7Ch] ; export table length
mov edx,[edx+78h]
test edx,edx
jz @fail ; fail if no export table
add edx,eax ; edx <- IMAGE_EXPORT_TABLE (edt)
mov [esp-32],ecx ; save export table length
mov [esp-28],edx ; save export table va

; check if getting by ordinal
cmp esi,0000ffffh
ja @F
mov ebx,eax
mov ecx,[edx+1Ch] ; ecx <- address table
add ecx,eax
mov eax,[edx+14h] ; eax <- no. of eat entries
; check if valid ordinal
cmp esi,eax
jae @fail
mov eax,esi
mov esi,[edx+10h] ; esi <- ordinal base
; ordinal base must be subtracted from given ordinals
; before indexing the export address table, but not
; from values in the ordinal table, which runs parallel
; to the name rva table
sub eax,esi ; subtract ordinal base
; index eat by ordinal
mov eax,[ecx+4*eax]
add eax,ebx
jmp @check_forwarder

@@: mov ebp,[edx+18h] ; ebp <- no. of names
mov ebx,[edx+20h]
push edx
add ebx,eax ; ebx <- name rva table
mov ecx,eax ; ecx <- image base
push ebx
; binary search loop
; ebp = num, esi = key, edi = name va, ebx = base, edx = mid
stc ; make sure cmov isn't executed first time
@bs_loop:
; move to second half if it's after the mid
cmova ebx,edx
test ebp,ebp
jz @bs_notfound
; get middle and halve no. left
shr ebp,1
mov esi,[esp+10h+8+8] ; esi <- szFn:LPSTR
mov edi,ecx
lea edx,[ebx+4*ebp]
; get name va from rva array
add edi,[edx]
; compare [esi] and [edi]
; sets flags according to str difference
mov al,[esi]
@@: cmp al,[edi]
jnz @bs_loop
add esi,1
add edi,1
test al,al
mov al,[esi]
jnz @B

; binary search done, resulting name va in edx

; get corresponding ordinal
; first work out where we are in the name rva array
pop ebx ; ebx <- name rva base
sub edx,ebx ; edx <- name rva offset (in bytes)
shr edx,1 ; now in _words_
pop esi ; esi <- export table
mov ebx,[esp+10h+4]
mov ecx,[esi+1Ch] ; ecx <- address table
add ecx,ebx
mov eax,[esi+24h] ; eax <- ordinal table
add eax,ebx
; index ordinal table
movzx eax,word ptr [eax+edx]
; index eat by ordinal (ordinal in eax, eat in ecx)
; ordinal table entries already have ordinal base subtracted
mov eax,[ecx+4*eax]
add eax,ebx

@check_forwarder:
; check if forwarder rva (e.g. "MYDLL.expfunc" or "MYDLL.#27")
mov ecx,[esp-32] ; ecx <- export table len
mov edx,[esp-28] ; edx <- export table va
add ecx,edx ; ecx <- export table end va

; If the address specified is not within the export section
; (as defined by the address and length indicated in the
; Optional Header), the field is an export RVA else a forwarder

cmp eax,edx
jb @ok
cmp eax,ecx
ja @ok

; so we have a forwarder..
mov esi,eax
@@: mov dl,[esi]
add esi,1
cmp dl,'.'
jnz @B

neg esi
lea ecx,[esi+eax] ; ecx <- -(name len)
inc ecx ; account for '.'
neg esi

mov ebp,esp ; save stack
lea esp,[esp+ecx-5] ; allocate len+5 on stack

mov edi,esp

; is it forwarded to an ordinal?
cmp byte ptr [esi],'#'
jnz @notord
push esi ; save this as it marks end of dll name
inc esi
; accumulating in edx
xor ecx,ecx
xor edx,edx
@@: mov cl,[esi]
add esi,1
test cl,cl
jz @toi_ok
; mul edx by 10
lea ebx,[edx*2]
lea edx,[ebx+edx*8]
sub cl,'0'
add edx,ecx
jmp @B
@toi_ok:
pop esi
; push ordinal
push edx
jmp @ffn_done
@notord:
; push fun name
push esi
@ffn_done:

; copy dll name and '.'
push edi
@@: mov dl,[eax]
mov [edi],dl
add eax,1
add edi,1
cmp eax,esi
jb @B
mov dword ptr [edi],'LLD'

call GetModuleHandle

push eax
call GetProcAddr ; call self

mov esp,ebp ; restore stack

jmp @ok
@bs_notfound:
add esp,8 ; we had saved the export and name table pointers
@fail:
xor eax,eax
@ok:
pop esi
pop edi
pop ebx
pop ebp
ret 8

GetProcAddr endp

OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF
Posted on 2004-02-10 17:36:49 by stormix