在 Rosetta 2 中打包恶意软件
摘要
本文基于 SummerCon 2026 的一场演讲,解释了如何使用 Apple 的 Rosetta 2 翻译层来打包恶意软件,涵盖了提前编译和即时编译技术。
<p><a href="https://lobste.rs/s/ppvyr1/packing_malware_rosetta_2">评论</a></p>
查看缓存全文
缓存时间: 2026/08/20 00:36
# 使用Rosetta 2打包恶意软件
来源:https://kernelkennel.com/blog/summercon2026-rosetta/
## https://kernelkennel.com/blog/summercon2026-rosetta/#tl-dr
**摘要**
如需演示文稿,我已将其附在本页末尾。你可以配合SummerCon的会议录像(https://www.youtube.com/watch?v=MAKB4AE7vc0)观看。我目前对实习机会及网络安全领域的同行交流很感兴趣——欢迎随时联系(https://kernelkennel.com/about/)!
## https://kernelkennel.com/blog/summercon2026-rosetta/#introduction
**引言**
Rosetta 2 是一种转译层,允许macOS在Apple Silicon上运行Mach-O x86_64二进制文件。简而言之,它是一个转译工具,尽可能通过静态编译转换代码,并通过`oahd_helper`缓存结果;对于间接调用等分支情况,则交由JIT编译处理。在下一个macOS大版本更新中,Rosetta 2将逐步被淘汰——新的工具包括用于游戏审查/移植的GPTK(Game Porting ToolKit)以及Linux相关的组件如`VZVirtualMachine`。尽管架构未知,但这个工具曾是M系列芯片平滑过渡的重要助力!
## https://kernelkennel.com/blog/summercon2026-rosetta/#understanding-how-rosetta-2-ends-up-giving-us-aarch64
**理解Rosetta 2如何提供AARCH64支持**
### https://kernelkennel.com/blog/summercon2026-rosetta/#ahead-of-time
**预编译机制**
如前所述,我们首先理解Rosetta 2如何利用预编译和即时编译保持MachO的高效运行。通过`oahd-helper`守护进程,系统会检查所有针对x86_64目标的执行调用,计算哈希值并与`/var/db/oah/`路径下的已知缓存比对。若命中缓存则直接使用;若未命中,则尝试进行尽可能一对一的x86_64到AARCH64翻译,并将结果缓存至`exec.aot`文件。对于可执行文件所需的所有依赖框架也会执行同样操作。所有翻译后的二进制文件将被存放在特定目录中。若无法翻译某代码块,系统会生成存根分支以便后续运行时解析——这与dyld通过`LAZY_SYMBOL_POINTERS`段解析动态库调用的机制类似。由于ARM64与x86架构差异巨大且需要维护状态,此过程会产生大量冗余信息。我们还需将原始x86指令附加到进程内存中,以便后续JIT翻译时参考。首次启动的性能优化细节可参阅我之前引用的dougallj博客(https://dougallj.wordpress.com/2022/11/09/why-is-rosetta-2-fast/)。
以下为AOT部分示例:
### https://kernelkennel.com/blog/summercon2026-rosetta/#just-in-time
**即时编译机制**
获得AOT缓存文件后,系统会将其存储在SIP保护的安全位置。由于间接调用已被存根化,在程序运行时需要解析所有存根。这包括`extern`调用、x86_64中的间接分支以及其他无法确定性解析的指令。Rosetta 2的`runtime`组件会查询之前提到的原始指令,通过红黑二叉树遍历解析新指令,并将其传递给MacBook上负责执行代码的TEXT段!
JIT部分示例:
### https://kernelkennel.com/blog/summercon2026-rosetta/#what-else-is-in-rosetta-2
**Rosetta 2的其他组件**
我们还提供其他工具来模拟AARCH64缺失的特性:
- 为RIP相对寻址分配的Rosetta返回栈
- 为线程本地存储分配的Rosetta线程信息
- 其他未深入研究的组件,可通过mmap查看!
通过LLDB调试时无法看到最终执行的指令,因为调试器会模拟相同的x86环境。这帮助从Intel Mac迁移的开发者在Apple Silicon上继续调试应用。AOT的`shared_cache`与`runtime`协同工作,解析所有函数起始点和代码块以弥补优化不足——该机制尚未涉及执行原语层面。malloc提供了多种内存保护机制,防止x86与AARCH64内存交叉污染。
## https://kernelkennel.com/blog/summercon2026-rosetta/#gamehacking-over-rosetta-2
**通过Rosetta 2实现游戏修改**
此内容曾在2025年Ruxmon墨尔本会议上详细讲解。灵感来源于Jai Verma关于Intel MacOS上AssaultCube游戏修改的博客(https://jaiverma.github.io/blog/ac-hack)。我针对Apple Silicon的Rosetta 2版本修改方案已发布于2025年博文(https://kernelkennel.com/blog/ruxmon-rosetta2/),此处不再赘述具体利用方式。简而言之:
1. 获取生命值原始实例的偏移量
2. 编写作弊程序遍历指针链获取玩家生命值
3. 解引用指针获取数值并设置为无敌模式
## https://kernelkennel.com/blog/summercon2026-rosetta/#setting-up-the-game-cheat
**配置游戏修改程序**
我们可以通过`DYLD_INSERT_LIBRARIES`环境变量手动添加库(类似Linux的`LD_PRELOAD`),强制dyld优先加载指定库。为何很少见人使用该变量?因为dyld会检查库的代码签名——未签名的库将被拒绝加载。**但Rosetta 2允许运行未签名二进制文件,包括在已签名进程中加载未签名库!**
## https://kernelkennel.com/blog/summercon2026-rosetta/#apple-silicon-security-paradigms-in-rosetta-2
**Rosetta 2中的Apple Silicon安全模型**
在Apple Silicon的iOS和许多macOS场景中,代码签名已成为强制要求。> 请留意`LC_LOAD_DYLIB`/`LC_LOAD_WEAK_DYLIB`命令...(暗示!)
**在此情况下,所有库和可执行文件都需要有效签名(无论是ad-hoc还是正式签名)才能运行**。DYLD将执行检查,所有Mach-O文件都包含签名特殊段。这些工具与Gatekeeper的触发机制协同,用户需额外验证可执行文件的安全性。根据Apple关于Rosetta 2处理未签名x86_64代码的文档,与常规签名检查方式不同:Rosetta 2加载AOT段时不需要映射原始x86的加载命令表。
## https://kernelkennel.com/blog/summercon2026-rosetta/#attack-surfaces-in-rosetta-2
**Rosetta 2的攻击面**
通过分析Rosetta 2的代码转译过程及规避机制,可识别以下基础攻击面:
1. 通过`dlopen`和库注入代码
2. 利用AOT缓存特性的函数存根化漏洞
3. 理解Rosetta 2如何绕过Gatekeeper的常规触发场景
4. 从内核`uname`获取的架构偏好决定x86/ARM执行路径
- 包括对MAP_JIT等特性的检查,以及待研究的shellcode相关机制
5. Rosetta 2维持x86_64特性的配置方式(malloc标志、寄存器等)
### https://kernelkennel.com/blog/summercon2026-rosetta/#aot-checksums
**AOT校验机制**
系统通过`oahd`在执行Intel可执行文件时检查AOT缓存。Project Champollion(https://ffri.github.io/ProjectChampollion/part2/#features-of-rosetta-2-runtime)详细说明了校验和的生成特征——关键在于保持x86_64代码段的相对虚拟地址和主程序的相对路径不变。
通过反编译`oahd`(致谢Koh Nakagawa)可进一步分析:
进一步使用`otool`查看Mach-O加载命令,确认其将AOT文件加载到内存中。
### https://kernelkennel.com/blog/summercon2026-rosetta/#code-like-you-re-on-x86-64-for-malware
**模拟x86_64环境的恶意代码设计**
通过posix_spawn()创建的子进程可继承架构偏好设置。Apple未公开的寄存器(如`ACTLR_EL1`)通过设置最低位实现全存储排序,确保`str`等存储操作遵循x86_64的顺序模型,而非使用ARM的弱内存排序(详见https://support.arm.com/documentation/102336/0101/Memory-ordering)。这是Apple Silicon芯片的特殊功能,Asahi Linux对此有文档记录(https://asahilinux.org/docs/hw/cpu/system-registers/)。
### https://kernelkennel.com/blog/summercon2026-rosetta/#examples-of-this-in-north-korean-malwares
**朝鲜恶意软件案例分析**
谷歌云安全威胁情报团队分析了Rosetta 2相关的入侵事件(https://cloud.google.com/blog/topics/threat-intelligence/rosetta2-artifacts-macos-intrusions),其中PoolRAT通过下载统一二进制文件,故意执行x86_64版本以利用Rosetta 2的缓存特性——尽管我们知道它并未处理所有内容。值得注意的是,由于`oahd`会省略文件名,统一日志默认无法捕获PoolRAT的归因信息,需自定义配置文件才能检测。这说明该特性可用于强制JIT转换。动态执行特性(如`(void*)(void*)`)意味着需要动态分析才能观察Rosetta 2的实际输出——以PoolRAT为例,其会自我删除,因此仅凭.aot文件无法获取完整代码样本。
## https://kernelkennel.com/blog/summercon2026-rosetta/#setting-up-a-sneakier-attack-on-rosetta-2
**构建更隐蔽的Rosetta 2攻击**
### https://kernelkennel.com/blog/summercon2026-rosetta/#finding-a-target-on-a-dependent-library
**定位依赖库目标**
相比使用`DYLD_INSERT_LIBRARIES`强制加载任意库,更优方案是通过`LAZY_SYMBOL_POINTERS`段解析外部库调用,此技术称为函数替换。AssaultCube游戏使用SDL2库渲染图形和处理输入事件(位于应用包内)。
可通过LLDB确认`SDL_Init`位于文件的`LAZY_SYMBOL_POINTERS`段中:
进一步验证加载的框架完整名称:
### https://kernelkennel.com/blog/summercon2026-rosetta/#understanding-time-of-resolution-and-use
**解析时机与使用场景分析**
为确认函数在实际进程中的解析位置(决定替换时机),可通过断点验证`SDL_Init`的运行时机(文档表明其用于启动SDL子系统)。也可通过静态分析AOT文件,验证转换后的`SDL_Init`调用及其相对路径`@rpath/SDL2.framework/...`。
确认这是可行的替换目标!
### https://kernelkennel.com/blog/summercon2026-rosetta/#swizzling-with-your-own-function-pointers
**自定义函数指针替换技术**
函数替换即用自定义函数指针替换原始指针,并保持状态传递以执行预期调用。示例代码:
```c
// clang -arch x86_64 -dynamiclib interpose.c -o
#include <stdio.h>
#define DYLD_INTERPOSE(_replacement, _replacee) \
__attribute__((used)) static struct { \
const void* replacement; \
const void* replacee; \
} _interpose_##_replacee __attribute__ ((section("__DATA, __interpose"))) = { \
(const void*) (unsigned long) &_replacement, \
(const void*) (unsigned long) &_replacee \
};
int my_printf(const char *format, ...) {
int ret = printf("Hello from interpose... uh oh!!!\n");
return ret;
}
DYLD_INTERPOSE(my_printf, printf);
```
```
$ DYLD_INSERT_LIBRARIES=./interpose.dylib ./hello
Hello from interpose... uh oh!!!
```
二进制文件中的`__DATA, __interpose`段存储了函数替换信息,通过指令编译器保留代码(避免因分析后变空而被剔除)。利用此技术可创建自定义dylib注入AssaultCube,聚焦替换过程并保持游戏启动状态:
### https://kernelkennel.com/blog/summercon2026-rosetta/#ensuring-it-s-cosy-in-our-applications-directory-with-all-the-other-libraries
**确保与其它库共存于应用目录**
使用lief(https://lief.re/)或`clang`替代`DYLD_INSERT_LIBRARIES`,通过DylibCommands实现强制(必需)或可选(可缺失)加载替换dylib。通过install_name指定相对于可执行文件路径的dylib位置:
```bash
clang -arch x86_64 \
-shared \
-install_name @executable_path/../Frameworks/evil.dylib \
-F assaultcube.app/Contents/Frameworks \
-Wl, -ld_classic \
-framework SDL2 \
-o assaultcube.app/Contents/Frameworks/evil.dylib \
evil.c
```
```python
import lief
SDL2 = "assaultcube.app/Contents/Frameworks/SDL2.framework/Versions/A/SDL"
binary = lief.parse(SDL2)
binary.add(lief.MachO.DylibCommand.weak_lib(
"@executable_path/../Frameworks/evil.dylib"
))
binary.remove_signature()
binary.write(SDL2)
```
## https://kernelkennel.com/blog/summercon2026-rosetta/#ok-fine-here-s-the-calc
**最终实现效果**
## https://kernelkennel.com/blog/summercon2026-rosetta/#conclusion-and-afterthoughts
**结论与思考**
Rosetta 2通过预编译转译完成主体工作,仅需即时编译处理必要部分的极致效率令人惊叹。该流程减少了传统应用运行时的工作量,同时确保JIT所需的状态维护具备高效性。这种两阶段转译设计优先考虑冗余性而非简洁性/优化度,使我们能够跨架构维护状态并添加新特性。内存排序、RIP相对寻址等优化使Rosetta 2在游戏中表现出色——我甚至在《激战2》中享受流畅体验!
相似文章
字节码虚拟机在意外场景中的应用 (2024)
本文探讨了字节码虚拟机的出人意料的应用,特别是Linux内核中的eBPF以及编译后二进制文件中用于调试信息的DWARF表达式。
使用Claude Code对Android恶意软件进行逆向工程
本文详细介绍了作者如何使用Claude Code对预装在廉价Android投影仪上的恶意软件进行逆向工程,识别并禁用了可疑的软件包。
@MSFTResearch:Project Ire 分析了最新发现的恶意软件样本,并通过逆向工程确定其意图——识别出 LOTUSLIT……
微软的 Project Ire 是一个自主恶意软件分类代理,它通过行为逆向工程成功识别出一个规避了主流 EDR 工具的 LOTUSLITE 变体,且无需依赖 IOC 签名。
@jsrailton: 新发现:恶意软件开发者在其间谍软件中添加了核武器与生物武器相关文本,目的是触发LLM安全拒绝,防止AI安全扫描器分析该恶意软件。
恶意软件开发者正在其间谍软件中添加关于核武器与生物武器的文本,以触发LLM安全拒绝机制,从而防止AI安全扫描器分析恶意软件。这展示了对激进安全对齐的一种实际利用,突显了攻击者可以利用的二级盲点。
新发现的PamStealer并非典型的macOS恶意软件
研究人员发现了PamStealer,这是一种巧妙的macOS恶意软件,采用AppleScript和Rust编写的信息窃取程序进行两阶段投递,并通过PAM验证密码以保持隐蔽。