ELF解析03 - 加载段

发布时间:2024年01月12日

本文主要讨论 mmap 函数以及如何使用 mmap 函数来加载一个 ELF 的可加载段。

01纠错

Android 8 及以后是会读取 section header 的,但不是所有的 section 都会读取。

https://cs.android.com/android/platform/superproject/main/+/main:bionic/linker/linker_phdr.cpp;l=29?q=linker_phdr&sq=&ss=android

Android 8 应该是一个分水岭,可以看到会有很多版本检查逻辑:

mmap 函数

这个函数的参数在 ?man7 里面有非常详细的说明,建议看看:

https://man7.org/linux/man-pages/man2/mmap.2.html

看一个例子:

#include?<stdio.h>
#include?<sys/mman.h>
#include?<dlfcn.h>

int?main()
{

????FILE?*fp?=?fopen("/data/local/tmp/four.bin",?"rb");
????void?*addr?=?mmap((void?*)0x80000000,?0x100,?PROT_EXEC?|?PROT_WRITE?|?PROT_READ,?MAP_PRIVATE?|?MAP_FIXED,?fileno(fp),?0);

????fclose(fp);

????printf("%p\n",?addr);

????printf("%08x\n",?*(__uint8_t?*)0x80000000);

????return?0;
}

需要注意的是,我们这里是脱离了Android Studio 来编写一个可执行程序,所以需要先搭建一下环境:

需要2个额外的文件来做交叉编译,试了下直接使用?aarch64-linux-gnu-gcc?编译出来的也无法执行,似乎必须得使用 ndk + cmake 来编译,不深入研究了,能跑就行。

2个额外文件的代码就不贴了,会上传到github上的 elf 文件夹里面。

https://github.com/aprz512/Android-Crack

将编译好的文件push到 /data/local/tmp/ 文件夹下,测试各个参数的作用。

第一个参数

第一个参数表示我们期望映射的地址,但是os并不一定会映射到这个地址,文档里面也说了,linux会选择一个页对齐的地址返回,如果该地址被占用了,会返回一个其他的地址。

第二个参数

第二个参数是映射的长度,这个很值得研究,需要分情况讨论。

我们以 four.bin 为例,该文件里面就只有 3 个字符串数字,大小为4个字节:

echo?"123"?>?four.bin

当mmap传递的参数小于文件大小时,我们修改代码,将第二个参数的值改为2:

void?*addr?=?mmap((void?*)0x80000000,?0x2,?PROT_EXEC?|?PROT_WRITE?|?PROT_READ,?MAP_PRIVATE?|?MAP_FIXED,?fileno(fp),?0);

尝试访问 0x80000002 处的地址,按照直觉来说,这个位置没有被分配,所以应该会出现 Segmentation fault 之类的错误。但是实际上并没有,输出结果为:

0x80000000
00000033

0x33对应的ascii就是字符“3”,这说明,它将整个文件都映射进去了,其实mmap是按照页来映射的,以 4KB 为单位。我们可以使用 IDA 验证一下,使用 ?getchar 卡住程序,然后调试程序:

可以看到 0x80000000 开始的位置确实是 four.bin 文件的数据。往下看直到 0x80000FFF的位置都是0值,从 0x80001000开始就不是进程空间地址了。

我们再做一个实验,将映射长度改为8KB,访问4KB+1的位置会如何呢?

0x80000000
00000033
Bus?error

可以看到,访问 0x80000002 位置依然没有问题,但是访问 0x80001000 位置却出现了 Bus error。这个文档里面也说明了,出现这个错误是因为访问了超出文件映射结尾的页地址导致的。

我们看下 IDA 情况:

可以看到,后面的一页都是问号,访问这里面的虚拟地址就会出现 bus error。

第三个参数

权限标志位,没啥好说的,不如看文档。

需要注意的是,这里我们先全给 RWX,因为需要重定位,否则会报错。

第四个参数

只说一个MAP_FIXED,前面说到第一个参数的时候,我们传递了一个值给os,可是os不一定理我们。但是有些情况我们一定要os映射到指定的位置才行,比如段的加载。elf里面有 .text / .data 段,这两个段之间是有关系的,它们之间存在相互引用,如果不将这两个玩意挨着放,那么它们之间的一些相对偏移就会出问题。

所以,如何解决这个问题呢?就是传递 MAP_FIXED 标志位,它表示如果不能映射到我们指定的位置,干脆就直接失败。

加载段

好了,有了上面的基础,我们就可以自己实现 elf 的段加载了,其实就是调用 mmap 函数,将可加载段映射一下就完事。

不过需要考虑的事情有:当文件大小小于内存大小的时候,要怎么解决?

上面我们已经验证过了,如果直接映射会出 bus error。

所以,我们可以分两次映射,第二次映射一个匿名文件,是不是想到 maps 里面没名字的那一行了。

我们可以先看 linker 源码学习一下。

linker 源码

https://github.com/aosp-mirror/platform_bionic/blob/donut-release2/linker/linker.c

由于linker的核心代码变化不会太大,所以强烈建议研究老版本的源码,弄清楚了再去看新版本的。

这里有一个图:

示例代码

#include?<stdio.h>
#include?<sys/mman.h>
#include?<dlfcn.h>
#include?<stdlib.h>
#include?<string.h>
#include?<elf.h>

#define?PAGE_SIZE?(4096)
#define?PAGE_MASK?(PAGE_SIZE?-?1)
#define?BASE?(0x80000000)

int?main()
{

????//?FILE?*fp?=?fopen("/data/local/tmp/four.bin",?"rb");
????//?void?*addr?=?mmap((void?*)0x80000000,?0x2000,?PROT_EXEC?|?PROT_WRITE?|?PROT_READ,?MAP_PRIVATE?|?MAP_FIXED,?fileno(fp),?0);

????//?fclose(fp);

????//?printf("%p\n",?addr);

????//?printf("%08x\n",?*(__uint8_t?*)0x80000002);
????//?//?printf("%08x\n",?*(__uint8_t?*)0x80001000);

????//?getchar();

????size_t?elf64_header?=?sizeof(Elf64_Ehdr);

????Elf64_Ehdr?*ehdr?=?(Elf64_Ehdr?*)malloc(elf64_header);

????FILE?*fp?=?fopen("/data/local/tmp/ls",?"rb");

????//?read?Elf64_Ehdr?bytes
????fread(ehdr,?elf64_header,?1,?fp);

????printf("e_phoff:?%08x\n",?ehdr->e_phoff);

????size_t?elf64_phdr?=?sizeof(Elf64_Phdr);
????int?phdr_num?=?ehdr->e_phnum;
????printf("phdr_num:?%d\n",?phdr_num);

????Elf64_Phdr?*phdr?=?(Elf64_Phdr?*)malloc(elf64_phdr?*?phdr_num);

????fseek(fp,?ehdr->e_phoff,?SEEK_SET);

????fread(phdr,?elf64_phdr?*?phdr_num,?1,?fp);

????uint64_t?len;
????uint64_t?tmp;
????uint64_t?pbase;
????uint64_t?extra_len;
????uint64_t?extra_base;

????for?(size_t?i?=?0;?i?<?phdr_num;?i++)
????{

????????//?printf("p_type:?%d\n",?phdr->p_type);
????????if?(phdr->p_type?==?PT_LOAD)
????????{
????????????//?这里计算的是?pbase?的值
????????????tmp?=?BASE?+?phdr->p_vaddr?&?(~PAGE_MASK);
????????????//?看图可知,这里的文件大小加上?(?base?+?p_vaddr?-?pbase),也就是?mask?off?的值
????????????len?=?phdr->p_filesz?+?(phdr->p_vaddr?&?PAGE_MASK);
????????????pbase?=?mmap(
????????????????tmp,
????????????????len,
????????????????PROT_EXEC?|?PROT_WRITE?|?PROT_READ,
????????????????MAP_PRIVATE?|?MAP_FIXED,
????????????????fileno(fp),
????????????????phdr->p_offset?&?(~PAGE_MASK));

????????????printf("mapped?addr:?%08x,?%08x\n",?pbase,?len);

????????????tmp?=?(unsigned?char?*)(((unsigned)pbase?+?len?+?PAGE_SIZE?-?1)?&?(~PAGE_MASK));
????????????if?(tmp?<?(BASE?+?phdr->p_vaddr?+?phdr->p_memsz))
????????????{
????????????????extra_len?=?BASE?+?phdr->p_vaddr?+?phdr->p_memsz?-?tmp;
????????????????extra_base?=?mmap((void?*)tmp,?extra_len,
??????????????????????????????????PROT_EXEC?|?PROT_WRITE?|?PROT_READ,
??????????????????????????????????MAP_PRIVATE?|?MAP_FIXED?|?MAP_ANONYMOUS,
??????????????????????????????????-1,?0);
????????????????printf("mapped?addr:?%08x,?%08x\n",?extra_base,?extra_len);
????????????}
????????}

????????phdr++;
????}

????free(ehdr);
????free(phdr);
????fclose(fp);
????printf("mapped?ok!!!");

????return?0;
}

运行结果如下:

sailfish:/data/local/tmp?#?./elf???????????????????????????????????????
e_phoff:?00000040
phdr_num:?9
mapped?addr:?80000000,?0005c45c
mapped?addr:?8005d000,?000047a0
mapped?addr:?80062000,?00003966

文章来源:https://blog.csdn.net/a5right/article/details/135544491
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。