使用GNU ld与仅含.text段的链接脚本制作的最小静态ELF可执行文件为何无法运行?
问题:Linux加载器拒绝执行精简程序头的ELF可执行文件
本文末尾的Base64编码ELF可执行文件是一个尝试将程序头精简到极致的实验产物。作者认为它完全合法,但Linux可执行加载器却拒绝执行:
$ strace ./experiment execve("./experiment", ["./experiment"], 0x7ffee000bd20 /* 69 vars */) = -1 EPERM (Operation not permitted)
以下是该文件的readelf输出及程序头信息:
$ readelf -hl ./experiment ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's补码,小端序 Version: 1(当前版本) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC(可执行文件) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x120 Start of program headers: 64(文件内偏移字节数) Start of section headers: 512(文件内偏移字节数) Flags: 0x0 Size of this header: 64(字节) Size of program headers: 56(字节) Number of program headers: 4 Size of section headers: 64(字节) Number of section headers: 6 Section header string table index: 5 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000190 0x0000000000000190 R E 0x1000 NOTE 0x0000000000000160 0x0000000000000160 0x0000000000000160 0x0000000000000030 0x0000000000000030 R 0x8 GNU_PROPERTY 0x0000000000000160 0x0000000000000160 0x0000000000000030 R 0x8 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 Section to Segment mapping: Segment Sections... 00 .text .note.gnu.property 01 .note.gnu.property 02 .note.gnu.property 03
Linux程序加载器为何不接受此文件?
Base64编码的程序
f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAIAEAAAAAAABAAAAAAAAAAAACAAAAAAAAAAAAAEAAOAAE AEAABgAFAAEAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAEAAAAAAACQAQAAAAAAAAAQ AAAAAAAABAAAAAQAAABgAQAAAAAAAGABAAAAAAAAYAEAAAAAAAAwAAAAAAAAADAAAAAAAAAACAAA AAAAAABT5XRkBAAAAGABAAAAAAAAYAEAAAAAAABgAQAAAAAAADAAAAAAAAAAMAAAAAAAAAAIAAAA AAAAAFHldGQGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA AAAASInnSIHnAPD//0jHxgAQAABIx8ALAAAADwVIhcB1C0jHwCIAAAAPBev1SInHSPffSMfAPAAA AA8FAAAAAAAAAAQAAAAgAAAABQAAAEdOVQABAAHABAAAAAEAAAAAAAAAAgABwAQAAAABAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAASAAEAIAEAAAAAAAA5AAAAAAAAAABfc3RhcnQA AC5zeW10YWIALnN0cnRhYgAuc2hzdHJ0YWIALnRleHQALm5vdGUuZ251LnByb3BlcnR5AAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAGwAAAAEAAAAGAAAAAAAAACABAAAAAAAAIAEAAAAAAAA5AAAAAAAAAAAAAAAAAAAAAQAA AAAAAAAAAAAAAAAAACEAAAAHAAAAAgAAAAAAAABgAQAAAAAAAGABAAAAAAAAMAAAAAAAAAAAAAAA AAAAAAgAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAACQAQAAAAAAADAAAAAA AAAABAAAAAEAAAAIAAAAAAAAABgAAAAAAAAACQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAwAEAAAAA AAAIAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAABEAAAADAAAAAAAAAAAAAAAAAAAAAAAA AMgBAAAAAAAANAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAA=
可复现该程序的步骤
cat > experiment.S <<\EOF #include <sys/syscall.h> .text .global _start .type _start, @function _start: mov %rsp, %rdi and $(~4095), %rdi mov $4096, %rsi mov $SYS_munmap, %rax syscall test %rax, %rax jne .Lfail .Lloop: mov $SYS_pause, %rax syscall jmp .Lloop .Lfail: mov %rax, %rdi neg %rdi mov $SYS_exit, %rax syscall .size _start, .-_start .section .note.GNU-stack,"",@progbits EOF cat > experiment.ld <<\EOF OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64") OUTPUT_ARCH(i386:x86-64) ENTRY(_start) SECTIONS { . = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS; .text : { *(.text) } } EOF cc -P -E experiment.S | as -o experiment.o - ld -o experiment -static -e _start -T experiment.ld experiment.o
解答
问题出在LOAD段的虚拟地址设置上:该ELF的LOAD段指定VirtAddr为0x0,而x86_64 Linux内核默认会禁止映射虚拟地址0开始的内存区域——这是一种安全机制,用于捕获NULL指针解引用的错误。当加载器尝试将LOAD段映射到地址0时,内核会拒绝这个操作,最终导致execve系统调用返回EPERM错误。
要修复这个问题,只需要修改链接脚本experiment.ld,将SEGMENT_START("text-segment", 0)中的0改为一个合法的非低地址值,比如0x400000(x86_64 ELF可执行文件的默认起始地址):
. = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
重新编译链接后,程序就能正常执行了。
内容的提问来源于stack exchange,提问作者zwol
相关产品推荐
相关产品推荐

