MacOS APP应用逆向分析初探小记 2025-05-18 13:15:26 Steven Xeldax [TOC] # 基础知识 ## Mac App应用结构 典型的MacOS应用通常以.app bundle的形式存在,这是一个特殊的目录结构,包含了应用的所有资源。 ``` MyApp.app/ ├── Contents/ │ ├── Info.plist # 应用配置信息 │ ├── MacOS/ # 可执行文件目录 │ │ └── MyApp # 主可执行文件 │ ├── Frameworks/ # 依赖的框架 │ ├── Resources/ # 资源文件(图片、nib文件等) │ └── PlugIns/ # 插件目录 ``` ## Mach-O 文件结构 https://github.com/Desgard/iOS-Source-Probe/blob/master/C/mach-o/Mach-O%20%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E6%8E%A2%E7%B4%A2.md 进程在众多操作系统中都有提及,它是作为一个正在执行的程序的实例,这是 UNIX 的一个基本概念。而进程的出现是特殊文件在内从中加载得到的结果,这种文件必须使用操作系统可以认知的格式,这样才对该文件引入依赖库,初始化运行环境以及顺利地执行创造条件。 **Mach-O**(Mach Object File Format)是 macOS 上的可执行文件格式,类似于 Linux 和大部分 UNIX 的原生格式 **ELF**(Extensible Firmware Interface)。为了更加全面的了解这块内容,我们看一下 macOS 支持的三种可执行格式:解释器脚本格式、通用二进制格式和 Mach-O 格式。 | 可执行格式 | magic | 用途 | | ------| ------ | ------ | | 脚本 | `\x7FELF` | 主要用于 shell 脚本,但是也常用语其他解释器,如 Perl, AWK 等。也就是我们常见的脚本文件中在 `#!` 标记后的字符串,即为执行命令的指令方式,以文件的 stdin 来传递命令 | | 通用二进制格式 | `0xcafebabe` <br />`0xbebafeca` | 包含多种架构支持的二进制格式,只在 macOS 上支持 | | Mach-O | `0xfeedface`(32 位)<br /> `0xfeedfacf`(64 位) | macOS 的原生二进制格式 | ---- 通用二进制格式(Universal Binary) 这个格式在有些资料中也叫胖二进制格式(Fat Binary),Apple 提出这个概念是为了解决一些历史原因,macOS(更确切的应该说是 OS X)最早是构建于 PPC 架构智商,后来才移植到 Intel 架构(从 Mac OS X Tiger 10.4.7 开始),通用二进制格式的二进制文件可以在 PPC 和 x86 两种处理器上执行。 说到底,通用二进制格式只不过是对多架构的二进制文件的打包集合文件,而 macOS 中的多架构二进制文件也就是适配不同架构的 Mach-O 文件。 **Fat Header** 的数据结构在 `<mach-o/fat.h>` 头文件中有定义,可以参看 `/usr/include/mach-o/fat.h` 找到定义头: ```c #define FAT_MAGIC 0xcafebabe #define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */ struct fat_header { uint32_t magic; /* FAT_MAGIC 或 FAT_MAGIC_64 */ uint32_t nfat_arch; /* 结构体实例的个数 */ }; struct fat_arch { cpu_type_t cputype; /* cpu 说明符 (int) */ cpu_subtype_t cpusubtype; /* 指定 cpu 确切型号的整数 (int) */ uint32_t offset; /* CPU 架构数据相对于当前文件开头的偏移值 */ uint32_t size; /* 数据大小 */ uint32_t align; /* 数据内润对其边界,取值为 2 的幂 */ }; ``` 对于 `cputype` 和 `cpusubtype` 两个字段这里不讲述,可以参看 `/usr/include/mach/machine.h` 头中对其的定义,另外 [Apple 官方文档](https://developer.apple.com/documentation/kernel/mach_header?language=objc)中也有简单的描述。 在 `fat_header` 中,`magic` 也就是我们之前在表中罗列的 *magic* 标识符,也可以类比成 UNIX 中 ELF 文件的 *magic* 标识。加载器会通过这个符号来判断这是什么文件,通用二进制的 *magic* 为 `0xcafebabe`。`nfat_arch` 字段指明当前的通用二进制文件中包含了多少个不同架构的 Mach-O 文件。`fat_header` 后会跟着多个 `fat_arch`,并与多个 Mach-O 文件及其描述信息(文件大小、CPU 架构、CPU 型号、内存对齐方式)相关联。  # 基本工具 xcode ``` xcode-select --install ``` otool:查看二进制文件信息 nm:查看符号表 strings:提取二进制文件中的字符串 class-dump:导出Objective-C类信息 Ghidra: 开源类IDA逆向工具(Hopper/IDA都可) Frida:动态插桩框架 LLDB/GDB:动态调试工具 # 静态分析 如果你使用的是hopper,他会自动的解析通用文件格式,自动解析多重架构,但是由于hopper还是需要付费的,Mac上使用IDA的话一般版本落后没有,还是使用ghidra开源版本进行分析。但是ghidra开源分析需要注意的是不能直接将二进制拖入ghidra中他无法识别通用文件格式,需要进行裁剪。  ``` lipo AppleApp -thin arm64 -output AppleApp_arm64 ``` 然后拖入Ghidra自动分析即可 https://sspai.com/post/87870 常用快捷键 ``` 功能键 功能 说明 反汇编界面操作 C 将汇编转为字节来查看 在反汇编界面选中按C即可 D 将字节解析为汇编 在反汇编界面按D可以将字节转为汇编查看 F 选中函数 建立或者修改函数(修改名字或者调用约定等) 在反汇编界面选中函数进行修改 L 修改标签修改名字 在反汇编界面操作,类似于IDA中的n 重命名 H 显示历史记录修改,历史记录标签 在反汇编窗口操作 CTRL + E 伪代码显示 在反汇编界面操作,就是IDA中的F5 G 地址跳转 在反汇编界面操作,跳转到你想要跳转的地址 convert 转换数据类型 在反汇编界面操作,选中常量值,邮件菜单中找到此选项. CTRL+SHIFT+G 修改反汇编语句 选中反汇编语句可以进行修改 CTRL + Z 撤销刚刚操作 IDA7.2之后才有 CTRL+SHIFT+F 交叉引用显示 与IDA中的交叉引用相似可以看到谁调用 ``` ``` 功能键 功能 说明 反汇编界面操作 L 修改名字 选中伪代码中的变量修改其名字 CTRL + L 修改类型 选中伪代码中的变量修改其类型 ``` # 动态调试 一般动态调试的过程为,复制源APP到自己目录下,使用自己的证书重签名(否则由于mac的安全机制无法对企业证书的APP 动态调试) ## 复制原文件 macOS 如果直接复制容易出现一些错误 ``` cp -rf "/Applications/Cloudflare WARP.app" . cp: /Applications/Cloudflare WARP.app/Contents/Frameworks/Sparkle.framework/XPCServices: No such file or directory ``` 这个错误是因为在尝试复制APP时,某些文件可能因为权限问题或符号链接问题无法访问。 可以使用 ditto 命令代替 cp ditto 是 macOS 上专门用于复制应用程序包(.app)的工具,能更好地处理资源文件和符号链接: ``` ditto "/Applications/Cloudflare WARP.app" "./Cloudflare WARP.app" ``` ## 自签名 在 macOS 上创建自签名代码签名证书并为应用签名的步骤如下: 创建自签名代码签名证书 使用钥匙串访问(Keychain Access): 打开 钥匙串访问(位于 /Applications/Utilities/)。 菜单栏选择 钥匙串访问 > 证书助理 > 创建证书...。 填写以下信息: 名称:例如 My Code Signing Cert。 证书类型:选择 代码签名。 勾选 “让我覆盖默认值”。 设置 有效期(如 3650 天,即 10 年)。 一路点击 继续,直到需要配置扩展密钥用法: 确保 代码签名 被勾选。 完成证书创建。  2. 签名应用程序 使用 codesign 命令: ``` codesign --force --deep --sign "My Code Signing Cert" /path/to/YourApp.app ``` --force:覆盖现有签名。 --deep:递归签名(慎用,可能需手动处理嵌套代码)。 替换 "My Code Signing Cert" 为你的证书名称,路径 /path/to/YourApp.app 为实际应用路径。 验证签名 查看签名详细信息 ``` codesign -dv --verbose=4 /path/to/YourApp.app ``` 验证签名是否有效 ``` codesign --verify -v /path/to/YourApp.app ``` 检查系统是否接受签名 ``` spctl --assess -v /path/to/YourApp.app ``` ## lldb动态调试 一般而言,macos下用的比较多的是lldb,gdb在linux上用的比较多。 ``` sudo /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/debugserver 127.0.0.1:9999 --attach 30966 ``` ## frida trace ``` frida-trace -i "*WarpApiClient*" -p 23914 ``` ``` sudo frida -l /Users/XXX/works/code/python/pythonProject/frida_script/hook_poll.js ./CloudflareWARP ``` ## nm查看符号表 ``` nm ./CloudflareWARP| grep -i device_info ``` # cheatsheet ``` /Users/steven/works/code/python/pythonProject/frida_script/hook_hyper_response.js -p 13539 nm ./CloudflareWARP | grep -i reqwest launchctl load -w /Library/LaunchDaemons/com.cloudflare.1dot1dot1dot1.macos.warp.daemon.plist launchctl unload -w /Library/LaunchDaemons/com.cloudflare.1dot1dot1dot1.macos.warp.daemon.plist launchtl stop XXXXXX ```