how_to_write_frida_in_ts
Marce1

习惯了用javascript写frida脚本,为什么要用typescript?

image-20250501220658462

学完javascript之后再看ts,应该就会觉得ts是一个大号的js——ts之于js就像c++之于c,前者是后者的超集,相当于js的一个大型dlc,这个dlc完全和本体兼容(ts脚本可以直接编译成js脚本),而且添加了很多特性的语言拓展:由弱类型语言变为强类型,静态类型检查在编译阶段就可以发现变量类型不匹配的错误;支持接口,方便开发与拓展;与node.js协作,有丰富的开源模块与工具,把你的脚本像项目一样管理(js其实就可以……

哇,好处一大堆。

那就让我们开始吧

环境配置

直接看网上的教程可能有点迷糊:我就写个hook脚本而已,要配这么多东西吗???

网上的教程(包括frida-agent-example)都是将ts项目构建在node.js项目之上,这一点跟着做可能会不明所以,其实是方便使用node.js的各种模块和功能(包括frida),同时又能利用ts的类型检查和其他特性。

  1. 确保你有node.js和npm环境

  2. 新建一个项目,或者文件夹

    1
    mkdir frida-agent-test
  3. 建立一个node.js项目

    1
    npm init

    终端上会让你设置一些其他的,包括程序入口,作者之类的,随你便。之后目录下会出现一个package.json,这个文件会记录一些项目的基本信息,包括你刚才的那些设置、自定义脚本命令、项目依赖之类的。

  4. 安装项目依赖——typescript

    1
    npm install typescript --save-dev

    为了保证项目的独立性,每次新建一个node.js项目时都要单独安装typescript

    某个项目可能需要使用 TypeScript 的最新特性,因此依赖较新的版本,而另一个项目可能因为兼容性问题需要使用旧版本

    –save-dev选项会将包安装到项目的node_modules目录下,同时作为开发依赖项记录到package.json中

image-20250428113830560

  1. 创建ts配置文件tsconfig.json

    1
    tsc --init

    tsconfig.json可以对ts项目中ts编译器的行为进行控制,比如指定js版本,严格类型检查之类的

    image-20250428114406918

    这个配置文件的内容多的要死,一条一条看吧

  2. 安装依赖

    1
    npm install @types/node @types/frida-gum frida-compile@10.2.1 --save-dev
    • @types/node

      • @types 是一个用于存放 TypeScript 类型定义文件的命名空间。在 TypeScript 项目中,很多 JavaScript 库本身并没有附带类型定义,这会导致 TypeScript 编译器无法理解这些库的类型信息,使用时会缺少类型检查和智能提示。而 @types 社区为众多流行的 JavaScript 库提供了类型定义文件。
      • @types/node 就是为 Node.js 标准库提供的类型定义包。当你使用 TypeScript 开发 Node.js 项目时,安装它可以让 TypeScript 编译器理解 Node.js 相关的 API 和模块的类型信息,提高开发效率和代码的可靠性。
    • frida-compile

      • Frida 是一款强大的动态代码插桩工具,可用于在运行时修改应用程序的行为。frida-compile 是 Frida 的一个编译工具,它可以帮助开发者将 Frida 脚本进行编译和打包,以便在不同的环境中使用。例如,你可以使用它来编写脚本,在运行时注入到目标应用程序中,实现调试、监控等功能。
    • @types/frida-gum

      • 同样属于 @types 命名空间,frida-gum 是 Frida 框架的一部分,它提供了一些用于动态代码插桩的基础功能和 API。@types/frida-gumfrida-gum 提供了 TypeScript 类型定义,这样在 TypeScript 项目中使用 frida-gum 时,就可以获得类型检查和智能提示等功能。像之前的Java、Interceptor、Module之类的api就由frida-gum提供

      image-20250428115146579

      如果在这一步之前你直接在项目下写ts脚本,可能编辑器会对Java、Module之类的api调用标错,但是安装了frida的依赖之后就ok了

    1. 编写脚本

      首先,将package.json中main入口修改为index.js

      然后编写一个简单的脚本:

      1
      2
      const test_str: string = 'hello world'
      console.log('I want to say ${test_str} to you')

      仿照frida-agent-example将自定义构建脚本写入package.json

      image-20250428121058328

frida-compile中参数-c是采用压缩方式编译(什么意思,我试了就是普通的编译成js呀),-w就是–watch,实时监测脚本改动并重新编译,执行编译指令后你在ts脚本上的改动会直接影响frida注入。

通过npm run就能执行自定义脚本的指令

image-20250428121235217

尝试编译

image-20250428121632083

据说这个报错是因为调用了废弃的api,我尝试将node版本降级后仍然会报错

image-20250428122229891

但是他仍然会正常编译出一个_agent.js,之后frida怎么用怎么来。我秉持着能用就行的准则,有时间再查解决方案。

ts和js有哪些不同?

难说。因为之前做的大部分hook练习都是用js写的,估计要自己摸索才能体会到ts的好。

那就把一些比较有意思的ts脚本练习记录弃置于此吧

frida入门小练习-第一题

image-20250501224616370

onClick通过VVVVV的VVVV方法检测flag是否正确,看看:

image-20250501224757212

输入的字符串通过一大坨加密再与预设字符串6f452303f18605510aac694b0f5736beebf110bf比对,只不过看到输入长度固定在5个字符以内且必须为数字就不用分析加密了,完全可以爆破eeee方法,分析返回的字符串是否与”6f452303f18605510aac694b0f5736beebf110bf”相同就能得出flag数字。

这几个加密方法都是静态方法,更加方便我们进行hook,可以直接指定类进行调用。你肯定有疑问:为什么不调用VVVV,就可以直接通过返回值判断字符串是否正确?VVVV需要调用一个OnClickListener的对象作为参数,这样的参数我们当然不好找。

这是我的第一版脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Java.perform((): void => {
const vvvvv: any = Java.use("com.kanxue.pediy1.VVVVV");
const target: string = '6f452303f18605510aac694b0f5736beebf110bf'
for(let i1: number = 48; i1<58; i1++){
for(let i2: number = 48; i2<58; i2++){
for(let i3: number = 48; i3<58; i3++){
for(let i4: number = 48; i4<58; i4++){
for(let i5: number = 48; i5<58; i5++){
let flag = String.fromCharCode(i1, i2, i3, i4, i5);
console.log(`testing: ${flag}`);
if(vvvvv.eeeee(flag).equals(target)){
console.log(`find numbers: ${flag}`);
return;
}
}
}
}
}
}
console.log('Not find!Something went wrong...')
})

由于我使用的是模拟器,爆破过多会导致脚本中断退出,但是没有关系,记住这次爆破大概中断在哪个数上,下次改改脚本接着爆就行了

但是这个脚本是爆不出来的。比对了其他writeup和我的脚本区别之后我发现了问题所在:

vvvvv.eeeee(flag)实际上返回的只是字符串吗?

是的,但是是java的字符串对象。java的字符串对象能直接和js的字符串对象比对吗?问题就在于此——hook java层的返回值可以打印出来,看起来别无二致,但是还是有区别!这样子一万年比不出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Java.perform((): void => {
const vvvvv: any = Java.use("com.kanxue.pediy1.VVVVV");
const target: string = '6f452303f18605510aac694b0f5736beebf110bf'
for(let i1: number = 50; i1<58; i1++){
for(let i2: number = 48; i2<58; i2++){
for(let i3: number = 48; i3<58; i3++){
for(let i4: number = 48; i4<58; i4++){
for(let i5: number = 48; i5<58; i5++){
let flag = String.fromCharCode(i1, i2, i3, i4, i5);
console.log(`testing: ${flag}`);
const result = Java.use("java.lang.String").$new(vvvvv.eeeee(flag)).toString();
if (result === target) {
console.log(`find numbers: ${flag}`);
return;
}
}
}
}
}
}
})

通过 Java.use("java.lang.String").$new() 转换为 Java 字符串对象,再使用 toString() 方法将其转换为 JavaScript 字符串,最后使用 === 进行严格比较,这样就能得出最终的flag为66888.

##reference

https://github.com/oleavr/frida-agent-example

https://blog.csdn.net/kinghzking/article/details/136772475

https://www.52pojie.cn/thread-1363328-1-1.html

https://github.com/BRUHItsABunny/FRIDASampleAgentTS

[原创]Frida 入门小练习-Android安全-看雪-安全社区|安全招聘|kanxue.com

由 Hexo 驱动 & 主题 Keep
总字数 47.9k 访客数 访问量