无法解析的外部符号main_ELF文件格式解析

76288f09fb5a8d3836d40ddac1f53e1a.png

原创: S3cana 合天智汇

原创投稿活动:https://mp.weixin.qq.com/s/Nw2VDyvCpPt_GG5YKTQuUQ

ELF文件格式的相关知识是Linux下进行pwn以及reverse的基础,是二进制可执行文件的一种形式,下面我们通过一个ELF文件的生成,并结合其ELF文件结构分析一下一个二进制文件在系统中执行时与权限相关的一些ELF结构知识点。文章内容较为浅显,大佬可略过,文章有不足之处,也恳请批评指正。

ELF文件的生成

一个main.c的文件,在linux系统上,经过gcc编译后可以生成一个可以执行的文件,以hello.c为例

#include<stdio.h>
int main(){
printf("Hello World n");
return 0;
}

一个ELF的生成,最开始是在系统中编写的一个源文件,依次会经过预处理,编译,汇编,链接过程后,会生成一个ELF格式的可以执行的文件。我们往往通过gcc hello.c -o hello即可生成一个文件名为hello的可执行文件,该程序会输出Hello World。

4ef12289203793a34c6f4ae8bdf61641.png

在如上中,我们通过gcc 一条命令将hello.c编译成能够执行的二进制文件,但在这一条命令中,同时包含了预处理,编译,汇编,链接的过程。下面分别来看这几个过程

预处理

主要是处理头文件,预编译主要是处理那些源代码中的以"#"开始的预编译指令,会删除注释

gcc -E hello.c -o hello.i

d51d5e00fdf8ef20aad987843ee817cd.png

编译

编译,经过编译后,可以生成.s的汇编文件

gcc -S hello.i -o hello.s

74344389f8e5cc7aea2d8f766e40a04b.png

汇编

汇编,经过汇编后,生成机器指令

as hello.s -o hello.o
or
gcc -c hello.s -o hello.o

98efeacfc1be51fdb7374884dbfcf81d.png

链接

经过汇编后生成的是目标文件,编译器编译源代码后生成的文件叫目标文件,在目标文件中,其本身是按照ELF文件的格式存储的,但是其中的一些符号以及地址还没有被调整,再经过链接后,其生成的即是hello的二进制的可执行文件。

ld -s -o hello hello.o

ELF文件内容解析

一个标准的ELF文件,是由文件头(ELF Header),程序头(Segment Header),节头(Section Header),符号表( Symbol Table),动态符号表(Dynamic Symbol Table)等组成,我们在010editor中,通过ELF Template来看一下hello的文件格式,如下图

0cf0837b3e603bf7339b237982831f3a.png

ELF Header

在ELF Header中的 file_identification 指明该文件类型为ELF的二进制文件

643c0c0b76b398c36e249d8035b4a60b.png

在ELF File Header中通(e_phoff e_shoff两个变量,我们可以找到Segment Header和 Section Header的位置,从而找到程序头和节表头的位置,在这两个表中,有对各自段以及节的详细的介绍。

7e70d81c39a908033b674125ebd0e1c4.png

在ELF Header中的e_entry_START_ADDRESS中,保存着程序执行的入口,地址0x400430,程序的start的入口地址

f74decd14c830c3abf646a139fad03f7.png

在程序实际中,我们可以看到start的位置也在0x400430的位置处。

1a8a214bfde483dd4f1df8cef09d1b57.png

ELF程序头

ELF程序头,是程序装载不可缺少的一部分,可以分为以下几个段

  • PT_LOAD ,即可以装载到系统中的段
  • PT_DYNAMIC, 动态段时动态链接的文件所必须的,包含着动态链接所需要的信息。
  • PT_NOTE,保存一些系统相关的附加信息
  • PT_INTERP,段只将位置和大小信息存放在一个以null 为终止符的字符串 中,是对程序解释器位置的描述
  • PT_PHDR 段保存了程序头表本身的位置和大小

bd268f890d094163c20d83a9f313e91b.png

其中在段的p_flags中,确定了该段的权限,在后文的节表头中,对相应的表有进一步的权限的确定。

60517e47f50d20319baf67c1f3e3ae9c.png

memsz的值对应的时1788,hex(1788)=0x6fc,所以该段的权限是读和可执行的权限,在0x400238到0x4006fc只有只读权限,这是由于节表中权限的设置导致的。

4964ee34dd1699364e04d3487db4fec2.png

在ELF节表中的权限

0afa6f45545f1897c6938c33629025a4.png

ELF 节表头

ELF中,包含很多的节表,我们可以通过readelf -S hello来查看

d24363fd1cc04d3a322f468a121025db.png

我们可以看到各种节表信息,常用以下几项

  • .text 该节保存了程序代码
  • .bss 该节主要保存
  • .data 保存了初始化的全局变量
  • .plt 包含了动态链接器调用从共享库导入的函数必需的相关的代码
  • .got.plt 保存了全局的偏移表等表,got表位于该节

在ELF 的描述节表的相关的数据结构中,s_flags的标志用于描述该节的权限,不同的数值,分别对应了不同的权限,具体如下图

dce82c86e700ea21fd1823eea9fc2a51.png

攻击相关got表劫持原理

在CTF的pwn相关题目中,我们可以通过栈溢出,以及堆溢出来实现shell权限的获取,获取权限往往可以通过劫持got表来实现,而这个实现的前提需要got表(.got.plt)权限为可写权限。首先看一下什么是plt表和got表

plt表,是过程链接表,程序动态调用的符号在可执行文件中是位置无关的符号,当程序调用时,会通过plt表将符号转移到绝对地址。

got表中保存了ELF文件在共享库中的绝对地址,在程序一开始运行的时候,got表是空的,当符号第一次调用的时候,会动态解析符号的绝对地址并填充到got表中,在第二次调用同一符号的时候,直接通过got表跳转。

62d2cb86d08db2d2d3b852effec303d7.png

这里对程序第一次解析符号的过程不加以详细描述,仅描述第二次调用时的跳转过程,改造源程序hello.c,将断点停在程序执行到第二个printf("Hello")时

#include<stdio.h>
int main(){
    printf("hello world");
    printf("Hello");
    return 0;
}

这里是call <0x400400>

b15245cb24c78bd12a175ed85b03486b.png

我们来看一下0x400400处的汇编代码,这里是跳转,jmp QWORT PTR[rip + 0x200c12]

196e373755b04a3f591cef0e8adffda2.png

可以看到是跳转到0x601018处,在下图中我们可以看到在0x601018处的值为0x00007ffff7a62800

c46cd76a939c8b373f33789483773b68.png

下面我们通过单步调试s跟进程序,可以看到rip指向了0x00007ffff7a62800

d0e429b8222a0ddc8c6544e8d7e9f3c3.png

通过readelf -S hello可以查看到其got表的起始位置0x601000

a9e667d3c80a44a6ae5461fd9f64555b.png

在实际的攻击的过程中,当我们将此处的got表的值更改为我们需要的system函数的时候,再配合传入实际的参数/bin/sh即可实现shell权限的获取。

防御相关

可以通过复写got表,实现got表劫持,当我们设置got表不可写的时候,该攻击也就失效了,RELRO技术可以实现该保护。在程序编译的时候,我们可以通过如下命令,实现got表不可写,需要注意的是,RELRO保护可分为 Partial RELRO 和Full RELRO,当编译的时候,我们指定 Partial RELRO 的时候,其got表仍旧可写

#Partial RELRO,gcc 默认也是Partial RELRO
gcc -z lazy -o test test.c
#Full RELRO
gcc hello.c -o -z now

合天网安实验室相关实验:ELF

实验:ELF(合天网安实验室)

声明:笔者初衷用于分享与普及网络知识,若读者因此作出任何危害网络安全行为后果自负,与合天智汇及原作者无关!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了解析ELF文件并获取链接脚本,您可以使用类似libelf的库。Libelf是一种开放源代码库,可用于访问ELF文件的内容。以下是一个简短的C程序示例,该程序使用libelf解析ELF文件并获取链接脚本: ``` #include <fcntl.h> #include <libelf.h> #include <stdio.h> int main() { //打开ELF文件 int fd = open("path/to/elf", O_RDONLY, 0); if (fd < 0) { perror("open failed"); return 1; } //加载ELF文件 Elf *e = elf_begin(fd, ELF_C_READ, NULL); if (e == NULL) { perror("elf_begin failed"); return 1; } //获取链接脚本的字符串节 size_t shstrndx; if (elf_getshdrstrndx(e, &shstrndx) != 0) { perror("elf_getshdrstrndx failed"); return 1; } Elf_Scn *scn = NULL; while ((scn = elf_nextscn(e, scn)) != NULL) { //获取节头 GElf_Shdr shdr; if (gelf_getshdr(scn, &shdr) != &shdr) { perror("gelf_getshdr failed"); return 1; } //获取节名 char *sec_name = elf_strptr(e, shstrndx, shdr.sh_name); if (sec_name == NULL) { perror("elf_strptr failed"); return 1; } //如果是链接脚本节 if (shdr.sh_type == SHT_PROGBITS && strcmp(sec_name, ".linker_script") == 0) { //获取数据 char *data = (char*)malloc(shdr.sh_size); if (data == NULL) { perror("malloc failed"); return 1; } if (gelf_getdata(scn, data, shdr.sh_size, shdr.sh_offset) == NULL) { perror("gelf_getdata failed"); return 1; } //打印链接脚本数据 printf("%s\n", data); free(data); } } //释放ELF 文件 elf_end(e); close(fd); return 0; } ``` 请注意,这只是一个简单的示例程序。实际上,解析ELF文件可能会更复杂,取决于所需的信息类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值