【iOS】iOS逆向系列四 - Mach-O

什么是Mach-O?可执行文件是怎么加载的?

一、APP分析

1.1. APP从开发到安装流程

  1. Objective-C或Swift代码、图片/音视频等文件资源、info.plist配置、XIB、Storyboard等构成了APP的源代码。

  2. 通过编译、链接、签名操作后,代码会构建成一个***.app文件。而这个app文件中包含开发所用的到APP资源以及代码,代码经过编译后会压缩在同名文件中,这个文件格式就是Mach-O,是一个可执行文件。

  3. 通过对***.app压缩(文件放入Payload文件夹中,然后再压缩)得到最终的安装包(压缩后需要把后缀名修改为ipa)。

  4. 一般情况下都是直接把***.ipa上传到AppStore进行审核,审核通过后用户就可以到AppStore中进行正常下载。APP也通过PP助手、iFunBox、Xcode等工具进行安装。

1.2. 逆向APP的思路

  1. 界面分析

    • 行为:查看界面相关元素之间的依赖关系
    • 工具:Cycript(代码层级结构)、Reveal(视图层级可视化结构)
  2. 代码分析

    • 行为:对Mach-O文件的静态分析
    • 工具:MachOView(MachO文件分析工具)、class-dump(MachO头文件导出命令)、Hopper Disassembler(MachO文件反编译成代码)、ida
  3. 动态调试

    • 行为:对运行中的APP进行代码调试
    • 工具:debugserver、LLDB
  4. 代码编写

    • 行为:注入代码到APP中
    • 必要时还可能需要重新签名、打包ipa

1.3. class-dump

顾名思义,它的作用就是把Mach-O文件的class信息给dump出来(把类信息给导出来),生成对应的.h头文件。

官方地址http://stevenygard.com/projects/class-dump/

下载完工具包后将class-dump文件复制到Mac的/usr/local/bin目录,这样在终端就能识别class-dump命令了。

注意:usr/bin系统已经不给用户写入权限了,后加入的指令都是放到/usr/local/bin目录中的。

常用格式:

1
class-dump  -H  Mach-O文件路径  -o  头文件存放目录

-H:表示要生成头文件。

-o:用于制定头文件的存放目录。

1.4. 代码的编译过程

OC/Swift语言通过编译生成汇编代码,汇编再编译后就是生成机器指令(Mach-O文件)。

在同一种架构平台下,每一条汇编指令都有与之对应的唯一的机器指令。所以机器指令(即Mach-O文件)可以反编译出对应的汇编代码。

但是汇编代码是不能反编译成OC/Swift代码的,因为不同的源代码编译成的汇编代码可能是一样的。

1.5. Hopper Disassmbler

Hopper Disassmbler能够将Mach-O文件的机器语言代码反编译成汇编代码、OC伪代码或者Swift伪代码。

二、动态库

2.1. 动态库共享缓存

从iOS3.1开始,为了提高性能,绝大部分的系统动态库文件(比如UIKit、MapKit、CoreGraphics等)都打包存放到了一个缓存文件中(dyld shared cache)。

缓存文件路径(iPhone):/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX

扩展:以前是动态库的Mach-O文件在/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/UIKit.framework里面可以看到,现在已经没有了。

dyld_shared_cache_armXX代表ARM处理器指令集架构(所有指令集原则上都是向下兼容的):

  • v6

    1
    2
    iPhone、iPhone3G
    iPod Touch、iPod Touch2
  • v7

    1
    2
    3
    4
    iPhone3GSiPhone4iPhone4S
    iPadiPad2iPad3(The New iPad)
    iPad mini
    iPod Touch3GiPod Touch4iPod Touch5
  • v7s

    1
    2
    iPhone5、iPhone5C
    iPad4
  • arm64

    1
    2
    3
    4
    5
    iPhone5S、iPhone6、iPhone6 Plus、iPhone6S、iPhone6S Plus
    iPhoneSE、iPhone7、iPhone7 Plus、iPhone8、iPhone8 Plus、iPhoneX
    iPad5、iPad Air、iPad Air2、iPad Pro、iPad Pro2
    iPad mini with Retina display、iPad mini3、iPad mini4
    iPod Touch6

动态库共享缓存一个非常明显的好处是节省内存。现在的ida、Hopper反编译工具都可以识别动态库共享缓存。

2.2. 动态库的加载

在Mac/iOS中,动态库是使用了/usr/lib/dyld程序来加载动态库。

dyld,有多种叫法,最常见的是:

  • dynamic link editor,动态链接编辑器
  • dynamic loader,动态加载器

dyld源码:https://opensource.apple.com/tarballs/dyld/

2.3. 从动态库共享缓存抽取动态库

动态库共享缓存文件中存放的是所有动态库,每次分析都需要加载很久(大概700MB左右),我们可以尝试把动态库分离出来,然后按需进行分析。

可以使用dyld源码中的launch-cache/dsc_extractor.cpp

  1. #if 0前面的代码删除(包括#if 0),把最后面的#endif也删掉。

  2. 编译dsc_extractor.cpp

    1
    clang++ -o dsc_extractor dsc_extractor.cpp
  3. 使用dsc_extractor

    1
    ./dsc_extractor 动态库共享缓存文件的路径 用于存放抽取结果的文件夹

上面命令执行完成后,用于存放抽取结果的文件夹里就是动态库共享缓存中的所有动态库。

注意:动态库共享缓存文件是在iPhone手机里(参考上面2.1),操作的时候需要把文件拷贝到电脑中。

三、Mach-O

Mach-O是Mach object的缩写,是Mac/iOS上用于存储程序、库的标准格式。

属于Mach-O格式的文件类型有:

可以在xnu源码中(https://opensource.apple.com/tarballs/xnu/),查看到Mach-O格式的详细定义(如下路径):

  • EXTERNAL_HEADERS/mach-o/fat.h
  • EXTERNAL_HEADERS/mach-o/loader.h

3.1. 常见的Mach-O文件类型

  • MH_OBJECT

    • 目标文件(.o)
    • 静态库文件(.a),静态库其实就是N个.o合并在一起
  • MH_EXECUTE:可执行文件

    • .app/xx
  • MH_DYLIB:动态库文件

    • .dylib
    • .framework/xx
  • MH_DYLINKER:动态链接编辑器

    • /usr/lib/dyld
  • MH_DSYM:存储着二进制文件符号信息的文件

    • .dSYM/Contents/Resources/DWARF/xx(常用于分析APP的崩溃信息)

在Xcode中查看target的Mach-O类型:

3.2. Mach-O的基本结构

官方描述:https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html

一个Mach-O文件包含3个主要区域:

  • Header

    • 文件类型、目标架构类型等
  • Load commands

    • 描述文件在虚拟内存中的逻辑结构、布局
  • Raw segment data

    • 在Load commands中定义的Segment的原始数据

3.3. 窥探Mach-O的结构

3.3.1. 命令行工具

  • file:查看Mach-O的文件类型

    • file 文件路径
  • otool:查看Mach-O特定部分和段的内容

  • lipo:常用于多架构Mach-O文件的处理

    • 查看架构信息:lipo -info 文件路径
    • 导出某种特定架构:lipo 文件路径 -thin 架构类型 -output 输出文件路径
    • 合并多种架构:lipo 文件路径1 文件路径2 -output 输出文件路径

3.3.2. GUI工具

MachOView:https://github.com/gdbinit/MachOView

3.4. Universal Binary

通用二进制文件,同时适用于多种架构的二进制文件,包含了多种不同架构的独立的二进制文件。

在Xcode中(Target -> Build Settings -> Architectures)就可以修改应用或静态库支持哪几种架构(x86_64、armv6、armv7、armv7s、arm64)。

Architectures:通用的一些架构值。默认值是$(ARCHS_STANDARD),这个默认值是Xcode内置的环境变量,不同版本的Xcode可能其值也不同。可以添加和删除架构类型。

Valid Architectures:当前应用支持的架构。可以添加和删除架构类型。

最终支持哪几种架构是根据Architectures和Valid Architectures的交集确定的。

通用架构的特点

  • 因为需要储存多种架构的代码,通用二进制文件通常比单一平台二进制的程序要大。

  • 由于两种架构有共同的一些资源,所以并不会达到单一版本的两倍之多。

  • 由于执行过程中,只调用一部分代码,运行起来也不需要额外的内存。

  • 因为文件比原来的要大,也被称为“胖二进制文件”(Fat Binary)。

3.5. dyld和Mach-O

dyld用于加载以下类型的Mach-O文件:

1
2
3
4
5
MH_EXECUTE

MH_DYLIB

MH_BUNDLE

APP的可执行文件、动态库都是由dyld负责加载的。

四、扩展

Drawin、xnu、Mach、BSD之间的关系如下所示: