首页 > 解决方案 > 创建带有只读标志的程序头会导致段错误

问题描述

我一直在使用 NASM 编写 ELF 二进制文件,并创建了一个打开了只读标志的段。运行程序会导致段错误。我在 replit 中测试了程序,它运行得很好,那么问题是什么?我用 .rodata 部分中的 hello world 字符串创建了一个常规的 NASM hello world 程序,并且运行良好。我用 readelf 检查了二进制文件,以确保字符串在只读段中。

我想出的唯一解决方案是在rodata段中设置可执行标志,使其具有读取/执行权限,但这很麻烦,我希望rodata段是只读的。

这是 ELF-64 hello world 的代码。

; hello.asm
[bits 64]
[org 0x400000]

fileHeader:
    db 0x7F, "ELF"
    db 2 ; ELF-64
    db 1 ; little endian
    db 1 ; ELF version
    db 0 ; System V ABI
    db 0 ; ABI version
    db 0, 0, 0, 0, 0, 0, 0 ; unused
    dw 2 ; executable object file
    dw 0x3E ; x86-64
    dd 1 ; ELF version
    dq text ; entry point
    dq 64 ; program header table offset
    dq nullSection - $$ ; section header table offset
    dd 0 ; flags
    dw 64 ; size of file header
    dw 56 ; size of program header
    dw 3 ; program header count
    dw 64 ; size of section header
    dw 4 ; section header count
    dw 3 ; section header string table index
nullSegment:
    times 56 db 0
textSegment:
    dd 1 ; loadable segment
    dd 0x4 ; read / execute permissions
    dq text - $$ ; segment offset
    dq text ; virtual address of segment
    dq 0 ; physical address of segment
    dq textSize ; size of segment in file
    dq textSize ; size of segment in memory
    dq 0x1000 ; alignment
rodataSegment:
    dd 1 ; loadable segment
    dd 0x4 ; read permission (setting this flag to 0x5 causes the program to run just fine)
    dq rodata - $$ ; segment offset
    dq rodata ; virtual address of segment
    dq 0 ; physical address of segment
    dq rodataSize ; size of segment in file
    dq rodataSize ; size of segment in memory
    dq 0x1000 ; alignment
text:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, messageLength
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall
textSize equ $ - text
rodata:
    message db "Hello world!", 0xA, 0
    messageLength equ $ - message
rodataSize equ $ - rodata
stringTable:
    db 0
    db ".text", 0
    db ".rodata", 0
    db ".shstrtab", 0
stringTableSize equ $ - stringTable
nullSection:
    times 64 db 0
textSection:
    dd 1 ; index into string table
    dd 1 ; program data
    dq 0x6 ; occupies memory & executable
    dq text ; virtual address of section
    dq text - $$ ; offset of section in file
    dq textSize ; size of section in file
    dq 0 ; unused
    dq 0x1000 ; alignment
    dq 0 ; unused
rodataSection:
    dd 7 ; index into string table
    dd 1 ; program data
    dq 0x2 ; occupies memory
    dq rodata ; virtual address of section
    dq rodata - $$ ; offset of section in file
    dq rodataSize ; size of section in file
    dq 0 ; unused
    dq 0x1000 ; no alignment
    dq 0 ; unused
stringTableSection:
    dd 15 ; index into string table
    dd 3 ; string table
    dq 0 ; no attributes
    dq stringTable ; virtual address of section
    dq stringTable - $$ ; offset of section in file
    dq stringTableSize ; size of section in file
    dq 0 ; unused
    dq 0 ; no alignment
    dq 0 ; unused

replitHello.asm: https://hastebin.com/ujanoguveq.properties // 它应该是几乎同一行

这是最小的 nasm hello world 程序。

; helloNasm.asm
section .text
global _start
_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, messageLength
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall

section .rodata
    message db "Hello NASM!", 0xA, 0
    messageLength equ $ - message

标签: linuxassemblyx86-64elf

解决方案


textSegment:
    dd 1 ; loadable segment
    dd 0x4 ; read / execute permissions

我假设您的意思0x5是上面的标志。

固定后,我看到以下部分:

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  NULL           0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000     0
  LOAD           0x0000e8 0x00000000004000e8 0x0000000000000000 0x000025 0x000025 R E 0x1000
  LOAD           0x00010d 0x000000000040010d 0x0000000000000000 0x00000e 0x00000e R   0x1000

mmap这要求内核在同一地址执行两个s ( 0x400000)。这些mmaps 中的第二个映射在第一个之上,结果如下/proc/$pid/maps

00400000-00401000 r--p 00000000 fe:02 22548440                           /tmp/t
7ffff7ff9000-7ffff7ffd000 r--p 00000000 00:00 0                          [vvar]
7ffff7ffd000-7ffff7fff000 r-xp 00000000 00:00 0                          [vdso]
7ffffffdd000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]

如您所见,程序文本是不可执行的,因此程序SIGSEGVs 在第一条指令上:

(gdb) run 
Starting program: /tmp/t 

Program received signal SIGSEGV, Segmentation fault.
0x00000000004000e8 in ?? ()
(gdb) x/i $pc
=> 0x4000e8:    mov    $0x1,%eax

要解决此问题,您必须将其中一个段移动到不同的页面(正如 Jester 正确指出的那样)。

另请注意,部分是完全不必要的(只有部分很重要)。特别是在该部分中设置A X标志.text对任何事情都没有影响。


推荐阅读