VMA 是虚拟内存的同质区域,具有:
例如,如果你有一个 VMA 是 RW 并且你 mprotect PROT_READ(你删除了写权限)在 VMA 中间的一部分,内核会将 VMA 分成三个 VMA (第一个是RW,第二个是R,最后一个是RW)。
让我们看一下来自可执行文件的典型 VMA:
$ cat /proc/$$/maps
00400000-004f2000 r-xp 00000000 08:01 524453 /bin/bash
006f1000-006f2000 r--p 000f1000 08:01 524453 /bin/bash
006f2000-006fb000 rw-p 000f2000 08:01 524453 /bin/bash
006fb000-00702000 rw-p 00000000 00:00 0
[...]
第一个 VMA 是文本段。第二,第三和第四个VMA是数据段。
.bss 的匿名映射
在这个过程的开始,你会有这样的东西:
$ cat /proc/$$/maps
00400000-004f2000 r-xp 00000000 08:01 524453 /bin/bash
006f1000-006fb000 rw-p 000f1000 08:01 524453 /bin/bash
006fb000-00702000 rw-p 00000000 00:00 0
[...]
这来自可执行文件 (readelf -l) 的程序头表的 PT_LOAD 条目,它描述了要映射到内存中的段:
类型 偏移 VirtAddr PhysAddr
FileSiz MemSiz 标志对齐
[...]
加载 0x00000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000f1a74 0x00000000000f1a74 R E 200000
加载 0x00000000000f1de0 0x00000000006f1de0 0x00000000006f1de0
0x0000000000009068 0x000000000000f298 读写 200000
[...]
如果您查看对应的PT_LOAD 条目,您会注意到该段的一部分没有在文件中表示(因为文件大小小于内存大小)。
不在可执行文件中的数据段部分用零初始化:动态链接器对这部分数据段使用MAP_ANONYMOUS 映射。这就是为什么它显示为一个单独的 VMA(它没有相同的支持文件)。
重定位保护(PT_GNU_RELRO)
当动态链接器完成重定位 (2) 后,它可能会将数据段的某些部分(.got 等部分)标记为只读,以避免 GOT 中毒攻击或错误。在程序头表的PT_GNU_RELRO 条目中描述的重定位后应保护的数据段部分:动态链接器mprotect(addr, len, PROT_READ) 完成重定位后的给定区域(3)。这个mprotect 调用将第二个VMA 分成两个VMA(第一个R 和第二个RW)。
类型 偏移 VirtAddr PhysAddr
FileSiz MemSiz 标志对齐
[...]
GNU_RELRO 0x00000000000f1de0 0x00000000006f1de0 0x00000000006f1de0
0x0000000000000220 0x0000000000000220 R
[...]
总结
VMA
00400000-004f2000 r-xp 00000000 08:01 524453 /bin/bash
006f1000-006f2000 r--p 000f1000 08:01 524453 /bin/bash
006f2000-006fb000 rw-p 000f2000 08:01 524453 /bin/bash
006fb000-00702000 rw-p 00000000 00:00 0
派生自PT_LOAD 和PT_GNU_RELRO 条目的VirtAddr、MemSiz 和Flags 字段:
类型 偏移 VirtAddr PhysAddr
FileSiz MemSiz 标志对齐
[...]
加载 0x00000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000f1a74 0x00000000000f1a74 R E 200000
加载 0x00000000000f1de0 0x00000000006f1de0 0x00000000006f1de0
0x0000000000009068 0x000000000000f298 读写 200000
[...]
GNU_RELRO 0x00000000000f1de0 0x00000000006f1de0 0x00000000006f1de0
0x0000000000000220 0x0000000000000220 R
[...]
首先,所有PT_LOAD 条目都是进程。它们中的每一个都使用mmap() 触发创建一个 VMA。此外,如果MemSiz > FileSiz,它可能会创建一个额外的匿名 VMA。
那么所有(实际上只有一次)PT_GNU_RELRO 都是进程。它们中的每一个都会触发 mprotect() 调用,这可能会将现有 VMA 拆分为不同的 VMA。
为了做你想做的,正确的方法大概是模拟mmap和mprotect调用:
// Virtual Memory Area:
struct Vma {
std::uint64_t addr, length;
std::string file_name;
int prot;
int flags;
std::uint64_t offset;
};
// Virtual Address Space:
class Vas {
private:
std::list<Vma> vmas_;
public:
Vma& mmap(
std::uint64_t addr, std::uint64_t length, int prot,
int flags, int fd, off_t offset);
int mprotect(std::uint64_t addr, std::uint64_t len, int prot);
std::list<Vma> const& vmas() const { return vmas_; }
};
for (Elf32_Phdr const& h : phdrs)
if (h.p_type == PT_LOAD) {
vas.mmap(...);
if (anon_size)
vas.mmap(...);
}
for (Elf32_Phdr const& h : phdrs)
if (h.p_type == PT_GNU_RELRO)
vas.mprotect(...);
一些计算示例
地址略有不同,因为 VMA 是页面对齐的 (3)(对于 x86 和 x86_64 使用 4Kio = 0x1000 页面):
第一个 VMA 由第一个 PT_LOAD 条目描述:
vma[0].start = page_floor(load[0].virt_addr)
= 0x400000
vma[0].end = page_ceil(load[1].virt_addr + load[1].phys_size)
= page_ceil(0x400000 + 0xf1a74)
= page_ceil(0x4f1a74)
= 0x4f2000
下一个VMA是数据段中被保护的部分,由PT_GNU_RELRO描述:
vma[1].start = page_floor(relro[0].virt_addr)
= page_floor(0xf1de0)
= 0x6f1000
vma[1].end = page_ceil(relro[0].virt_addr + relo[0].mem_size)
= page_ceil(0x6f1de0 + 0x220)
= page_ceil(0x6f2000)
= 0x6f2000
[...]
与各部分的对应
部分标题:
[Nr] 名称类型地址偏移量
大小 EntSize 标志链接信息对齐
[0]空0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[1] .interp 程序 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI 标记注 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[3] .note.gnu.build-i 注释 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[4] .gnu.hash GNU_HASH 0000000000400298 00000298
0000000000004894 0000000000000000 A 5 0 8
[5] .dynsym 动态 0000000000404b30 00004b30
000000000000d6c8 0000000000000018 A 6 1 8
[6].dynstr STRTAB 00000000004121f8 000121f8
0000000000008c25 0000000000000000 A 0 0 1
[7].gnu.version VERSYM 000000000041ae1e 0001ae1e
00000000000011e6 0000000000000002 A 5 0 2
[8].gnu.version_r VERNEED 000000000041c008 0001c008
00000000000000b0 0000000000000000 A 6 2 8
[9].rela.dyn RELA 000000000041c0b8 0001c0b8
00000000000000c0 0000000000000018 A 5 0 8
[10] .rela.plt RELA 000000000041c178 0001c178
00000000000013f8 0000000000000018 人工智能 5 12 8
[11] .init 程序 000000000041d570 0001d570
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt 程序 000000000041d590 0001d590
0000000000000d60 0000000000000010 AX 0 0 16
[13] .text 程序 000000000041e2f0 0001e2f0
0000000000099c42 0000000000000000 AX 0 0 16
[14] .fini 程序 00000000004b7f34 000b7f34
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata 程序 00000000004b7f40 000b7f40
000000000001ebb0 0000000000000000 A 0 0 64
[16] .eh_frame_hdr 程序 00000000004d6af0 000d6af0
000000000000407c 0000000000000000 A 0 0 4
[17] .eh_frame 程序 00000000004dab70 000dab70
0000000000016f04 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 00000000006f1de0 000f1de0
0000000000000008 0000000000000000 西澳 0 0 8
[19] .fini_array FINI_ARRAY 00000000006f1de8 000f1de8
0000000000000008 0000000000000000 西澳 0 0 8
[20] .jcr 程序 00000000006f1df0 000f1df0
0000000000000008 0000000000000000 西澳 0 0 8
[21] .动态动态 00000000006f1df8 000f1df8
0000000000000200 0000000000000010 西澳 6 0 8
[22] .得到程序 00000000006f1ff8 000f1ff8
0000000000000008 0000000000000008 西澳 0 0 8
[23] .got.plt 程序 00000000006f2000 000f2000
00000000000006c0 0000000000000008 WA 0 0 8
[24] .data 程序 00000000006f26c0 000f26c0
0000000000008788 0000000000000000 西澳 0 0 64
[25] .bss NOBITS 00000000006fae80 000fae48
00000000000061f8 0000000000000000 WA 0 0 64
[26] .shstrtab STRTAB 0000000000000000 000fae48
00000000000000ef 0000000000000000 0 0 1
如果您将部分的地址 (readelf -S) 与 VMA 的范围进行比较,您会发现映射:
00400000-004f2000 r-xp /bin/bash:.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
006f1000-006f2000 r--p /bin/bash:.init_array、.fini_array、.jcr、.dynamic、.got
006f2000-006fb000 rw-p /bin/bash : .got.plt, .data, .bss 开头
006fb000-00702000 rw-p - : .bss 的其余部分
注意事项
(1):事实上,它更复杂:出于页面对齐的原因,.bss 部分的一部分可能会在可执行文件中表示。
(2):实际上,当它完成非惰性重定位时。
(3):MMU 操作使用页面粒度,因此mmap()、mprotect()、munmap() 调用的内存范围扩展为覆盖整页。