前言

用原始的 Frida API 写了大量的 *.js 脚本进行 安全测试。存在如下问题:

  • 在测试期间写好一些比较好用的函数,每次复用需要复制粘贴,即大量 重复化 的代码无法很好的复用
  • 长时间探索过程积攒了众多代码,有些可能不需要,但需要保留,万一后续要用呢

本科学过一点点 前端的我,思考能否将 nodejs, npm, typescript 等模块化的 思想引入到 Frida 脚本编写中

环境准备

  • git : Download
  • nodejs(自带npm): 下载
  • frida
  • 支持 package.json 的智能IDE,如 WebStorm,VSCode

frida 的 nodejs 工程(支持ts)

官方git链接: https://github.com/oleavr/frida-agent-example

$ git clone git://github.com/oleavr/frida-agent-example.git
$ cd frida-agent-example/
$ npm install
$ frida -U -f com.example.android --no-pause -l _agent.js

分析示例nodejs工程

package.json

{
  "name": "frida-agent-example",
  "version": "1.0.0",
  "description": "Example Frida agent written in TypeScript",
  "private": true,
  "main": "agent/index.ts",
  "scripts": {
    "prepare": "npm run build",
    "build": "frida-compile agent/index.ts -o _agent.js -c",
    "watch": "frida-compile agent/index.ts -o _agent.js -w"
  },
  "devDependencies": {
    "@types/frida-gum": "^16.2.0",
    "@types/node": "^14.14.2",
    "frida-compile": "^10.0.0"
  }
}

补充:package.json 文件结构说明,具体见参考2

  • nameversion: 项目名称、版本(遵守“大版本.次要版本.小版本”的格式)
  • scripts: 指定了运行脚本命令的npm命令行缩写,如 build 指定了运行 npm run build 时,要执行 frida-compile agent/index.ts -o _agent.js -c 命令
  • dependencies, devDependencies: 指定了项目 运行/开发 所依赖的模块
  • bin:指定各个内部命令对应的可执行文件的位置(见例子就明白了)
  • main: 指定了加载的入口文件,require('moduleName') 就会加载这个入口文件

node scripts 示例:

"scripts": {
    "preinstall": "echo here it comes!",
    "postinstall": "echo there it goes!",
    "start": "node index.js",
    "test": "tap test/*.js" 
}

分析结果:

  • 该项目依赖于 frida-gum, node, frida-compile 三个模块
  • 入口文件为 agent/index.ts(可以自己改)

package.json自动编写方法

  • npm init: 命令交互形式生成基本的文件
  • npm install xxx --save[-dev]: 若模块不在文件中,可以单独安装,并使用save参数将其写入文件

拓展思考:模块化是一种趋势,适应趋势才能站在巨人的肩膀上做事情,减少重复劳动。

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "lib": ["esnext"],
    "allowJs": true,
    "noEmit": true,
    "strict": true,
    "esModuleInterop": true
  }
}

描述typescript 属性,作用如下:

  • 表示当前目录是 TypeScript 项目的根目录
  • 指定了用来编译这个项目的根文件和编译选项
  • "compilerOptions"可以忽略,具体看 参考4
  • target: 指定ECMAScript目标版本
  • lib: 编译过程中需要引入的库文件的列表
  • allowJs: 允许编译javascript文件
  • noEmit: 不生成输出文件
  • strict: 启用所有严格类型检查选项
  • esModuleInterop: 为了兼容 旧的 js项目,对于 import 转译规则发送变化

前端的坑~~ ??要不要入?

初始化项目&编写脚本

$ npm install

这期间会根据 package.json 中的 依赖自动安装到当前目录的 node_modules 目录

  • -g 全局安装

安装后,在vscode编写 tsjs 写代码都可以 自动补全

frida-compile——ts 编译为 可被 frida 加载的 js 文件

直接把 ts 文件作为 脚本 输入到 frida,不会被识别,需要通过 “compile”

一般编译 ts 到 js 需要借助 tsc

tsc是啥?

  • 通过 npm install -g typescript 安装typescript
  • tsc xx.ts 输入同名的 js 文件,这样就可以 用 ts 写 js 应用

而 ES6+ 等方便的特性 同样不会被 frida 的 duk 引擎识别,这个问题可以通过 --runtime=v8 解决,但 不支持ts 问题 在 frida 如何解决?

那就是 frida-compile,这个模块很好的解决了我们目前的痛点:

  • 默认使用的 duk 不支持最新的 EMCAScript 特性
  • 单个 js 难以管理大型项目
  • 可以将 ts 或 es6+ 转译为 Duktape 可用的 ES5 语法

另外,它还可以:

  • 支持 Browserify 的打包
  • 支持 ES6 modules, source map 和 uglify 代码压缩
  • 甚至可生成 Duktape 字节码
  • 支持使用 require 或 es6 module 引用第三方 npm 包

frida-compile 本身就是一个 npm 包,细心点可以发现我们 clone 的 nodejs 项目中依赖于 这个包,script 命令中 也使用了 这个包。

单文件测试

package.json 中定义的 main 所在的文件中,直接按照 传统的 单个 js 思想编写,如

import { log } from "./logger";
let a = "Hello Frida with TypeScript";
log(a)
Java.perform(function () {
    log("Java Hook")
    let javaString = Java.use("java.lang.String")
    let bStr = javaString.$new("hello_world").getBytes()
    debugger
    log(bStr)
})

在一个 terminal 运行如下命令:

$ npm run watch

这条命令会自动监视文件修改,并自动编译 index.ts 文件,输出 _agent.js

再另一个 terminal 运行:

$ frida -U -f com.philips.lighting.hue2 -l .\_agent.js --runtime=v8 --debug

这样即使在 ts 小幅度修改代码, _agent.js 也会随即修改,frida 也会同步的应用新脚本,从而实现热更新的效果。

模块化思想实战

在此之前,需要了解 TypeScript, ES6+ 等模块化编程思想

问题解决

frida-compile 深入

编译由一个或多个Node.js模块组成的Frida脚本。

$ frida-compile -h
Usage: compile [options] <module>

Options:
  -V, --version             output the version number
  -o, --output <file>       set output <file>
  -w, --watch               watch for changes and recompile
  -b, --bytecode            output bytecode
  -S, --no-sourcemap        omit sourcemap
  -c, --compress            compress using UglifyJS2
  -a, --use-absolute-paths  use absolute source paths
  -h, --help                display help for command

示例用法:

  • frida-compile agent -o agent.js
  • build: frida-compile agent/index.ts -o _agent.js -c
  • watch: frida-compile agent/index.ts -o _agent.js -w

编译后的js如何调试

直接用 Chrome调试,部分语义信息还是可以还原的,同时还可以发现 如下提示:

frida 对应python 版本 frida nodejs_typescript


几个关键点:

  • 源映射
  • 将关联的文件添加到文件树
  • 作用:将已解析的源文件 作为 常规 JavaScript 进行调试