OC逆向12:Mach-O文件(上)

张建 lol

前言

本文主要介绍 Mach-O文件格式 以及 通用二进制文件

Mach-O 文件概述

  • Mach-O其实是 Mach Object 文件格式的缩写,是 Mac 以及 iOS可执行文件的格式,类似于 Window 上的 PE格式(Portable Executable),linux 上的 elf格式(Executable And LinkingFormat)。

  • Mach-O 是一种 用于可执行文件、目标代码、动态库的文件格式,作为 a.out格式 的替代,Mach-O 提供了更强的扩展性

Mach-O 文件格式

常见的 Mach-O格式 有以下几种:

  • 目标文件 .o
  • 库文件,细分主要有以下几种:
    • .a
    • .dylib
    • .framework
  • 可执行文件
  • dyld
  • dsym

我们可以通过终端的 file 指令来查看文件的类型

1
2
$cd 文件目录
$file 文件

目标文件 .o

  1. 新建一个 .c 文件
1
2
% cd Desktop 
% touch demo.c
  1. 在 .c 文件中实现如下
1
2
3
4
5
6
#include <stdio.h>

int main(){
printf("test\n");
return 0;
}
  1. 通过 clang 命令编译 .c 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% clang -c demo.c

% file demo.o
demo.o: Mach-O 64-bit object x86_64

% clang demo.o

% ls
a.out demo.c demo.o

% file a.out
a.out: Mach-O 64-bit executable x86_64

% ./a.out
test
  • ls:查看当前文件夹
  • clang -c demo.c : 将 .c文件 编译成 .o文件(OC在后端中使用的是LLVM编译,而前端使用的工具是 clang,即理解为 LLVM包含clang)
  • file demo.o : 查看文件类型 Mach-O文件、64位、x86_64架构
  • clang demo.o : 将 .o文件 编译成 可执行文件
  • ./a.out : 执行可执行文件
  1. 也可以通过一行命令将 .c文件 编译成可执行文件 : clang -o demo2 demo.c
1
2
3
4
5
6
7
% clang -o demo2 demo.c

% ls
demo.c demo2

% ./demo2
test
  1. 重复4 的操作,再次生成一个可执行文件 demo3,此时问题来了,这三个可执行文件(即 a、demo2、demo3)是否是一样的?我们可以通过 md5 验证,如果hash值一样,则说明一样的,反之不一样
1
2
3
4
5
6
7
8
% md5 a.out
MD5 (a.out) = 922bae326e46b2ac90f5ad4bf2ed031c

% md5 demo2
MD5 (demo2) = 922bae326e46b2ac90f5ad4bf2ed031c

% md5 demo3
MD5 (demo3) = 922bae326e46b2ac90f5ad4bf2ed031c

中间产物 .o文件

其中 .c.out 文件的区别是中间多了一个 .o 文件。而在我们的实际开发中,其实是由多个源码的,所以最终的 可执行文件是由多个源码 生成的,如下所示,将 两个.o 文件编译成 一个可执行文件

  • 新建两个 .c 文件

test.c 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

// 函数外定义变量 x 和 y
int x;
int y;
int addtwonum()
{
// 函数内声明变量 x 和 y 为外部变量
extern int x;
extern int y;
// 给外部变量(全局变量)x 和 y 赋值
x = 1;
y = 2;
return x+y;
}

test1.c 代码如下:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main()
{
int result;
result = 1;

printf("result 为: %d",result);
return 0;
}
  • 通过 clang两个.c文件 生成 两个.o文件clang -c test1.c test.c
1
2
3
% clang -c test1.c test.c
% ls
test1.o test.c test.o test1.c
  • 通过 clang 将 两个.o文件 编译成 两个可执行文件clang -o demo test1.o test.o
1
% clang -o demo test1.o test.o

如果出现如下报错

1
2
3
4
5
6
% clang -o demo test1.o test.o
duplicate symbol '_main' in:
test1.o
test.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

说明: text.ctest1.c 中的 代码相同,需要将其中一个中的代码删除即可,即保证 test1.c 和 test.c 内代码不同

  • 如果此时换一下链接顺序呢?例如: clang -o demo1 test.o test1.o

  • 多个源码一次性生成可执行文件: clang -o demo2 test1.c test.c

  • 对比上述生成三个可执行文件,是否是同一个?这里我们也通过 md5 生成的 hash值 进行对比:

1
2
3
4
5
6
% md5 demo
MD5 (demo) = 4be533adc572558230adcbd067379fea
% md5 demo1
MD5 (demo1) = a3ff882d7d541fe3bc2a11538bc2e031
% md5 demo2
MD5 (demo2) = 4be533adc572558230adcbd067379fea

结论:通过对比发现,如果改变了 .o文件的 连接顺序,那么 Mach-O 也会发生改变

  • 这里可以通过 objdump --macho -d demo 查看 Mach-O 链接顺序,如下:
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
% objdump --macho -d demo
demo:
(__TEXT,__text) section
_main:
100003f30: 55 pushq %rbp
100003f31: 48 89 e5 movq %rsp, %rbp
100003f34: 48 83 ec 10 subq $16, %rsp
100003f38: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
100003f3f: c7 45 f8 01 00 00 00 movl $1, -8(%rbp)
100003f46: 8b 75 f8 movl -8(%rbp), %esi
100003f49: 48 8d 3d 56 00 00 00 leaq 86(%rip), %rdi ## literal pool for: "result \344\270\272: %d"
100003f50: b0 00 movb $0, %al
100003f52: e8 2d 00 00 00 callq 0x100003f84 ## symbol stub for: _printf
100003f57: 31 c0 xorl %eax, %eax
100003f59: 48 83 c4 10 addq $16, %rsp
100003f5d: 5d popq %rbp
100003f5e: c3 retq
100003f5f: 90 nop
_addtwonum:
100003f60: 55 pushq %rbp
100003f61: 48 89 e5 movq %rsp, %rbp
100003f64: 48 8d 0d a9 40 00 00 leaq _y(%rip), %rcx
100003f6b: 48 8d 05 9e 40 00 00 leaq _x(%rip), %rax
100003f72: c7 00 01 00 00 00 movl $1, (%rax)
100003f78: c7 01 02 00 00 00 movl $2, (%rcx)
100003f7e: 8b 00 movl (%rax), %eax
100003f80: 03 01 addl (%rcx), %eax
100003f82: 5d popq %rbp
100003f83: c3 retq

源文件链接顺序是 main + addtwonum

  • 这里可以通过 objdump --macho -d demo1 查看 Mach-O 链接顺序,如下:
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
% objdump --macho -d demo1
demo1:
(__TEXT,__text) section
_addtwonum:
100003f20: 55 pushq %rbp
100003f21: 48 89 e5 movq %rsp, %rbp
100003f24: 48 8d 0d e9 40 00 00 leaq _y(%rip), %rcx
100003f2b: 48 8d 05 de 40 00 00 leaq _x(%rip), %rax
100003f32: c7 00 01 00 00 00 movl $1, (%rax)
100003f38: c7 01 02 00 00 00 movl $2, (%rcx)
100003f3e: 8b 00 movl (%rax), %eax
100003f40: 03 01 addl (%rcx), %eax
100003f42: 5d popq %rbp
100003f43: c3 retq
100003f44: 90 nop
100003f45: 90 nop
100003f46: 90 nop
100003f47: 90 nop
100003f48: 90 nop
100003f49: 90 nop
100003f4a: 90 nop
100003f4b: 90 nop
100003f4c: 90 nop
100003f4d: 90 nop
100003f4e: 90 nop
100003f4f: 90 nop
_main:
100003f50: 55 pushq %rbp
100003f51: 48 89 e5 movq %rsp, %rbp
100003f54: 48 83 ec 10 subq $16, %rsp
100003f58: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
100003f5f: c7 45 f8 01 00 00 00 movl $1, -8(%rbp)
100003f66: 8b 75 f8 movl -8(%rbp), %esi
100003f69: 48 8d 3d 32 00 00 00 leaq 50(%rip), %rdi ## literal pool for: "result \344\270\272: %d"
100003f70: b0 00 movb $0, %al
100003f72: e8 09 00 00 00 callq 0x100003f80 ## symbol stub for: _printf
100003f77: 31 c0 xorl %eax, %eax
100003f79: 48 83 c4 10 addq $16, %rsp
100003f7d: 5d popq %rbp
100003f7e: c3 retq

源文件的链接顺序是 addtwonum + main

  • 这里可以通过 objdump --macho -d demo2 查看 Mach-O 链接顺序,如下:
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
% objdump --macho -d demo2
demo2:
(__TEXT,__text) section
_main:
100003f30: 55 pushq %rbp
100003f31: 48 89 e5 movq %rsp, %rbp
100003f34: 48 83 ec 10 subq $16, %rsp
100003f38: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
100003f3f: c7 45 f8 01 00 00 00 movl $1, -8(%rbp)
100003f46: 8b 75 f8 movl -8(%rbp), %esi
100003f49: 48 8d 3d 56 00 00 00 leaq 86(%rip), %rdi ## literal pool for: "result \344\270\272: %d"
100003f50: b0 00 movb $0, %al
100003f52: e8 2d 00 00 00 callq 0x100003f84 ## symbol stub for: _printf
100003f57: 31 c0 xorl %eax, %eax
100003f59: 48 83 c4 10 addq $16, %rsp
100003f5d: 5d popq %rbp
100003f5e: c3 retq
100003f5f: 90 nop
_addtwonum:
100003f60: 55 pushq %rbp
100003f61: 48 89 e5 movq %rsp, %rbp
100003f64: 48 8d 0d a9 40 00 00 leaq _y(%rip), %rcx
100003f6b: 48 8d 05 9e 40 00 00 leaq _x(%rip), %rax
100003f72: c7 00 01 00 00 00 movl $1, (%rax)
100003f78: c7 01 02 00 00 00 movl $2, (%rcx)
100003f7e: 8b 00 movl (%rax), %eax
100003f80: 03 01 addl (%rcx), %eax
100003f82: 5d popq %rbp
100003f83: c3 retq

源文件的链接顺序是 main + addtwonum

上述所说的链接顺序,对应到我们日常开发中是指工程中的 target -> Build Phase -> Compiles Sources,这里就是对应源文件的 编译顺序,如果源文件的顺序发生了变化,生成的可执行文件是不一样的

库 文件

  • 什么是库?

    主要是程序代码的集合,即将N个文件组织起来,是共享程序代码的一种方式,

  • 库的分类

    • 开源库(公有库):源码是公开的,可以看到每个文件的实现,例如Github中的AFNetworking、SDWebImage等

    • 闭源库(私有库):源码未公开,是经过编译后的二进制文件,看不到具体的实现,再细分又分为 静态库 和 动态库。

静态库 & 动态库

  1. 静态库:静态库的存在形式主要有两种(建议用.framework):.a + .framework
  • .a 是一个 纯二进制文件,.a 不能直接使用,至少需要.h文件配合,可能还会需要资源文件

  • .framework 中除了有二进制文件外,还有资源文件,且可以 直接使用

  • 两者的关系: .a + .h + sourceFile = .frmework

  • 优势:方便共享代码,便于合理使用

    • 实现iOS的模块化,即将固定业务模块化为静态库
    • 共享代码,但不希望被看到代码的具体实现
  1. 动态库:动态库的存在形式也是两种:.dylib + .framework

  2. .framework 为什么即是静态库又是动态库?

  • 系统的 .framework 是动态库
  • 自定义的 .framework 是静态库
  1. 静态库和动态库的区别
  • 静态库链接 时会被完整的拷贝到项目中,如果有多个APP都是用了同一个静态库,会拷贝多份,浪费内存
  • 动态库 不会复制,只有一份,在程序运行时 动态加载 到内存中,多个APP共用一份,节约内存

验证 .a .dylib 是否是 Mach-O文件

1
$ cd 项目根目录下
  1. 验证 .a
  • 在项目中,通过find命令查找到 .a 文件: 终端输入 find /usr -name "*.a"

  • 随机查看一个 .a 文件,是一个 dynamically 类型: file .a文件路径

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
53
54
zhangjian@zhangjiandeMBP SRSF % find /usr -name "*.a"
find: /usr/sbin/authserver: Permission denied
/usr/local/lib/libbrotlidec-static.a
/usr/local/lib/libev.a
/usr/local/lib/libjemalloc.a
/usr/local/lib/libnghttp2.a
/usr/local/lib/libevent_extra.a
/usr/local/lib/libevent.a
/usr/local/lib/libevent_core.a
/usr/local/lib/libprotobuf.a
/usr/local/lib/liblz4.a
/usr/local/lib/libprotoc.a
/usr/local/lib/libprotobuf-lite.a
/usr/local/lib/libbrotlienc-static.a
/usr/local/lib/libbrotlicommon-static.a
/usr/local/lib/libuv.a
/usr/local/lib/libzstd.a
/usr/local/lib/libevent_openssl.a
/usr/local/lib/libmysqlservices.a
/usr/local/lib/libjemalloc_pic.a
/usr/local/lib/libmysqlclient.a
/usr/local/lib/libevent_pthreads.a
/usr/local/Cellar/nghttp2/1.43.0/lib/libnghttp2.a
/usr/local/Cellar/mysql-client/8.0.25/lib/libmysqlclient.a
/usr/local/Cellar/libuv/1.41.0/lib/libuv.a
/usr/local/Cellar/jemalloc/5.2.1_1/lib/libjemalloc.a
/usr/local/Cellar/jemalloc/5.2.1_1/lib/libjemalloc_pic.a
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlidec-static.a
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlienc-static.a
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlicommon-static.a
/usr/local/Cellar/icu4c/69.1/lib/libicui18n.a
/usr/local/Cellar/icu4c/69.1/lib/libicutest.a
/usr/local/Cellar/icu4c/69.1/lib/libicuio.a
/usr/local/Cellar/icu4c/69.1/lib/libicudata.a
/usr/local/Cellar/icu4c/69.1/lib/libicuuc.a
/usr/local/Cellar/icu4c/69.1/lib/libicutu.a
/usr/local/Cellar/lz4/1.9.3/lib/liblz4.a
/usr/local/Cellar/zstd/1.5.0/lib/libzstd.a
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlservices.a
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlclient.a
/usr/local/Cellar/libevent/2.1.12/lib/libevent_extra.a
/usr/local/Cellar/libevent/2.1.12/lib/libevent.a
/usr/local/Cellar/libevent/2.1.12/lib/libevent_core.a
/usr/local/Cellar/libevent/2.1.12/lib/libevent_openssl.a
/usr/local/Cellar/libevent/2.1.12/lib/libevent_pthreads.a
/usr/local/Cellar/openssl@1.1/1.1.1k/lib/libcrypto.a
/usr/local/Cellar/openssl@1.1/1.1.1k/lib/libssl.a
/usr/local/Cellar/libev/4.33/lib/libev.a
/usr/local/Cellar/protobuf/3.17.3/lib/libprotobuf.a
/usr/local/Cellar/protobuf/3.17.3/lib/libprotoc.a
/usr/local/Cellar/protobuf/3.17.3/lib/libprotobuf-lite.a

% file /usr/local/Cellar/protobuf/3.17.3/lib/libprotobuf-lite.a
/usr/local/Cellar/libevent/2.1.12/lib/libevent_openssl.a: current ar archive random library

这个.a库是看不出来是 dynamically类型

  1. 验证 .dylib
  • 在项目中通过 find 查找 .dylib文件find /usr -name "*.dylib"

  • 查看 .dylib 文件: file .dylib文件路径

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
find: /usr/sbin/authserver: Permission denied
/usr/local/Homebrew/Library/Homebrew/test/support/fixtures/mach/fat.dylib
/usr/local/Homebrew/Library/Homebrew/test/support/fixtures/mach/i386.dylib
/usr/local/Homebrew/Library/Homebrew/test/support/fixtures/mach/x86_64.dylib
/usr/local/lib/libmysqlrouter_http_auth_backend.1.dylib
/usr/local/lib/libmysqlharness_stdx.1.dylib
/usr/local/lib/libevent_pthreads-2.1.7.dylib
/usr/local/lib/libbrotlidec.1.dylib
/usr/local/lib/libuv.dylib
/usr/local/lib/libbrotlicommon.1.dylib
/usr/local/lib/liblz4.1.9.3.dylib
/usr/local/lib/libcares.dylib
/usr/local/lib/libmysqlharness.1.dylib
/usr/local/lib/libbrotlienc.dylib
/usr/local/lib/libprotobuf-lite.dylib
/usr/local/lib/libevent_openssl-2.1.7.dylib
/usr/local/lib/libevent_extra.dylib
/usr/local/lib/libbrotlienc.1.dylib
/usr/local/lib/libuv.1.dylib
/usr/local/lib/libevent_extra-2.1.7.dylib
/usr/local/lib/libprotoc.28.dylib
/usr/local/lib/libprotobuf.28.dylib
/usr/local/lib/libmysqlharness_tls.1.dylib
/usr/local/lib/libevent.dylib
/usr/local/lib/libcares.2.dylib
/usr/local/lib/libcares.2.4.2.dylib
/usr/local/lib/libbrotlienc.1.0.9.dylib
/usr/local/lib/libmysqlclient.21.dylib
/usr/local/lib/libev.dylib
/usr/local/lib/libprotoc.dylib
/usr/local/lib/libzstd.1.5.0.dylib
/usr/local/lib/libevent-2.1.7.dylib
/usr/local/lib/libevent_pthreads.dylib
/usr/local/lib/liblz4.dylib
/usr/local/lib/libmysqlrouter_io_component.1.dylib
/usr/local/lib/libmysqlrouter_http.1.dylib
/usr/local/lib/libjemalloc.2.dylib
/usr/local/lib/libnghttp2.14.dylib
/usr/local/lib/libprotobuf.dylib
/usr/local/lib/libmysqlclient.dylib
/usr/local/lib/libevent_openssl.dylib
/usr/local/lib/liblz4.1.dylib
/usr/local/lib/libmysqlrouter_http_auth_realm.1.dylib
/usr/local/lib/libbrotlicommon.dylib
/usr/local/lib/libnghttp2.dylib
/usr/local/lib/libzstd.1.dylib
/usr/local/lib/libbrotlidec.dylib
/usr/local/lib/libmysqlrouter.1.dylib
/usr/local/lib/libbrotlidec.1.0.9.dylib
/usr/local/lib/libevent_core.dylib
/usr/local/lib/libbrotlicommon.1.0.9.dylib
/usr/local/lib/libev.4.dylib
/usr/local/lib/libzstd.dylib
/usr/local/lib/libjemalloc.dylib
/usr/local/lib/libprotobuf-lite.28.dylib
/usr/local/lib/libevent_core-2.1.7.dylib
/usr/local/Cellar/nghttp2/1.43.0/lib/libnghttp2.14.dylib
/usr/local/Cellar/nghttp2/1.43.0/lib/libnghttp2.dylib
/usr/local/Cellar/mysql-client/8.0.25/lib/libssl.dylib
/usr/local/Cellar/mysql-client/8.0.25/lib/libssl.1.1.dylib
/usr/local/Cellar/mysql-client/8.0.25/lib/plugin/libssl.1.1.dylib
/usr/local/Cellar/mysql-client/8.0.25/lib/plugin/libcrypto.1.1.dylib
/usr/local/Cellar/mysql-client/8.0.25/lib/libmysqlclient.21.dylib
/usr/local/Cellar/mysql-client/8.0.25/lib/libcrypto.dylib
/usr/local/Cellar/mysql-client/8.0.25/lib/libmysqlclient.dylib
/usr/local/Cellar/mysql-client/8.0.25/lib/libcrypto.1.1.dylib
/usr/local/Cellar/libuv/1.41.0/lib/libuv.dylib
/usr/local/Cellar/libuv/1.41.0/lib/libuv.1.dylib
/usr/local/Cellar/jemalloc/5.2.1_1/lib/libjemalloc.2.dylib
/usr/local/Cellar/jemalloc/5.2.1_1/lib/libjemalloc.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlidec.1.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlicommon.1.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlienc.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlienc.1.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlienc.1.0.9.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlicommon.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlidec.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlidec.1.0.9.dylib
/usr/local/Cellar/brotli/1.0.9/lib/libbrotlicommon.1.0.9.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicui18n.69.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicuio.69.1.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicuio.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicuuc.69.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicutest.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicudata.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicudata.69.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicutu.69.1.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicui18n.69.1.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicuuc.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicutu.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicui18n.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicutest.69.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicudata.69.1.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicutest.69.1.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicuuc.69.1.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicuio.69.dylib
/usr/local/Cellar/icu4c/69.1/lib/libicutu.69.dylib
/usr/local/Cellar/lz4/1.9.3/lib/liblz4.1.9.3.dylib
/usr/local/Cellar/lz4/1.9.3/lib/liblz4.dylib
/usr/local/Cellar/lz4/1.9.3/lib/liblz4.1.dylib
/usr/local/Cellar/c-ares/1.17.1/lib/libcares.dylib
/usr/local/Cellar/c-ares/1.17.1/lib/libcares.2.dylib
/usr/local/Cellar/c-ares/1.17.1/lib/libcares.2.4.2.dylib
/usr/local/Cellar/zstd/1.5.0/lib/libzstd.1.5.0.dylib
/usr/local/Cellar/zstd/1.5.0/lib/libzstd.1.dylib
/usr/local/Cellar/zstd/1.5.0/lib/libzstd.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlrouter_http_auth_backend.1.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlharness_stdx.1.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlharness.1.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlharness_tls.1.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlclient.21.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlrouter_io_component.1.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlrouter_http.1.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlclient.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlrouter_http_auth_realm.1.dylib
/usr/local/Cellar/mysql/8.0.25_1/lib/libmysqlrouter.1.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent_pthreads-2.1.7.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent_openssl-2.1.7.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent_extra.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent_extra-2.1.7.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent-2.1.7.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent_pthreads.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent_openssl.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent_core.dylib
/usr/local/Cellar/libevent/2.1.12/lib/libevent_core-2.1.7.dylib
/usr/local/Cellar/openssl@1.1/1.1.1k/lib/libssl.dylib
/usr/local/Cellar/openssl@1.1/1.1.1k/lib/libssl.1.1.dylib
/usr/local/Cellar/openssl@1.1/1.1.1k/lib/libcrypto.dylib
/usr/local/Cellar/openssl@1.1/1.1.1k/lib/engines-1.1/padlock.dylib
/usr/local/Cellar/openssl@1.1/1.1.1k/lib/engines-1.1/capi.dylib
/usr/local/Cellar/openssl@1.1/1.1.1k/lib/libcrypto.1.1.dylib
/usr/local/Cellar/libev/4.33/lib/libev.dylib
/usr/local/Cellar/libev/4.33/lib/libev.4.dylib
/usr/local/Cellar/protobuf/3.17.3/lib/libprotobuf-lite.dylib
/usr/local/Cellar/protobuf/3.17.3/lib/libprotoc.28.dylib
/usr/local/Cellar/protobuf/3.17.3/lib/libprotobuf.28.dylib
/usr/local/Cellar/protobuf/3.17.3/lib/libprotoc.dylib
/usr/local/Cellar/protobuf/3.17.3/lib/libprotobuf.dylib
/usr/local/Cellar/protobuf/3.17.3/lib/libprotobuf-lite.28.dylib
/usr/lib/libobjc-trampolines.dylib
/usr/lib/libLeaksAtExit.dylib
/usr/lib/libstdc++.6.dylib
/usr/lib/libpython2.7.dylib
/usr/lib/libhunspell-1.2.0.dylib
/usr/lib/libMTLCapture.dylib
/usr/lib/system/libsystem_pthread.dylib
/usr/lib/system/introspection/libsystem_pthread.dylib
/usr/lib/system/introspection/libdispatch.dylib
/usr/lib/system/libsystem_kernel.dylib
/usr/lib/system/libsystem_platform.dylib
/usr/lib/libgmalloc.dylib
/usr/lib/swift/libswiftRemoteMirror.dylib
/usr/lib/swift/libswiftCreateML.dylib
/usr/lib/libiodbc.2.dylib
/usr/lib/libffi-trampolines.dylib
/usr/lib/ssh-keychain.dylib
/usr/lib/libpython.dylib
/usr/lib/libiodbcinst.2.dylib

% file /usr/lib/libpython.dylib
/usr/lib/libpython.dylib: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64] [arm64e]
/usr/lib/libpython.dylib (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64
/usr/lib/libpython.dylib (for architecture arm64e): Mach-O 64-bit dynamically linked shared library arm64e
  1. 可执行文件

这里的可执行文件,即一般是指日常项目中,编译后生成的可执行文件,可以通过 file 查看其文件类型

1
2
% file /Users/zhangjian/Desktop/demo1 
/Users/zhangjian/Desktop/demo1: Mach-O 64-bit executable x86_64
  1. dyld

dyld(the dynamic link editor)是苹果的 动态连接器,是苹果操作系统一个重要的组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作,而且它是开源的,任何人可以通过苹果官网下载它的源码来阅读理解它的运作方式,了解系统加载动态库的细节。

  • 共享缓存机制

在iOS系统中,每个程序依赖的动态库都需要通过dyld(位于/usr/lib/dyld)一个一个 加载到内存,然而,很多系统库几乎是每个程序都会用到的,如果在每个程序运行的时候都重复的去加载一次,势必造成运行缓慢,为了优化启动速度和提高程序性能,共享缓存机制就应运而生。所有默认的动态链接库被合并成一个大的缓存文件,放到/System/Library/Caches/com.apple.dyld/目录下,按照不同的架构分别保存着

  • 验证dyld

查找Mac中的dyld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
zhangjian@zhangjiandeMBP ~ % cd /usr/lib  
zhangjian@zhangjiandeMBP lib % ls

charset.alias libstdc++.6.dylib
cron log
dsc_extractor.bundle pam
dtrace pkgconfig
dyld python2.7
groff rpcsvc
libLeaksAtExit.dylib ruby
libMTLCapture.dylib sasl2
libffi-trampolines.dylib sqlite3
libgmalloc.dylib ssh-keychain.dylib
libhunspell-1.2.0.dylib swift
libiodbc.2.dylib system
libiodbcinst.2.dylib updaters
libobjc-trampolines.dylib xpc
libpython.dylib zsh
libpython2.7.dylib

zhangjian@zhangjiandeMBP lib % ls dyld
dyld

查看 dyld的文件类型: file dyld,是一个 动态链接器,其本身也是一个Mach-O文件

1
2
3
4
5
zhangjian@zhangjiandeMBP lib % file dyld
dyld: Mach-O universal binary with 3 architectures: [i386:Mach-O dynamic linker i386] [x86_64:Mach-O 64-bit dynamic linker x86_64] [arm64e]
dyld (for architecture i386): Mach-O dynamic linker i386
dyld (for architecture x86_64): Mach-O 64-bit dynamic linker x86_64
dyld (for architecture arm64e): Mach-O 64-bit dynamic linker arm64e
  1. dsym文件
  • dsym介绍

Xcode编译项目后,我们会看到一个同名的 dSYM文件,dSYM是 保存16进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM文件,位于/Users/<用户名>/Library/Developer/Xcode/Archives目录下,所以对于每一个发布版本我们都很有必要保存对应的 Archives 文件。

当我们软件 release 模式打包或上线后,不会像我们在xcode中那样直观的看到 崩溃 的错误日志,这个时候我们就需要 分析crash report文件了,iOS设备中会有日志文件保存我们每个应用出错的函数内存地址,通过 xcodeorganizer 可以将iOS设备中的 deviceLog 导出成 crash文件,这个时候我们就可以 通过出错的函数地址去 查询dSYM 文件中程序对应的函数和文件名,但前提是我们需要有软件版本对应的dSYM文件,这也是为什么我们很有必要保存每个发布版本的 Archives 文件了

  • 验证 .dSYM文件

程序 真机+release 编译时,有一个 .dSYM文件

  • .dSYM 也是一个 mach-o 文件,是一个 符号表,主要用于出现崩溃后可以通过这个文件 取符号,方便 查询问题

通用二进制文件

mac系统所支持的cpu及硬件平台发生了很大的变化,为了解决软件在多个平台硬件平台上的 兼容性 问题,苹果开发了一个 通用的二进制文件格式(Universal Binary),又称 胖二进制(Fat Binary)

  • 苹果公司提出的一种程序代码,能同时 适用多种架构的二进制文件
  • 同一个程序包中同时为多种架构提供最理想的性能。
  • 因为需要存储多种代码,通用二进制应用程序通常比单一平台二进制的程序要大
  • 但是由于两种架构有 共通的非执行资源(代码以外的),所以并不会达到单一版本的两倍之多
  • 而且由于执行中只调用一部分代码,运行起来也不需要额外的内存

演示

  • 在日常开发的项目中,可以通过Build Setting -> Mach-O type,指定 Mach-O文件的类型,如下图:

  • 一般我们通过真机生成的可执行文件,其架构是arm64,是一个单一架构

    • iOS 9.0 以上的系统否支持 64位架构
    1
    2
    3
    % cd /Users/zhangjian/Library/Developer/Xcode/DerivedData/SRSF-bzdqlauzaknekvclilbreawtebqi/Build/Products/Debug-iphoneos/中图云书房.app
    % file 中图云书房
    中图云书房: Mach-O 64-bit executable arm64
  • 同时也可以在 Build Setting -> Architectures 中设值编译的 架构

    • 环境变量 $(ARCHS_STANDRAD) :包含 arm64、armv7

    • 还有一种架构:armv7s,iPhone5、iPhone5c可用的架构,可在工程中添加,此时查看 Release + 真机环境 可执行文件,支持3个架构
1
2
3
4
5
6
7
% cd /Users/zhangjian/Library/Developer/Xcode/DerivedData/Demo-edwjvapoqaffzeeiyzyhfryhfrsr/Build/Products/Release-iphoneos/Demo.app 

% file Demo
Demo: Mach-O universal binary with 3 architectures: [arm_v7:Mach-O executable arm_v7] [arm_v7s:Mach-O executable arm_v7s] [arm64:Mach-O 64-bit executable arm64]
Demo (for architecture armv7): Mach-O executable arm_v7
Demo (for architecture armv7s): Mach-O executable arm_v7s
Demo (for architecture arm64): Mach-O 64-bit executable arm64

ARM架构

ARM架构过去称作 进阶精简指令集机器(Advanced RISC Machine,更早称作:Acorn RISC Machine),是一个 32位精简指令集(RISC)处理器架构,ARM处理器非常适用于通讯领域,符合其主要设计目标为低耗电的特性

ARMIntel处理器的第一个区别是,前者使用 精简指令集(RISC),而后者使用 复杂指令集(CISC)

ARM处理器指令集:是指计算机ARM操作系统指令系统

  • armv6、armv7、armv7s、arm64、arm64e 都是arm处理器的指令集,所有指令集原则上都是 向下兼容

  • 苹果A7处理器支持两个不同的指令集:

32位 ARM指令集(armv6 | armv7 | armv7s
64位 ARM指令集(arm64

  • i386 | x86_64Mac 处理器的指令集

iOS 设备支持的指令集

通用二进制源码

  • 通过 CMD + shift + O 搜索 fat.h
  • 找到其中通用二进制文件的头部结构 fat_header 如下所示
1
2
3
4
struct fat_header {
uint32_t magic; /* magic字段被定义为常量FAT_MAGIC,表示这是一个胖二进制 */
uint32_t nfat_arch; /* 表示有多少个Mach-O文件 */
};
  • 每个胖二进制都用 fat_arch 结构表示,在 fat_header 之后,紧接着一个活多个连续的fat_arch结构体
1
2
3
4
5
6
7
struct fat_arch {
cpu_type_t cputype; /* cpu 类型 */
cpu_subtype_t cpusubtype; /* cpu的子类型 */
uint32_t offset; /* 制定了当前cpu架构数据相对于当前文件开头的偏移量 */
uint32_t size; /* 数据的大小 */
uint32_t align; /* 数据的内存对齐边界,取值必须是2的次方,它确保了当前cpu架构的目标文件在加载到内存中时,数据是经过内存优化对齐的 */
};

终端指令

  • 可以通过tool来查看fat_header信息:otool -f 可执行文件
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
% cd /Users/zhangjian/Library/Developer/Xcode/DerivedData/Demo-edwjvapoqaffzeeiyzyhfryhfrsr/Build/Products/Release-iphoneos/Demo.app 

% otool -f Demo
Fat headers
fat_magic 0xcafebabe
nfat_arch 3
architecture 0
cputype 12
cpusubtype 9
capabilities 0x0
offset 16384
size 79280
align 2^14 (16384)
architecture 1
cputype 12
cpusubtype 11
capabilities 0x0
offset 98304
size 79280
align 2^14 (16384)
architecture 2
cputype 16777228
cpusubtype 0
capabilities 0x0
offset 180224
size 80000
align 2^14 (16384)
zhangjian@zhangjiandeMBP Demo.app %
  • 可以通过lipo命令拆分、合并胖二进制文件,常用命令如下:

    • $lipo -info MachO文件:使用lifo -info 可以查看MachO文件包含的架构

    • $lipo MachO文件 -thin 架构 -output 输出文件路径:使用lipo -thin 拆分某种架构

    • $lipo -create MachO1 MachO2 -output 输出文件路径:使用lipo -create 合并 多种架构

lipo演示

  • 查看二进制文件中包含的架构:lipo -info Demo
1
2
3
4
% cd /Users/zhangjian/Library/Developer/Xcode/DerivedData/Demo-edwjvapoqaffzeeiyzyhfryhfrsr/Build/Products/Release-iphoneos/Demo.app 

% lipo -info Demo
Architectures in the fat file: Demo are: armv7 armv7s arm64
  • 拆分:lipo Demo -thin armv7 -output Demo_armv7,如果拆分没有的架构会报错

  • 查看拆分后的可执行文件类型:file Demo_armv7

1
2
3
% lipo Demo -thin armv7 -output Demo_armv7
% file Demo_armv7
Demo_armv7: Mach-O executable arm_v7
  • 合并:lipo -create Demo_armv7 Demo_arm64 -output Demo_v7_64

总结

  • Mach-O 其实是 Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式,是一种 用于可执行文件、目标代码、动态库的文件格式,且 Mach-O提供了更强的扩展性

  • 常见的Mach-O格式:.o库文件(.a、.dylib、.framework)可执行文件dyld.dsym

    • .a + .h + sourceFile = .framwork

    • 动态.framework:系统Framework库

    • 静态.framwork:自定义的Framework库

  • 查看文件类型命令:file 文件路径

  • 查看Mach-O源文件的链接顺序:objdump --macho -d 可执行文件

  • dyld(the dynamic link editor)是苹果的动态链接器,mac中路径为/usr/lib

  • dSYM 是保存 16 进制函数地址映射信息的中转文件,位于/Users/<用户名>/Library/Developer/Xcode/Archives目录。可以用于通过出错的函数地址去查询 dSYM 文件中程序对应的函数名和文件名

  • 通用二进制文件(Universal Binary,也称为胖二进制(Fat Binary))。主要适用于解决多个平台的兼容性问题

  • 通过otool来查看fat_header信息:otool -f 可执行文件

  • lipo命令拆分、合并胖二进制文件

    • $lipo -info MachO文件:使用lifo -info 可以查看MachO文件包含的架构

    • $lipo MachO文件 –thin 架构 –output 输出文件路径:使用lifo –thin 拆分某种架构

    • $lipo -create MachO1 MachO2 -output 输出文件路径: 使用lipo -create 合并多种架构

  • Post title:OC逆向12:Mach-O文件(上)
  • Post author:张建
  • Create time:2022-01-25 09:33:32
  • Post link:https://redefine.ohevan.com/2022/01/25/OC逆向/OC逆向12:Mach-O文件(上)/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.