如何判斷 ELF 程序頭中的 p_vaddr 是實際記憶體地址還是只是與共享庫基地址的偏移量?
我試圖通過讀取程序頭來定位 libc 程序段在程序記憶體中的位置。
在 Centos 6 上,當我在 libc.so.6 文件上使用 readelf 時,VirtAddr 包含程序段在程序記憶體中載入的正確地址:
[user@centos6 src]$ readelf -l /lib64/libc.so.6 --wide Elf file type is DYN (Shared object file) Entry point 0x3032c1ee30 There are 10 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000003032c00040 0x0000003032c00040 0x000230 0x000230 R E 0x8 INTERP 0x15aab0 0x0000003032d5aab0 0x0000003032d5aab0 0x00001c 0x00001c R 0x10 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x000000 0x0000003032c00000 0x0000003032c00000 0x18a00c 0x18a00c R E 0x200000 LOAD 0x18a700 0x0000003032f8a700 0x0000003032f8a700 0x004f58 0x009228 RW 0x200000 DYNAMIC 0x18db40 0x0000003032f8db40 0x0000003032f8db40 0x0001f0 0x0001f0 RW 0x8 NOTE 0x000270 0x0000003032c00270 0x0000003032c00270 0x000044 0x000044 R 0x4 TLS 0x18a700 0x0000003032f8a700 0x0000003032f8a700 0x000010 0x000068 R 0x8 GNU_EH_FRAME 0x15aacc 0x0000003032d5aacc 0x0000003032d5aacc 0x0065ec 0x0065ec R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x8 GNU_RELRO 0x18a700 0x0000003032f8a700 0x0000003032f8a700 0x003900 0x003900 R 0x1
所以在這種情況下,DYNAMIC 段位於 0x0000003032f8db40
但是在 Centos 7 上, VirtAddr 包含一個偏移量,我必須將 libc 基地址添加到該偏移量中才能找到該段在記憶體中的位置:
[user@centos7 src]$ readelf -l /usr/lib64/libc.so.6 --wide Elf file type is DYN (Shared object file) Entry point 0x22660 There are 10 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000230 0x000230 R E 0x8 INTERP 0x18eb00 0x000000000018eb00 0x000000000018eb00 0x00001c 0x00001c R 0x10 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x1c3170 0x1c3170 R E 0x200000 LOAD 0x1c36f0 0x00000000003c36f0 0x00000000003c36f0 0x0051b0 0x009b10 RW 0x200000 DYNAMIC 0x1c6b60 0x00000000003c6b60 0x00000000003c6b60 0x0001f0 0x0001f0 RW 0x8 NOTE 0x000270 0x0000000000000270 0x0000000000000270 0x000044 0x000044 R 0x4 TLS 0x1c36f0 0x00000000003c36f0 0x00000000003c36f0 0x000010 0x0000a0 R 0x10 GNU_EH_FRAME 0x18eb1c 0x000000000018eb1c 0x000000000018eb1c 0x006aec 0x006aec R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10 GNU_RELRO 0x1c36f0 0x00000000003c36f0 0x00000000003c36f0 0x003910 0x003910 R 0x1
在這種情況下,DYNAMIC 段將位於 0x00000000003c6b60 + libc 基地址。
我猜這可能是由於 Centos 7 上的 ASLR 導致 libc 庫每次載入到不同的地址。在 Centos 6 上,似乎 libc 總是載入到相同的地址。
有沒有辦法僅通過讀取 ELF 標頭來確定我是否需要將 libc 基地址添加到 VirtAddr 以獲取程序段在記憶體中的實際位置?
我想我想通了,你需要檢查所有的 PT_LOAD 段並找到 p_vaddr 最低的段(我稱之為lowest_pt_load)。
然後計算出記憶體位置:
libc_base + segment.p_addr - lowest_pt_load.p_vaddr
在 Centos 6 的情況下發生的情況是,lowest_pt_load 等於 libc 基地址,導致它們被抵消。
來源:https ://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblk/index.html#chapter6-83432
基地址
執行檔和共享目標文件有一個基地址,它是與程序目標文件的記憶體映像相關的最低虛擬地址。基地址的一種用途是在動態連結期間重新定位程序的記憶體映像。
執行檔或共享對象文件的基地址在執行期間根據三個值計算得出:記憶體載入地址、最大頁面大小和程序可載入段的最低虛擬地址。程序頭中的虛擬地址可能不代表程序記憶體映像的實際虛擬地址。請參閱“程序載入(特定於處理器)”。
要計算基地址,您需要確定與 PT_LOAD 段的最低 p_vaddr 值關聯的記憶體地址。然後,您通過將記憶體地址截斷為最大頁面大小的最接近倍數來獲取基地址。根據載入到記憶體中的文件類型,記憶體地址可能與 p_vaddr 值不匹配。