0%

【iOS逆向】Mach-O文件

Mach-O是Mach object的缩写,是Mac/iOS上用于存储程序、库的标准格式,这里简要解析Mach-O文件格式,结构和一些要点

Mach-O格式

Mach-O是一个以数据块分组的二进制字节流,这些数据块包含元信息,比如字节顺序、CPU类型、数据块大小等等

典型的Mach-O文件通常包含三个主要区域

  • Header: 描述Mach-O文件的基本信息,如标识文件类型,目标架构类型,内存对齐大小,大小端序,加载命令数量,flags信息等
  • Load commands: 描述文件中每个段的信息(物理内存大小,虚拟内存等信息)
  • Raw data: 在Load command中定义的segment的原始数据

如下图

查看Mach-O文件结构

  1. file: 查看Mach-O文件类型

    1
    2
    # Hugu为可执行文件
    file Hugo
  2. otool:查看Mach-O特定部分和段的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 查看mach-o header
    otool -h Hugo

    # 查看load command
    otool -l Hugo

    # 查看链接的动态库
    otool -L Hugo

    # otool 支持很多参数,查看不同的信息
  3. size:查看段的内存分布

    1
    size -l -m -x Hugo

    输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
    Segment __TEXT: 0x1308000 (vmaddr 0x100000000 fileoff 0)
    Section __text: 0x104f4cc (addr 0x100005150 offset 20816)
    Section __stubs: 0x42d8 (addr 0x10105461c offset 17122844)
    Section __stub_helper: 0x42c0 (addr 0x1010588f4 offset 17139956)
    Section __objc_methname: 0xbf942 (addr 0x10105cbb4 offset 17157044)
    Section __objc_classname: 0x130d2 (addr 0x10111c4f6 offset 17941750)
    Section __objc_methtype: 0x1c635 (addr 0x10112f5c8 offset 18019784)
    Section __cstring: 0xf26fd (addr 0x10114bc00 offset 18136064)
    Section __ustring: 0xd3a0 (addr 0x10123e2fe offset 19129086)
    Section __const: 0x2db07 (addr 0x10124b6a0 offset 19183264)
    Section __gcc_except_tab: 0x21a80 (addr 0x1012791a8 offset 19370408)
    Section __swift5_typeref: 0xc382 (addr 0x10129ac28 offset 19508264)
    Section __swift5_fieldmd: 0xb028 (addr 0x1012a6fac offset 19558316)
    Section __swift5_builtin: 0x76c (addr 0x1012b1fd4 offset 19603412)
    Section __swift5_reflstr: 0x1417d (addr 0x1012b2740 offset 19605312)
    Section __swift5_assocty: 0xd38 (addr 0x1012c68c0 offset 19687616)
    Section __swift5_proto: 0x624 (addr 0x1012c75f8 offset 19691000)
    Section __swift5_types: 0x998 (addr 0x1012c7c1c offset 19692572)
    Section __swift5_capture: 0xb524 (addr 0x1012c85b4 offset 19695028)
    Section __swift5_protos: 0x50 (addr 0x1012d3ad8 offset 19741400)
    Section __dof_RACSignal: 0x37b (addr 0x1012d3b28 offset 19741480)
    Section __dof_RACCompou: 0x2e8 (addr 0x1012d3ea3 offset 19742371)
    Section __unwind_info: 0x31ce4 (addr 0x1012d418c offset 19743116)
    Section __eh_frame: 0x2184 (addr 0x101305e70 offset 19947120)
    total 0x1302e97
    Segment __DATA: 0x560000 (vmaddr 0x101308000 fileoff 19955712)
    Section __got: 0x1190 (addr 0x101308000 offset 19955712)
    Section __la_symbol_ptr: 0x2c90 (addr 0x101309190 offset 19960208)
    Section __mod_init_func: 0x40 (addr 0x10130be20 offset 19971616)
    Section __const: 0x48950 (addr 0x10130be60 offset 19971680)
    Section __cfstring: 0x6fea0 (addr 0x1013547b0 offset 20268976)
    Section __objc_classlist: 0x68c8 (addr 0x1013c4650 offset 20727376)
    Section __objc_nlclslist: 0x2c0 (addr 0x1013caf18 offset 20754200)
    Section __objc_catlist: 0x1ef8 (addr 0x1013cb1d8 offset 20754904)
    Section __objc_nlcatlist: 0x88 (addr 0x1013cd0d0 offset 20762832)
    Section __objc_protolist: 0x11f8 (addr 0x1013cd158 offset 20762968)
    Section __objc_imageinfo: 0x8 (addr 0x1013ce350 offset 20767568)
    Section __objc_const: 0x3c85f8 (addr 0x1013ce358 offset 20767576)
    Section __objc_selrefs: 0x2e430 (addr 0x101796950 offset 24734032)
    Section __objc_protorefs: 0x4a8 (addr 0x1017c4d80 offset 24923520)
    Section __objc_classrefs: 0x5c00 (addr 0x1017c5228 offset 24924712)
    Section __objc_superrefs: 0x39d8 (addr 0x1017cae28 offset 24948264)
    Section __objc_ivar: 0xc120 (addr 0x1017ce800 offset 24963072)
    Section __objc_data: 0x59c10 (addr 0x1017da920 offset 25012512)
    Section __data: 0x1c885 (addr 0x101834530 offset 25380144)
    Section __swift_hooks: 0xb8 (addr 0x101850db8 offset 25497016)
    Section __bss: 0x12e30 (addr 0x101850e70 offset 0)
    Section __common: 0x1b88 (addr 0x101863ca0 offset 0)
    total 0x55d825
    Segment __LINKEDIT: 0x168000 (vmaddr 0x101868000 fileoff 25509888)
    total 0x1019d0000
  4. 也可以通过MachOView查看Segment相关信息

    _

  5. 还有一个功能非常强大的二进制编辑器010Editor,需要安装一下MachO模板,与MachOView类似,使用起来也非常方便

    _

我们通过machoview查看

_

名称 解释
magic Mach-O魔数,FAT:0xcafebabe, ARMv7:0xfeedface, ARM64:0xfeedfacf
cputype、cpusubtype CPU架构及子版本
filetype mach-o文件类型
ncmds 加载命令的数量
sizeofcmds 所有加载命令的大小
flags dyld加载需要的一些标记
reserved 64位保留字段

在Header信息中,flags标志信息有一个标志为MH_PIE(在xnu项目的EXTERNAL_HEADERS/mach-o/loader.h中可以找到),意思是开启ASLR

笔者尝试把MH_PIE去掉,然后重签名跑到真机上,发现会闪退

Mach-O文件类型

在苹果开源的内核xnu源码中EXTERNAL_HEADERS/mach-o/loader.h可以找到Mach-O文件格式的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
#define MH_OBJECT 0x1  /* relocatable object file */
#define MH_EXECUTE 0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3 /* fixed VM shared library file */
#define MH_CORE 0x4 /* core file */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* dynamically bound shared library */
#define MH_DYLINKER 0x7 /* dynamic link editor */
#define MH_BUNDLE 0x8 /* dynamically bound bundle file */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static */
/* linking only, no section contents */
#define MH_DSYM 0xa /* companion file with only debug */
/* sections */
#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */

常见的类型有

  • MH_OBJECT
    • 目标文件(.o)
    • 静态库文件(.a)(多个目标文件合并)
  • MH_EXECUTE:
    • 可执行文件,我们编译出来的App的主程序就是该类型
  • MH_DYLIB:动态库
    • dylib文件
    • framework动态库(.framework/xx)
  • MH_DYLINKER
    • /usr/lib/dyld:动态链接器
  • MH_DSYM
    • .dSYM/Contents/Resources/DWARF/xx(符号表)

Universal Binary(通用二进制文件)

包含多个CPU架构的二进制文件,在运行时,只会加载对应架构的二进制

1
2
3
4
$ file jcm
jcm: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64]
jcm (for architecture armv7): Mach-O executable arm_v7
jcm (for architecture arm64): Mach-O 64-bit executable arm64

Segment

在machoview的load commands可以看到所有段的

_

  • LC_SEGMENT_64:记录一个段,加载后被映射到内存中
  • LC_DYLD_INFO_ONLY:记录动态链接的重要信息,动态链接器要根据它来进行地址重定向
  • LC_SYMTAB:文件所使用的符号表,符号数,字符串表的偏移量和大小
  • LC_DYSYMTAB:动态链接器所使用的符号表,找到后获取间接符号表偏移量
  • LC_LOAD_DYLINKER:默认的加载器路径(/usr/bin/dylb
  • LC_UUID: Mach-O文件的唯一标识
  • LC_MAIN:程序的入口,动态链接器获取该地址,然后程序跳转到该处运行
  • LC_SOURCE_VERSION:构建二进制文件的源代码版本
  • LC_VERSION_MIN_IPHONEOS:最低支持系统版本
  • LC_ENCRYPTION_INFO_64:文件加密信息,我们判断是否脱壳,就是用了这里的信息
  • LC_RPATH:链接器搜索路径列表,主要搜索framework
  • LC_FUNCTION_STARTS: 函数其实地址表,调试器货其他程序能判断一个地址是否在该表范围内
  • LC_DATA_IN_CODE: 定义在代码内的非指令表
  • LC_CODE_SIGNATURE: 代码签名信息

LC_SEGMENT_64

图中可以看到,LC_SEGMENT_64段有4个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
* LC_SEGMENT_64(`__PAGEZERO`)
* VM Address:0x0000000000000000
* VM Size: 0x0000000100000000
* File Offset: 0
* File Offset: 0
* LC_SEGMENT_64(`__TEXT`)
* VM Address:0x100000000
* VM Size: 0x001308000
* File Offset: 0x000000000
* File Size: 0x001308000
* LC_SEGMENT_64(`__DATA`)
* VM Address:0x101308000
* VM Size: 0x000560000
* File Offset: 0x001308000
* File Size: 0x00054C000
* LC_SEGMENT_64(`__LINKEDIT`)
* VM Address:0x101868000
* VM Size: 0x000168000
* File Offset: 0x001854000
* File Size: 0x000166AB0

上面可以看出

  • PAGEZERO段并不占用文件大小,当mach-o加载到内存时会占用8个字节(64位)
  • TEXT段从0开始的,也就是header和loadcommand也属于TEXT段,加载到内存后,排在PAGEZERO后面

_

__TEXT段

TEXT段包含可执行的代码和一些只读数据,静态链接器设置该段位可读可执行,进程被允许执行这些代码,但不能修改

  • __text:主程序代码
  • __stubs: 帮助动态链接库绑定符号
  • __const: const关键字修饰的常量
  • __cstring: 只读c语言字符串
  • __objc_methname: OC方法名
  • __objc_classname:OC类名
  • __objc_methtype:OC方法类型(方法签名)
  • __unwind_info:编译器自动生成,用于确定异常发生时栈所对应的信息

__DATA段

包含了将会被更改的数据,静态链接器设置该段的虚拟内存权限位可读写

  • __got: 全局非懒绑定符号指针表
  • __la_symbol_ptr: 懒绑定符号指针表
  • __mod_init_func:C++类的构造函数
  • __const:未初始化过的常亮
  • __cfstring:CoreFoundation字符串
  • __objc_class_list:OC类列表
  • __objc_nlclslist:实现了+load方法的OC类列表
  • __objc_catlist:OC分类列表
  • __objc_protolist:OC协议列表
  • __objc_imageinfo:镜像信息,可用它区别Objective1.0与2.0
  • __objc_const:OC初始化过的常量
  • __objc_selrefs:OC选择器引用列表
  • __objc_protorefs:OC协议引用列表
  • __objc_classrefs:OC类引用列表
  • __objc_superrefs: OC父类引用列表
  • __objc_ivar: OC类的实例变量
  • __objc_data: OC初始化过的变量
  • __data:实际初始化数据段
  • __common:未初始化过的符号声明
  • __bss:未初始化的全局变量

__LINKEDIT

包含动态链接库的原始数据,如符号,字符串,重定向表条目等

dyld

dyld用于加载以下类型的Mach-O文件,在iOS中,App的可执行文件和动态库都是由dyld负责加载的

  • MH_EXECUTE
  • MH_DYLIB
  • MH_BUNDLE

关于dyld如何加载mach-o文件,可以参考这里

ASLR(Address Space Layout Randomization)

iOS4.3开始引入了ASLR技术,地址空间布局随机化,系统在加载Mach-O文件的时候,会随机在头部添加一部分内存空间,从而让Mach-O文件在每次加载到内存时的地址都不相同,是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术

ASLR 会在程序每次载入的时候随机在原来的基础上添加随机的内存区域,已达到每次运行的程序地址都不一样,相当于__PAGEZERO段的位置后移了

我们可以在lldb调试器查看程序每个模块的偏移大小ASLR偏移的大小

1
2
3
4
5
# -o 表示虚拟地址偏移量,-f 表示路径
(lldb) image list -o -f

# 输出
[ 0] 0x00000000003f8000 /private/var/containers/Bundle/Application/943EF984-E5F8-45FC-A466-99474D559B68/Test.app/Test(0x00000001003f8000)
  • 0x00000000003f8000 为主程序Test的内存偏移地址,这个偏移地址每次启动App都是随机的
  • 0x00000001003f8000 为主程序__TEXT段的起始地址,即ASLR偏移地址+__PAGEZERO的大小

由于MachO文件加载到内存中的数据和MachO文件的数据是一致的,连续的,所以,我们可以通过ASLR的偏移地址加上在MachO静态分析的地址,得到运行时的地址,我们再Hopper查看到的地址,其实就是没有算上ASLR,即上面的0x00000000003f8000

_

再Hopper搜索[ViewController test:]方法,可以看到该函数的地址为0x0000000100005744,根据上面得到的ASLR的偏移地址,就可以算出函数在内存中的地址,由于Hopper显示的地址是算上__PAGEZERO段的,所以直接加上即可

1
0x00000000003f8000 + 0x0000000100005744

小结

了解Mach-O可以帮助我们理解dyld的加载Mach-O的过程以及与Mach-O相关的读取或操作,为逆向分析提供更好的思路