1. 程式人生 > >ELF檔案載入與動態連結(二)

ELF檔案載入與動態連結(二)

GOT應該儲存的是puts函式的絕對虛地址,這裡為什麼儲存的卻是[email protected]的第二條指令呢? 原來“直譯器”將動態庫載入記憶體後,並沒有直接將函式地址更新到GOT表中,而是在函式第一次被呼叫時,才會進行函式地址的重定位,這樣做的好處是可以加快程式載入速度,尤其對大型程式來說。有關這方面的更詳細的資訊,可以搜尋“動態連結庫的延遲繫結技術”。

繼續看第二條指令,pushq $0x0代表什麼? 檢視Hello world程式的重定位節:

[email protected]:~/workdir$ readelf -a hello
...
Relocation section '.rela.plt' at offset 0x398 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0
000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
000000601028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0

其中的第一項就是puts的重定位資訊,$0x0即代表puts相對於.rela.plt節的相對偏移。類似的可以看到$0x1代表__libc_start_main的相對偏移,$0x2代表__gmon_start__的相對偏移。

而OFFSET這個域表示的是puts函式在GOT表項中的位置,0000000000601018,從[email protected]的第一條指令可以看出這一點。

向堆疊中壓入這個偏移量的目的一是找到puts函式的符號名,二是找到puts函式地址在GOT表項中的位置,以便後面定位到puts的相對虛地址時寫入到這個GOT表項。

[email protected]的第三條指令就跳到了PLT0的位置。這條指令只是將0x400400這個數值壓入堆疊,它實際上是GOT表項的第二個元素即GOT[1],上面寫了GOT[1]是共享庫連結串列的地址。

接著PLT0的第二條指令即跳到了GOT[2]中所儲存的地址,即_dl_runtime_resolve函式的入口。_dl_runtime_resolve的定義如下:

_dl_runtime_resolve:
    pushl %eax      # Preserve registers otherwise clobbered.
    pushl %ecx
    pushl %edx
    movl 16(%esp), %edx # Copy args pushed by PLT in register.  Note
    movl 12(%esp), %eax # that `fixup' takes its parameters in regs.
    call _dl_fixup      # Call resolver.
    popl %edx       # Get register content back.
    popl %ecx
    xchgl %eax, (%esp)  # Get %eax contents end store function address.
    ret $8          # Jump to function address.

 

_dl_runtime_resolve的彙編程式碼請參考: http://althing.cs.dartmouth.edu/secref/resources/plt-got.txt

從呼叫puts函式到現在,總共有兩次壓棧操作,一次是壓入puts函式的重定向資訊的偏移量(pushq $0x0),一次是GOT[1](pushq 0x200c02(%rip))。上面彙編指令的兩次movl操作就是將這兩個資料分別取到edx和eax,然後呼叫_dl_fixup找到puts函式的實際載入地址,將它寫到GOT中,並把這個地址壓入eax作為_dl_runtime_resolve函式的返回值。xchagl指令恢復eax暫存器,並將puts函式地址壓到棧頂,這樣當執行ret指令後,控制就轉移到puts函式內部。ret指令同時也完成了清棧動作,使棧頂指向puts函式的返回地址(main函式中callq的下一條指令),這樣當puts函式返回時程式就走到正確的位置執行。

當第二次呼叫puts函式時,由於其地址已經存在GOT對應表項中,直接根據地址跳轉就可以。下面畫了一張圖理解上述過程:

 

總結

使用者通過shell執行程式,shell通過exceve進入系統呼叫。【使用者態】
sys_execve經過一系列過程,並最終通過ELF檔案的處理函式load_elf_binary將使用者程式和ELF“直譯器”載入進記憶體,並將控制權交給“直譯器”。【核心態】
ELF“直譯器”進行相關共享庫的載入,並最終把控制權交給使用者程式。由“直譯器”處理使用者程式執行過程中符號的動態解析。【使用者態】

參考資料

    1. ELF檔案格式分析 —— 滕啟明

    2. ELF檔案的載入和動態連結過程

    3. PLT/GOT探索,http://althing.cs.dartmouth.edu/secref/resources/plt-got.txt

    4. Stackoverflow,_dl_runtime_resolve — When do the shared objects get loaded in to memory?

    5. rip相對定址,http://bbs.pediy.com/showthread.php?t=193425

    6. RIP相對定址技術,一句話總結,http://www.vxjump.net/files/virus_analysis/X64_vir_infect.txt