前言
用原始的 Frida API 写了大量的 *.js
脚本进行 安全测试。存在如下问题:
- 在测试期间写好一些比较好用的函数,每次复用需要复制粘贴,即大量 重复化 的代码无法很好的复用
- 长时间探索过程积攒了众多代码,有些可能不需要,但需要保留,万一后续要用呢
本科学过一点点 前端的我,思考能否将 nodejs, npm, typescript 等模块化的 思想引入到 Frida 脚本编写中
环境准备
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
name
、version
: 项目名称、版本(遵守“大版本.次要版本.小版本”的格式)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编写 ts
和 js
写代码都可以 自动补全
frida-compile——ts 编译为 可被 frida 加载的 js 文件
直接把 ts 文件作为 脚本 输入到 frida,不会被识别,需要通过 “compile”
一般编译 ts 到 js 需要借助 tsc
tsc是啥?
- 通过
npm install -g typescript
安装typescripttsc 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调试,部分语义信息还是可以还原的,同时还可以发现 如下提示:
几个关键点:
- 源映射
- 将关联的文件添加到文件树
- 作用:将已解析的源文件 作为 常规 JavaScript 进行调试