Mach-O的动态链接相关知识
0x00 摘要
通过分析Mach-O
的动态链接过程,加深对Mach-O
文件结构的理解。对Mach-O文件格式的简单的分析看这里这里。
0x01 Mach-O Lazy Bind
Mach-O文件的通过dyld加载的时候并没有确定每一个函数的具体地址在哪里,而是在真正调用该函数的时候通过过程连接表(procedure linkage table),后面简称PLT,来进行一次lazybind。
结合Mach-O文件的分析与代码的调试简单的分析一下,只能算是管中窥豹了。
源码很简单。
1 2 3 4 5 6 7 8 |
#include <stdio.h> int main(int argc, const char * argv[]) { // insert code here... printf("Hello, World!\n"); printf("2Hello, World!\n"); return 0; } |
分别在两个printf函数处下断点,启动程序。
1.1 第一次调用prinf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
lazy_bind`main: 0x100000f10 <+0>: pushq %rbp 0x100000f11 <+1>: movq %rsp, %rbp 0x100000f14 <+4>: subq $0x20, %rsp 0x100000f18 <+8>: leaq 0x57(%rip), %rax ; "Hello, World!\n" 0x100000f1f <+15>: movl $0x0, -0x4(%rbp) 0x100000f26 <+22>: movl %edi, -0x8(%rbp) 0x100000f29 <+25>: movq %rsi, -0x10(%rbp) 0x100000f2d <+29>: movq %rax, %rdi 0x100000f30 <+32>: movb $0x0, %al -> 0x100000f32 <+34>: callq 0x100000f56 ; symbol stub for: printf 0x100000f37 <+39>: leaq 0x47(%rip), %rdi ; "2Hello, World!\n" 0x100000f3e <+46>: movl %eax, -0x14(%rbp) 0x100000f41 <+49>: movb $0x0, %al 0x100000f43 <+51>: callq 0x100000f56 ; symbol stub for: printf 0x100000f48 <+56>: xorl %ecx, %ecx 0x100000f4a <+58>: movl %eax, -0x18(%rbp) 0x100000f4d <+61>: movl %ecx, %eax 0x100000f4f <+63>: addq $0x20, %rsp 0x100000f53 <+67>: popq %rbp 0x100000f54 <+68>: retq |
在0x100000f52 <+34>
行处通过callq 0x100000f64
来调用printf。
执行callq
指令之后代码跳转到这里:
1 2 |
lazy_bind`printf: -> 0x100000f56 <+0>: jmpq *0xb4(%rip) ; (void *)0x0000000100000f6c |
1.2 __Data,__la_symbol_ptr 获取函数地址
这里的jmpq要跳转到0x0000000100000f6c
这个地址是从__Data,__la_symbol_ptr中的Lazy Symbol Pointers中获取到的。(怎么来的不是很清楚?)希望通过stub来调用printf函数。
通过命令行查看0x100001010
处的地址获得了同样的值。
1 2 3 |
(lldb) x 0x100001010 0x100001010: 6c 0f 00 00 01 00 00 00 00 00 00 00 00 00 00 00 l............... 0x100001020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ |
1.3 通过__stub__helper进行lazybind
在Mach-O中每一个Symbol Stub可能有以下两种行为其中之一:
- 跳转到函数的指令,执行函数体
- 通过动态链接器查找函数的Symbol(符号),然后执行函数。
通过工具查看__stubs的Section数据,发现只有一个函数就是_printf。
这里的Data其实就是上面看到的jmpq
代码。执行之后代码跳转到了这样的代码片段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
-> 0x100000f6c: pushq $0x0 0x100000f71: jmp 0x100000f5c 0x100000f76: gs ;这里往下都没有!!!! 0x100000f78: insb %dx, %es:(%rdi) 0x100000f79: insb %dx, %es:(%rdi) 0x100000f7a: outsl (%rsi), %dx 0x100000f7b: subb $0x20, %al 0x100000f7d: pushq %rdi 0x100000f7e: outsl (%rsi), %dx 0x100000f7f: jb 0x100000fed 0x100000f81: andl %ecx, %fs:(%rdx) 0x100000f84: addb %dh, (%rdx) 0x100000f86: gs 0x100000f88: insb %dx, %es:(%rdi) 0x100000f89: insb %dx, %es:(%rdi) 0x100000f8a: outsl (%rsi), %dx |
这里就是通过_stub_helper来调用dyld_stubbinder函数来计算printf函数的真实地址。通过下面的\_TEXT,__stub_helper具体信息可以看出,jmpq 0x100000f5c,就是在压入参数0x0(函数的link的时候给的编号)之后跳转到Section的起始处,调用了binder。
binder是一块汇编代码。这里就不做解释了。作用就是计算具体的函数地址,并调用printf。
1.4 第二次调用printf函数
这个释放断点,程序将在调用第二个printf函数的地方停下来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
lazy_bind`main: 0x100000f10 <+0>: pushq %rbp 0x100000f11 <+1>: movq %rsp, %rbp 0x100000f14 <+4>: subq $0x20, %rsp 0x100000f18 <+8>: leaq 0x57(%rip), %rax ; "Hello, World!\n" 0x100000f1f <+15>: movl $0x0, -0x4(%rbp) 0x100000f26 <+22>: movl %edi, -0x8(%rbp) 0x100000f29 <+25>: movq %rsi, -0x10(%rbp) 0x100000f2d <+29>: movq %rax, %rdi 0x100000f30 <+32>: movb $0x0, %al 0x100000f32 <+34>: callq 0x100000f56 ; symbol stub for: printf 0x100000f37 <+39>: leaq 0x47(%rip), %rdi ; "2Hello, World!\n" 0x100000f3e <+46>: movl %eax, -0x14(%rbp) 0x100000f41 <+49>: movb $0x0, %al -> 0x100000f43 <+51>: callq 0x100000f56 ; symbol stub for: printf 0x100000f48 <+56>: xorl %ecx, %ecx 0x100000f4a <+58>: movl %eax, -0x18(%rbp) 0x100000f4d <+61>: movl %ecx, %eax 0x100000f4f <+63>: addq $0x20, %rsp 0x100000f53 <+67>: popq %rbp 0x100000f54 <+68>: retq |
执行指令之后发现和第一次调用printf已经不一样了。
1 2 |
lazy_bind`printf: -> 0x100000f56 <+0>: jmpq *0xb4(%rip) ; (void *)0x00007fff96b1815c: printf |
通过指令再一次查看0x100001010
处的内存值。
1 2 3 |
(lldb) x 0x100001010 0x100001010: 5c 81 b1 96 ff 7f 00 00 00 00 00 00 00 00 00 00 \............... 0x100001020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ |
也就是说 __Data,__la_symbol_ptr中指向printf地址的值已经发生了变化,指向了printf的指令。
1.5 小结
证明了,延迟绑定只会在第一次调用的时候发生。整个流程与linux中的PLT与GOT在实现逻辑上基本是相同的,只是具体的代码实现不一样。
0x02 相关LoadCommand
上面只是通过调试简单的了解了动态链接的表现,要理解动态链接还需对几个数据结构有所了解。
2.1 LC_SYMTAB
LC_SYMTAB这个LoadCommand主要提供了两个信息
- Symbol Table的偏移量与Symbol Table中元素的个数
- String Table的偏移量与String Table的长度
2.1.1 Symbol Table
在计算机科学中,符号表是一种用于语言翻译器(例如编译器和解释器)中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。
–wiki
简单的理解就是Symbol Table里面包含了所有会被调用的函数的信息,无论是已经bind的还是没有bind的函数。
2.2.2 String Table
这个很好理解,在符号处理时所有会用到的字符串放在了这里。和__TEXT,__cstring不同。
2.2 LC_DYSYMTAB
LC_DYSYMTAB的数据结构,如图所示。这一个LoadCommand与动态链接相关的就是红框标出的两个字段,标示了需要动态符号表的偏移量与符号个数。
动态符号表的数据结构非常的简单,是一个32bit的索引的数组。通过索引可以在Symbol Table中寻找到对应的函数信息。
0x03 小结
通过分析两次printf的调用流程,加深对Mach-O结构以及动态链接的流程理解,为进一步理解dyld的工作原理,源码阅读提供了知识的储备。
通过和Linux的PTL与GOT比较可以更容易理解逻辑。
整个流程是如何通过代码实现的还需要进一步的分析与研究。
0x04 参考
1.Dynamic Linking: ELF vs. Mach-O
http://timetobleed.com/dynamic-linking-elf-vs-mach-o/
2.Dynamic symbol table duel: ELF vs Mach-O, round 2
http://timetobleed.com/dynamic-symbol-table-duel-elf-vs-mach-o-round-2/
原文地址:http://turingh.github.io/2016/03/10/Mach-O%E7%9A%84%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5/
Mach-O的动态链接相关知识相关推荐
- bazel 链接第三方动态库_C/C++编程知识:Linux 动态库相关知识整理
动态库和静态库在C/C++开发中很常见,相比静态库直接被编译到可执行程序,动态库运行时加载使得可执行程序的体积更小,更新动态库可以不用重新编译可执行程序等诸多好处.作者是一个Linux后台开发,这些知 ...
- Linux下ELF格式可执行文件及动态链接相关部分的解析
Linux下面的ELF文件主要由ELF头.程序头和各个段组成. 二进制可执行文件结构 本文使用的示例程序如下.首先把它编译为可执行文件,再使用Linux下面的hexdump命令,把可执行文件完全转换为 ...
- union all动态表_深入窥探动态链接
本文为看雪论坛优秀文章看雪论坛作者ID:1Oin00x00 前言本文主要分析了在延迟绑定中,调用某函数之后如何找到正确的地址.文章中深入的分析了这个过程,并且分析完之后针对该链接介绍了一些攻击手法和程 ...
- 一篇长文带你深析Linux动态链接的全过程
目录 为什么要动态链接? 地址无关代码 共享模块的全局变量问题 延迟绑定(PLT) 动态链接相关结构 .interp段 .dynamic段 动态符号表 动态链接重定位表 动态链接时进程堆栈初始化信息 ...
- 《程序员的自我修养》读书笔记——动态链接
之前介绍过静态链接,动态链接相对于静态链接稍微要麻烦一些.总体来说,两者的过程都复杂,步骤太多,涉及到重定位,符号修正,地址修正等等.--复杂 动态链接 静态链接在计算机早期还是比较流行的,但是到了后 ...
- ELF 动态链接 so的动态符号表(.dynsym)
静态链接中有一个专门的段叫符号表 -- ".symtab"(Symbol Table), 里面保存了所有关于该目标文件的符号的定义和引用. 动态链接中同样有一个段叫 动态符号表 - ...
- 动态链接 lazy binding 的原理与 GOT 表的保留表项
前言 我最近对动态库的实现非常好奇,自己琢磨了半天没有看出什么名堂,就想着能不 能找到一本讲相关内容的书籍,网上搜索了下发现确实有这样的一本书,书名为 <Linux 二进制分析>. 动态库 ...
- 程序员自我修养阅读笔记——动态链接
1 为什么需要动态链接 动态链接,顾名思义,就是只有在程序需要调用对应的库中的实现时才将对应的库的映像文件加载到内存.相比而言,静态链接是在编译阶段就将需要的目标文件中的相关实现连接到可执行文件中 ...
- 程序员的自我修养--链接、装载与库笔记:动态链接
1. 为什么要动态链接 静态链接诸多缺点,比如浪费内存和磁盘空间.模块更新困难等. 内存和磁盘空间:静态链接的方式对于计算机内存和磁盘的空间浪费非常严重,特别是在多进程操作系统情况下. 程序开发和发布 ...
最新文章
- RelativeLayout各个属性
- GLog 初始化说明
- open-falcon_NASA在Falcon 9上带回了蠕虫-其背后的故事是什么?
- [jQuery] 你有写过jQuery的扩展吗?都有哪些写法?
- 郑州财税金融职业学院的计算机考试,郑州财税金融职业学院考试流程
- Mysql 外键创建失败原因
- PHP距离春节还剩多少天,2019年春节倒计时 现在距离2019年春节还有多少天 - 中国万年历...
- 【电商AI】商业情境中的机器学习|湾区人工智能
- mysql 64位整型_高性能MySQL笔记精简(整数和实数优化)
- Airbnb搜索:Embedding表示学习
- 中英文自动翻译(有道翻译、彩云小译)
- 工业以太网与现场总线
- 解决手机刷机卡在开机谷歌验证
- silverlight mysql_Silverlight中衔接MySQL数据库实例详解
- python 获取csv的列数_Python读取excel的两种方法
- python绘制小提琴图_matplotlib 小提琴图(violin plot)
- vue3项目打包优化三步走
- OpenStack实操用到的网络知识
- Excel2019关闭时无响应
- 国内外研究现状和发展趋势