我正在尝试查找ELF文件的基址。我知道你可以使用readelf查找程序入口点和不同的部分细节(基地址,大小,标志等)。
例如,x86体系结构的程序基于链接器的0x8048000。使用readelf我可以看到程序入口点,但输出中没有特定字段告诉基地址。
$ readelf -e test
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048390
Start of program headers: 52 (bytes into file)
Start of section headers: 4436 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 30
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000024 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481d0 0001d0 000070 10 A 6 1 4
在章节细节中,我可以看到偏移量是根据ELF的基地址计算的。
因此,.dynsym
部分从地址0x080481d0开始,偏移量为0x1d0。这意味着基地址为0x08048000。这是对的吗?
类似地,对于在PPC,ARM,MIPS等不同体系结构上编译的程序,我看不到它们的基地址,只能看到OEP,Section Headers。
答案 0 :(得分:12)
您需要检查段表,即程序标题(readelf -l
)。
Elf file type is EXEC (Executable file)
Entry point 0x804a7a0
There are 9 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x10fc8 0x10fc8 R E 0x1000
LOAD 0x011000 0x08059000 0x08059000 0x0038c 0x01700 RW 0x1000
DYNAMIC 0x01102c 0x0805902c 0x0805902c 0x000f8 0x000f8 RW 0x4
NOTE 0x000168 0x08048168 0x08048168 0x00020 0x00020 R 0x4
TLS 0x011000 0x08059000 0x08059000 0x00000 0x0005c R 0x4
GNU_EH_FRAME 0x00d3c0 0x080553c0 0x080553c0 0x00c5c 0x00c5c R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
第一个(最低)LOAD
段的虚拟地址是文件的默认加载基础。你可以看到它的文件是0x08048000。
答案 1 :(得分:1)
它在链接描述文件中定义。您可以使用ld --verbose
转储默认链接描述文件。示例输出:
GNU ld (GNU Binutils) 2.23.1
Supported emulations:
elf_x86_64
elf32_x86_64
elf_i386
i386linux
elf_l1om
elf_k1om
using internal linker script:
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
"elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("/nix/store/kxf1p7l7lgm6j5mjzkiwcwzc98s9f1az-binutils-2.23.1/x86_64-unknown-linux-gnu/lib64"); SEARCH_DIR("/nix/store/kxf1p7l7lgm6j5mjzkiwcwzc98s9f1az-binutils-2.23.1/lib64"); SEARCH_DIR("/nix/store/kxf1p7l7lgm6j5mjzkiwcwzc98s9f1az-binutils-2.23.1/x86_64-unknown-linux-gnu/lib"); SEARCH_DIR("/nix/store/kxf1p7l7lgm6j5mjzkiwcwzc98s9f1az-binutils-2.23.1/lib");
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
.interp : { *(.interp) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
(剪断)
如果你错过了它:__executable_start = SEGMENT_START("text-segment", 0x400000))
。
对我而言,当我将一个简单的.o文件链接到二进制文件时,入口点地址非常接近0x400000。
ELF元数据中的入口点地址是此值,加上从.text
部分开头到_start
符号的偏移量。另请注意,可以配置_start
符号。再次从我的默认链接描述文件示例:ENTRY(_start)
。
答案 2 :(得分:1)
.text 部分的ELF映射基地址由binutils项目中的 ld(1)加载器脚本在脚本模板{{ 3}}。
该脚本定义了加载程序 ld(1)使用的以下变量:
# TEXT_START_ADDR - the first byte of the text segment, after any
# headers.
# TEXT_BASE_ADDRESS - the first byte of the text segment.
# TEXT_START_SYMBOLS - symbols that appear at the start of the
# .text section.
您可以使用以下命令检查当前值:
~$ ld --verbose |grep SEGMENT_START
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
. = SEGMENT_START("ldata-segment", .);
文本段映射值是:
此外,ELF程序的解释器基址在辅助矢量数组中的索引为 AT_BASE 处定义。辅助向量数组是 Elf_auxv_t 结构的数组,位于进程堆栈中的 envp 之后。在将ELF二进制文件加载到Linux内核elf.sc的函数create_elf_tables()中时进行配置。以下代码段显示了如何读取值:
$ cat at_base.c
#include <stdio.h>
#include <elf.h>
int
main(int argc, char* argv[], char* envp[])
{
Elf64_auxv_t *auxp;
while(*envp++ != NULL);
for (auxp = (Elf64_auxv_t *)envp; auxp->a_type != 0; auxp++) {
if (auxp->a_type == 7) {
printf("AT_BASE: 0x%lx\n", auxp->a_un.a_val);
}
}
}
$ clang -o at_base at_base.c
$ ./at_base
AT_BASE: 0x7fcfd4025000
fs/binfmt_elf.c Linux Auxiliary Vector definition
它以前是x86 32位体系结构上的固定地址,但是现在使用ASLR,它是随机的。如果需要,可以使用Auxiliary Vector Reference i386 -R 禁用随机化。