0%

frida初探

Frida 简析&安装

Frida 是一款功能强大的动态代码插桩工具,主要用于 iOS 和 Android 应用程序,它允许开发人员在不修改目标应用程序源代码的情况下,注入新的代码来观察和修改应用程序的行为

Frida 支持多种编程语言,如 Python、JavaScript 和 C++,它可以通过命令行或图形界面进行使用,Frida 具有以下主要功能:

  • 注入代码:Frida 可以注入新的代码到目标应用程序中,这些代码可以观察和修改应用程序的行为
  • 获取函数调用信息:Frida 可以获取目标应用程序中函数的调用信息,包括函数名、参数和返回值
  • 修改函数行为:Frida 可以修改目标应用程序中函数的行为,例如修改函数的返回值或修改函数输入参数
  • 监控线程:Frida 可以监控目标应用程序中的线程,包括线程的创建、退出和状态变化
  • 获取全局变量:Frida 可以获取目标应用程序中的全局变量,并对其进行读取或修改

安装 Frida:

1
2
3
4
sudo apt update
sudo apt install build-essential libffi-dev libssl-dev python3-dev
pip3 install frida-tools
pip3 install frida

安装 Frida-server:

Frida API 使用案例

官方文档如下:JavaScript API | Frida • A world-class dynamic instrumentation toolkit

开启 frida-server,并确保 frida 可以监控到非自己的子进程:

1
2
sudo sysctl kernel.yama.ptrace_scope=0
sudo frida-server
  • sysctl kernel.yama.ptrace_scope=0 命令用于更改 Linux 内核的 ptrace 权限限制
    • ptrace_scope 为1时,如果 tracer 和 tracee 进程没有父子关系则 ptrace 无效,root 用户不理会此条件
    • ptrace_scope 为2时,如果 tracer 和 tracee 进程没有 CAP_SYS_PTRACE 能力则 ptrace 无效,root 用户可以获取任何能力,所以不理会该限制
    • ptrace_scope 为3时,对于所有用户均禁止 ptrace
1
cat /proc/sys/kernel/yama/ptrace_scope
  • 查看 kernel.yama.ptrace_scope 的状态

测试样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <unistd.h>

void f (int n){
printf ("Number: %d\n", n);
}

int main (int argc, char * argv[]){
int i = 0;
printf ("f() is at %p\n", f);
while (1){
f (i++);
sleep (1);
}
}

Frida 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from __future__ import print_function
import frida
import sys

session = frida.attach(15529)
script = session.create_script("""
var write_address = Module.findExportByName('libc.so.6', 'write');
Interceptor.attach(ptr(write_address), {
onEnter: function(args) {
const fd = args[0].toInt32();
const addr = args[1];
const size = args[2].toInt32();
const formattedString = `write(${fd},${addr},${size})`;
send(formattedString);
}
});
""")
def on_message(message, data):
print(message)
script.on('message', on_message)
script.load()
sys.stdin.read()
  • frida.attach:使用进程名或者进程号指定进程,attach 进程创建 session
  • session.create_script:创建 JavaScript hook 脚本
  • script.on:注册用于通信的回调函数
  • script.load:加载脚本
  • sys.stdin.read:保持主线程运行

效果如下:

1
2
3
4
5
6
7
Number: 545
Number: 546
Number: 547
---------------------------------------------
{'type': 'send', 'payload': 'write(1,0x55e0ed3372a0,12)'}
{'type': 'send', 'payload': 'write(1,0x55e0ed3372a0,12)'}
{'type': 'send', 'payload': 'write(1,0x55e0ed3372a0,12)'}
  • 该脚本会输出传入被 hook 函数的参数

Frida Hook 脚本常用对象

hook 操作:Interceptor

1
2
3
4
5
6
Interceptor.attach(ptr("%s"), {
onEnter: function(args) {
send(args[0].toInt32()); // 这里也可以直接修改args的值来改变传入的参数
onLeave: function onLeave(retval) {
}
});
  • Interceptor.attach 用于 hook 操作,其 attach 方法有两个参数:
    • 将要被 hook 的函数在内存中加载的地址(这个地址可以通过 Module.findExportByName(链接库名,函数名) 来获取)
    • 包含回调函数的对象(由两个回调函数组成)
  • ptr 接收一个字符串,用于构造一个指针
  • onEnter 在进入该函数时调用,args 为传入该函数的参数
  • onLeave 在函数返回时被调用,retval 该函数的返回值
  • send 函数用于 hook 脚本向外部发送信息,通过在外部 python 脚本中用 script.on 设置回调函数来接收信息

调用函数:NativeFunction

1
2
3
4
5
6
7
8
9
10
var write_address = Module.findExportByName('libc.so.6', 'write');
var run = new NativeFunction(write_address, 'size_t', ['size_t','pointer','size_t']);
Interceptor.attach(ptr(write_address), {
onEnter: function(args) {
const fd = args[0].toInt32();
const addr = args[1];
const size = args[2].toInt32();
run(fd,addr,size);
}
});
  • NativeFunction 构造函数接受三个参数:函数地址,返回值,参数类型(数组表示)
  • 上述案例的作用就是将 write 函数再执行一遍,效果如下:
1
2
3
4
5
6
Number: 1410
Number: 1410
Number: 1411
Number: 1411
Number: 1412
Number: 1412

分配空间:Memory

1
2
3
4
5
6
7
8
9
10
var write_address = Module.findExportByName('libc.so.6', 'write');
var st1 = Memory.allocUtf8String("Hack_by_YHellow!\\n");
Interceptor.attach(ptr(write_address), {
onEnter: function(args) {
var st2 = Memory.allocUtf8String("Can be output\\n");
send(args[1]+"->"+st2);
args[1] = st2;
args[2] = ptr('17');
}
});
  • Memory 对象下的方法可以操作被 attach 进程的内存空间,该方法分配的空间会在函数结束后被销毁(在 onEnter 中分配的内存空间会在 onEnter 结束后被释放,在全局分配的空间则会在整个程序结束后释放)
1
2
3
4
5
6
7
{'type': 'send', 'payload': '0x561ec38592a0->0x7f21a810bc00'}
{'type': 'send', 'payload': '0x7f21a810ac40'}
{'type': 'send', 'payload': '0x561ec38592a0->0x7f21a89aa530'}
{'type': 'send', 'payload': '0x7f21a810ac40'}
-------------------------
�ܚ�!� '�! ���!� '�! �� �!� '�! ��!� '�! ���!� '�! @v�!� '�! ؚ�!� '�! �� �!� '�! ���!� '�! �� �!� '�! ��!� '�! ���!� '�! @v�!� '�! ؚ�!� '�! �� �!� '�! ��!� '�! ���!� '�! ؚ�!� '�! �� �!� '�! Number: 32
Number: 33
  • 由于 onEnter 结束后 Memory.allocUtf8String 申请的空间会被释放,被 hook 的 write 会打印出乱码

读取内存:hexdump

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var write_address = Module.findExportByName('libc.so.6', 'write');
var run = new NativeFunction(write_address, 'size_t', ['pointer','pointer']);

Interceptor.attach(ptr(write_address), {
onEnter: function(args) {
const fd = args[0].toInt32();
const addr = args[1];
const size = args[2].toInt32();
send(hexdump(addr,{
offset: 0,
length: size,
header: false,
ansi: false
}));
}
});
  • 函数 hexdump 有两个参数:需要读取的地址,读取设置

Frida 远程调试

frida.get_device_manager 是 Frida 库中的一个函数,用于获取当前设备的设备管理器对象(设备管理器对象用于管理 Frida 连接到目标设备的进程)

其中有一个 add_remote_device 方法用于添加远程设备(需要提供 IP:Port 或域名),返回一个 device 对象用于表示该远程设备

使用 device 对象的 attach 方法就可以连接远程 frida 服务器上的某个进程:

1
2
3
4
str_host = 'xx.xx.xx.xx:xx'
manager = frida.get_device_manager()
remote_device = manager.add_remote_device(str_host)
session = remote_device.attach("xx")

远程受害机:修改 ptrace 权限并启动 frida 服务器

1
2
sudo sysctl kernel.yama.ptrace_scope=0
sudo frida-server

本地主机:编写 frida python 脚本

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
import frida
import sys

str_host = '192.168.11.130:6666'
manager = frida.get_device_manager()
remote_device = manager.add_remote_device(str_host)
print(remote_device)
session = remote_device.attach("spy")
print(session)

scr = """
var write_address = Module.findExportByName('libc.so.6', 'write');
var run = new NativeFunction(write_address, 'size_t', ['pointer','pointer']);

Interceptor.attach(ptr(write_address), {
onEnter: function(args) {
const fd = args[0].toInt32();
const addr = args[1];
const size = args[2].toInt32();
send(hexdump(addr,{
offset: 0,
length: size,
header: false,
ansi: false
}));
}
});
"""

script = session.create_script(scr)
def on_message(message ,data):
print (message["payload"])
script.on("message" , on_message)
script.load()
sys.stdin.read()

运行效果如下:

1
2
3
> 1
Name of person: yhellow
ID: 5894042113563141378
1
2
3
4
5
6
7
Device(id="socket@192.168.11.130:6666", name="192.168.11.130:6666", type='remote')
Session(pid=4341)
563e56b3124c 4e 61 6d 65 20 6f 66 20 70 65 72 73 6f 6e 3a 20 Name of person:
7fc731d82f0c 49 44 3a 20 35 38 39 34 30 34 32 31 31 33 35 36 ID: 589404211356
7fc731d82f1c 33 31 34 31 33 37 38 3141378
7ffdd928bf37 0a .
563e56b31234 3e 20 >