汇编语言中的CALL指令 - 它是如何工作的?

5

我希望能够清晰地解释,在Windows环境(PE可执行文件)中,如何使用CALL XXXXXXXXXXXXXXX指令。我一直在研究PE格式,但是我对CALL ADDRESS指令、从DLL导入函数以及CALL ADDRESS如何访问DLL中的代码之间的关系感到困惑。除了ASLR和其他安全功能可能会移动DLL之外,可执行文件如何应对这种情况?


2
http://en.wikipedia.org/wiki/Relocation_%28computing%29#Relocation_table - Hans Passant
3个回答

11

直接使用普通的相对调用导入是不起作用的,这就是为什么不会这样做的原因。

要调用已导入的函数,需要通过 Import Address Table (IAT) 进行调用。简单来说,IAT 中的条目首先指向函数名称(即最初作为 Import Name Table 的副本),然后这些指针被加载程序更改为指向实际的函数。

IAT 位于固定地址,但如果映像已被重新基址化,则可以重新定位,因此通过它进行调用只涉及单个间接引用-因此使用带有内存操作数(即简单常量)的 call r/m 调用已导入的函数,例如 call [0x40206C]


只有两点需要指出:如果“不起作用”指的是在机器码级别和汇编语言级别使用CALL ADDRESS,那么它确实可以正常工作。也就是说,这种说法是不正确的。另外,如果你的意思是IAT在程序的不同运行中位于相同的地址,那么它也不是固定的地址。也就是说,这种说法也是不正确的。我没有对这个回答进行负评,因为在我的回答评论中,你已经表示“固定地址”的意思并不是指它在固定地址上,所以可能这个回答是可以修改的。 - Cheers and hth. - Alf
有关具体、完整、可重现的示例,请参见我的答案。 - Cheers and hth. - Alf
@Cheersandhth.-Alf,你知道我在技术上包括了“但如果图像已经被重新定位,则可以重新定位”(重新定位是唯一会移动的情况,对吧?),这不够吗?特别是考虑到OP正在研究PE格式,而这个问题,我认为只是为了消除使用相对调用来调用导入项的困惑。我很抱歉我不同意“调用地址有效”的说法,因为你并没有使用相对调用来调用导入项,你只是在调用跳转。顺便说一句:我仍然是你答案的唯一点赞者 ;) - harold
机器码 CALL ADDRESS 确实调用了一个导入,但该导入本身并不是 DLL 函数。正如汇编级别所示,CALL ADDRESS 可以映射到不同的机器码指令。我认为这可能会缩小你所说的“它”的范围,但当指代物的含义由约束条件决定时,而不是通常的相反情况,即句子说明指代物的某些内容时,这种描述变得非常循环。 - Cheers and hth. - Alf
你可以尝试使用三个不同的调用来测试这个例子,它已经很完整了。并且在回答中尽量缩小那个极其模糊的“它”的范围,将其替换为你想要引用的内容。 - Cheers and hth. - Alf
显示剩余2条评论

5

2013年1月22日:添加了更多简单的具体例子和讨论,因为(A)一个错误的答案被选为解决方案,(B)我的原始答案显然没有被一些读者理解,包括OP。对此我很抱歉,是我的错。当时我匆忙回答,只是添加了一个我已经掌握的代码示例。


我如何理解这个问题。

您问道,

“我一直在研究PE格式,但是我对CALL ADDRESS指令、从dll中导入函数以及CALL ADDRESS如何访问DLL中的代码之间的关系感到非常困惑。”

CALL ADDRESS这个术语在C++级别上没有太多意义,因此我假设您指的是汇编语言或机器码级别的CALL ADDRESS。

问题在于,当一个DLL被加载到某个地址而不是首选地址时,如何将call指令与DLL函数连接起来?


简而言之

  • 在机器代码级别上,指定地址的call通过调用一个最小的转发例程来工作,该例程由单个jmp指令组成。 jmp指令通过表查找调用DLL函数。 通常,DLL的导入库会导出DLL函数本身,带有__imp__名称前缀的包装程序以及没有这种名称前缀的包装程序,例如__imp__MessageBoxA@16_MessageBoxA@16

也就是说,除了我下面编造的名称外,汇编器通常将

    call MessageBox

翻译为

    call MessageBox_forwarder
     ; 这里可以是任何内容
    MessageBox_forwarder: jmp ds:[MessageBox_tableEntry]

当加载DLL时,加载器会将相关地址放置在表中。

  • 在汇编语言级别,使用标识符指定的例程的call可能会映射到一个转发器的call或通过表查找直接调用DLL函数的call,这取决于为标识符声明的类型。

  • 甚至对于从同一DLL导入的内容,DLL函数地址可以有多个表。但通常将它们视为一个大表,然后称为“导入地址表”(IAT)。IAT表(更准确地说是表)每个都在图像中的固定位置,即当代码被加载到不首选的位置时,它们会随着代码一起移动,而不是在固定的地址。

当前选择的解决方案答案在以下方面是不正确的:

  • 该答案认为“这样做行不通,这就是为什么不会这样做。”,其中“它”可能指的是CALL ADDRESS。但在汇编或机器码级别上使用CALL ADDRESS调用DLL函数是完全可行的,只要正确使用。

  • 该答案认为IAT位于固定地址。但实际上并非如此。


CALL ADDRESS指令正常工作。

让我们考虑一个具体的CALL ADDRESS指令,其中地址是非常著名的DLL函数的地址,即从[user32.dll] DLL调用MessageBoxA Windows API函数:

call MessageBoxA

使用此指令没有问题。
如下所示,在机器码级别上,这个“call”指令本身只包含一个偏移量,导致调用一个“jmp”指令,该指令在函数指针的导入地址表中查找DLL例程地址,通常由加载器在加载相关的DLL时进行修复。
为了能够检查机器代码,这里有一个完整的32位x86汇编语言程序,使用具体的例子指令:
.model flat, stdcall
option casemap :none        ; Case sensitive identifiers, please.
_as32bit        textequ <DWord ptr>

public  start

ExitProcess                     proto stdcall :DWord

MessageBoxA_t                   typedef proto stdcall :DWord, :DWord, :DWord, :DWord
extern MessageBoxA              : MessageBoxA_t
extern _imp__MessageBoxA@16     : ptr MessageBoxA_t

MB_ICONINFORMATION      equ     0040h
MB_SETFOREGROUND        equ     00010000h
infoBoxOptions          equ     MB_ICONINFORMATION or MB_SETFOREGROUND

    .const
boxtitle_1  db  "Just FYI 1 (of 3):", 0
boxtitle_2  db  "Just FYI 2 (of 3):", 0
boxtitle_3  db  "Just FYI 3 (of 3):", 0
boxtext     db  "There’s intelligence somewhere in the universe", 0

    .code
start:
    push infoBoxOptions
    push offset boxtitle_1
    push offset boxtext
    push 0
    call MessageBoxA                    ; Call #1 - to jmp to DLL-func.

    push infoBoxOptions
    push offset boxtitle_2
    push offset boxtext
    push 0
    call ds:[_imp__MessageBoxA@16]      ; Call #2 - directly to DLL-func.

    push infoBoxOptions
    push offset boxtitle_3
    push offset boxtext
    push 0
    call _imp__MessageBoxA@16           ; Call #3 - same as #2, due to type of identifier.

    push 0  ; Exit code, 0 indicates success.
    call ExitProcess
end

使用Microsoft的工具链进行组装和链接,其中/debug链接器选项要求链接器生成一个PDB调试信息文件,以便与Visual Studio调试器一起使用:
[d:\dev\test\call]
> ml /nologo /c asm_call.asm
 正在汇编: asm_call.asm
[d:\dev\test\call] > link /nologo asm_call.obj kernel32.lib user32.lib /entry:start /subsystem:windows /debug [d:\dev\test\call] > dir asm* /b asm_call.asm asm_call.exe asm_call.ilk asm_call.obj asm_call.pdb
[d:\dev\test\call] > _
现在调试的一个简单方法是启动Visual Studio([devenv.exe]程序),然后在Visual Studio中,单击[调试逐步执行],或者只需按F11即可:
[d:\dev\test\call]
> devenv asm_call.exe

[d:\dev\test\call]
> _

enter image description here

在上图中展示了Visual Studio 2012调试器的操作,最左边的大红箭头显示了机器码指令中的地址信息,即0000004E十六进制(注意:最低位字节位于最低地址,即内存中首位),而另一个大红箭头则显示了这个非常小的神奇数字不可思议地指定了_MessageBoxA@16函数,据调试器所知,该函数位于地址01161064h hex处。
  • CALL ADDRESS指令中的地址数据是一个偏移量,相对于下一条指令的地址,因此它不需要进行任何DLL放置更改的修补。

  • 调用转到的地址只包含一个jmp ds: [IAT_entry_for_MessageBoxA]

  • 这个转发代码来自导入库,而不是DLL,因此它也不需要修补(但显然它确实会得到一些特殊处理,就像DLL函数地址一样)。

第二个调用指令直接执行了第一个jmp所做的事情,即在IAT表中查找DLL函数地址。
现在可以看到第三个调用指令在机器代码级别上与第二个指令相同。显然不知道如何在汇编中模拟Visual C++的declspec(dllimport)。上述声明方式是一种方法,可能与文本等效结合使用。


IAT不在固定地址。

以下C++程序报告了它被加载的地址,它从哪些模块导入了什么DLL函数,以及各种IAT表位于何处。

当使用现代版本的Microsoft工具链进行构建时,只使用默认值,它通常在每次运行时都会加载到不同的地址。

您可以通过使用链接器选项/dynamicbase:no来防止这种行为。

#include <assert.h>         // assert
#include <stddef.h>         // ptrdiff_t
#include <sstream>
using std::ostringstream;

#undef UNICODE
#define UNICODE
#include <windows.h>

template< class Result, class SomeType >
Result as( SomeType const p ) { return reinterpret_cast<Result>( p ); }

template< class Type >
class OffsetTo
{
private:
    ptrdiff_t offset_;
public:
    ptrdiff_t asInteger() const { return offset_; }
    explicit OffsetTo( ptrdiff_t const offset ): offset_( offset ) {}
};

template< class ResultPointee, class SourcePointee >
ResultPointee* operator+(
    SourcePointee* const            p,
    OffsetTo<ResultPointee> const   offset
    )
{
    return as<ResultPointee*>( as<char const*>( p ) + offset.asInteger() );
}

int main()
{
    auto const pImage =
        as<IMAGE_DOS_HEADER const*>( ::GetModuleHandle( nullptr ) );
    assert( pImage->e_magic == IMAGE_DOS_SIGNATURE );

    auto const pNTHeaders =
        pImage + OffsetTo<IMAGE_NT_HEADERS const>( pImage->e_lfanew );
    assert( pNTHeaders->Signature == IMAGE_NT_SIGNATURE );

    auto const& importDir =
        pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

    auto const pImportDescriptors = pImage + OffsetTo<IMAGE_IMPORT_DESCRIPTOR const>(
        importDir.VirtualAddress //+ importSectionHeader.PointerToRawData
        );

    ostringstream stream;
    stream << "I'm loaded at " << pImage << ", and I'm using...\n";
    for( int i = 0;  pImportDescriptors[i].Name != 0;  ++i )
    {
        auto const pModuleName = pImage + OffsetTo<char const>( pImportDescriptors[i].Name );

        DWORD const offsetNameTable = pImportDescriptors[i].OriginalFirstThunk;
        DWORD const offsetAddressTable = pImportDescriptors[i].FirstThunk;  // The module "IAT"

        auto const pNameTable = pImage + OffsetTo<IMAGE_THUNK_DATA const>( offsetNameTable );
        auto const pAddressTable = pImage + OffsetTo<IMAGE_THUNK_DATA const>( offsetAddressTable );

        stream << "\n* '" << pModuleName << "'";
        stream << " with IAT at " << pAddressTable << "\n";
        stream << "\t";
        for( int j = 0; pNameTable[j].u1.AddressOfData != 0; ++j )
        {
            auto const pFuncName =
                pImage + OffsetTo<char const>( 2 + pNameTable[j].u1.AddressOfData );
            stream << pFuncName << " ";
        }
        stream << "\n";
    }

    MessageBoxA(
        0,
        stream.str().c_str(),
        "FYI:",
        MB_ICONINFORMATION | MB_SETFOREGROUND
        );
}

enter image description here


一个能够自我复制的Windows机器码程序。

最后,根据我的原始回答,这里有一个我为另一个目的编写的微软汇编器(MASM)程序,它展示了一些问题,因为它的本质是(它产生的输出源代码在汇编和运行后会产生相同的源代码,以此类推),它必须是完全可重定位的代码,并且仅仅需要来自普通程序加载器的最基本帮助:

.model flat, stdcall
option casemap :none        ; Case sensitive identifiers, please.
dword_aligned textequ <4>   ; Just for readability.

    ; Windows API functions:
    extern  ExitProcess@4: proc         ; from [kernel32.dll]
    extern  GetStdHandle@4: proc        ; from [kernel32.dll]
    extern  WriteFile@20: proc          ; from [kernel32.dll]
    extern  wsprintfA: proc             ; from [user32.dll]

    STD_OUTPUT_HANDLE       equ     -11

        ; The main code.
GlobalsStruct   struct  dword_aligned
    codeStart               dword   ?
    outputStreamHandle      dword   ?
GlobalsStruct   ends
globals         textequ     <(GlobalsStruct ptr [edi])>

    .code
startup:
    jmp     code_start

    ; Trampolines to add references to these functions.
myExitProcess:    jmp ExitProcess@4
myGetStdHandle:   jmp GetStdHandle@4     
myWriteFile:      jmp WriteFile@20
mywsprintfA:      jmp wsprintfA


;------------------------------------------------------------------
;
;               The code below is reproduced, so it's all relative.

code_start:
    jmp     main

prologue:
byte    ".model flat, stdcall", 13, 10
byte    "option casemap :none", 13, 10
byte    13, 10
byte    "    extern  ExitProcess@4: proc", 13, 10
byte    "    extern  GetStdHandle@4: proc", 13, 10
byte    "    extern  WriteFile@20: proc", 13, 10
byte    "    extern  wsprintfA: proc", 13, 10
byte    13, 10
byte    "    .code", 13, 10
byte    "startup:", 13, 10
byte    "    jmp     code_start", 13, 10
byte    13, 10
byte    "jmp ExitProcess@4", 13, 10
byte    "jmp GetStdHandle@4", 13, 10
byte    "jmp WriteFile@20", 13, 10
byte    "jmp wsprintfA", 13, 10
byte    13, 10
byte    "code_start:", 13, 10
prologue_nBytes         equ     $ - prologue

epilogue:
byte    "code_end:", 13, 10
byte    "    end startup", 13, 10
epilogue_nBytes         equ     $ - epilogue

dbDirective             byte    4 dup( ' ' ), "byte       "
dbDirective_nBytes      equ     $ - dbDirective

numberFormat            byte    " 0%02Xh", 0
numberFormat_nBytes     equ     $ - numberFormat

comma                   byte    ","
windowsNewline          byte    13, 10

write:
    push    0           ; space for nBytesWritten
    mov     ecx, esp    ; &nBytesWritten

    push    0           ; lpOverlapped
    push    ecx         ; &nBytesWritten
    push    ebx         ; nBytes
    push    eax         ; &s[0]
    push    globals.outputStreamHandle
    call    myWriteFile

    pop     eax         ; nBytesWritten
    ret

displayMachineCode:
    dmc_LocalsStruct    struct  dword_aligned
        numberStringLen     dword   ?
        numberString        byte    16*4 DUP( ? )
        fileHandle          dword   ?
        nBytesWritten       dword   ?
        byteIndex           dword   ?
    dmc_LocalsStruct    ends
    dmc_locals          textequ     <[ebp - sizeof dmc_LocalsStruct].dmc_LocalsStruct>

    mov     ebp, esp
    sub     esp, sizeof dmc_LocalsStruct

    ; Output prologue that makes MASM happy (placing machine code data in context):
    ; lea     eax, prologue
        mov     eax, globals.codeStart
        add     eax, prologue - code_start
    mov     ebx, prologue_nBytes
    call    write

    ; Output the machine code bytes.
    mov     dmc_locals.byteIndex, 0

dmc_lineLoop:
    ; loop start
            ; Output a db directive
        ;lea     eax, dbDirective
            mov     eax, globals.codeStart
            add     eax, dbDirective - code_start
        mov     ebx, dbDirective_nBytes
        call    write

    dmc_byteIndexingLoop:
        ; loop start
                ; Create string representation of a number
            mov     ecx, dmc_locals.byteIndex
            mov     eax, 0
            ;mov     al, byte ptr [code_start + ecx]
                mov     ebx, globals.codeStart
                mov     al, [ebx + ecx]
            push    eax
            ;push    offset numberFormat
                mov     eax, globals.codeStart
                add     eax, numberFormat - code_start
                push    eax
            lea     eax, dmc_locals.numberString
            push    eax
            call    mywsprintfA
            add     esp, 3*(sizeof dword)
            mov     dmc_locals.numberStringLen, eax

                ; Output string representation of number
            lea     eax, dmc_locals.numberString
            mov     ebx, dmc_locals.numberStringLen
            call    write

                ; Are we finished looping yet?
            inc     dmc_locals.byteIndex
            mov     ecx, dmc_locals.byteIndex
            cmp     ecx, code_end - code_start
            je      dmc_finalNewline
            and     ecx, 07h
            jz      dmc_after_byteIndexingLoop

                ; Output a comma
            ; lea     eax, comma
                mov     eax, globals.codeStart
                add     eax, comma - code_start
            mov     ebx, 1
            call    write
            jmp dmc_byteIndexingLoop
        ; loop end

    dmc_after_byteIndexingLoop:
            ; New line
        ; lea     eax, windowsNewline
            mov     eax, globals.codeStart
            add     eax, windowsNewline - code_start
        mov     ebx, 2
        call    write
        jmp     dmc_lineLoop;
    ; loop end

dmc_finalNewline:
        ; New line
    ; lea     eax, windowsNewline
        mov     eax, globals.codeStart
        add     eax, windowsNewline - code_start
    mov     ebx, 2
    call    write

    ; Output epilogue that makes MASM happy:
    ; lea     eax, epilogue
        mov     eax, globals.codeStart
        add     eax, epilogue - code_start
    mov     ebx, epilogue_nBytes
    call    write

    mov     esp, ebp
    ret

main:
    sub esp, sizeof GlobalsStruct
    mov edi, esp

    call    main_knownAddress
main_knownAddress:
    pop     eax
    sub     eax, main_knownAddress - code_start
    mov     globals.codeStart, eax

    push    STD_OUTPUT_HANDLE
    call    myGetStdHandle
    mov     globals.outputStreamHandle, eax

    call displayMachineCode

    ; Well behaved process exit:
    push 0                          ; Process exit code, 0 indicates success.
    call myExitProcess

code_end:
    end startup

这里是自我复制的输出:

.model flat, stdcall
option casemap :none

    extern  ExitProcess@4: proc
    extern  GetStdHandle@4: proc
    extern  WriteFile@20: proc
    extern  wsprintfA: proc

    .code
startup:
    jmp     code_start

jmp ExitProcess@4
jmp GetStdHandle@4
jmp WriteFile@20
jmp wsprintfA

code_start:
    byte        0E9h, 03Bh, 002h, 000h, 000h, 02Eh, 06Dh, 06Fh
    byte        064h, 065h, 06Ch, 020h, 066h, 06Ch, 061h, 074h
    byte        02Ch, 020h, 073h, 074h, 064h, 063h, 061h, 06Ch
    byte        06Ch, 00Dh, 00Ah, 06Fh, 070h, 074h, 069h, 06Fh
    byte        06Eh, 020h, 063h, 061h, 073h, 065h, 06Dh, 061h
    byte        070h, 020h, 03Ah, 06Eh, 06Fh, 06Eh, 065h, 00Dh
    byte        00Ah, 00Dh, 00Ah, 020h, 020h, 020h, 020h, 065h
    byte        078h, 074h, 065h, 072h, 06Eh, 020h, 020h, 045h
    byte        078h, 069h, 074h, 050h, 072h, 06Fh, 063h, 065h
    byte        073h, 073h, 040h, 034h, 03Ah, 020h, 070h, 072h
    byte        06Fh, 063h, 00Dh, 00Ah, 020h, 020h, 020h, 020h
    byte        065h, 078h, 074h, 065h, 072h, 06Eh, 020h, 020h
    byte        047h, 065h, 074h, 053h, 074h, 064h, 048h, 061h
    byte        06Eh, 064h, 06Ch, 065h, 040h, 034h, 03Ah, 020h
    byte        070h, 072h, 06Fh, 063h, 00Dh, 00Ah, 020h, 020h
    byte        020h, 020h, 065h, 078h, 074h, 065h, 072h, 06Eh
    byte        020h, 020h, 057h, 072h, 069h, 074h, 065h, 046h
    byte        069h, 06Ch, 065h, 040h, 032h, 030h, 03Ah, 020h
    byte        070h, 072h, 06Fh, 063h, 00Dh, 00Ah, 020h, 020h
    byte        020h, 020h, 065h, 078h, 074h, 065h, 072h, 06Eh
    byte        020h, 020h, 077h, 073h, 070h, 072h, 069h, 06Eh
    byte        074h, 066h, 041h, 03Ah, 020h, 070h, 072h, 06Fh
    byte        063h, 00Dh, 00Ah, 00Dh, 00Ah, 020h, 020h, 020h
    byte        020h, 02Eh, 063h, 06Fh, 064h, 065h, 00Dh, 00Ah
    byte        073h, 074h, 061h, 072h, 074h, 075h, 070h, 03Ah
    byte        00Dh, 00Ah, 020h, 020h, 020h, 020h, 06Ah, 06Dh
    byte        070h, 020h, 020h, 020h, 020h, 020h, 063h, 06Fh
    byte        064h, 065h, 05Fh, 073h, 074h, 061h, 072h, 074h
    byte        00Dh, 00Ah, 00Dh, 00Ah, 06Ah, 06Dh, 070h, 020h
    byte        045h, 078h, 069h, 074h, 050h, 072h, 06Fh, 063h
    byte        065h, 073h, 073h, 040h, 034h, 00Dh, 00Ah, 06Ah
    byte        06Dh, 070h, 020h, 047h, 065h, 074h, 053h, 074h
    byte        064h, 048h, 061h, 06Eh, 064h, 06Ch, 065h, 040h
    byte        034h, 00Dh, 00Ah, 06Ah, 06Dh, 070h, 020h, 057h
    byte        072h, 069h, 074h, 065h, 046h, 069h, 06Ch, 065h
    byte        040h, 032h, 030h, 00Dh, 00Ah, 06Ah, 06Dh, 070h
    byte        020h, 077h, 073h, 070h, 072h, 069h, 06Eh, 074h
    byte        066h, 041h, 00Dh, 00Ah, 00Dh, 00Ah, 063h, 06Fh
    byte        064h, 065h, 05Fh, 073h, 074h, 061h, 072h, 074h
    byte        03Ah, 00Dh, 00Ah, 063h, 06Fh, 064h, 065h, 05Fh
    byte        065h, 06Eh, 064h, 03Ah, 00Dh, 00Ah, 020h, 020h
    byte        020h, 020h, 065h, 06Eh, 064h, 020h, 073h, 074h
    byte        061h, 072h, 074h, 075h, 070h, 00Dh, 00Ah, 020h
    byte        020h, 020h, 020h, 062h, 079h, 074h, 065h, 020h
    byte        020h, 020h, 020h, 020h, 020h, 020h, 020h, 030h
    byte        025h, 030h, 032h, 058h, 068h, 000h, 02Ch, 00Dh
    byte        00Ah, 06Ah, 000h, 08Bh, 0CCh, 06Ah, 000h, 051h
    byte        053h, 050h, 0FFh, 077h, 004h, 0E8h, 074h, 0FEh
    byte        0FFh, 0FFh, 058h, 0C3h, 08Bh, 0ECh, 083h, 0ECh
    byte        050h, 08Bh, 007h, 005h, 005h, 000h, 000h, 000h
    byte        0BBh, 036h, 001h, 000h, 000h, 0E8h, 0D7h, 0FFh
    byte        0FFh, 0FFh, 0C7h, 045h, 0FCh, 000h, 000h, 000h
    byte        000h, 08Bh, 007h, 005h, 057h, 001h, 000h, 000h
    byte        0BBh, 00Fh, 000h, 000h, 000h, 0E8h, 0BFh, 0FFh
    byte        0FFh, 0FFh, 08Bh, 04Dh, 0FCh, 0B8h, 000h, 000h
    byte        000h, 000h, 08Bh, 01Fh, 08Ah, 004h, 019h, 050h
    byte        08Bh, 007h, 005h, 066h, 001h, 000h, 000h, 050h
    byte        08Dh, 045h, 0B4h, 050h, 0E8h, 02Ah, 0FEh, 0FFh
    byte        0FFh, 083h, 0C4h, 00Ch, 089h, 045h, 0B0h, 08Dh
    byte        045h, 0B4h, 08Bh, 05Dh, 0B0h, 0E8h, 08Fh, 0FFh
    byte        0FFh, 0FFh, 0FFh, 045h, 0FCh, 08Bh, 04Dh, 0FCh
    byte        081h, 0F9h, 068h, 002h, 000h, 000h, 074h, 02Bh
    byte        083h, 0E1h, 007h, 074h, 013h, 08Bh, 007h, 005h
    byte        06Eh, 001h, 000h, 000h, 0BBh, 001h, 000h, 000h
    byte        000h, 0E8h, 06Bh, 0FFh, 0FFh, 0FFh, 0EBh, 0AAh
    byte        08Bh, 007h, 005h, 06Fh, 001h, 000h, 000h, 0BBh
    byte        002h, 000h, 000h, 000h, 0E8h, 058h, 0FFh, 0FFh
    byte        0FFh, 0EBh, 086h, 08Bh, 007h, 005h, 06Fh, 001h
    byte        000h, 000h, 0BBh, 002h, 000h, 000h, 000h, 0E8h
    byte        045h, 0FFh, 0FFh, 0FFh, 08Bh, 007h, 005h, 03Bh
    byte        001h, 000h, 000h, 0BBh, 01Ch, 000h, 000h, 000h
    byte        0E8h, 034h, 0FFh, 0FFh, 0FFh, 08Bh, 0E5h, 0C3h
    byte        083h, 0ECh, 008h, 08Bh, 0FCh, 0E8h, 000h, 000h
    byte        000h, 000h, 058h, 02Dh, 04Ah, 002h, 000h, 000h
    byte        089h, 007h, 06Ah, 0F5h, 0E8h, 098h, 0FDh, 0FFh
    byte        0FFh, 089h, 047h, 004h, 0E8h, 023h, 0FFh, 0FFh
    byte        0FFh, 06Ah, 000h, 0E8h, 084h, 0FDh, 0FFh, 0FFh
code_end:
    end startup

@JonathonReinhart:很抱歉代码没有帮助你理解任何东西,但我不认为这是我的错。无论如何,如果你从标签“start”开始,那么在第一条指令之后有一个“jmp”指令表。这个表与链接器生成并由加载器修复的DLL函数指针表(跳转到DLL函数)几乎相同,并且使用方式也相同。因此,代码明确了这个问题,在文本中只是抽象地讨论了这个问题。需要学习者付出一些努力 - Cheers and hth. - Alf
请注意,该表并非“位于固定地址”,正如SO读者投票支持的另一个答案所说。整个重点在于调用指令使用相对寻址,因此可以自由地重新定位图像,而无需修复单个调用指令。 - Cheers and hth. - Alf
@harold:“通常”并不是非常可靠的。我建议不要依赖它。无论如何,在Windows Vista之前,可执行文件(与DLL相对)通常在固定地址加载,该地址在头文件中指定,主要是因为可执行文件可以作为第一个加载的模块。但仍然可能在其他地址加载。随着Windows Vista的推出,微软引入了“地址空间布局随机化”(ASLR)作为一种安全性通过混淆措施。这是/曾经是一种选择加入的东西,您可以通过/DYNAMICBASE链接器选项请求。 - Cheers and hth. - Alf
当然,映像被重新定位时IAT也会移动,这是毋庸置疑的 :) - harold
@Alf:很酷。我曾经以编写内存破坏漏洞为生,每天都要处理ASLR,但当然,你最懂。 - SecurityMatt
显示剩余2条评论

0
链接器。当你的可执行文件被链接时,链接器将替换并基于所有DLL地址。由于虚拟内存的存在,所有进程都在相同的基地址加载,从而使寻址更加容易。由于DLL是位置无关代码(PIL),加载程序可以为应用程序重新定位DLL。因为代码引用的符号可以被链接器重新定位,所以它永远不需要担心自己的位置。
编辑:刚意识到其中一些是错误的——Linux的动态库是PIL,而Windows的动态库则不是(这就是为什么我们必须进行重新定位的原因)。

3
要么你打字太快了,要么你的键盘坏了。 :) - Alexey Frunze
@AlexeyFrunze:感谢您的编辑 :) (我用iPad打字--对于代码,我会去电脑上写,但对于文本,我相信我的iPad键盘不会让我看起来像一个打字无能的人)。 - Linuxios
@Cheersandhth.-Alf: 但比iPad的好。大多数情况下,我使用全尺寸的苹果键盘,所以我的打字比较好 :)。 - Linuxios

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接