【问题标题】:Why Segment fault when writing to writeable .data section? Using Ubuntu, x86, nasm, gdb, readelf为什么写入可写 .data 部分时出现段错误?使用 Ubuntu、x86、nasm、gdb、readelf
【发布时间】:2020-10-31 14:49:27
【问题描述】:

我正在学习使用汇编编写一个简单的 shell 代码。当执行 mov 操作码以覆盖 db 数据时,出现段错误。为什么?任何指导表示赞赏!使用 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'

阅读 cmets 后的附加信息:

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

是在 32 位 Ubuntu 安装上编译和运行。以下是我正在使用的各种命令行(对于变体 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
$15 = {<text variable, no debug info>} 0x8048080 <zero>
gdb-peda$ p one
$16 = {<text variable, no debug info>} 0x8048082 <one>
gdb-peda$ p two
$17 = {<text variable, no debug info>} 0x8048098 <two>
gdb-peda$ p $ebx
$18 = 0x804809d
gdb-peda$ p msg
$19 = 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 输出告诉我什么?

【问题讨论】:

  • 在另一个程序中运行这个 shellcode 或者当你自己运行这个 shellcode 时,你是否遇到了 seg 错误?
  • 我也希望您将其构建为 32 位应用程序而不是 64 位应用程序,并且您在 32 位应用程序中运行 shellcode。这在 64 位应用程序中不起作用。
  • 我建议将 .text 和 .data 部分合并为一个,因为您可能最初拥有它以确保所有数据和代码是连续的。然后尝试将--omagic 选项与ld 一起使用以使.text 和.data 段读写。
  • @Michael Patch 刚刚删除了 .data 段(就像我最初拥有的那样)并强制所有 .text 可使用 --omagic 写入。它奏效了。

标签: assembly x86 gdb nasm shellcode


【解决方案1】:

使用 gdb 调试确认数据在运行时与代码连续,程序的 readelf 分析确认数据段是可写的。

您希望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 段中,并且该段开始于不同的页面。

您可能会感到困惑的是,您的链接器可能会留下.data副本,下面是two 的代码(我的版本没有——仅此而已0s 给我)。

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

【讨论】:

  • 谢谢。我的负载表也显示在非连续地址。我信任 gdb,它显示连续的地址。我将在问题的上方发布摘录。当 readelf 说它们将被分开加载时,为什么 gdb 显示连续? ...作为一种解决方法,有没有办法强制部分 .text 可写?最后,您说第二段不可写,但 readelf 输出显示它带有“W”标志 - 这不意味着它是可写的吗?如果它甚至不在我期望的位置,也许 writeable 是无关紧要的。
  • 删除 .data 段并强制 .text 可写 (ld --omagic)。现在负载表只显示一个负载段,它是连续的,并且可以正常工作。感谢您向我展示如何阅读负载表。
  • .data 段方法的另一个观察:用标签标记字符串位置,然后使用 gdb 和该标签,地址与调用指令之后的地址不同。 .text 和 .data 位置都包含字符串。这很奇怪。
  • @Brian 其实并不奇怪。如果您提出一个新问题,并显示来自readelf -Wl ...完整 输出,我可以(可能)准确解释为什么会发生这种情况。
  • 我按要求发布了来自readelf -Wl .. 的输出。字符串声明似乎会导致在两个不同的内存位置创建字符串??
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-03
  • 2022-01-23
相关资源
最近更新 更多