iOS逆向 Mach-O文件 与 脱壳

1,873 阅读8分钟

Mach-O 文件

Mach-O是Mach object的缩写,是Mac\iOS上用来存储程序、库的标准格式 开发过程中写的代码最终转换成Mach-O文件执行。

Mach-O文件类型

可以点击下载xnu源码,在源码中的EXTERNAL_HEADERS/mach-o/loader.h 文件中,我们可以看到Mach-O格式的所有文件类型

xun是苹果MacOS\iOS等操作系统的内核

常见的Mach-O文件类型

Mach-O的基本结构

Mach-O组成

Mach-O由3个部分组成

  • Header,包含文件类型、目标架构类型等等
  • Load commands,是描述文件在虚拟内存中的逻辑结构和布局,相当于一份目录索引
  • Raw segment data,在Load commands中所定义的Segment,在这里都能找到原始数据。
Raw segment data存放了所有的原始数据,而Load commands相当于Raw segment data的索引目录

窥探Mach-O的结构

  • 命令行工具,通过file命令查看Mach-O文件的基本信息
file 文件路径

  • otool,查看Mach-O特定部分和段的内容
#查看Mach-O文件的header信息
otool -h 文件路径

#查看Mach-O文件的load commands信息
otool -l 文件路径

  • lipo,用来处理多架构Mach-O文件,常用命令如下
#查看架构信息
lipo -info 文件路径

#导出某种类型的架构
lipo 文件路径 -thin 架构类型 -output 输出文件路径

#合并多种架构类型
lipo 文件路径1 文件路径2 -output 输出文件路径

  • GUI工具,MachOView的使用

Universal Binary(通用二进制文件)

通用二进制文件就是同时适用于多种架构的二进制文件,它包含了多种不同架构的独立的二进制文件,它有以下特点

  • 因为需要存储多种架构的代码,所以通用二进制文件要比单架构二进制文件要大
  • 因为两种种架构之间可以共用一些资源,所以两种架构的通用二进制文件大小不会达到单一架构版本的两倍。
  • 运行过程中只会调用其中的部分代码,所以运行起来不会占用额外的内存
  • 通用二进制文件通常也被称为“胖二进制文件(Fat binary)”

dyld和Mach-O

dyld是iOS中用来加载可执行文件、动态库的工具,其实它本身也是一个Mach-O文件。

什么是dyld?

  • dyld 动态加载器(又叫做动态链接编辑器)
  • dyld的源码可以点击此处下载

dyld的作用。

dyld可以用来加载以下三种类型的Mach-O文件

  • MH_EXECUTE
  • MH_DYLIB
  • MH_BUNDLE 通过查看dyld的源码可以看到加载文件时的类型校验

从编码到App安装到手机

想要了解Mach-O文件,首先要了解从编写代码,开发App到App打包并安装到手机上的整个过程。

  1. 首先我们编写完成代码之后,会通过LLVM编译器预处理我们的代码,比如将宏放在指定的位置
  2. 预处理结束之后,LLVM会对代码进行词法分析和语法分析,生成AST。AST是抽象语法树,主要用来进行快速遍历,实现静态代码检查的功能。
  3. AST会生成IR,IR是一种更加接近机器码的语言,通过IR可以生成不同平台的机器码。对于iOS平台,IR生成的可执行文件就是Mach-O.
  4. 然后通过链接器将符号和地址绑定在一起,并且将项目中的多个Mach-O文件合并成一个Mach-O文件。
  5. 最后通过签名等操作生成.app文件,然后对.app文件进行压缩就生成了我们可以安装的ipa包。
  • 当然,ipa包的安装途径有两种:

通过开发者账号上传到App Store,然后在App Store上下载安装。 通过PP助手、iFunBox、Xcode等工具来安装

逆向App ,准备工作

调试工具

calss-dump (解出头文件)

class-dump工具包下载链接

  • class-dump的作用就是把Mach-O文件的class信息给导出来,生成对应的.h头文件
  • class-dump的常用命令如下
# -H表示需要生成头文件  -o用于指定头文件的存放目录
class-dump -H Mach-O文件路径 -o 头文件存放目录

Hopper Disassmbler

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

下载地址

#找出哪里引用了这个方法
Shift + Option + X

动态库和静态库

在iOS开发中,有很多功能都是现成可用的,不关你的App在用,其它的App也在用,比如UIKit框架、GUI框架、I/O、网络等等。这些库都是通过链接器链接到Mach-O文件中的。

静态库

静态库是编译时链接的库,需要连接进入Mach-O文件中,如果需要更新就必须重新编译一次,无法做到动态加载和更新。

动态库

动态库是运行时链接的库

Mach-O是文件编译之后的产物,所以动态库并没有参与Mach-O文件的编译和链接。所以Mach-O文件中没有包含动态库的符号定义,也就是说这些符号会直接显示未定义,但是他们的名字和对应库的路径会被记录下来。在运行时通过dlopen和dlsym导入动态库时,会根据记录的路径找到对应的库,再通过记录的名字符号找到绑定的地址。

动态库共享缓存(dyld shared cache)

从iOS3.1开始,为了提高性能,绝大部分的系统动态库文件都打包存放到了一个缓存文件中(dyld shared cache),缓存路径是/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX

dyld_shared_cache_armX里面的X代表ARM处理器指令集的架构

ARM指令集

ARM指令集(CPU指令的集合)有以下几种

以上所有的指令集都是向下兼容的

为什么要使用动态库共享缓存呢?最大的好处就是节省内存。

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

由于动态库共享缓存太大,如果想获取其中某个动态库,例如UIKit,就需要从动态库共享缓存中抽取对应的动态库

  1. 使用dyld源码中提供的方式来进行抽取,工具在源码中的launch-cache/dsc_extractor.cpp文件中
  • 首先需要去掉源码中的#if 0判断
  • 然后使用如下命令编译dsc_extractor.cpp文件
clang++ -o dsc_extractor dsc_extractor.cpp

此处是将dsc_extractor.cpp编译生成可执行文件dsc_extractor

  • 进入执行文件dsc_extractor所在目录。通过以下的命令来抽取动态库
./dsc_extractor 动态库共享缓存文件的路径 用于存放抽取结果的目录

  1. 抽取完成之后,使用Hopper Disassmbler打开想要逆向的动态库,就可以看到动态库中的源码信息

加壳与脱壳

加壳

通常我们从App Store下载的应用,拿到安装包之后,通过class-dump导出头文件的时候,会发现无法导出头文件,原因就是App Store对我们上传的App进行了加壳操作。

什么是加壳?

利用特殊的算法,对可执行文件的编码进行改变(比如压缩、加密),以达到保护程序代码的目的

  • 未加壳,我们App的可执行文件一旦执行,那么可执行文件中的代码会被装载到内存中
  • 加壳后,可执行文件中的所有代码被加密之后就无法使用ldyd进行加载了,所以在可执行文件外部包了一层壳程序,壳程序可以直接运行。壳程序的作用就是使用解密算法对我们的可执行文件进行解密操作,解密完成之后就会去执行文件,将代码装载进内存中。

如何判断程序被加壳(加密)?

  • 使用MachOView加载可执行文件,查看Load Commands -> LC_ENCRYPTION_INFO -> Crypt ID,0表示未加密,>=1表示已加密

这里的Crypt ID表示加密工具类型,如果为1,表示使用编号为1的加密工具进行加密

  • 使用otool命令行也可以查看Load Commands
otool -l 可执行文件路径 | grep crypt

由于Load Commands内容较多,所以通过crypt关键字进行检索

脱壳

摘掉壳程序,将未加密的可执行文件还原出来

  • 硬脱壳就是直接将壳程序通过执行解密算法得到我们所需要的可执行文件,在iOS中,一般使用硬脱壳。
  • 动态脱壳是指将程序运行之后,直接从内存中导出我们所需要的可执行文件

#脱壳工具 Clutch 和 dumpdecrypted 使用与iOS12 一下,iOS12以上用 CrackerXI

CrackerXI(脱壳)

  • 下载源 (apt.wxhbts.com)

  • 打开CrackerXI,选择已安装的应用进行脱壳,等待,成功后显示脱壳后的ipa包在手机存储的位置 /var/mobile/Documents/CrackerXI/app名称_CrackerXI.ipa