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只读。