Skip to content

Latest commit

 

History

History
181 lines (133 loc) · 7.57 KB

File metadata and controls

181 lines (133 loc) · 7.57 KB

语言: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية

← DynCode 编译器

内核模式(Ring-0)DynCode 支持

-fdyncode 最初仅覆盖 ring-3 载荷(PEB walk、svc/syscall stub、libSystem 桥接)。Ring-0 载荷(Windows 驱动、Linux/Android 内核模块、macOS kext)不能简单复用 ring-3 ABI:TEB/PEB 不存在、syscall 指令是用户到内核的陷入(驱动不应发出它们)、x86_64 还需要不同的代码模型和禁用红区。

1. 核心开关:-mdyncode-context={user,kernel}

  • 用户模式(默认):维持现有 PEB / syscall stub 流水线。
  • 内核模式:
    • 禁用 SyscallStubPass(svc/syscall 在 ring-0 中无意义)。
    • 禁用 WinPEBImportPass(PEB 在用户态 TEB 中,ring-0 不可达)。
    • Windows 内核 TargetDesc 清除 TCB/PEB 读取模板和 syscall 寄存器描述。
    • 注入平台特定驱动标志(见第 3 节)。
    • 启用 KernelImportPass 进行自动解析器支持的调用点改写(第 4 节)。
    • 注入 -D__NEVERC_DYNCODE_KERNEL__=1,使用户态 shim 头(<windows.h> / <unistd.h> / 等)发出 #error,防止意外包含。

2. TargetDesc 新字段

执行级别是 describeTriple() 的附加维度:

  • TargetDesc::Level:User 或 Kernel。
  • TargetDesc::KernelImport:WindowsKernelResolverShim / LinuxKallsymsShim / DarwinXNUKextShim / None。仅 Level == Kernel 时有意义。
  • TargetDesc::KernelInjectFlags:仅内核模式的驱动标志静态数组。

为新 OS/arch 添加内核模式支持仍是"多加一行表"。

3. 每平台驱动标志差异

维度 x86_64 内核 AArch64 内核
红区 -mno-red-zone 天然不存在
代码模型 -mcmodel=kernel 复用现有 -mcmodel=small
隐式 SIMD -mno-sse -mno-sse2 -mno-mmx -mgeneral-regs-only
栈探针 继承 -mno-stack-arg-probe 相同

这些标志以"用户态基线 + 内核增量"方式叠加。

4. KernelImportPass:自动解析器注入

Ring-0 符号解析在平台间差异显著:

  • Windows:PsLoadedModuleList + RtlFindExportedRoutineByName,或 MmGetSystemRoutineAddress。
  • Linux/Android:kallsyms_lookup_name(5.7+ 需 kprobe 变通),或 ksymtab。
  • macOS:OSKextLookupKextWithIdentifier + Mach-O 符号表。

KernelImportPass 自动重写未解析的 extern 直接调用为解析器支持的间接调用。用户写普通 C;pass 处理改写。

4.1 隐式参数注入

当模块含需要解析器的 extern 直接调用时,用户代码:

void dyncode_entry(void) {
    printk("hello %d\n", 7);
}

被转换为等效于:

void dyncode_entry(void *__resolver, void *__cookie) {
    void *fn = __resolver(hash("printk"), __cookie);
    ((int(*)(const char*, ...))fn)("hello %d\n", 7);
}

用户无需手动写 resolver/cookie 参数 — KernelImportPass 在入口前端注入。纯计算载荷或已显式接受 neverc_kern_resolve_t 的载荷不触发此自动前置。

4.2 调用点优先改写

每个直接 extern 调用点就地替换:

  1. 从内部全局变量加载 resolver 和 cookie
  2. 调用 resolver(FNV1a_hash(bare_name), cookie)
  3. 将返回的 void* 转换为正确的函数指针类型
  4. 转发所有原始参数;返回结果

选择调用点改写(而非通用包装器)是为了支持 printk("x=%d", v) 等可变参数辅助函数。LLVM IR 中的通用包装器无法可靠地转发匿名可变参数。

取地址的 extern 不会被自动包装;诊断会引导用户直接调用辅助函数,或让 loader 传入预解析的函数指针。

4.3 哈希算法

FNV-1a 64 位,与 <neverc/dyncode/kernel.h> 中的 neverc_kern_hash() 一致:

uint64_t h = 0xcbf29ce484222325ull;
while (*s) { h ^= (unsigned char)*s++; h *= 0x100000001b3ull; }

pass 在哈希前剥离前导下划线(Mach-O _ 前缀)以确保跨平台一致。

4.4 Loader 调用约定

typedef int (*Entry)(void *resolver, void *cookie /*, user params... */);
Entry e = (Entry)dyncode_memory;
e(my_resolver, my_cookie);

4.5 三层防御

如果 KernelImportPass 无法完成改写:

  1. IR 层:pass 输出诊断
  2. MIR 层:DynCodeMIRPrepPass::auditExternalReferences 重审计
  3. 提取器:拒绝所有未解析重定位

4.6 内核辅助函数名表驱动诊断

Tables/KernelHelperNames.def 列出每个 OS 的常见 ring-0 辅助函数。两条诊断路径均查询此表。

5. Android 内核 vs Android Ring-3

  • Ring-3:bionic + Linux syscall ABI。复用 Linux arm64 表项。
  • Ring-0:纯 Linux 内核(GKI/KMI)。复用 LinuxKallsymsShim + KernelInjectFlags。

6. 头文件划分

模式 允许的 shim 头 拒绝的 shim 头
用户态 <windows.h> / <unistd.h> / 等 <neverc/dyncode/kernel.h>
内核态 <neverc/dyncode/kernel.h> / <string.h> / <stdlib.h> / <stddef.h> / <stdint.h> <windows.h> / <unistd.h> / 等

<neverc/dyncode/kernel.h> 暴露 neverc_kern_resolve_t、neverc_kern_hash() 和 NEVERC_KERNEL_ENTRY 宏。

7. 编写 Ring-0 DynCode

7.1 纯计算载荷

#include <neverc/dyncode/kernel.h>
NEVERC_KERNEL_ENTRY
int dyncode_entry(int a, int b) {
    int s = a * 13 + b * 7;
    for (int i = 0; i < 4; ++i) s ^= (s << 3) + i;
    return s;
}
neverc -fdyncode -mdyncode-context=kernel \
       -target aarch64-linux-gnu dyncode.c -o sc.bin

7.2 基于解析器的载荷(推荐用于真实驱动)

#include <neverc/dyncode/kernel.h>
typedef void (*PrintkFn)(const char *fmt, int a, int b);
NEVERC_KERNEL_ENTRY
int dyncode_entry(neverc_kern_resolve_t resolver, void *cookie,
                    int a, int b) {
    PrintkFn pf = (PrintkFn)resolver(neverc_kern_hash("printk"), cookie);
    if (pf) pf("neverc: %d %d\n", a, b);
    return a * 13 + b * 7;
}

7.3 宿主驱动骨架(Linux LKM 示例)

static void *my_resolver(uint64_t hash, void *cookie) {
    struct sym_table *tbl = cookie;
    for (int i = 0; tbl[i].name; ++i)
        if (fnv1a(tbl[i].name) == hash) return tbl[i].addr;
    return NULL;
}
typedef int (*Entry)(void *(*)(uint64_t, void*), void*, int, int);
Entry e = (Entry)kmem;
int result = e(my_resolver, st, 1, 2);

8. 路线图

阶段 状态 内容
内核上下文切换 + 平台标志 完成 -mdyncode-context=kernel、KernelInjectFlags、pass 门控
解析器改写 + 诊断回退 完成 KernelImportPass 自动调用点改写;MIR / 提取器回退
Ring-0 纯计算载荷 完成 8 triple 覆盖
基于解析器的载荷 完成 <neverc/dyncode/kernel.h> + 压力测试覆盖
自动 extern → 解析器改写 完成 KernelImportPass 含隐式参数注入
内核 SDK 头子集 计划中 根据真实驱动载荷需求添加