iOS系统分析(二)Mach-O二进制文件解析
0x01 Mach-O格式简单介绍
Mach-O文件格式是 OS X 与 iOS 系统上的可执行文件格式,类似于windows的 PE 文件 与 Linux(其他 Unix like)的 ELF 文件,如果不彻底搞清楚Mach-O的格式与相关内容,那么深入研究 xnu 内核就无从谈起。
Mach-O文件的格式如下图所示:

有如下几个部分组成:
1. Header:保存了Mach-O的一些基本信息,包括了平台、文件类型、LoadCommands的个数等等。
2. LoadCommands:这一段紧跟Header,加载Mach-O文件时会使用这里的数据来确定内存的分布。
3. Data:每一个segment的具体数据都保存在这里,这里包含了具体的代码、数据等等。
0x02 FAT二进制数据 ,数据结构定义在 \<mach-o/fat.h\>


1. 第一段为magic 魔数,这里注意大小端,读出来之后需要看下是0xCAFEBABE还是 0xBEBAFECA(否则即为thin),需要根据这个来转后续读取的字节的字节序。 可以看出来 前4byte 为 0xBEBAFECA ,说明为fat。
2. 第二段为arch count,也就是该App或dSYM中包含哪些CPU架构,比如armv7、arm64等,这个例子中为2(后4byte 0x 00 00 00 02),表示包含了两种cpu架构。
`sizeof(struct fat-header) = 8byte`
3. 后续段中包含cputype(0x 0C 00 00 01)、cpusubtype (0x 00 00 00 00)、offset (0x 00 10 00 00)、size(0x 00 F0 27 00)等数据,根据fat中的结构定义,依次读取,这里需要说明的是,如果只包含一种CPU架构的话,是没有这段fat头定义的,可以跳过这部分,直接读取Arch数据。
`sizeof(struct fat-arch) = 20byte`
4. 根据fat头中读取的offset数据,我们可以跳到文件对应的arch数据的位置,当然如果只有一种架构的话就不需要计算偏移量了。 下图给出解析的函数

0x03 Mach Header二进制数据
通过magic我们可以区分出是32-bit还是64-bit,64-bit多了4个字节的保留字段,这里同样需要注意字节序的问题,也就是判断magic,来确定是否需要转换字节序。
`sizeof(struct mach-header-64) = 32byte` ; `sizeof(struct mach-header) = 28byte`

根据mach-header与mach-header_64的定义,很明显可以看出,Headers的主要作用就是帮助系统迅速的定位Mach-O文件的运行环境,文件类型。

FileType
因为Mach-O文件不仅仅用来实现可执行文件,同时还用来实现了其他内容
1. 内核扩展
2. 库文件
3. CoreDump
4. 其它

下面是一些精彩用到的文件类型
1. MH-OBJECT 编译过程中产生的 obj文件 (gcc -c xxx.c 生成xxx.o文件)
2. MH-EXECUTABLE 可执行二进制文件 (/usr/bin/ls)
3. MH-CORE CoreDump (崩溃时的Dump文件)
4. MH-DYLIB 动态库(/usr/lib/里面的那些共享库文件)
5. MH-DYLINKER 连接器linker(/usr/lib/dyld文件)
6. MH-KEXT-BUNDLE 内核扩展文件 (自己开发的简单内核模块)
flags
Mach-O headers还包含了一些很重要的dyld的加载参数。

1. MH-NOUNDEFS 目标没有未定义的符号,不存在链接依赖
2. MH-DYLDLINK 该目标文件是dyld的输入文件,无法被再次的静态链接
3. MH-PIE 允许随机的地址空间(开启ASLR -\>Address Space Layout Randomization)
4. MH-ALLOW-STACK-EXECUTION 栈内存可执行代码,一般是默认关闭的。
5. MH-NO-HEAP-EXECUTION 堆内存无法执行代码

0x04 LoadCommands
Load Commands 直接就跟在Header后面,所有command占用内存的总和在Mach-O Header里面已经给出了。在加载过Header之后就是通过解析LoadCommand来加载接下来的数据了。定义如下:

cmd字段
根据cmd字段的类型不同,使用了不同的函数来加载。简单的列出一张表看一看在内核代码中不同的command类型都有哪些作用。
1. LC-SEGMENT;LC-SEGMENT-64 在内核中由load-segment 函数处理(将segment中的数据加载并映射到进程的内存空间去)
2. LC-LOAD-DYLINKER 在内核中由load-dylinker 函数处理(调用/usr/lib/dyld程序)
3. LC-UUID 在内核中由load-uuid 函数处理 (加载128-bit的唯一ID)
4. LC-THREAD 在内核中由load-thread 函数处理 (开启一个MACH线程,但是不分配栈空间)
5. LC-UNIXTHREAD 在内核中由load-unixthread 函数处理 (开启一个UNIX posix线程)
6. LC-CODE-SIGNATURE 在内核中由load-code-signature 函数处理 (进行数字签名)
7. LC-ENCRYPTION-INFO 在内核中由 set-code-unprotect 函数处理 (加密二进制文件)
UUID 二进制数据 128byte
UUID是16个字节(128bit)的一段数据,是文件的唯一标识,前面提到的符号化时,这个UUID必须要和App二进制文件中的UUID一致,才能被正确的符号化。dwarfdump查看的UUID就是这段数据。读取这部分数据时通过Command结构读取的,也就是第一段(0x0000001B)表示接下来的数据类型,第二段(0x00000018)数据的大小(包含Command数据)。
SymTab 二进制数据
1. 符号表数据块结构,前二段依然是Command数据。后边4段分别为符号在文件中的偏移量(0x001DF5E0)、符号个数(0x001DF5E0)、字符串在文件中的偏移量(0x0020C3A0)、字符串表大小(0x000729A8)。
2. 接下来就是读取Segment和Section数据块了,和上面读取数据块结构一样是根据Command结构读取,下图展示的Segment数据和Section数据,它们在二进制文件中它们是连续的,也就是每一条Segment数据后面会紧跟着多条对应的Section数据,Section的数据总数是通过Segment结构中的nsects决定的。
3. 这里我写了一个简单地Mach-O解析工具 [https://github.com/liutianshx2012/Tmacho](https://github.com/liutianshx2012/Tmacho)

Segment数据
加载数据时,主要加载的就是LC-SEGMET活着LC-SEGMENT_64。其他的Segment的用途在这里不做深究。
LCSEGMENT以及LC-SEGMENT-64 定义如下图。


可以看出,这里大部分的数据是用来帮助内核将Segment映射到虚拟内存的。
nsects 字段,标示了Segment中有多少secetion ,section是具体有用的数据存放的地方。
TEXT的vmaddr也就是程序的加载地址; —DWARF中表明了DWARF数据块的信息,表示dSYM是DWARF格式的数据结构。
` sizeof(struct segment-command) = 56byte ; sizeof(struct segment-command-64) = 72byte`
Section数据

从Section数据中,我们可以找到—debug-info、—debug-pubnames, —debug-line等调试信息,通过这些调试信息我们可以找到程序中符号的起始地址、变量类型等信息。如果我们要符号化的话,就可以通过解析这些数据得到我们想要的信息。
Symbol 数据
通过SymTab中的数据可以得到Symbol在文件中的位置和个数,Symbol块数据中包含了符号的起始地址、字符串的偏移量等数据,这部分数据结构可以参考\<nlist.h\> 和 \<stabl.h\>。在这部分数据全部读取后,就可以读取所有的符号数据了,也就是接下来的数据。
Symbol String 数据
1. 通过SymTab和Symbo中的数据可以得到每个符号字符串在文件中的偏移量和大小,每个符号数据是以0结尾的字符串。
2. 我们通过以上两部分数据的组合就可以得到每个symbo在程序中的加载地址了。这些数据对于以后做符号工作都非常的有帮助。
3. 到此,关于dSYM文件中头部数据读取就完成了。头部数据都有相应的数据结构定义,读取时相对会比较容易些,解析数据时要注意字节序的问题,32-bit和64-bit数据结构的差异、字节长度的差异,DWARF版本的差异,每个数据块之间都是紧密联系的,一个字节的读取偏差就会造成后续数据的读取错误,正所谓差之毫厘,失之千里。
原文链接:http://blog.tingyun.com/web/article/detail/1341
iOS系统分析(二)Mach-O二进制文件解析的更多相关文章
- 【iOS 单例设计模式】底层解析与运用
[iOS 单例设计模式]底层解析与运用 一.单例设计名词解释: (官方解释)单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例.(形象比喻)程序 — 公司 单例实例 - 管理 ...
- iOS开发-二维码扫描和应用跳转
iOS开发-二维码扫描和应用跳转 序言 前面我们已经调到过怎么制作二维码,在我们能够生成二维码之后,如何对二维码进行扫描呢? 在iOS7之前,大部分应用中使用的二维码扫描是第三方的扫描框架,例如Z ...
- IOS CoreData 多表查询demo解析
在IOS CoreData中,多表查询上相对来说,没有SQL直观,但CoreData的功能还是可以完成相关操作的. 下面使用CoreData进行关系数据库的表与表之间的关系演示.生成CoreData和 ...
- h5端呼起摄像头扫描二维码并解析
2016年6月29日补充: 最近做了一些与表单相关的项目,使用了h5的input控件,在使用过程中遇到了很多的坑.也包括与这篇文章相关的. 首先我们应该知道使用h5新提供的属性getUserMedia ...
- [开源]C#二维码生成解析工具,可添加自定义Logo
二维码又称 QR Code,QR 全称 Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的 Bar Code 条形码能存更多的信息,也能表示更多的数据类型:比如:字 ...
- visio二次开发——图纸解析之线段
多写博客,其实还是蛮好的习惯的,当初大学的时候导师就叫我写,但是就是懒,大学的时候,谁不是魔兽或者LOL呢,是吧,哈哈哈. 好了,接着上一篇visio二次开发——图纸解析,我继续写. 摘要: (转发请 ...
- 转:二十一、详细解析Java中抽象类和接口的区别
转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...
- ios 网络数据下载和JSON解析
ios 网络数据下载和JSON解析 简介 在本文中笔者将要给大家介绍ios中如何利用NSURLConnection从网络上下载数据,如何解析下载下来的JSON数据格式,以及如何显示数据和图片的异步下载 ...
- 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore
前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票 ...
随机推荐
- NodeJs 开发微信公众号(三)微信事件交互
微信公众号有个规则,一旦开启了开发者模式,其他的常规功能就都必须通过接口调用完成.比如说自定义菜单功能,必须通过发送post请求的方式生成.本章就通过关注到取消关注的整个过程来谈一谈nodejs是怎么 ...
- 【初探Spring】------Spring IOC(三):初始化过程---Resource定位
我们知道Spring的IoC起到了一个容器的作用,其中装得都是各种各样的Bean.同时在我们刚刚开始学习Spring的时候都是通过xml文件来定义Bean,Spring会某种方式加载这些xml文件,然 ...
- 2000条你应知的WPF小姿势 基础篇<8-14>
在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师,对C#和WPF有着极深的热情.最为出色的是他维护了两个博客:2,000Things You Should Know ...
- xamarin android,UWP 网络类型和IP地址
App开发经常要判断网络连通情况,并判断网络类型,获取网络IP.xamarin中可以使用Dependencies提供各平台下的方法,现把各平台代码记录如下: using System; using S ...
- 为WLW开发Latex公式插件
WLW是写博客的利器,支持离线.格式排版等,而且拥有众多的插件.博客园推荐了代码插入插件,但是没有提供WLW的公式编译插件.目前我的一般做法是:先在Word下使用MathType编辑好公式,然后将公式 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(50)-Easyui 扁平化皮肤
最近抽了点时间仿做了点皮肤,只供欣赏!扁平化
- Handler系列之原理分析
上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...
- 从xfire谈WebService接口化编程
前段时间有博友在看我的博文<WebService入门案例>后,发邮件问我关于WebService 接口在java中的开发,以及在实际生产环境中的应用.想想自己入职也有一段时间了,似乎也该总 ...
- Unity3D中常用的数据结构总结与分析
来到周末,小匹夫终于有精力和时间来更新下博客了.前段时间小匹夫读过一份代码,对其中各种数据结构灵活的使用赞不绝口,同时也大大激发了小匹夫对各种数据结构进行梳理和总结的欲望.正好最近也拜读了若干大神的文 ...
- docker进入后台运行的容器
转载请注明出处 我们运行docker容器的时候,使用了-d参数,把容器在后台运行后. 这个时候,我们使用docker ps命令,我们就可以知道哪些程序在后台运行. 我们要怎么进入到docker ...