Skip to content

macos can't stub #49

Description

@coolxv

Apple silicon enables memory protection for all apps, regardless of whether they adopt the Hardened Runtime. Intel-based Mac computers enable memory protection only for apps that adopt the Hardened Runtime.

理解 x86-64 上的命令
printf '\x07' | dd of=test_function bs=1 seek=160 count=1 conv=notrunc

这个命令的作用是,将十六进制值 0x07 这一个字节,写入到文件 test_function 从开头数第 160 个字节的位置上,并且不截断文件。

为什么是 \x07?
在 macOS/iOS 的虚拟内存管理中,内存保护权限是用位掩码表示的:

VM_PROT_READ = 0x01 (可读)
VM_PROT_WRITE = 0x02 (可写)
VM_PROT_EXECUTE = 0x04 (可执行)
0x07 正好是 0x01 | 0x02 | 0x04 的结果,即 可读、可写、可执行 (RWX) 的权限。

为什么是 seek=160?
mprotect 系统调用无法将一个内存段的权限提升到超过其在可执行文件(Mach-O 格式)中定义的 最大权限 (maxprot)。为了让 mprotect 能够成功赋予 RWX 权限,你必须先修改 Mach-O 文件头中对应段的 maxprot 字段。

在 x86-64 架构下,一个典型的 Mach-O 文件布局中:

mach_header_64 占用 32 字节。
接下来是一系列的加载命令 (Load Commands)。
通常第一个是 LC_SEGMENT_64 (__PAGEZERO),第二个是 LC_SEGMENT_64 (__TEXT)。
在 LC_SEGMENT_64 结构体中,maxprot 字段的偏移量是 56 字节。
对于一个常见的 x86-64 程序,__TEXT 段的加载命令 (LC_SEGMENT_64) 的起始位置 + maxprot 的偏移量正好是 160 字节。

计算:32 (header) + 72 (size of __PAGEZERO command) + 56 (offset of maxprot) = 160
所以,这个命令精确地定位到了 __TEXT 段的 maxprot 字段,并将其值修改为 0x07。
在 ARM64 (Apple Silicon) macOS 上的操作
在 ARM64 架构的 macOS 上,核心原理完全相同:你需要找到 __TEXT 段(或者你希望修改的其他段)的 maxprot 字段,并将其值修改为 0x07。

但是,你不能再使用固定的 seek=160。

因为 ARM64 架构的 Mach-O 文件头部布局、加载命令的顺序或大小可能与 x86-64 的不同,导致 maxprot 的文件偏移量发生变化。硬编码偏移量是不可靠且危险的。

有两种推荐的方法来完成这个操作:

方法一:手动计算偏移量 (不推荐,但能解释原理)
这是一种“手术刀”式的方法,需要你手动查找偏移量。

使用 otool 查看加载命令
首先,你需要分析你的 ARM64 可执行文件,列出所有的加载命令及其大小。

Bash

otool -l ./your_arm_binary
查找并计算
在 otool 的输出中,找到 LC_SEGMENT_64 且 segname 为 __TEXT 的那一段。然后,把在它之前所有 Load command 的 cmdsize 加起来。

例如,你可能会看到这样的输出:

./your_arm_binary:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777228          0  0x00           2    26       3240 0x00218085
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __PAGEZERO
---
Load command 1
      cmd LC_SEGMENT_64
  cmdsize 744
  segname __TEXT
     vmaddr 0x0000000100000000
     vmsize 0x0000000000004000
    fileoff 0
   filesize 16384
    maxprot 0x00000005  <-- r-x (我们需要修改这里)
   initprot 0x00000005  <-- r-x
     nsects 4
      flags 0x0
---
计算最终偏移量
maxprot 在 LC_SEGMENT_64 结构体内的偏移量仍然是 56 字节。

最终偏移量 = 32 (Mach-O Header 大小) + (所有在 __TEXT 段之前的 cmdsize 之和) + 56

以上面的例子为例,__TEXT 段之前只有一个 LC_SEGMENT_64 (__PAGEZERO),其 cmdsize 为 72。

计算:32 + 72 + 56 = 160
在这个特定的例子里,偏移量恰好还是 160。但这纯属巧合,你的二进制文件可能会有 LC_UUID 等其他命令,导致偏移量不同。你必须为你自己的文件计算一次。
执行 dd 命令
一旦你计算出了正确的偏移量 <offset>,就可以执行 dd 命令了。

Bash

# 假设你计算出的偏移量是 216
printf '\x07' | dd of=./your_arm_binary bs=1 seek=216 count=1 conv=notrunc
方法二:使用专业工具 (强烈推荐)
手动计算偏移量很容易出错。使用专门解析 Mach-O 文件的工具会更安全、更简单。jtool2 就是一个非常强大的选择。

安装 jtool2
你可以从其官网下载或者使用其他方式安装。它是一个广泛用于 iOS/macOS 逆向工程的工具。

使用 jtool2 修改权限
jtool2 提供了一个专门的命令来修改 maxprot,你无需关心任何偏移量。

Bash

# 将 __TEXT 段的最大权限修改为 rwx
jtool2 --replacemaxprot rwx ./your_arm_binary __TEXT
这条命令会自动找到 __TEXT 段并精确地修改 maxprot 字段。你也可以用它修改其他段,比如 __DATA。

至关重要的一步:重新签名
在现代 macOS 上,无论是 x86-64 还是 ARM64,只要你修改了可执行文件的二进制内容,它的代码签名就会失效。一个签名失效的应用是无法运行的。

因此,在用 dd 或 jtool2 修改完文件后,你必须对其进行重新签名。对于本地开发和测试,可以使用 ad-hoc 签名:

Bash

# 使用 ad-hoc 签名,强制覆盖原有签名
codesign -s - --force ./your_arm_binary
如果你需要保留原始的 entitlements 等信息,可以使用更完整的命令:

Bash

codesign -s - --force --preserve-metadata=entitlements,requirements,flags,runtime ./your_arm_binary
总结
要在 ARM 架构的 macOS 上实现同样的目标,请遵循以下步骤:

目标: 修改可执行文件中目标段(如 __TEXT)的 maxprot 字段为 0x07 (RWX)。
方法 (推荐):
使用 jtool2 工具,它能自动解析文件格式。
执行命令: jtool2 --replacemaxprot rwx ./your_arm_binary __TEXT
必须步骤:
修改后,文件签名会失效。
必须使用 codesign 进行 ad-hoc 重新签名: codesign -s - --force ./your_arm_binary
这个流程比依赖固定偏移量的 dd 命令更加健壮和可靠,适用于任何 Mach-O 文件,无论其具体头部布局如何。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions