为什么在写入可写 .data 部分时出现段错误?使用 Ubuntu、x86、nasm、gdb、readelf

Why Segment fault when writing to writeable .data section? Using Ubuntu, x86, nasm, gdb, readelf

我正在学习使用汇编编写简单的 shell 代码。当执行 mov 操作码以覆盖数据库数据时,出现段错误。为什么?任何指导表示赞赏!使用 gdb 调试确认数据在 运行 时与代码连续,并且程序的 readelf 分析确认数据段是可写的。

    section .text
    global _start
        _start:

          ; The following code calls execve("/bin/sh", argv, envp=0)
          jmp short two
    one:
          pop ebx
          xor eax, eax
          mov [ebx+12], eax
          mov [ebx+7], al
          mov [ebx+8], ebx
          lea ecx, [ebx+8]
          lea edx, [ebx+12]
          mov al, 11
          int 0x80
    two:
          call one
section .data align=1
          db '/bin/shzargvenvp'

阅读评论后的补充信息:

当 运行 在 linux 命令行 (./myshdb) 上独立运行时,以及当我使用 gdb 进入 mov 指令时(将中断设置为“一”),它会出现段错误,运行,然后重复步骤)。

是在 32 位 Ubuntu 安装上编译并 运行ning。以下是我正在使用的各种命令行(对于变体 shell 代码工作都可以正常工作):

nasm -f elf32 -g -F stabs myshdb.s -o myshdb.o
objdump -Mintel --disassemble myshdb.o
ld myshdb.o -o myshdb
readelf -a myshdb
gdb myshdb

使用不同的算法,编译和命令都可以正常工作,程序 运行 也很好。这是关于数据紧接在代码之后的接近度并试图写入给我带来麻烦的数据部分。最初它都是 .text 部分,但显然是只读的,所以我认为在 1 字节边界上对齐的数据声明会起作用。 1 字节边界有效,但不知何故,即使 readelf 说它已加载为可写,写入也不起作用。注意带有“W”标志的数据段中的 16 个字节(大小=0x10)。

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        08048080 000080 000025 00  AX  0   0 16
  [ 2] .data             PROGBITS        080490a5 0000a5 000010 00  WA  0   0  1
  [ 3] .stab             PROGBITS        00000000 0000b8 0000d8 0c      4   0  4
  [ 4] .stabstr          STRTAB          00000000 000190 00000a 00      0   0  1
  [ 5] .shstrtab         STRTAB          00000000 00029b 000036 00      0   0  1
  [ 6] .symtab           SYMTAB          00000000 00019c 0000d0 10      7   9  4
  [ 7] .strtab           STRTAB          00000000 00026c 00002f 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

数据是否紧跟代码?下面的 gdb 输出,在执行第一个 mov 操作码之前停止。数据在代码之后连续出现。 EBX 包含地址 0x80480a5,它指向紧跟在代码之后的有效字符串数据。检查内存 (x 0x80480a5) 也确认了一个连续的位置。

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x80480a5 ("/bin/shZargvenvp")
ECX: 0x0 
EDX: 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xbfffeda0 --> 0x1 
EIP: 0x804808d (<one+3>:    mov    DWORD PTR [ebx+0xc],eax)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048088 <zero>:    jmp    0x80480a0 <two>
   0x804808a <one>: pop    ebx
   0x804808b <one+1>:   xor    eax,eax
=> 0x804808d <one+3>:   mov    DWORD PTR [ebx+0xc],eax
   0x8048090 <one+6>:   mov    DWORD PTR [ebx+0x8],ebx
   0x8048093 <one+9>:   mov    BYTE PTR [ebx+0x7],al
   0x8048096 <one+12>:  lea    ecx,[ebx+0x8]
   0x8048099 <one+15>:  lea    edx,[ebx+0xc]
   [------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0804808d in one ()
gdb-peda$ x 0x80480a5
0x80480a5:  "/bin/shZargvenvp"

@Employed Russian 要求从 reaelf -Wl 打印输出。这是我从头开始重建时的信息:

---------- code snippet compiled with nasm, ld -----------------
zero: jmp short two
one:  pop ebx
      xor eax, eax
      mov [ebx+12], eax
      mov [ebx+8], ebx
      mov [ebx+7], al
      lea ecx, [ebx+8]
      lea edx, [ebx+12]
      mov al, 11
      int 0x80
two:  call one
section .data align=1
msg:   db '/bin/sh0argvenvp' 

-------- readelf output as requested --------
readelf -Wl myshdb

Elf file type is EXEC (Executable file)
Entry point 0x8048080
There are 2 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x0009d 0x0009d R E 0x1000
  LOAD           0x00009d 0x0804909d 0x0804909d 0x00010 0x00010 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text 
   01     .data 

-------------- run with gdb and step to mov instructions ----------
---------------registers--------------
EAX: 0x0 
EBX: 0x804809d ("/bin/sh0argvenvp")

----------- memory address checks ------------
gdb-peda$ p zero
 = {<text variable, no debug info>} 0x8048080 <zero>
gdb-peda$ p one
 = {<text variable, no debug info>} 0x8048082 <one>
gdb-peda$ p two
 = {<text variable, no debug info>} 0x8048098 <two>
gdb-peda$ p $ebx
 = 0x804809d
gdb-peda$ p msg
 = 0x6e69622f
gdb-peda$ x 0x804809d
0x804809d:  "/bin/sh0argvenvp"
gdb-peda$ x msg
0x6e69622f: <error: Cannot access memory at address 0x6e69622f>

换句话说,字符串消息可直接从代码 (0x804809d) 之后的内存位置获得。然而 msg 标签映射到 0x6e69622f,我如何使用 gdb 查看那里的数据? reaelf -Wl 输出告诉我什么?

Debugging with gdb confirms the data is contiguous with the code at run time and readelf analysis of the program confirms the data segment is writeable.

您希望 db '...' 立即关注 CALL one

实际上 不会发生,您的 .data 部分位于不同的 (因为它需要不同的权限) :

readelf -Wl myshdb
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x00094 0x00094 R   0x1000
  LOAD           0x001000 0x08049000 0x08049000 0x0001d 0x0001d R E 0x1000
  LOAD           0x002000 0x0804a000 0x0804a000 0x00010 0x00010 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00
   01     .text
   02     .data

请注意 .data 在第二个 LOAD 段中,该段从不同的页面开始。

可能会让您感到困惑的是,您的链接器 可能 在 [=17] 的代码后留下 .data 副本 =](我的版本没有——对我来说都是 0)。

在任何情况下,您的代码都会尝试写入 first LOAD 段,紧接 two 结束后的位置,但是该段(显然)不可写。