首页
社区
课程
招聘
[原创]一种基于 Rosetta2 二进制翻译的 Syscall 拦截方法
发表于: 2小时前 68

[原创]一种基于 Rosetta2 二进制翻译的 Syscall 拦截方法

2小时前
68

一种基于 Rosetta2 二进制翻译的 Syscall 拦截方法

目录

引言

随着 Wine 等兼容技术的持续发展,大量 Windows 游戏与应用程序已能流畅运行于 Linux 平台。然而,将这一生态迁移至 MacOS 时仍面临显著障碍。

由于 MacOS 内核缺失 Seccomp 机制,Wine 无法对进程的系统调用进行内核级拦截。这导致那些绕过 Windows API 直接执行 Syscall 指令的程序在 MacOS 上触发错误的系统调用号,最终引发崩溃或行为异常。

针对这一平台差异,本文提出了一种创新的解决方案,即利用 Apple Silicon 架构下的 Rosetta 2 二进制翻译器,在指令转换层面实现对 x86 系统调用的捕获与重定向,从而在用户态构建起类 Seccomp 的拦截能力。

技术路线

传统方案

在 Linux 平台上,Wine 依赖 BPF 在内核态设立"陷阱门"。当进程执行 Syscall 指令时,内核先检查过滤器规则,若为 Windows 调用则返回用户态交由 Wine 模拟。

然而,macOS 的 XNU 内核先天缺失 Seccomp 机制,且出于安全策略禁止第三方内核扩展介入系统调用路径。

Wine 自身的用户态 Hook 仅能覆盖通过 ntdll.dll 导出的接口,对于直接内联汇编执行 Syscall 的指令流无能为力。这类指令会直接陷入内核,触发错误的 macOS 系统调用号,导致出现异常。

Rosetta2 拦截

Apple Silicon 架构下的 Rosetta2 作为 x86_64 到 ARM64 的动态二进制翻译器,强制所有 x86 指令经过其翻译管道。

这一架构特性创造了独特的干预窗口:无论原始 x86 代码如何编写,都必须经过 Rosetta2 的解码、翻译,最终生成 ARM64 代码才能执行。

并且 Rosetta2 在用户态运行,其翻译后的指令、内部数据结构、乃至生成的本地代码均处于用户态可访问的内存空间。

这为我们提供了在不触碰内核的前提下,修改其内部流程以拦截 Syscall。

Rosetta2 二进制翻译原理

Rosetta2 其大致分为三个部分包括 oahd / oahd-helperruntimelibRosettaAot

oahd / oahd-helper

其中oahd为 Rosetta2 守护进程,其负责管理与创建 AOT 文件。

在启动 x86 程序时,翻译引擎会通过 XPC 向oahd请求 AOT 文件,此时oahd会检查/var/db/oah/*目录下是否存在对应程序的 AOT 文件,若 AOT 不存在,其会调用oahd-helper并传入文件句柄以创建对应的 AOT 文件。

oahd-helper中 AOT 流程大致分为以下几个步骤

读取 Mach-O 文件 -> 判断文件类型 -> 解析区段等信息 -> 转换为rosetta::librosetta_aot::ModuleResult -> rosetta::librosetta_aot::translate识别函数翻译并构建映射 -> 获取翻译后的 Data -> 应用各种 Fixup -> 构建新的 Mach-O 文件 -> 映射符号 -> 签名输出

其对程序进行了静态分析并调用了libRosettaAot进行 AOT

runtime

runtime为 Rosetta2 的运行时代码,在启动 x86 程序时系统首先加载runtime,再有runtimeoahd请求 AOT 文件。

runtime在请求到 AOT 文件后会解析验证 AOT 文件与映射信息并将其加载到内存之中,并移交控制权到 AOT 代码。

runtime 也大致由三个部分构成,分别是AOT LoaderJIT EngineRuntime Helper

其中AOT Loader用于验证签名并加载之前所属的 AOT 文件,JIT Engine则用于动态解码翻译某些之前静态分析所漏掉的函数,如:动态链接库中的函数、ShellCode、由于不透明谓词未能分析到的函数。

Runtime Helper则用于为翻译后的代码提供功能支持,其中 Syscall 其会在翻译过程中被转换为call helper_syscall类似的代码,Syscall 的功能将由Runtime Helper提供,其将 x86 的调用转换为符合当前系统的调用以模拟 Syscall。

此外JIT Engine也由Runtime Helper触发,其辅助函数中包含helper_resolve_address其在第一次调用某个函数时,会通过此辅助函数获取欲调用函数翻译后的地址。

此函数调用后runtime将会从代码映射红黑树中搜索欲调用的函数是否被翻译(AOT 中文件中的映射也将被加载),若目标函数未被翻译(即未被 AOT 或首次调用),runtime则会调用libRosettaAotrosetta::librosetta_aot::ir_create函数进行翻译(与 AOT 不同这里使用的另一个更加精细的翻译函数),翻译后指令将被JIT Engine加载到内存之中。

JIT Engine辅助函数大致流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uint64_t helper_resolve_address(Context context, uint64_t x86_address, uint64_t stubs_sh_address) {
    Translator translator = context->translator;
    uint32_t type = context->type;
    uint64_t arm_addrress;
 
    // 寻找已翻译的函数中是否包含当前欲调用函数
    if (find_translation_in_tree_x86(translator, x86_address, &arm_addrress, 0, stubs_sh_address, type)) {
        return arm_addrress;
    } else {
 
        // 如果没有找到就现场进行翻译
        return jit_translation_for_x86_address(translator, x86_address, 0, 0, stubs_sh_address, type);
    }
}

Syscall 辅助函数大致流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 参数与寄存器映射一致
uint64_t __usercall helper_syscall@<X0>(
    uint32_t svc_code@<W0>,
    uint64_t rcx@<X1>,
    uint64_t rdx@<X2>,
    uint64_t rbx@<X3>,
    uint64_t rsp@<X4>,
    uint64_t rbp@<X5>,
    uint64_t rsi@<X6>,
    uint64_t rdi@<X7>,
    uint64_t r8@<X8>) {
     
    switch(svc_code) {
        // 调用转换具体实现
    }
 
}

libRosettaAot

libRosettaAot 为核心翻译引擎,其中包含了控制流提取,基本块/指令翻译,映射/重定位等多个功能。

根据其内部残留字符串可知其支持 x86 的下列指令集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Translator.cpp                     普通 x86 指令集
TranslatorAes.cpp                  x86 aes 加速指令集
TranslatorArithmetic.cpp
TranslatorBitTest.cpp
TranslatorFlags.cpp
TranslatorFlagsSaveRestore.cpp
TranslatorHelpers.cpp
TranslatorMulDiv.cpp
TranslatorShifts.cpp
TranslatorSseConversions.cpp       x86 sse 加速指令集
TranslatorSseFpComparisons.cpp
TranslatorSseMovesShuffles.cpp
TranslatorSseShifts.cpp
TranslatorSseStringOps.cpp
TranslatorStringOps.cpp
TranslatorX87.cpp                  x87 浮点数指令集

其内部寄存器映射(来自ProjectChampollion):

x86_64 arm64
RAX x0
RCX x1
RDX x2
RBX x3
RSP x4
RBP x5
RSI x6
RDI x7
R8 x8
R9 x9
R10 x10
R11 x11
R12 x12
R13 x13
R14 x14
R15 x15
XMM{0--15} q{0--15}

拦截思路

基于以上翻译原理我们可以使用以下思路对特定地址或特定调用号的 Syscall 进行拦截。

钩挂helper_resolve_address -通过 Signal 传递 JIT 地址映射到 Wine 层-> Wine 根据 x86 地址获取模块并判断是否是需要拦截,若需要拦截则将转换后地址写入环境变量 -环境变量-> 钩挂helper_syscall回溯调用栈与环境变量判断是否需要拦截,若需拦截则通过 Signal 抛出异常 -异常-> 最后 Wine 层收到异常对 Syscall 进行替换拦截并跳回

鸣谢

ProjectChampollion
rosettax87


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回