首页
社区
课程
招聘
5
Frida 源码分析
发表于: 2024-8-15 16:13 4538

Frida 源码分析

2024-8-15 16:13
4538

Frida 源码分析

Frida 简介

简单介绍一下 frida,frida 是一款用来进行 hook 的框架注入工具,其暴露给用户的接口,可以用 js 脚本语言进行方便快捷的 hook 和注入,而且其支持三种主流平台:pc/ios/andriod,尤其在安卓平台上,用 frida 可以说是必备技能。

但是,在使用的大多时候,我们都只是使用 js 代码对指定的地址/java 对象进行了 hook,其实现的原理并没有认真的去分析,近来觉得技术上有了瓶颈,逐花时间尝试对其原理进行分析。

源码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
.
├── frida-clr
├── frida-core   frida 的主要功能实现模块
├── frida-gum    frida的基础框架,提供inline hook、代码追踪、内存监控、符号查找等
├── frida-qml    qt的qml界面
├── frida-tools  frida-tools
└── releng       编译相关
 
frida-gum
├── bindings               绑定js平台所用入口
│   ├── gumjs
│   └── gumpp
├── docs                   
├── ext                    主要是win平台的调试相关
│   ├── dbghelp
│   └── symsrv
├── gum                    gum项目的主要实现目录,包括注入、库相关、内存管理、api解决等,子目录包含对不同平台封装的一些汇编指令
│   ├── arch-arm
│   ├── arch-arm64
│   ├── arch-mips
│   ├── arch-x86
│   ├── backend-arm
│   ├── backend-arm64
│   ├── backend-darwin
│   ├── backend-dbghelp
│   ├── backend-elf
│   ├── backend-freebsd
│   ├── backend-libdwarf
│   ├── backend-libunwind
│   ├── backend-linux
│   ├── backend-mips
│   ├── backend-posix
│   ├── backend-qnx
│   ├── backend-windows
│   └── backend-x86
├── libs                   导出目录
│   └── gum
├── subprojects            导入目录
├── tests                  
│   ├── core
│   ├── data
│   ├── gumjs
│   ├── gumpp
│   ├── heap
│   ├── prof
│   └── stubs
├── tools
└── vapi                   vala的支持目录
 
frida-core                一些上层实现
├── inject                 调api
├── lib                    库
│   ├── agent              注入时调用的库
│   ├── base
│   ├── gadget
│   ├── payload
│   ├── pipe
│   └── selinux
├── portal               
├── server
├── src
│   ├── api
│   ├── compiler
│   ├── darwin
│   ├── droidy
│   ├── freebsd
│   ├── fruity
│   ├── linux
│   ├── qnx
│   ├── socket
│   └── windows
├── tests
│   ├── labrats
│   └── pipe
├── tools
└── vapi

Frida-core

把其源码结构看清之后,单独把 gum 项目和 core 项目摘出来,gum 项目是整个 frida 的基础框架,包括注入,不同平台,不同架构等基础转换实现原理都在这个项目中。

Inject--Java 层注入

  • 目的 学习对于安卓程序 hook java 层代码
  • 猜想 在内存中查找 dex 文件,匹配对应的类和方法修改其指向的函数地址

在 js 中调用的 Hook java 层的代码位于另一个项目分支 frida_java_bridge 中,其使用了 gum 项目中的 native 层 hook 函数。

流程如下:frida-js->frida-gum-> 调用 native_jni->jni 链接 java

首先需要获取系统加载的虚拟机,使用 JNI_GetCreatedJavaVMs 获取,该函数是 jvm 的导出函数,由 libart.so 或 libdvm.so 进行导出

1
2
3
4
5
6
7
const vms = Memory.alloc(pointerSize);
  const vmCount = Memory.alloc(jsizeSize);
  checkJniResult('JNI_GetCreatedJavaVMs', temporaryApi.JNI_GetCreatedJavaVMs(vms, 1, vmCount));
  if (vmCount.readInt() === 0) {
    return null;
  }
  temporaryApi.vm = vms.readPointer();

有了系统运行的虚拟机,就可以调用虚拟机中的 jni 函数。

vm.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const handle = api.vm;
  let attachCurrentThread = null;
  let detachCurrentThread = null;
  let getEnv = null;
 
  function initialize () {
    const vtable = handle.readPointer();
    const options = {
      exceptions: 'propagate'
    };
    attachCurrentThread = new NativeFunction(vtable.add(4 * pointerSize).readPointer(), 'int32', ['pointer', 'pointer', 'pointer'], options);
    detachCurrentThread = new NativeFunction(vtable.add(5 * pointerSize).readPointer(), 'int32', ['pointer'], options);
    getEnv = new NativeFunction(vtable.add(6 * pointerSize).readPointer(), 'int32', ['pointer', 'pointer', 'int32'], options);
  }

调用 NativeFunction 获取 vm 虚函数表指针,实现最基础的 js 调用 jvm。

NativeFunction 实现原理,将 native 层的调用格式转换成 js 能够直接调的格式,相当于导出了 jvm 的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class NativeFunction extends Function {
    handle: BNativePointer;
 
    #retType: Marshaler;
    #argTypes: Marshaler[];
 
    constructor(address: BNativePointer, retType: NativeFunctionReturnType, argTypes: NativeFunctionArgumentType[]) {
        super();
 
        this.handle = address;
 
        this.#retType = getMarshalerFor(retType);
        this.#argTypes = argTypes.map(getMarshalerFor);
 
        return new Proxy(this, {
            apply(target, thiz, args) {
                return target._invoke(args);
            }
        });
    }
// 真实调用位置,在ts里代表陷阱函数,执行到代理函数时,此处会调用
    _invoke(args: any[]): any {
        const nativeArgs = args.map((v, i) => this.#argTypes[i].toNative(v));
        const nativeRetval = _invoke(this.handle.$v, ...nativeArgs); //调用原来真实的调用
        return this.#retType.fromNative(nativeRetval);
    }
}

实现了 jvm 的函数导出和调用,接下来还需要定位到 class,在 jvm 中定位一个加载的 class 通过 findclass 来找到。

Jvm 下的类

在 java 环境下,所有可执行的程序被编译成 class 文件也叫类,类从被加载到虚拟机内存中开始到卸载出内存为止,整个生命周期为 加载-》验证-》准备-》解析-》初始化-》使用-》卸载

加载

类加载器

1
2
类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

[注意]看雪招聘,专注安全领域的专业人才平台!

收藏
免费 5
支持
分享
赞赏记录
参与人
雪币
留言
时间
clz3216
你的分享对大家帮助很大,非常感谢!
2025-2-16 16:25
sinker_
期待更多优质内容的分享,论坛有你更精彩!
2024-8-18 03:22
huangjw
感谢你分享这么好的资源!
2024-8-17 14:15
你瞒我瞒
非常支持你的观点!
2024-8-16 15:43
xsSkyFall
感谢你的积极参与,期待更多精彩内容!
2024-8-16 09:25
最新回复 (9)
雪    币: 48
活跃值: (2253)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
tql
2024-8-15 17:01
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
学到了,感谢楼主
2024-8-16 00:07
0
雪    币: 2381
活跃值: (3029)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
nb
2024-8-16 08:51
0
雪    币: 859
活跃值: (945)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
tql
2024-8-16 09:15
0
雪    币: 2504
活跃值: (4134)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
6
收藏=学会
2024-8-16 10:25
0
雪    币: 4169
活跃值: (4532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
楼主,native层呢?
2024-8-16 12:52
0
雪    币: 482
活跃值: (1122)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
学到了
2024-8-17 09:45
0
雪    币: 482
活跃值: (1122)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9

学到了,感谢楼主

2024-8-17 09:46
0
雪    币: 2381
活跃值: (3029)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
tql
2024-11-18 19:27
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册