News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

PROTO vs EXTERNDEF, direct or indirect API call.

Started by WYVERN666, January 10, 2012, 04:01:36 PM

Previous topic - Next topic

WYVERN666

Well, i don't know wich is the most appropriate name for this topic, but i have here two almost identical programs importing external API in a different manner:

1) PROTO version:

.386
.MODEL flat, stdcall
 
INCLUDELIB \lib\kernel32.lib
INCLUDELIB \lib\user32.lib

ExitProcess PROTO :DWORD
MessageBoxA PROTO :DWORD, :DWORD, :DWORD, :DWORD

.CODE
start:         
        PUSH    0
        PUSH    offset title
        PUSH    offset msg
        PUSH    0
        CALL    MessageBoxA

        PUSH    0
        CALL    ExitProcess
       
.DATA
        title db 'Title', 0
        msg db 'Message', 0

END start

This is the most common/popular approach, i think mostly because this allows parameter checking with the "invoke" macro.
The external API is resolved indirectly through a "JMP table":
Quote
...
0040100E   E8 0D000000             CALL    <JMP.&user32.MessageBoxA>                                          ; Jump to user32.MessageBoxA
00401013   6A 00                       PUSH    0
00401015   E8 00000000             CALL    <JMP.&kernel32.ExitProcess>                                           ; Jump to kernel32.ExitProcess
0040101A - FF25 00204000           JMP     NEAR DWORD PTR DS:[<&kernel32.ExitProcess>]
00401020 - FF25 08204000           JMP     NEAR DWORD PTR DS:[<&user32.MessageBoxA>]
...


2) EXTERNDEF version:

.386
.MODEL flat, stdcall
 
INCLUDELIB \lib\kernel32.lib
INCLUDELIB \lib\user32.lib

EXTERNDEF _imp__ExitProcess@4:DWORD
EXTERNDEF _imp__MessageBoxA@16:DWORD

.CODE
start:         
        PUSH    0
        PUSH    offset title
        PUSH    offset msg
        PUSH    0
        CALL    [_imp__MessageBoxA@16]

        PUSH    0
        CALL    [_imp__ExitProcess@4] 
       
.DATA
        title db 'Title', 0
        msg db 'Message', 0

END start

Here the external API is resolved directly:
Quote
...
0040100E   FF15 08204000           CALL    NEAR DWORD PTR DS:[<&user32.MessageBoxA>]
00401014   6A 00                        PUSH    0
00401016   FF15 00204000           CALL    NEAR DWORD PTR DS:[<&kernel32.ExitProcess>]
...

Ok, can somebody explain me something about this?, for e. wich is the recommended version?. There is any advantage using a "JMP table" calling indirectly? isnt slower?. Or Maybe i can mix both ways?.

Here there are two posts i found about "direct" and "indirect" call:

"Calling an imported function, the naive way":
http://blogs.msdn.com/b/oldnewthing/archive/2006/07/21/673830.aspx

"How a less naive compiler calls an imported function":
http://blogs.msdn.com/b/oldnewthing/archive/2006/07/24/676669.aspx


WYVERN666

@jj WOW, really a very informative topic. Thanks

baltoro

If they had some kind of official recognition for ALL-TIME GREAT MASM THREADS,...surely, the above mentioned thread,...would be a contender.
Thanks, Jochen.
Baltoro

dedndave

for the sake of readability, the PROTO version is best
as far as speed is concerned, it makes little difference for 98% of the API functions, really
this is especially true for the functions you are using in your example code

a handful of API functions are quite fast   :P
but, for speed to be an issue in those cases, the call is likely inside a loop or otherwise called thousands of times
something like TextOut, ExtTextOut, or other draw functions are the only real examples i can think of
these functions are pretty fast, but it's always nice to speed up a draw loop   :U

it is worth mentioning that the import/externdef method does not yield a direct (relative) call, either
it merely once dereferences a twice referenced call   :bg

near the end of the thread Jochen mentioned...
http://www.masm32.com/board/index.php?topic=11541.msg87615#msg87615
i wrote some code that completely dereferences the call

dedndave

#5
here is an updated version of that code
DeRef   PROTO   :LPVOID,:LPVOID,:LPVOID

;**********************************************************************************************

        OPTION  PROLOGUE:None
        OPTION  EPILOGUE:None

DeRef   PROC    lpStart:LPVOID,lpStop:LPVOID,lpExclusions:LPVOID

;Dereference IAT Branches
;DednDave - 7, 2011
;
;  Dereferences all branches into the IAT, starting at lpStart, and ending at lpStop.
;If lpStop is NULL, the beginning of the IAT is used as the stop address.
;An exclusion list may be used to exclude specific addresses from conversion.
;Addresses in the list represent the address of the first instruction byte.
;If present, the list must be terminated with a NULL dword.
;No exclusion list is used if the lpExclusions parameter is NULL.
;
;  This version allows for the conversion of JMP's, CALL's and conditional branches.
;It will convert declspec JMP's and CALL's, including those into the MSVCRT, etc.
;
;  The converted instructions always result in a relative branch directly to the imported code.
;This will prevent hooking into the IAT, which may be overridden by hooking prior to DeRef
;execution or by use of the exclusion list.
;
;----------------------------------------------------
;
;[EBP+28] = lpExclusions
;[EBP+24] = lpStop
;[EBP+20] = lpStart
;[EBP-4]  = address of IAT start
;[EBP-8]  = address of IAT end
;[EBP-12] = adjusted lpStop (lpStop-4)
;[EBP-16] = original access protection value
;[EBP-20] = dereference count
;
;----------------------------------------------------

        push    ebx
        push    esi
        push    edi
        push    ebp
        push    6
        xor     edx,edx
        pop     ebx
        mov     esi,VirtualProtect
        mov     ebp,esp
        mov     eax,25FFh
        push    esi
        jmp short DeRef1

DeRef0: mov     edx,esi
        sub     esi,ebx

DeRef1: cmp     ax,[esi]
        jz      DeRef0

        pop     esi
        or      edx,edx
        push    edx                        ;[EBP-4] = address of IAT start
        jz      DeRefL

DeRef2: mov     ecx,esi
        add     esi,ebx
        cmp     ax,[esi]
        jz      DeRef2

        mov     edi,[ebp+24]               ;lpStop
        add     ecx,2
        or      edi,edi
        push    ecx                        ;[EBP-8] = address of IAT end
        jnz     DeRef3

        mov     edi,edx

DeRef3: mov     edx,edi
        mov     esi,[ebp+20]               ;lpStart
        sub     edx,4
        sub     edi,esi
        push    edx                        ;[EBP-12] = adjusted lpStop (lpStop-4)
        mov     [ebp+24],edi               ;[EBP+24] is now code stream length
        push    eax                        ;[EBP-16] = previous access protection value
        INVOKE  VirtualProtect,esi,edi,PAGE_EXECUTE_READWRITE,esp
        or      eax,eax
        push    0                          ;[EBP-20] = dereference count
        jnz     DeRefH

        jmp     DeRefL

DeRef4: xor     ecx,ecx
        cmp     al,0E8h                    ;CALL relative (to JMP indirect)
        mov     edx,ecx
        jz      DeRef5

        cmp     al,0E9h                    ;JMP relative (to JMP indirect)
        jz      DeRef6

        inc     ecx
        cmp     ax,15FFh                   ;CALL indirect (declspec)
        mov     edx,ecx
        jz      DeRef6

        cmp     ax,25FFh                   ;JMP indirect (declspec)
        jz      DeRef6

        dec     edx
        cmp     al,0Fh                     ;relative conditional branch candidate
        jnz     DeRefI

        cmp     ah,80h                     ;relative conditional branch candidate
        jb      DeRefI

        cmp     ah,8Fh                     ;relative conditional branch (to JMP indirect)
        jbe     DeRef6

        jmp     DeRefI

DeRef5: cmp     esi,offset DeRefL-4
        jz      DeRefI

DeRef6: mov     edi,esi
        add     edi,ecx
        cmp     edi,[ebp-12]               ;adjusted lpStop (lpStop-4)
        ja      DeRefK

        or      edx,edx
        mov     ebx,[edi]
        jz      DeRef9

        push    esi
        mov     esi,[ebp-4]                ;address of IAT start
        add     esi,2

DeRef7: cmp     ebx,[esi]
        jnz     DeRef8

        pop     esi
        jmp short DeRefA

DeRef8: add     esi,6
        cmp     esi,[ebp-8]                ;address of IAT end
        jbe     DeRef7

        pop     esi
        jmp     DeRefI

DeRef9: add     ebx,edi
        add     ebx,4
        cmp     ebx,[ebp-4]                ;address of IAT start
        jb      DeRefI

        cmp     ebx,[ebp-8]                ;address of IAT end
        ja      DeRefI

        push    ebx
        push    eax
        push    edx
        sub     ebx,[ebp-4]                ;address of IAT start
        mov     eax,0AAAAAAABh
        push    ebx
        mul     ebx
        shr     edx,2
        mov     eax,6
        mul     edx
        pop     ebx
        cmp     eax,ebx
        pop     edx
        pop     eax
        pop     ebx
        jnz     DeRefI

DeRefA: mov     ecx,[ebp+28]               ;lpExclusions
        or      ecx,ecx
        jz      DeRefD

        push    eax
        dec     esi
        jmp short DeRefC

DeRefB: add     ecx,4
        cmp     eax,esi
        jnz     DeRefC

        inc     esi
        pop     eax
        jmp short DeRefI

DeRefC: mov     eax,[ecx]
        or      eax,eax
        jnz     DeRefB

        inc     esi
        pop     eax

DeRefD: or      edx,edx
        jz      DeRefF

        cmp     ah,15h
        mov     ebx,[ebx]
        mov     ax,0E890h
        jz      DeRefE

        inc     ah

DeRefE: mov     [esi-1],ax
        jmp short DeRefG

DeRefF: mov     ecx,[ebx+2]
        mov     ebx,[ecx]

DeRefG: sub     ebx,edi
        sub     ebx,4
        mov     [edi],ebx
        inc dword ptr [ebp-20]             ;dereference count
        add     edi,4
        mov     esi,edi

DeRefH: mov     ax,[esi]
        inc     esi
        jmp short DeRefJ

DeRefI: inc     esi
        mov     al,ah
        mov     ah,[esi]

DeRefJ: cmp     esi,[ebp-12]               ;adjusted lpStop (lpStop-4)
        jbe     DeRef4

DeRefK: lea     edx,[ebp-12]
        INVOKE  VirtualProtect,[ebp+20],[ebp+24],[ebp-16],edx

DeRefL: pop     eax                        ;return dereference count
        leave
        pop     edi
        pop     esi
        pop     ebx
        ret     12

DeRef   ENDP

        OPTION  PROLOGUE:PrologueDef
        OPTION  EPILOGUE:EpilogueDef

;**********************************************************************************************

WYVERN666

@dedndave Sorry, i dont understand what you mean with "Dereference IAT Branches", what is the proposit ??  :eek

dedndave

#7
well, when you use INVOKE, it generates a "relative" CALL to a JMP
        call    IatJumpTable+SomeOffset

at the location, there is an "indirect" JMP that branches to an address that is in yet another table
        jmp dword ptr [SomePointer]

at the location "SomePointer" is the actual address of the code to be executed
(which is filled in by the operating system at load-time)

the routine in my previous post locates that address
it then uses the address of the original CALL (from your INVOKE) and calculates the "relative" CALL
        call    CodeAtSomePointer

when you use the EXTERNDEF (or "declspec") method, one of these references is removed
using the names in the above scenario, it generates code that looks something like this...
        call dword ptr [SomePointer]
thus, eliminating the IAT table entry step
notice that it is still an indirect CALL

WYVERN666