MAC、iOS平台可执行文件:Mach-O

APP的生命周期

  1. 开发生成ipa包的本质过程

    图1

    1. 我们开发完程序,command +B 编译,然后打开文件目录Products,发现有一个app包
      1. 这个过程其实经历了编译、连接、签名
    2. 右击选择show in finder,可以找到这个app包
    3. 然后右击这个包选择显示包内容,我们可以看见上图列表
      1. Xib被编译成nib文件,storyboard 被编译成storyboardc文件
      2. 有一个签名文件
      3. 那么.m的代码被编译成什么了呢? 就是那个黑色的可执行文件
        1. EverythingSendUser.app 中的EverythingSendUser文件是iOS可执行文件
        2. 文件的格式是Mach-O(machine object)
    4. app文件是如何变成ipa文件呢?
      1. 新建文件夹,命名为Payload
      2. 右击压缩为zip格式
      3. 修改文件后缀名为ipa
      4. 修改ipa文件名为EverythingSendUser
      5. 这个就是整个压缩成ipa的过程
      6. 如果你讲一个打包好的ipa文件,后缀名改为zip然后解压,会发现本质就是这样
  2. ipa文件是如何安装到手机上呢?

    1. 方法一:
      1. 开发者将ipa文件上传到App Store,用户在App Store下载安装
    2. 方法二:
      1. 通过pp助手、iFunbox、Xcode等工具安装
      2. 注意:如果你的开发账号是免费版的,那么安装的APP也就6天的使用时间。

逆向APP的思路

  1. 界面分析
    1. Cycript、Reveal工具分析界面的控件
  2. 代码分析
    1. 对Mach-O文件的静态分析
    2. MachOView、class-dump、Hopper Disassembler、ida等,这些工具分析
    3. 作用就是大概知道代码怎么写的
  3. 动态调试
    1. 对运行中的APP进行代码调试
    2. debugserver、LLDB
    3. 将程序跑起来,然后在运行中进行调试
  4. 代码编写
    1. 注入代码到APP中
    2. 必要时还可能需要重新签名、打包ipa

class-dump

  1. 顾名思义,它的作用就是把Mach-O文件的class信息给dump出来(把类信息给导出来),生成对应的.h头文件(只能生产.h头文件)
  2. 官方地址:http://stevenygard.com/projects/class-dump/
  3. 下载完工具包后双击dmg将class-dump文件复制到Mac的/usr/local/bin目录,这样在终端就能识别class-dump命令了
    1. cd /usr/local/bin
    2. open ./
    3. 然后复制进去就可以了
    4. MAC上终端指令的原理是什么呢?
      1. 我们在终端敲一个指令比如 cd,那么他就会去两个地方去找这个指令
        1. 第一个地方:/usr/bin目录中去找,打开这个路径,我们会发现里面后很多命令,其中就有cd命令
          1. 注意: 自从mac10.11之后这个目录就不允许写入内容了,因此只能使用下面的路径了
        2. 第二个地方:/usr/local/bin,这个也有很多命令。
          1. 我们可以看到这个目录下有如下命令:
          2. pod: cocoapods命令
          3. jekyll: 博客命令
          4. 等等我们自己安装的命令
  4. 常用格式
    1. class-dump  -H  Mach-O文件路径  -o  头文件存放目录
      1. -H表示要生成头文件
      2. -o用于制定头文件的存放目录
  5. 使用步骤
    1. 拿到ipa文件
    2. 将ipa改为zip,然后解压
    3. 右击app的显示包内容,将mach-o文件复制到一个文件中
    4. 然后执行class-dump  -H  Mach-O文件路径  -o  头文件存放目录命令,就可以拿到这个项目中所有类的头文件了,而且这个头文件中会将每个类的所有方法都列表出来。
    5. 将这个头文件文件夹直接拖到sublime(这个东西自己百度搜搜)中即可以快速查看了。

Hopper

  1. 代码的编译过程(即OC代码是如何变成Mach-O文件的呢?)
    1. 首先会编译成汇编
    2. 然后再编译成机器指令
    3. 这个机器指令就是Mach-O文件

    图1

  2. Hopper Disassmbler
    1. Hopper Disassmbler能够将Mach-O文件的机器语言代码反编译成汇编代码、OC伪代码或者Swift伪代码
    2. 下载地址
    3. 常用快捷键
      1. Shift + Option + X
      2. 找出哪里引用了这个方法
    4. 使用
      1. 将mach-O直接拖入到Hopper Disassmbler即可分析出,所有的汇编代码
      2. 点击工具栏的按钮,可以转换成OC伪代码。

系统的动态库

  1. 动态库共享缓存(dyld shared cache)
    1. 从iOS3.1开始,为了提高性能,绝大部分的系统动态库文件都打包存放到了一个缓存文件中(dyld shared cache)
    2. 缓存文件路径(iPhone逆向后可以查看手机文件):/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX
      1. 也就是说系统的动态库都放在上面那个文件中了
    3. dyld_shared_cache_armX的X代表ARM处理器指令集架构
    4. armv6
      1. Phone、iPhone3G
      2. iPod Touch、iPod Touch2
    5. armv7
      1. iPhone3GS、iPhone4、iPhone4S
      2. iPad、iPad2、iPad3(The New iPad)
      3. iPad mini
      4. iPod Touch3G、iPod Touch4、iPod Touch5
    6. armv7s
      1. iPhone5、iPhone5C
      2. iPad4
    7. arm64
      1. iPhone5S、iPhone6、iPhone6 Plus、iPhone6S、iPhone6S Plus
      2. iPhoneSE、iPhone7、iPhone7 Plus、iPhone8、iPhone8 Plus、iPhoneX
      3. iPad5、iPad Air、iPad Air2、iPad Pro、iPad Pro2
      4. iPad mini with Retina display、iPad mini3、iPad mini4
      5. iPod Touch6
    8. 所有指令集原则上都是向下兼容的
    9. 动态库共享缓存一个非常明显的好处是节省内存
    10. 现在的ida、Hopper反编译工具都可以识别动态库共享缓存
  2. 动态库共享缓存的好处
    1. 这样做的好处就是节省了内存
    2. 所谓的动态库就是动态链接、动态加载,在内存中只有一份的,大家公用这一份内存。
    3. 如果把不同的动态库放在不同的文件
      1. 由于每个动态库都是mach-O文件,并且里面包含了这个文件的一些描述信息
      2. 因此多个动态库的话每个文件都需要这些描述信息
    4. 如果吧不同的动态库放在同一个文件
      1. 这样的话相当于把不同的各个动态库压缩成一个文件,大家公共有一个描述信息等,这样更节省内存。
  3. 动态库的加载
    1. 在Mac\iOS中,是使用了/usr/lib/dyld程序来加载动态库
    2. dyld
      1. dynamic link editor,动态链接编辑器
      2. dynamic loader,动态加载器
    3. dyld源码
      1. https://opensource.apple.com/tarballs/dyld/
    4. 苹果开源的一些源码
      1. https://opensource.apple.com/tarballs
  4. 从动态库共享缓存抽取动态库 (学逆向时再看)
    1. 由于所有的动态库都压缩成一个文件(dyld_shared_cache_armX)了,但是如果我只想要UIKit这个动态库的Mach-O呢? 这就需要抽取了
    2. 可以使用dyld源码中的launch-cache/dsc_extractor.cpp这个文件,编译成一个可执行文件
    3. 编译dsc_extractor.cpp
      1. clang++ -o dsc_extractor dsc_extractor.cpp
    4. 使用dsc_extractor命令
      1. ./dsc_extractor  动态库共享缓存文件的路径   用于存放抽取结果的文件夹

Mach-O

  1. Mach-O是Mach object的缩写,是Mac\iOS上用于存储程序、库的标准格式
  2. 属于Mach-O格式的文件类型有
    图1
  3. 可以在xnu源码中,查看到Mach-O格式的详细定义(https://opensource.apple.com/tarballs/xnu/)
    1. EXTERNAL_HEADERS/mach-o/fat.h
    2. EXTERNAL_HEADERS/mach-o/loader.h
  4. 常见的Mach-O文件类型
    1. MH_OBJECT
      1. 目标文件(.o)
      2. 静态库文件(.a),静态库其实就是N个.o合并在一起
    2. MH_EXECUTE:可执行文件
      1. .app/xx
    3. MH_DYLIB:动态库文件
      1. .dylib
      2. .framework/xx
    4. MH_DYLINKER:动态链接编辑器
      1. /usr/lib/dyld
    5. MH_DSYM:存储着二进制文件符号信息的文件
      1. .dSYM/Contents/Resources/DWARF/xx(常用于分析APP的崩溃信息)
      2. 符号信息就是指函数名、类名等
      3. 如何找呢?
        1. 将当前项目设置为真机模式
        2. 编译command +B
        3. 在项目列表Products 的下面找到 XXX.app,右击选择show in finder
        4. 就能看见一个.dsym文件了
  5. 在Xcode中查看target的Mach-O类型
    1. target - Build settings -> 搜索 mach 就能看见mach-o的类型了。

      Universal Binary(通用二进制文件)

  6. 通用二进制文件
    1. 同时适用于多种架构的二进制文件
    2. 包含了多种不同架构的独立的二进制文件
    3. 保证支持多种CPU架构
    4. 因为需要储存多种架构的代码,通用二进制文件通常比单一平台二进制的程序要大
    5. 由于两种架构有共同的一些资源,所以并不会达到单一版本的两倍之多
    6. 由于执行过程中,只调用一部分代码,运行起来也不需要额外的内存
    7. 因为文件比原来的要大,也被称为“胖二进制文件”(Fat Binary)
  7. 如何查看一个mach-o文件的类型呢?
    1. 比如查看以个静态库framework中macho的类型
    2. 展开这个framework,找到对应的macho文件
    3. 然后复制到一个文件夹,打开终端,cd 文件路径
    4. file mach-o文件,就会显示出该mach-o的类型

       macdeMacBook-Pro:未命名文件夹 2 mac$ file KASSilentLive 
       KASSilentLive: Mach-O universal binary with 2 architectures: [arm_v7:current ar archive] [arm64]
       KASSilentLive (for architecture armv7):	current ar archive
       KASSilentLive (for architecture arm64):	current ar archive
      
      1. 从上面可以看出这个静态库,是个通用二进制文件,支持2种架构,arm_v7/arm64
    5. 同时还可以通过lipo -info mach-o文件命令直接看改文件所支持的架构。
  8. 这种通用二进制架构如何配置呢?
    1. 项目的target->build settings ->architecthures
    2. 这个就是用来设置通用二进制文件支持哪些架构。
    3. 这个目录下有2个地方可以设置:
      1. architecthures
        1. standard -architecthures $(ARCHS_STANDARD)
        2. 这个是Xcode自己内置(默认)的环境变量,不同Xcode值不一样
        3. 比如:Xcode 9是armv7、arm64
        4. 如果觉得Xcode内置的不够,那么可以选择other自己添加,比如添加:armv7s
      2. valid architecthures
        1. 这个是干什么呢?
        2. Xcode生成通用二进制文件会取architecthures跟valid architecthures的交集

查看Mach-O的结构

  1. 命令行工具
    1. file:查看Mach-O的文件类型
      1. 打开终端,cd Mach-O文件路径,然后file Mach-O文件即可,也可以直接file  文件路径
    2. lipo:常用于多架构Mach-O文件的处理
      1. 查看架构信息:lipo  -info  文件路径
        1. 直接会打印出该macho文件支持的CPU架构
      2. 导出某种特定架构:lipo  文件路径  -thin  架构类型  -output  输出文件路径
        1. 当这个macho文件支持多种架构,但是我只想导出特定的某个架构,比如armv7
        2. lipo  KASSilentLive  -thin  armv7  -output  /Users/mac/Desktop/test/KASSilentLive_armv7
        3. 这样就把KASSilentLive这个通用二进制文件,导出为单一的KASSilentLive_armv7了
      3. 合并多种架构:lipo -create 文件路径1  文件路径2  -output  输出文件路径
        1. 作用是将多个不同架构类型的macho文件,合并成一个支持多种架构的macho文件
        2. 比如:
        3. lipo -create /Users/mac/Desktop/test/KASSilentLive_armv7  /Users/mac/Desktop/test/KASSilentLive_armv64  -output  /Users/mac/Desktop/test/KASSilentLive_armv7and64
    3. otool:查看Mach-O特定部分和段的内容
      1. 在终端直接输入otool回车,就会显示出帮助信息,告诉我们这个命令行都有那些作用
  2. GUI工具
    1. MachOView
    2. 通过可视化的形式,显示出Mach-O的信息。
    3. 下载后运行会有问题
      1. bug1: 找不到10.9SDK
        1. TARGET ->Building Settings->architecthures->Base SDK->macOS
      2. bug2: stdlibc++ headers not found

        Build Setting->C++ Standard Library->libstdc++
        修改为
        Build Setting->C++ Standard Library->libc++
        
    4. 运行起来,然后在顶部工具栏,file->open ->找到macho文件,就可以展示macho的信息了。

Mach-O的基本结构

  1. 官方描述 https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html
  2. 无论任何类型的Mach-O文件他都有共同的结构,一个Mach-O文件包含3个主要区域
    图1

    1. Header
      1. Mach-O文件类型、目标架构类型等
    2. Load commands
      1. 描述文件在虚拟内存中的逻辑结构、布局
      2. 就是可执行文件加载到内存中的分配布局
      3. 就是用来描述有哪些段、那些段的大小等信息
    3. Raw segment data
      1. 在Load commands中定义的Segment的原始数据
      2. 就是Load commands中描述的段的实际数据

dyld和Mach-O

  1. dyld用于加载以下类型的Mach-O文件
    1. MH_EXECUTE
    2. MH_DYLIB
    3. MH_BUNDLE
  2. APP的可执行文件、动态库都是由dyld负责加载的

ASLR

  1. 什么是ASLR
    1. Address Space Layout Randomization,地址空间布局随机化
    2. 是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术
  2. iOS4.3开始引入了ASLR技术
  3. 未使用ASLR
    1. 函数代码存放在__TEXT段中
    2. 全局变量存放在__DATA段中
    3. 可执行文件的内存地址是0x0
      1. __PAGEZERO为预留的段
    4. 代码段(__TEXT)的内存地址
      1. 就是LC_SEGMENT(__TEXT)中的VM Address(virtual Memory Adress 虚拟内存地址)
      2. arm64:0x100000000(8个0)
      3. 非arm64:0x4000(3个0)
    5. 可以使用size -l -m -x来查看Mach-O的内存分布

    图1

  4. 使用了ASLR
    1. 一旦使用了ASLR,让程序每次载入的时候,Mach-O的起始地址不是固定的,防止别人能够定位到代码地址。
    2. LC_SEGMENT(__TEXT)的VM Address
      1. 固定长度0x100000000
    3. ASLR随机产生的Offset(偏移)
      1. 0x5000(大小随机的)
      2. 也就是可执行文件的内存地址

    图1

  5. 函数的内存地址
    1. 函数的内存地址(VM Address) = File Offset + ASLR Offset + __PAGEZERO Size
    2. Hopper、IDA中的地址都是未使用ASLR的VM Address
      1. 即将一个Mach-O文件导入到Hopper解析后,我们查找到某个函数,然后找到对应地址
      2. 这个地址仅仅是Mach-O文件的地址,还没有载入到虚拟内存的
      3. 因此,要找到载入内存后的真正函数地址= File Offset + ASLR Offset + __PAGEZERO Size
Table of Contents