专业的企业网站设计与编辑,网络营销实际上就是网上营销,曲靖网站开发,医疗 企业 网站制作本文主要讨论 mmap 函数以及如何使用 mmap 函数来加载一个 ELF 的可加载段。
01纠错
Android 8 及以后是会读取 section header 的#xff0c;但不是所有的 section 都会读取。 https://cs.android.com/android/platform/superproject/main//main:bionic/linker/linker_phdr…本文主要讨论 mmap 函数以及如何使用 mmap 函数来加载一个 ELF 的可加载段。
01纠错
Android 8 及以后是会读取 section header 的但不是所有的 section 都会读取。 https://cs.android.com/android/platform/superproject/main//main:bionic/linker/linker_phdr.cpp;l29?qlinker_phdrsqssandroid Android 8 应该是一个分水岭可以看到会有很多版本检查逻辑 mmap 函数
这个函数的参数在 man7 里面有非常详细的说明建议看看 https://man7.org/linux/man-pages/man2/mmap.2.html 看一个例子
#include stdio.h
#include sys/mman.h
#include dlfcn.hint 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
000000330x33对应的ascii就是字符“3”这说明它将整个文件都映射进去了其实mmap是按照页来映射的以 4KB 为单位。我们可以使用 IDA 验证一下使用 getchar 卡住程序然后调试程序 可以看到 0x80000000 开始的位置确实是 four.bin 文件的数据。往下看直到 0x80000FFF的位置都是0值从 0x80001000开始就不是进程空间地址了。
我们再做一个实验将映射长度改为8KB访问4KB1的位置会如何呢
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 bytesfread(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