注:继前段时间连载多篇 ELF 相关文章后,今次再连载 4 篇,每周 1 篇,欢迎关注并分享。分享本文到朋友圈后再加微信 tinylab 可以申请整个系列的 PDF 合集(共 15 篇,126 页)。


Linux ELF 系列文章合集(持续连载中):

  • 吴章金falcon:上手9套工具,玩转二进制文件
  • 吴章金falcon:为 Linux a.out 举行一个特殊的告别仪式
  • 吴章金falcon:在 498 行极小 OS 上跑标准 ELF 程序
  • 吴章金falcon:如何创建一个可执行的 Linux 共享库
  • 吴章金falcon:深度剖析 Linux 共享库的“位置无关”实现原理
  • 吴章金falcon:通过操作 Section 为 Linux ELF 程序新增数据
  • 吴章金falcon:实例解析 Linux C 语言程序之变量类型
  • 吴章金falcon:《360°剖析Linux ELF》视频课程新增 15 份实验材料,累计已超 70 份
  • 吴章金falcon:ELF 转二进制(1/4): 用 objcopy 把 ELF 转成 Binary 并运行

本文首发于 ELF转二进制: 允许把 Binary 文件加载到任意位置

背景简介

有一天,某位同学在讨论群聊起来:

除了直接把 C 语言程序编译成 ELF 运行以外,是否可以转成二进制,然后通过第三方程序加载到内存后再运行。

带着这样的问题,我们写了四篇文章,这是其二。

上篇 介绍了如何把 ELF 文件转成二进制文件,并作为一个新的 Section 加入到另外一个程序中执行。

这个代码包括两个段,一个 text 段,一个 data 段,默认链接完以后,text 中是通过绝对地址访问 data 的,ELF 转成 Binary 后,这个地址也写死在 ELF 中,如果要作为新的 Seciton 加入到另外一个程序,那么链接时必须确保 Binary 文件的加载地址跟之前的 ELF 加载地址一致,否则数据存放的位置就偏移了,访问不到,所以上篇文章用了一个客制化的 ld script,在里头把 Binary Seciton 的加载地址(运行时地址)写死的。

让数据地址与加载地址无关

本篇来讨论一个有意思的话题,那就是,是否可以把这个绝对地址给去掉,只要把这个 Binary 插入到新程序的 Text 中,不关心加载地址,也能运行?

想法是这样:data 应该跟 text 关联起来,也就是说,用相对 .text 的地址,因为 Binary 里头的 .rodata 是跟在 .text 后面,在文件中的相对位置其实是固定的,是否可以在运行时用一个偏移来访问呢?也就是在运行过程中,获取到 .text 中的某个位置,然后通过距离来访问这个数据?

在运行时获取 eip

由于加载地址是任意的,用 .text 中的符号也不行,因为在链接时也一样是写死的(用动态链接又把问题复杂度提升了),所以,唯一可能的办法是 eip,即程序地址计数器。

但是 eip 是没有办法直接通过寄存器获取的,得通过一定技巧来,下面这个函数就可以:

eip2ecx:movl   (%esp), %ecxret

这个函数能够把 eip 放到 ecx 中。

原理很简单,那就是调用它的 call 指令会把 next eip 放到 stack,并跳到 eip2ecx。所以 stack 顶部就是 eip。这里也可以直接用 pop %ecx

所以这条指令能够拿到 .here 的地址,并且存放在 ecx 中:

call   eip2ecx
.here:....section .rodata
.LC0:.string "Hello Worldxax0"

通过 eip 与数据偏移计算数据地址

然后接下来,由于汇编器能够算出 .here 离 .LC0(数据段起始位置):.LC0 - .here,对汇编器而言,这个差值就是一个立即数。如果在 ecx 上加上(addl)这个差值,是不是就是数据在运行时的位置?

我们在 .here 放上下面这条指令:

call   eip2ecx
.here:addl   $(.LC0 - .here), %ecx....section .rodata
.LC0:.string "Hello Worldxax0"

同样能够拿到数据的地址,等同于:

movl   $.LC0, %ecx              # ecx = $.LC0, the addr of string

下面几个综合一起回顾:

  • addl 这条指令的位置正好是运行时的 next eip (call 指令的下一条)
  • .here 在汇编时确定,指向 next eip
  • .LC0 也是汇编时确定,指向数据开始位置
  • .LC0 - .here 刚好是 addl 这条指令跟数据段的距离/差值
  • call eip2ecx 返回以后,ecx 中存了 eip
  • addl 这条指令把 ecx 加上差值,刚好让 ecx 指向了数据在内存中的位置

完整代码如下:

# hello.s
#
# as --32 -o hello.o hello.s
# ld -melf_i386 -o hello hello.o
# objcopy -O binary hello hello.bin
#.text
.global _start
_start:xorl   %eax, %eaxmovb   $4, %al                  # eax = 4, sys_write(fd, addr, len)xorl   %ebx, %ebxincl   %ebx                     # ebx = 1, standard outputcall   eip2ecx
.here:addl   $(.LC0 - .here), %ecx    # ecx = $.LC0, the addr of string# equals to: movl   $.LC0, %ecxxorl   %edx, %edxmovb   $13, %dl                 # edx = 13, the length of .stringint    $0x80xorl   %eax, %eaxmovl   %eax, %ebx               # ebx = 0incl   %eax                     # eax = 1, sys_exitint    $0x80eip2ecx:movl   (%esp), %ecxret.section .rodata
.LC0:.string "Hello Worldxax0"

链接脚本简化

这个生成的 hello.bin 链接到 run-bin,就不需要写死加载地址了,随便放,而且不需要调整 run-bin 本身的加载地址,所以 ld.script 的改动可以非常简单:

$ git diff ld.script ld.script.new
diff --git a/ld.script b/ld.script.new
index 91f8c5c..e14b586 100644
--- a/ld.script
+++ b/ld.script.new
@@ -60,6 +60,11 @@ SECTIONS/* .gnu.warning sections are handled specially by elf32.em.  */*(.gnu.warning)}
+  .bin          :
+  {
+    bin_entry = .;
+    *(.bin)
+  }.fini           :{KEEP (*(SORT_NONE(.fini)))

直接用内联汇编嵌入二进制文件

在这个基础上,可以做一个简化,直接用 .pushsection.incbin 指令把 hello.bin 插入到 run-bin 即可,无需额外修改链接脚本:

$ cat run-bin.c
#include <stdio.h>asm (".pushsection .text, "ax" n"".globl bin_entry n""bin_entry: n"".incbin "./hello.bin" n"".popsection"
);extern void bin_entry(void);int main(int argc, char *argv[])
{bin_entry();return 0;
}

这个内联汇编的效果跟上面的链接脚本完全等价。

把数据直接嵌入代码中

进一步简化汇编代码把 eip2ecx 函数去掉:

# hello.s
#
# as --32 -o hello.o hello.s
# ld -melf_i386 -o hello hello.o
# objcopy -O binary hello hello.bin
#.text
.global _start
_start:xorl   %eax, %eaxmovb   $4, %al                  # eax = 4, sys_write(fd, addr, len)xorl   %ebx, %ebxincl   %ebx                     # ebx = 1, standard outputcall   eip2ecx
eip2ecx:pop    %ecxaddl   $(.LC0 - eip2ecx), %ecx  # ecx = $.LC0, the addr of string# equals to: movl   $.LC0, %ecxxorl   %edx, %edxmovb   $13, %dl                 # edx = 13, the length of .stringint    $0x80xorl   %eax, %eaxmovl   %eax, %ebx               # ebx = 0incl   %eax                     # eax = 1, sys_exitint    $0x80.LC0:.string "Hello Worldxax0"

再进一步,直接把数据搬到 next eip 所在位置:

# hello.s
#
# as --32 -o hello.o hello.s
# ld -melf_i386 -o hello hello.o
# objcopy -O binary hello.o hello
#.text
.global _start
_start:xorl   %eax, %eaxmovb   $4, %al                  # eax = 4, sys_write(fd, addr, len)xorl   %ebx, %ebxincl   %ebx                     # ebx = 1, standard outputcall   next                     # push eip; jmp next
.LC0:.string "Hello Worldxax0"
next:pop    %ecx                     # ecx = $.LC0, the addr of string# eip is just the addr of string, `call` helped usxorl   %edx, %edxmovb   $13, %dl                 # edx = 13, the length of .stringint    $0x80xorl   %eax, %eaxmovl   %eax, %ebx               # ebx = 0incl   %eax                     # eax = 1, sys_exitint    $0x80

小结

本文通过 eip + 偏移地址 实现了运行时计算数据地址,不再需要把 Binary 文件装载到固定的位置。

另外,也讨论到了如何用 .pushsection/.popsection 替代 ld script 来添加新的 Section,还讨论了如何把数据直接嵌入到代码中。

更多用法欢迎订阅吴老师的 10 小时 C 语言进阶视频课:《360° 剖析 Linux ELF》,课程提供了超过 70 多份实验材料,其中 15 个例子演示了 15 种程序执行的方法。

相关课程

课程详情

课程资料汇总

订阅课程

  • PC 报名:https://yomocode.com/courses/9
  • 手机报名:https://w.url.cn/s/AMcKZ3a
  • 其他合作需求请联系微信:tinylab

elf section类型_ELF 转二进制(2/4): 允许把 Binary 文件加载到任意位置相关推荐

  1. elf section类型_ELF结构(主要是符号表)

    1.1.1   整体结构 ELF对象格式用于目标文件(.o扩展名)和执行文件. 有些信息只出现在目标文件或执行文件中. ELF文件由下列部件构成. ELF header必须放在文件的开始;其他部件可以 ...

  2. elf section类型_ELF文件解析(一):Segment和Section

    ELF 是Executable and Linking Format的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的 ...

  3. elf section类型_ELF文件格式解析

    ELF文件格式解析 ELF(Executable and Linking Format) 1) 可重定位的对象文件(Relocatable file) 2) 可执行的对象文件(Executable f ...

  4. elf section类型_ELF格式解析库之基本数据类型

    ELF格式简介 ELF是现代linux/unix流行的程序可执行链接格式,它有官方定义文档,具体的资料在Tool Interface Standard Executable and Linking F ...

  5. elf section类型_ELF文件格式分析(示例代码)

    要求: 1.分析文件头. 2.通过文件头找到section header table,理解其内容. 3.通过section header table找到各section. 4.理解常见的.text . ...

  6. elf section类型_在 498 行极小 OS 上跑标准 ELF 程序

    本文首发于 在 498 行极小系统跑标准 ELF 程序 简介 ELF 在 Linux 系统中作为标准可执行文件格式已经存在了 ~25 年. 如果要在 Linux 下直接研究 ELF,通常很难绕过 Li ...

  7. AOSP6.0.1 系统中增加新的category类型与app绑定,并在hotseat容器中加载app

    在launcher3(桌面程序)的hotseat中,可以通过修改dw_phone_hotseat.xml在指定位置加载指定app(仅限于已经指定category类型为APP_xxxx的app或者是系统 ...

  8. 文件加解密(任意文件类型的文件)

    直接上代码: 支持任意类型的文件的加密和解密 可以自由修改密钥 需要修改文件路径 文件加解密. #define _CRT_SECURE_NO_WARNINGS #include <stdlib. ...

  9. curl mimetype类型_根据二进制流及文件头获取文件类型mime-type

    图片格式查看: 用十六进制编辑器察看过图片的文件头,分析的结果,供大家参考: 1.JPEG/JPG - 文件头标识 (2 bytes): $ff, $d8 (SOI) (JPEG 文件标识) - 文件 ...

  10. Linux下的ELF文件、链接、加载与库(含大量图文解析及例程)

    Linux下的ELF文件.链接.加载与库 链接是将将各种代码和数据片段收集并组合为一个单一文件的过程,这个文件可以被加载到内存并执行.链接可以执行与编译时,也就是在源代码被翻译成机器代码时:也可以执行 ...

最新文章

  1. 用户'NT AUTHORITY\NETWORK SERVICE' 登录失败
  2. php新版本废弃 preg_replace /e 修饰符
  3. linux添加固件到内核,树莓派4 BCM43455 NVRAM添加到Linux固件Git
  4. 关于HTML Object中三个Style实例的区别
  5. 继续C#开发or转做产品
  6. 芯片巨头为何痴恋开源软件?英特尔Imad Sousou来解密
  7. ARGB 转换为RGB
  8. mysql主主 主键冲突_mysql主从复制原理,主主复制时主键冲突解决
  9. 留言板小程序开发笔记3
  10. maven 在pom.xml 中指定仓库位置
  11. centos6.x文本操作
  12. HDU 2084 数塔
  13. 编程开源_5种最适合编程的开源字体
  14. Confluence 6 协同编辑问题解决
  15. 签offer VS 签三方
  16. 微信公众号二次开发可以做哪些功能?
  17. eclipse次行风格的设置
  18. 泰拉瑞亚无限物品服务器,泰拉瑞亚1.4.0.5.2.1无限道具版
  19. Android开发三:数据库设计及应用(一)
  20. 让图片填满Word表格里的单元格

热门文章

  1. FFmpeg滤镜代码级分析
  2. 从头开始写项目Makefile(六):参数传递、条件判断、include
  3. per_cpu机制的详解
  4. Idle进程的切换过程
  5. pandas获取dataframe的行数,列数,元素个数
  6. 最长递增子序列(LIS longest-increment-subsequence)最长连续递增子序列 最大连续子序列和
  7. SparkSQL简单教程
  8. yum install php-pecl-mongo,pecl安装php mongodb扩展
  9. c语言pow函数原型_c语言中pow函数的用法是什么?
  10. java composite 模式_《JAVA设计模式》中的组合模式Composite