zoukankan      html  css  js  c++  java
  • iOS系统分析(二)Mach-O二进制文件解析

    更多技术干货请戳:听云博客

    0x01  Mach-O格式简单介绍

    Mach-O文件格式是 OS X 与 iOS 系统上的可执行文件格式,类似于windows的 PE 文件 与 Linux(其他 Unix like)的 ELF 文件,如果不彻底搞清楚Mach-O的格式与相关内容,那么深入研究 xnu 内核就无从谈起。

    Mach-O文件的格式如下图所示:

    1.png

    有如下几个部分组成:

    1. Header:保存了Mach-O的一些基本信息,包括了平台、文件类型、LoadCommands的个数等等。

    2. LoadCommands:这一段紧跟Header,加载Mach-O文件时会使用这里的数据来确定内存的分布。

    3. Data:每一个segment的具体数据都保存在这里,这里包含了具体的代码、数据等等。

    0x02 FAT二进制数据 ,数据结构定义在 <mach-o/fat.h>

    06.png

    08.png

    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数据的位置,当然如果只有一种架构的话就不需要计算偏移量了。 下图给出解析的函数

    05.png

    0x03 Mach Header二进制数据

    通过magic我们可以区分出是32-bit还是64-bit,64-bit多了4个字节的保留字段,这里同样需要注意字节序的问题,也就是判断magic,来确定是否需要转换字节序。  

    `sizeof(struct mach-header-64) = 32byte`  ; `sizeof(struct mach-header) = 28byte`

    07.png

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

    2.png

    FileType 

    因为Mach-O文件不仅仅用来实现可执行文件,同时还用来实现了其他内容

    1. 内核扩展

    2. 库文件

    3. CoreDump

    4.  其它

    3.png

    下面是一些精彩用到的文件类型

    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的加载参数。

    4.png

    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   堆内存无法执行代码

    5.png

    0x04 LoadCommands

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

    6.png

    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)

    09.png

    Segment数据

    加载数据时,主要加载的就是LC-SEGMET活着LC-SEGMENT_64。其他的Segment的用途在这里不做深究。

    LCSEGMENT以及LC-SEGMENT-64 定义如下图。

     

    7.png

    10.png

    可以看出,这里大部分的数据是用来帮助内核将Segment映射到虚拟内存的。

    nsects 字段,标示了Segment中有多少secetion ,section是具体有用的数据存放的地方。

    TEXT的vmaddr也就是程序的加载地址; —DWARF中表明了DWARF数据块的信息,表示dSYM是DWARF格式的数据结构。 

    ` sizeof(struct segment-command) = 56byte   ;   sizeof(struct segment-command-64) = 72byte`

    Section数据

    8.png

    从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

  • 相关阅读:
    父子进程 signal 出现 Interrupted system call 问题
    一个测试文章
    《淘宝客户端 for Android》项目实战 html webkit android css3
    Django 中的 ForeignKey ContentType GenericForeignKey 对应的数据库结构
    coreseek 出现段错误和Unigram dictionary load Error 新情况(Gentoo)
    一个 PAM dbus 例子
    漫画统计学 T分数
    解决 paramiko 安装问题 Unable to find vcvarsall.bat
    20141202
    js
  • 原文地址:https://www.cnblogs.com/TingyunAPM/p/6067520.html
Copyright © 2011-2022 走看看