I wrote a "hello world" program with PIE/PIC enabled. I observed the program headers have 2 LOAD entries:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
... ...
LOAD 0x000000 0x00000000 0x00000000 0x00870 0x00870 R E 0x1000
LOAD 0x000eb0 0x00001eb0 0x00001eb0 0x0015c 0x00164 RW 0x1000
So in my understanding, the ELF binary will be loaded into 2 pages. The 1st page contains the binary from offset 0 to file size 0x870, and it is Read & Execute. Since they are 0x1000 aligned, the 2nd entry will be loaded in the 2nd page, which contains the binary(from offset 0xeb0 to 0xeb0+0x15c). This page has Read & Write privilege.
While I "pmap" the running process (or cat /proc/pid/maps), it show there are 3 pages for the running program:
b7554000 4K rw--- [ anon ]
b7555000 1700K r-x-- libc-2.19.so
b76fe000 4K ----- libc-2.19.so
b76ff000 8K r---- libc-2.19.so
b7701000 4K rw--- libc-2.19.so
b7702000 12K rw--- [ anon ]
b771b000 16K rw--- [ anon ]
b771f000 4K r-x-- [ anon ]
b7720000 128K r-x-- ld-2.19.so
b7740000 4K r---- ld-2.19.so
b7741000 4K rw--- ld-2.19.so
b7742000 4K r-x-- main
b7743000 4K r---- main
b7744000 4K rw--- main
bfe68000 132K rw--- [ stack ]
total 2032K
So how the ELF binary is loaded, and how the program header indicates a LOAD directive?
Part of the sections headers like:
[13] .text PROGBITS 00000480 000480 0002c2 00 AX 0 0 16
[14] .fini PROGBITS 00000744 000744 000014 00 AX 0 0 4
[15] .rodata PROGBITS 00000758 000758 000060 00 A 0 0 4
[16] .eh_frame PROGBITS 000007b8 0007b8 000094 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0000084c 00084c 000024 00 A 0 0 4
[18] .jcr PROGBITS 00001eb0 000eb0 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 00001eb4 000eb4 000004 00 WA 0 0 4
[20] .init_array INIT_ARRAY 00001eb8 000eb8 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 00001ebc 000ebc 000108 08 WA 5 0 4
[22] .got PROGBITS 00001fc4 000fc4 00001c 00 WA 0 0 4
[23] .got.plt PROGBITS 00001fe0 000fe0 000020 00 WA 0 0 4
[24] .data PROGBITS 00002000 001000 00000c 00 WA 0 0 4
[25] .tm_clone_table PROGBITS 0000200c 00100c 000000 00 WA 0 0 4
[26] .bss NOBITS 0000200c 00100c 000008 00 WA 0 0 4
I observed the program headers have 2 LOAD entries:
You've omitted an important program header: GNU_RELRO, which tells the loader that after mapping the LOAD segments, it should mprotect part of them as read-only.
When mprotect with different permissions is called on existing mapping, the kernel has to split that mapping into multiple mappings, which is where the extra entry is coming from.
In my test binary:
LOAD 0x000000 0x00000000 0x00000000 0x0075c 0x0075c R E 0x1000
LOAD 0x000ef4 0x00001ef4 0x00001ef4 0x0012c 0x00130 RW 0x1000
...
GNU_RELRO 0x000ef4 0x00001ef4 0x00001ef4 0x0010c 0x0010c R 0x1
The kernel actually maps the LOAD segments before the loader even starts, and the map at that point looks like this:
56555000-56556000 r-xp 00000000 fc:02 801500 /tmp/a.out
56556000-56558000 rw-p 00000000 fc:02 801500 /tmp/a.out
f7fdb000-f7fdc000 r-xp 00000000 00:00 0 [vdso]
...
After the mprotect, the map looks like this:
56555000-56556000 r-xp 00000000 fc:02 801500 /tmp/a.out
56556000-56557000 r--p 00000000 fc:02 801500 /tmp/a.out
56557000-56558000 rw-p 00001000 fc:02 801500 /tmp/a.out
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With