在 Rosetta 2 中打包恶意软件

Lobsters Hottest 新闻

摘要

本文基于 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》中享受流畅体验!

相似文章