强制内存保护粒度 (x86-64)

强制内存保护粒度 (x86-64)

我正在试验Linux如何分配和保护内存。

对于我的一些实验,我用 C 创建了一个小程序:

#include <stdio.h>

int gv=10;

int main(){

    char *v=(char*)0x601000;//0x601030
    printf("gv=%p\n", &gv);
    scanf("%s", v);
    printf("You gave=%s\n", v);

}

编译后(部分relro),readelf -t a.out返回:

There are 30 section headers, starting at offset 0x1a18:

Section Headers:
 [Nr] Name
      Type              Address          Offset            Link
      Size              EntSize          Info              Align
      Flags
 [ 0]
      NULL                   NULL             0000000000000000  0000000000000000  0
      0000000000000000 0000000000000000  0                 0
      [0000000000000000]:
 [ 1] .interp
      PROGBITS               PROGBITS         0000000000400238  0000000000000238  0
      000000000000001c 0000000000000000  0                 1
      [0000000000000002]: ALLOC
 [ 2] .note.ABI-tag
      NOTE                   NOTE             0000000000400254  0000000000000254  0
      0000000000000020 0000000000000000  0                 4
      [0000000000000002]: ALLOC
 [ 3] .note.gnu.build-id
      NOTE                   NOTE             0000000000400274  0000000000000274  0
      0000000000000024 0000000000000000  0                 4
      [0000000000000002]: ALLOC
 [ 4] .gnu.hash
      GNU_HASH               GNU_HASH         0000000000400298  0000000000000298  5
      000000000000001c 0000000000000000  0                 8
      [0000000000000002]: ALLOC
 [ 5] .dynsym
      DYNSYM                 DYNSYM           00000000004002b8  00000000000002b8  6
      0000000000000078 0000000000000018  1                 8
      [0000000000000002]: ALLOC
 [ 6] .dynstr
      STRTAB                 STRTAB           0000000000400330  0000000000000330  0
      0000000000000058 0000000000000000  0                 1
      [0000000000000002]: ALLOC
 [ 7] .gnu.version
      VERSYM                 VERSYM           0000000000400388  0000000000000388  5
      000000000000000a 0000000000000002  0                 2
      [0000000000000002]: ALLOC
 [ 8] .gnu.version_r
      VERNEED                VERNEED          0000000000400398  0000000000000398  6
      0000000000000030 0000000000000000  1                 8
      [0000000000000002]: ALLOC
 [ 9] .rela.dyn
      RELA                   RELA             00000000004003c8  00000000000003c8  5
      0000000000000018 0000000000000018  0                 8
      [0000000000000002]: ALLOC
 [10] .rela.plt
      RELA                   RELA             00000000004003e0  00000000000003e0  5
      0000000000000060 0000000000000018  12                8
      [0000000000000042]: ALLOC, INFO LINK
 [11] .init
      PROGBITS               PROGBITS         0000000000400440  0000000000000440  0
      000000000000001a 0000000000000000  0                 4
      [0000000000000006]: ALLOC, EXEC
 [12] .plt
      PROGBITS               PROGBITS         0000000000400460  0000000000000460  0
      0000000000000050 0000000000000010  0                 16
      [0000000000000006]: ALLOC, EXEC
 [13] .text
      PROGBITS               PROGBITS         00000000004004b0  00000000000004b0  0
      00000000000001b2 0000000000000000  0                 16
      [0000000000000006]: ALLOC, EXEC
 [14] .fini
      PROGBITS               PROGBITS         0000000000400664  0000000000000664  0
      0000000000000009 0000000000000000  0                 4
      [0000000000000006]: ALLOC, EXEC
 [15] .rodata
      PROGBITS               PROGBITS         0000000000400670  0000000000000670  0
      0000000000000029 0000000000000000  0                 8
      [0000000000000002]: ALLOC
 [16] .eh_frame_hdr
      PROGBITS               PROGBITS         000000000040069c  000000000000069c  0
      0000000000000034 0000000000000000  0                 4
      [0000000000000002]: ALLOC
 [17] .eh_frame
      PROGBITS               PROGBITS         00000000004006d0  00000000000006d0  0
      00000000000000f4 0000000000000000  0                 8
      [0000000000000002]: ALLOC
 [18] .init_array
      INIT_ARRAY             INIT_ARRAY       0000000000600e10  0000000000000e10  0
      0000000000000008 0000000000000000  0                 8
      [0000000000000003]: WRITE, ALLOC
 [19] .fini_array
      FINI_ARRAY             FINI_ARRAY       0000000000600e18  0000000000000e18  0
      0000000000000008 0000000000000000  0                 8
      [0000000000000003]: WRITE, ALLOC
 [20] .jcr
      PROGBITS               PROGBITS         0000000000600e20  0000000000000e20  0
      0000000000000008 0000000000000000  0                 8
      [0000000000000003]: WRITE, ALLOC
 [21] .dynamic
      DYNAMIC                DYNAMIC          0000000000600e28  0000000000000e28  6
      00000000000001d0 0000000000000010  0                 8
      [0000000000000003]: WRITE, ALLOC
 [22] .got
      PROGBITS               PROGBITS         0000000000600ff8  0000000000000ff8  0
      0000000000000008 0000000000000008  0                 8
      [0000000000000003]: WRITE, ALLOC
 [23] .got.plt
      PROGBITS               PROGBITS         0000000000601000  0000000000001000  0
      0000000000000038 0000000000000008  0                 8
      [0000000000000003]: WRITE, ALLOC
 [24] .data
      PROGBITS               PROGBITS         0000000000601038  0000000000001038  0
      0000000000000008 0000000000000000  0                 4
      [0000000000000003]: WRITE, ALLOC
 [25] .bss
      NOBITS                 NOBITS           0000000000601040  0000000000001040  0
      0000000000000008 0000000000000000  0                 1
      [0000000000000003]: WRITE, ALLOC
 [26] .comment
      PROGBITS               PROGBITS         0000000000000000  0000000000001040  0
      000000000000002d 0000000000000001  0                 1
      [0000000000000030]: MERGE, STRINGS
 [27] .shstrtab
      STRTAB                 STRTAB           0000000000000000  000000000000106d  0
      0000000000000108 0000000000000000  0                 1
      [0000000000000000]:
 [28] .symtab
      SYMTAB                 SYMTAB           0000000000000000  0000000000001178  29
      0000000000000648 0000000000000018  45                8
      [0000000000000000]:
 [29] .strtab
      STRTAB                 STRTAB           0000000000000000  00000000000017c0  0
      0000000000000255 0000000000000000  0                 1
      [0000000000000000]:

readelf -l a.out回报:

Elf file type is EXEC (Executable file)
Entry point 0x4004b0
There are 9 program headers, starting at offset 64

Program Headers:
 Type           Offset             VirtAddr           PhysAddr
                FileSiz            MemSiz              Flags  Align
 PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                0x00000000000001f8 0x00000000000001f8  R E    8
 INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                0x000000000000001c 0x000000000000001c  R      1
     [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
 LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                0x00000000000007c4 0x00000000000007c4  R E    200000
 LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                0x0000000000000230 0x0000000000000238  RW     200000
 DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
                0x00000000000001d0 0x00000000000001d0  RW     8
 NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                0x0000000000000044 0x0000000000000044  R      4
 GNU_EH_FRAME   0x000000000000069c 0x000000000040069c 0x000000000040069c
                0x0000000000000034 0x0000000000000034  R      4
 GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                0x0000000000000000 0x0000000000000000  RW     10
 GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                0x00000000000001f0 0x00000000000001f0  R      1

Section to Segment mapping:
 Segment Sections...
  00
  01     .interp
  02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
  03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
  04     .dynamic
  05     .note.ABI-tag .note.gnu.build-id
  06     .eh_frame_hdr
  07
  08     .init_array .fini_array .jcr .dynamic .got

在该程序的一个实例中,当通过 gdb 运行时,/proc/self/maps 返回:

00400000-00401000 r-xp 00000000 00:30f 2262070116                        .../a.out
00600000-00601000 r--p 00000000 00:30f 2262070116                        .../a.out
00601000-00602000 rw-p 00001000 00:30f 2262070116                        .../a.out
7ffff7a18000-7ffff7bd0000 r-xp 00000000 fd:00 137613                     /usr/lib64/libc-2.17.so
7ffff7bd0000-7ffff7dd0000 ---p 001b8000 fd:00 137613                     /usr/lib64/libc-2.17.so
7ffff7dd0000-7ffff7dd4000 r--p 001b8000 fd:00 137613                     /usr/lib64/libc-2.17.so
7ffff7dd4000-7ffff7dd6000 rw-p 001bc000 fd:00 137613                     /usr/lib64/libc-2.17.so
7ffff7dd6000-7ffff7ddb000 rw-p 00000000 00:00 0
7ffff7ddb000-7ffff7dfc000 r-xp 00000000 fd:00 137605                     /usr/lib64/ld-2.17.so
7ffff7fbd000-7ffff7fc0000 rw-p 00000000 00:00 0
7ffff7ff7000-7ffff7ffa000 rw-p 00000000 00:00 0
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0                          [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00021000 fd:00 137605                     /usr/lib64/ld-2.17.so
7ffff7ffd000-7ffff7ffe000 rw-p 00022000 fd:00 137605                     /usr/lib64/ld-2.17.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

考虑到这一点,我期望 0x601000-0x602000 之间的内存区域是可写的。但是当我运行包含 24 个或更多字符的程序时,它崩溃了v=0x601000

当我改成v0x6010时20然后程序在我输入0x1000后崩溃了-0x20人物。

这种行为如何解释?内存保护不是按页粒度强制执行的吗?看起来 0x601018-0x601020 之间的内存区域在某种程度上是只读的。

我猜问题出在 .got.plt 部分(加载于 0x601000,大小为 0x38)内,但它到底是什么?


编辑:

的输出ld --verbose | fgrep -A 3 -B 3 -i relro是:

 .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
 .dynamic        : { *(.dynamic) }
 .got            : { *(.got) *(.igot) }
 . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
 .got.plt        : { *(.got.plt)  *(.igot.plt) }
 .data           :

这可能告诉我们 RELRO 与此有关,但是,内存不应该以页面大小粒度进行保护吗?

答案1

.got.plt对于进行库调用(包括但不限于调用libc)至关重要。一旦你破坏了该部分,就禁止进行任何 libc 调用。

如果您通过 运行二进制文件gdb,则不应在 中出现段错误scanf— 您应该在 中出现段错误printf@plt,因为无法使用损坏的.got.plt部分进行动态调用。

您应该仍然能够进行非动态调用和系统调用:

#include <stdio.h>
  __asm(
    "finish:"
        "mov $60, %rax\n"
        "mov $42, %rdi\n"
        "syscall\n"
     );

int gv=10;

int main(){

  char *v=(char*)0x601000;//0x601030
  printf("gv=%p\n", &gv);
  scanf("%s", v);
  _Noreturn void finish(void);
  finish();
  //should exit with 42

  printf("You gave=%s\n", v);

}

答案2

我已经找到问题了。通过用垃圾填充该区域,我覆盖了 printf 的 get 条目,该条目的最终调用导致了分段。

所以如果我们将代码修改为:

#include <stdio.h>

int gv=10;

int main(){

  char *v=(char*)0x601000;
  printf("gv=%p\n", &gv);
  scanf("%s", v);
  //printf("You gave=%s\n", v);

}

用0x1000个字符填充v应该没有问题(注意\0)

第一篇文章的 RELRO 参考是无关的。

相关内容