昨天的两个版本让 pattern 成为 Agent 和工具中的一等公民。本次发布着重于共享:frida-compile 现在可以构建库,因此 npm 包能够把 pattern 与 TypeScript 一同发布,任何人都可导入使用。还要感谢 @hsorbo,Swift ApiResolver 现已能在 Linux 上运行,Windows 支持也紧随其后。

库

假设你编写了一些 pattern 及其辅助函数,并希望发布给其他 Agent 使用。此前必须先运行一次 frida-compile 获取 pattern 类型定义,再用 tsc 生成包,同时还要仔细配置 tsconfig.json,使其与 Frida.Compiler 解析导入的方式一致。对同一份源码运行两个编译器实在多余。

使用 frida-compile –library 后,Frida.Compiler 会完成全部工作。下面是一个在游戏中查找玩家的包中的 lib/index.ts:

import { Player } from "./patterns/player.hexpat";

export function findPlayers(range: RangeDetails, health: number): Player[] {
    const pattern = Player.pattern({ health });
    return Memory.scanSync(range.base, range.size, pattern)
        .map(({ address }) => Player.at(address));
}

export function describe(player: Player): string {
    const { x, y, z } = player.position;
    return `health=${player.health} lives=${player.lives} at (${x}, ${y}, ${z})`;
}

旁边是 lib/patterns/player.hexpat:

#pragma abi native

struct Player {
    u32 health;
    u32 lives;
    Vec3 position;
};

struct Vec3 {
    float x;
    float y;
    float z;
};

源码就只有这些。package.json 不需要 typescript、@types/frida-gum 或 tsconfig.json,只需 npm 上的 frida-compile:

{
  "name": "frida-module-example",
  "version": "1.1.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "files": ["/dist"],
  "type": "module",
  "scripts": {
    "prepare": "frida-compile --library lib/index.ts -o dist"
  },
  "devDependencies": {
    "frida-compile": "^19.1.0"
  }
}

执行构建后得到:

$ frida-compile --library lib/index.ts -o dist
$ find dist -type f | sort
dist/index.d.ts
dist/index.js
dist/index.js.map
dist/patterns/player.hexpat
dist/patterns/player.hexpat.d.ts

每个源文件对应一个模块和一个声明文件,pattern 会一并复制,其生成的类型定义也放在旁边。声明内容正如你所期待:

import { Player } from "./patterns/player.hexpat";
export declare function findPlayers(range: RangeDetails, health: number): Player[];
export declare function describe(player: Player): string;

请注意,pattern 以源码形式发布,而不是 Frida.Compiler 转换后的 JavaScript。这是有意为之:生成的代码依赖使用方构建所提供的运行时,并可跨包去重;其他 pattern 以及 Luma 等工具也能导入它;而且 pattern 语言比我们生成的代码稳定得多。使用方的 Frida.Compiler 会在构建过程中用几毫秒完成编译,并缓存结果。

接下来看看使用方。执行 npm install frida-module-example 后,Agent 既可使用辅助函数,也可直接使用 pattern:

import { findPlayers, describe } from "frida-module-example";
import { Player } from "frida-module-example/dist/patterns/player.hexpat";

const arena = Memory.alloc(Player.size * 2);
const alice = Player.at(arena);
alice.health = 100;
alice.lives = 3;
alice.position.z = 1.5;

for (const player of findPlayers({ base: arena, size: Player.size * 2 } as RangeDetails, 100))
  console.log(describe(player));
$ frida -q -p 0 -l agent.ts
health=100 lives=3 at (0, 0, 1.5)

再补充一些细节:

  • -w 会在编辑时持续更新库;同时迭代包和应用时,它与 npm link 配合得很好。
  • source map 会生成在各模块旁边,并内联源码,因此无需发布 lib/,使用方也能获得真实的堆栈跟踪。传入 -S 可不生成它们。
  • 错误报告方式与 Agent 构建相同,路径相对于项目;只要存在错误,就不会写入任何内容:
lib/broken.ts:1:14 - error TS2322: Type 'string' is not assignable to type 'number'.
compilation failed
  • 输出会在公共目录下镜像源码树,因此 lib/index.ts 会生成到 dist/index.js。如果 tsconfig.json 设置了 rootDir,则会遵循该设置。
  • 此功能对应 frida-core 中的 Compiler.build_library() 和 Compiler.watch_library(),因此所有语言绑定都可使用;frida-compile 则同时在 frida-tools 和 npm 包中公开它。

Swift

Swift ApiResolver 昨天刚获得查找类型、协议及其遵循关系的能力,但此前只能在 Apple 平台工作。它会查找 libswiftCore.dylib,从 libsystem_malloc 借用 free(),并按 Mach-O 名称匹配元数据节,因此 Linux 上的每次查询都会以“unsupported Swift runtime”失败。

Håvard 修复了所有这些问题:解析器现在按平台确定 Swift 核心库和元数据节名称;没有 libsystem_malloc 时,从 C 运行时获取 free();还为每个实例分别保存名称还原器,因为 Linux 上的 dlclose() 会取消映射 libswiftCore,缓存的指针可能比它存活更久。他还让 Swift 测试真正运行在 Linux CI 上——此前它们一直被静默跳过——并将跳过正确报告为跳过,而不是通过。非常感谢 Håvard!

有了这些基础,Windows 支持只是很小的一步:使用 swiftCore.dll、.sw5* 节,并通过 UCRT 释放还原后的名称。Windows 有个值得注意的特点:swiftrt.obj 会在每个元数据节两端放置清零的起止标记,解析器现在会跳过它们。因此,只要加载了 Swift 运行时,new ApiResolver(‘swift’) 及其 functions:、types:、protocols: 和 conformances: 查询在各平台都能以相同方式工作。

结语

尽情体验吧!

变更日志

  • compiler:通过 Compiler.build_library() 和 Compiler.watch_library() 新增库构建,并以 frida-compile –library 公开。(上文已介绍。)
  • swift-api-resolver:新增 Linux 支持,按平台确定 Swift 核心库和元数据节名称,并为每个解析器实例保存名称还原器。感谢 @hsorbo!
  • swift-api-resolver:新增 Windows 支持,包括跳过 swiftrt.obj 生成的清零节标记。
  • swift-api-resolver:在测试中查找 Swift 工具链,使其在 GitHub 的 Ubuntu runner 上真正运行,而不是静默跳过。感谢 @hsorbo!
  • gumjs:解析指令操作数时处理所有 arm64 向量排列。v0.b[0] 等单通道和窄排列此前会进入不可达的默认分支;编译时移除断言后会导致进程崩溃。感谢 @hsorbo!
  • windows:修复节名称和大小。恰好八个字符的名称没有 NUL 终止符;大小现在使用加载器所见的虚拟大小,而不是按文件对齐的原始大小。
  • swift:为库构建重新生成绑定。