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)'}
|
Frida Hook 脚本常用对象
hook 操作:Interceptor
1 2 3 4 5 6
| Interceptor.attach(ptr("%s"), { onEnter: function(args) { send(args[0].toInt32()); 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 >
|