0x01 内存布局

大部分Linux:

  • .text 汇编代码
  • .data 有初始值的数据
  • .bss 无初始值的数据
  • heap 堆
  • shared object 共享对象区域(libc.so之类的)
  • stack 栈
  • kernel-area 内核区域

实际数值大概像这样:

  • gdb-peda/pwndbg 下可以直接使用vmmap

  • 也可以通过cat /proc/$PID/maps

  • 每行有各种属性(Read,Write,eXec)

    • p是private mapping,变更不会反映在文件上
    • 地址是0x1000的倍数,这个单位被称为”页”
  • 进程启动时,ELF加载确保这样的内存布局

    • 使用mmap()进行确保,并且通过sbrk()来做自动补齐
    • 进程启动后,进程自身也可以使用mmap()来进行追加确保

补足

  • 这里是虚拟内存,与物理内存存在差异
  • 物理内存和虚拟内存都与内核相关联,这是一种被称为MMU(Memory Management Unit)的机制
  • 用户程序只能看到虚拟内存,并不需要考虑物理内存

NX、ASLR、PIE

这个例子有0x0804XXXX和0xfXXXXXXX两种类型的起始地址,前者在堆之外每次启动不会变化,后者每次启动都会发生改变,这被称为ASLR(Address space layout randomization,地址空间配置随机加载)。

对前者也进行随机化的技术叫做PIE(position-independent executable, 地址无关可执行文件).

这个例子中stack,mapped之类的很多地方都是rwx,也就是说,可以在这些内存区域进行读取,写入和执行。

但是,这样的话安全性较差,因此NX(DEP)会使得text区域没有执行权限

RELRO

使用外部库(共享对象,*.so)时,他们会映射到地址空间的各个位置。

如果每次都计算这些库提供的函数地址,这不太方便,计算一次后保存到一个映射表会很方便后续使用。

这个表被称为GOT(Global Offset Table,全剧映射表),它存在于地址固定的区域。

GOT实际上就在这里,但如果它是可写的,某些情况下如果它被重写,这个表也就变得不值得信任了。

因此,我们会使用一种叫做RELRO(Full-RELRO)的技术,在启动时计算所有外部库的所有函数地址,写入GOT中,然后使GOT只读。