ELF格式解读-程序头与内存布局

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ELF格式解读-程序头与内存布局相关的知识,希望对你有一定的参考价值。

前言

ELF格式解读-(1) elf头部与节头

我们知道ELF中有一个头部叫程序头,这个头部专门用于将ELF加载内存中使用。
我们知道操作系统使用页表管理内存,如果一个ELF所有节(section)小于页表大小(一般4k),那么一个节一个页内存会极大浪费内存使用。因此我们会把具有相同属性(比如可读)节合并成一个段(segment)在去加载,那么这个合并信息就在这个程序头中。

如下图中只读节.text .rodata合并为一个段。

以下本文案例代码:

//main.c
#include <stdio.h>

static int mystaticVar = 3 ;
int myglobalvar=5;
int myglobalvar2=6;
extern void testfun();
int main()
	testfun();
	printf("hello world %d \\r\\n",mystaticVar);
	return 0;

void hell()
     testfun();

//test.c
 __attribute__((visibility("default"))) void testfun()

__attribute__((visibility("hidden")))  void testfun2()

int libGLobal=2;

相关编译命令

gcc -fPIC -shared -o test.so test.c
gcc -no-pie -o main.out main.c test.so

实战

我们的程序头大小位置由ELF头部指定,具体可参考博主另一文章ELF格式解读-(1) elf头部与节头

程序头其实是一个Elf64_Phdr数组,每个Elf64_Phdr就是一个段,但是不是所有的段都会被加载到内存中。

typedef struct 
	Elf32_Word	p_type;
	Elf32_Off	p_offset;
	Elf32_Addr	p_vaddr;
	Elf32_Addr	p_paddr;
	Elf32_Word	p_filesz;
	Elf32_Word	p_memsz;
	Elf32_Word	p_flags;
	Elf32_Word	p_align;
 Elf32_Phdr;

typedef struct 
	Elf64_Word	p_type;
	Elf64_Word	p_flags;
	Elf64_Off	p_offset;
	Elf64_Addr	p_vaddr;
	Elf64_Addr	p_paddr;
	Elf64_Xword	p_filesz;
	Elf64_Xword	p_memsz;
	Elf64_Xword	p_align;
 Elf64_Phdr;

p_type

这个字段决定这个段是什么类型,下面是字段枚举表:

Namevalue
PT_NULL0
PT_LOAD1
PT_DYNAMIC2
PT_INTERP3
PT_NOTE4
PT_SHLIB5
PT_PHDR6
PT_TLS7
PT_LOOS0x60000000
PT_Hios0x6fffffff
PT_LOPROC0x70000000
PT_HIPROC0x7fffffff
PT_GNU_EH_FRAME0x6474e550
PT_GNU_STACK0x6474e551
PT_GNU_RELRO0x6474e552

上面虽然类型很多,但是除了PT_LOAD以及PT_GNU_RELRO会被加载内存映射中,其他类别段是不会占内存的,只是提示性信息给加载器等。

PT_LOAD:这个类型会进行内存映射,比如只读代码段,数据段等
PT_GNU_RELRO:这个类型会进行内存映射,主要用于防止got表篡改,这个段在加载前是可读写的,用于重定位got,加载完成后变为只读。

p_flags

这个节是否可读可写可执行

以下是枚举值,可以进行组合表示可读可写可执行

Namevalue
PF_X0x1
PF_W0x2
PF_R0x4

p_offset

这个段所在文件的偏移

p_vaddr

内存中的虚拟地址

p_paddr

一般和p_vaddr相同

p_filesz

这个段在文件中大小

p_memsz

这个段在内存中的大小(一定会大于等于p_filesz,如果大于一般为对齐原因)

p_align

对齐数值


我们可以使用readelf -l去查看一个程序的程序头


我们来看看第二个LOAD信息

Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x000225 0x000225 R E 0x1000


03     .init .plt .plt.sec .text .fini

这个段是 .init .plt .plt.sec .text .fini这几个节组合体,段大小为0x000225,我们再看看这些节信息

我们手动计算这些节大小总和:

.init  00001b
.plt 000030
.plt.sec 000020
.text 0001a5
.fini 00000d

00001b+000030+000020+0001a5+00000d = 21D

我们计算得到21D大小,但是段显示确实225,那么多出的8字节来自哪?

其实部分个节区会间隔若干0填充的字节,这里正好是8

比如本例中.text.fini间隔3字节,.init.plt间隔5字节,一个8字节。如下图所示

因为段数据是连续的,所以包含上面的5字节。

我们运行本例程序查看实际内存布局:
使用gdb挂起程序:

我们linux的虚拟文件系统查看内存上的映射关系:

GUN_RELRO并没有按照参照程序头那样划分内存

参考

Why ELF program headers have two LOAD entries, while the program layout three sections

Hardening ELF binaries using Relocation Read-Only (RELRO)

[阅读型]CTF中linux pwn的四大基本防御措施

Program Header

11.2. Program Header

以上是关于ELF格式解读-程序头与内存布局的主要内容,如果未能解决你的问题,请参考以下文章

ELF格式解读- elf头部与节头

ELF格式解读- elf头部与节头

elf文件格式和运行时内存布局

ELF格式解读 Dynamic节

ELF格式解读 Dynamic节

ELF格式解读-符号表