ELF解析02 - linker

01里面,我们看到了修改后的 ls 程序在 maps 里面的布局:

在 01 里面,我们说到,r---p 这个段是一个 padding 段,但是实际上不是的。

我们一行行介绍,从第一行看起:

bash 复制代码
5fde6d4000-5fde731000 r-xp 00000000 103:13 1450012 /data/local/tmp/ls 

这个显然是对应的:

第二行:

bash 复制代码
5fde731000-5fde734000 r--p 0005c000 103:13 1450012                       /data/local/tmp/ls

这个段的权限标志是 r-- ,这个与010中两个可加载段都不匹配,这是为啥呢?

其实,这个段是010中第二个可加载段的一部分,只不过它的权限被修改了。是被下面这个段修改的:

这个段描述的就是,在 elf 被重定位之后,将虚拟位置 5D660 且大小为 10656 字节的区域的权限改成只读的。

我们算一下:

且由于4kb对齐,所以5fde731000 就是需要被修改为只读的起始地址。

10656 的十六进制是29A0,对齐后是 3000,所以5fde731000-5fde734000 就是一个 r-- 段。

第三行:

bash 复制代码
5fde734000-5fde736000 rw-p 0005f000 103:13 1450012                       /data/local/tmp/ls

这就就是对应第二个可加载段中中间的一部分。

第四行:

css 复制代码
5fde736000-5fde73a000 rw-p 00000000 00:00 0

这个就是第二个可加载段的结尾部分,为啥呢?而且它为啥没有对应的文件路径呢?

起始可以比对一下第二个可加载段的 RAM 大小,就知道这个段是第二个可加载段的结尾部分。至于为啥它没有名字,那是因为它的文件大小要小于映射后的 RAM 大小,所以RAM超出文件的部分就没有文件映射了,很奇特是不是?!!

下面进入正题。

Program Header

这个段里面就2块数据有用:

文件偏移与虚拟地址,它们都是40H,而且这个值与 elf_header 必须是相等的。这个值某种意义上就是 elf_header 的大小。那为啥还需要在这里再储存一次呢?

可以简单理解,是因为 linker 里面有一些指针指向的是 elf_header,而有些指针指向的是 program_header,互相转换的时候,会使用指针偏移来计算,偏移大小就是这里的 40H,所以就在这里也记录了值。

p_data 就是整个 program_header_table 的内容。

p_type 不知道有没有用,没试过改这里,假定有用吧。

Interpreter Path

这个段描述的是解释器路径:

这个段与上面的 Program Header 段必须要出现在可加载段前面。

这个段里面,我们只需要关注其 p_data,会发现它是一个字符串:

bash 复制代码
/system/bin/linker64

我们的手机上是有这个文件的。

ELF 的可加载段就是有这个文件来加载的,包括重定位操作等等。

既然ELF里面使用了一个路径来描述它,那么是不是意味着我们可以修改这个字符串,使用我们自定义的 linker 来加载 ELF 文件呢?

做如下操作:

  • 将 /system/bin/ls cp 到 /data/local/tmp/ls 位置
  • 将 /system/bin/linker64 copy 到 /data/local/tmp/er64 位置
  • 将 /data/local/tmp/ls 的这个段的加载器路径改为 /data/local/tmp/er64
  • 将 /data/local/tmp/er64 的入口地址改为死循环

注意加载器路径长度不能超过 p_filesz_SEGMENT_FILE_LENGTH 这个限制,而且要以 00 字节结尾。

修改完之后,我们再运行 /data/local/tmp/ls 程序,查看其 maps 文件信息:

lua 复制代码
5d2ab79000-5d2abd6000 r-xp 00000000 103:13 1450012                       /data/local/tmp/ls
5d2abd6000-5d2abdb000 rw-p 0005c000 103:13 1450012                       /data/local/tmp/ls
5d2abdb000-5d2abdf000 rw-p 00000000 00:00 0
73e9eee000-73e9eef000 r--p 00000000 00:00 0                              [vvar]
73e9eef000-73e9ef0000 r-xp 00000000 00:00 0                              [vdso]
73e9ef0000-73e9fe7000 r-xp 00000000 103:13 1450018                       /data/local/tmp/er64
73e9fe8000-73e9fed000 rw-p 000f7000 103:13 1450018                       /data/local/tmp/er64
73e9fed000-73ea061000 rw-p 00000000 00:00 0
7fcd826000-7fcd847000 rw-p 00000000 00:00 0                              [stack]

发现,相比之前少了很多东西,什么 libc 相关的东西都没有。

这个就是因为现在进程卡在了非常早的时机,连这个 ELF 文件自身的段的重定位都没有做,只是光将 ELF 文件中的东西放到内存里面了。

那么上面的信息中,前面两行里面,我们发现 ls 文件被加载了,这又是为啥?我们明明已经在 linker 的入口函数加了一个死循环,linker 根本不会执行加载逻辑才对。

其实是因为,这两个可执行段不是由 ls 中指定的 linker 加载的,而是其他进程中的 linker 加载的,现在先简单的理解为是 init 进程干的吧(虽然也不一定对,等后面搞os再研究,不知要到猴年马月)。剩下的其他 ELF 依赖文件,比如 libc 等将由路径中指定的 linker 来加载。

我们使用 IDA 来验证一下,先附加到进程:

可以看到,这里面就两个 so 被加载。

我们还原一下 linker 的入口地址指令,让其继续执行,并且在 ls 的入口地址加上断点。

按 F8 步过 linker_init,发现此时,linker 加载了很多的 so:

再按 F9 让程序执行到 ls 入口地址:

linker 的特点

它没有 imports:

因为它不依赖其他 so,它也不能依赖其他 so,因为它运行的时候,其他 so 都还没加载呢。比如 open 函数,它就不能使用 libc 中的 open 函数,它必须自己实现 open 函数才行。它必须实现所有自己需要的函数。

相关推荐
我只会写Bug啊33 分钟前
复制可用!纯前端基于 Geolocation API 实现经纬度获取与地图可视化
前端·高德地图·地图·百度地图·经纬度
刘一说40 分钟前
Vue3 模块语法革命:移除过滤器(Filters)的深度解析与迁移指南
前端·vue.js·js
lkbhua莱克瓦241 小时前
JavaScript核心语法
开发语言·前端·javascript·笔记·html·ecmascript·javaweb
Trae1ounG1 小时前
这是什么dom
前端·javascript·vue.js
比老马还六1 小时前
Bipes项目二次开发/扩展积木功能(八)
前端·javascript
易营宝1 小时前
全球建站SaaS平台能提升SEO评分吗?是否值得切换?
大数据·前端·人工智能
513495921 小时前
在Vue.js项目中使用docx和file-saver实现Word文档导出
前端·vue.js·word
AC赳赳老秦2 小时前
Prometheus + DeepSeek:自动生成巡检脚本与告警规则配置实战
前端·javascript·爬虫·搜索引擎·prometheus·easyui·deepseek
接着奏乐接着舞。2 小时前
前端大数据渲染性能优化:Web Worker + 分片处理 + 渐进式渲染
大数据·前端·性能优化
Beginner x_u2 小时前
CSS 中的高度、滚动与溢出:从 height 到 overflow 的完整理解
前端·css·overflow·min-height