Skip to content

macos can't stub #49

@coolxv

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 文件,无论其具体头部布局如何。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions