Mach-O格式分析
推荐使用的工具和文档
- ImHex https://github.com/WerWolv/ImHex
- otool -h ./a.out
- brew install --cask machoview
- Hopper Disassembler
- XNU源码:https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/osfmk/mach/machine.h

header部分,共32个字节
0-3:Magic。0xFEEDFACE是32位,0xFEEDFACF是64位。
4-7:CpuType。0xC是ARM,0x0100000C是ARM64。
8-10:SubCPUType。只有3个字节。0x0,表示CPU_SUBTYPE_ARM_ALL
11:Capability: 1个字节。Capability bits used in the definition of cpu_subtype. 目前只有当表示为Lib64时有值。
12-15:FileType: 4个字节。2表示是可执行文件。1是不可执行的二进制文件,比如静态链接库。还有其它类型,不一一列举。
16-19:NumberOfCommands: 4个字节。表示后面出现的loadCommands的数量。这里是17个指令。
20-23:: SizeOfCommands: 4个字节。表示后面出现的loadCommands的总大小。这里是1032字节。
24-27:Flags: 4个字节。表示一些标志位,每一个bit都有相应的含义。比如是否是动态链接,是否是可执行文件等等。完整的内容如下(太长了,AI辅助生成。)
1. noUndefs:没有未定义的符号。
2. incrLink: 增量链接。
3. dyldLink: 动态链接。
4. bindAtLoad: 加载时绑定。
5. prebind: 预绑定。
6. splitSegAtLoad: 加载时分割。
7. lazyInit: 懒惰初始化。
8. twoLevel: 两级命名空间。
1. forceFlat: 强制扁平化。
2. noMultiDefs: 没有重复定义。
3. noFixPrebinding: 不进行预绑定。
4. prebindable: 可预绑定。
5. allModsBound: 所有模块都已绑定。
6. subSectionViasSymbols: 子节通过符号。
7. canonical: 正规的。
8. weakDefined: 弱定义。
1. bindsToWeak: 绑定到弱符号。
2. allowStackExecution: 允许栈执行。
3. rootSafe: 根安全。
4. setuidSafe: setuid安全。
5. noReexportDylibs: 不重新导出动态库。
6. pie: PIE。
7. deadStrip: 死代码剥离。
8. hasTlvDescriptor: 有TLV描述符。
1. noHeapExecution: 没有堆执行。
2. appExtensionSafe: 应用扩展安全。
3. nlistOutOfSyncWithDyldInfo: nlist与dylidInfo不同步。
4. simSupports: 模拟器支持。
28-31: 未定义
loadCommands部分
这一部分按照[command - commandSize - data]格式构成一个数组,元素个数是上面提到的NumberOfCommands,总长度是上面提到的SizeOfCommands。
指令0:LC_SEGMENT64(__PAGEZERO),vmAddress:0,vmSize:4GB,fileOffset:0,fileSize:0。__PAGEZERO 的作用是在可执行文件的起始地址创建一个不可访问的内存页,以防止程序意外访问无效的内存地址,如空指针解引用,从而提高程序的安全性。
指令1: LC_SEGMENT64(__TEXT),vmAddress: 4GB, vmSize: 16KB,fileOffset: 0, fileSize: 16kB,最大保护:可读可执行,初始保护:可读可执行,4个Section。__TEXT 段在 Mach-O 文件格式中用于存储程序的代码和只读数据,并通过内存保护机制确保其安全性,同时优化了内存使用和程序性能。
Section 0: __text,地址:4294983508(4G + 16212),size:56个字节,偏移:16212(0x3F54),4字节对齐(section alignment (power of 2)),flags: 0x80000400。flags很琐碎,后续补充。__text专门用于存储程序的可执行代码
Section 1: __stubs,地址:4294983564(4G + 16212 + 56),size:12个字节,偏移:16268(0x3F8C),4字节对齐,flags: 0x80000408。__stubs用于存储函数调用的跳转指令,这些指令指向动态链接库中的实际函数实现。
Section 2: __cstring,地址:4294983576(4G + 16212 + 56 + 12),size:16个字节,偏移:16280(0x3F98),1字节对齐,flags: 0x02。__cstring提供了一个集中存储字符串常量的地方,这些字符串常量在程序运行时被引用。"Hello, World!"就在里面
Section 3: __unwind_info,地址:4294983592(4G + 16212 + 56 + 12 + 16),size:88个字节,偏移:16296(0x3FA8),4字节对齐,flags: 0x0。__unwind_info存储了用于异常处理的元数据,这些数据帮助运行时系统在发生异常时正确地展开堆栈,恢复程序的执行环境。
指令2: LC_SEGMENT64(__DATA_CONST),vmAddress: 4294983680(4G + 16212 + 56 + 12 + 16 + 88), vmSize: 16KB,fileOffset: 16kB, fileSize: 16kB,最大保护:可读可写,初始保护:可读可写,1个Section。__DATA_CONST用于存储程序中不会被修改的常量数据。
Section 0: __got,地址:4294983680(4G + 16212 + 56 + 12 + 16 + 88),size:8个字节,偏移:16384(0x4000),8字节对齐,flags: 0x06。__got(Global Offset Table,全局偏移表)节的作用是存储指向动态链接库中符号的指针。当程序运行时,动态链接器会解析这些符号并将其地址写入__got节中。这样,程序在运行时可以通过__got节快速访问动态链接库中的函数和变量,而无需每次都进行符号解析。
指令3: LC_SEGMENT64(__LINKEDIT),vmAddress: 4295000064(4G + 16212 + 56 + 12 + 16 + 88 + 16384), vmSize: 16KB,fileOffset: 32kB, fileSize: 664字节,最大保护:只读,初始保护:只读,0个Section。__LINKEDIT段用于存储链接编辑器所需的信息,包括符号表、重定位条目和字符串表等。
指令3: LC_DYLD_CHAINED_FIXUPS,vmAddress: 4295000064(4G + 16212 + 56 + 12 + 16 + 88 + 16384), vmSize: 16KB,fileOffset: 32kB, fileSize: 664字节,最大保护:只读,初始保护:只读,0个Section。__LINKEDIT段用于存储链接编辑器所需的信息,包括符号表、重定位条目和字符串表等。
0x3F54内容分析
__TEXT里面包含的是可执行代码,在这里就是main函数入口。这里我们拿出反编译工具Hopper Disassembler

汇编代码很简单,所以我就交给AI来给大家翻译了
这段代码是使用ARM64汇编语言编写的,它实现了一个非常简单的功能:打印"Hello, World!"消息。下面是对这段代码的逐行解释:
_main:
0000000100003f54 sub sp, sp, #0x20
sub sp, sp, #0x20:将栈指针sp向下移动32字节(0x20),为局部变量和保存的寄存器分配空间。
0000000100003f58 stp fp, lr, [sp, #0x10]
stp fp, lr, [sp, #0x10]:将帧指针fp和链接寄存器lr保存到栈上,位置是栈指针sp加上16字节。
0000000100003f5c add fp, sp, #0x10
add fp, sp, #0x10:将帧指针fp设置为当前栈指针sp加上16字节,用于访问函数的局部变量。
0000000100003f60 mov w8, #0x0
mov w8, #0x0:将寄存器w8(即x8的低位32位)设置为0。
0000000100003f64 str w8, [sp, #0x10 + var_C]
str w8, [sp, #0x10 + var_C]:将寄存器w8的值存储到栈上,位置是sp加上16字节再减去局部变量var_C的偏移量。
0000000100003f68 stur wzr, [fp, var_4]
stur wzr, [fp, var_4]:将零寄存器wzr的值存储到帧指针fp减去局部变量var_4的偏移量的位置。
0000000100003f6c str wzr, [sp, #0x10 + var_8]
str wzr, [sp, #0x10 + var_8]:将零寄存器wzr的值存储到栈上,位置是sp加上16字节再减去局部变量var_8的偏移量。
0000000100003f70 adrp x0, #0x100003000 ; 0x100003f98@PAGE
adrp x0, #0x100003000:将寄存器x0设置为指向页面地址0x100003000的值。
0000000100003f74 add x0, x0, #0xf98 ; 0x100003f98@PAGEOFF, argument "format" for method imp___stubs__printf, "Hello, World! \\n"
add x0, x0, #0xf98:将寄存器x0的值加上0xf98,得到完整的地址0x100003f98,这个地址存储了字符串"Hello, World! \n"。
0000000100003f78 bl imp___stubs__printf ; printf
bl imp___stubs__printf:调用printf函数,打印x0寄存器指向的字符串。
0000000100003f7c ldr w0, [sp, #0x10 + var_C]
ldr w0, [sp, #0x10 + var_C]:从栈上加载之前存储的值到寄存器w0。
0000000100003f80 ldp fp, lr, [sp, #0x10]
ldp fp, lr, [sp, #0x10]:从栈上恢复帧指针fp和链接寄存器lr的值。
0000000100003f84 add sp, sp, #0x20
add sp, sp, #0x20:将栈指针sp向上移动32字节,恢复栈状态。
0000000100003f88 ret
ret:返回到调用函数的地方。
; endp
; endp:表示函数的结束。
总的来说,这段代码设置了一个栈帧,初始化了一些局部变量,调用printf函数打印"Hello, World!",然后清理栈帧并返回。这是一个典型的汇编语言程序结构,展示了如何进行函数调用和栈管理。
0x3FA8内容分析
0x4000内容分析
0x8000内容分析