Frida用法详解【附用例】
文章目录
- 0x01 工具安装
- 0x02 设备环境配置(Android)
- 0x03 命令行运行
- 0x04 Python脚本运行
- 0x05 基本使用
- 0x06 常用API整理
- 0x07 开发用例
Frida,是一个类似 Xposed/Substrate 的跨平台、动态、插装工具,借助 python 和 javascript,可以非常快速便捷地操作目标进程、修改函数参数返回值等,支持 Android/iOS/Macos/Linux/Windows,参考官网
0x01 工具安装
有两种安装方式,命令行直接安装
# python 支持
$ pip3 install frida
# CLI命令行界面工具,支持 frida、frida-ls-devices、frida-ps、frida-kill、frida-trace、frida-discover
$ pip3 install frida-tools
或者直接从源码编译
$ git clone git://github.com/frida/frida.git
$ cd frida
# Linux 下直接 make 编译
# Macos 或 iOS 下需要安装证书,然后导出证书名指定为环境变量,再编译
$ export MAC_CERTID=frida-cert/export IOS_CERTID=frida-cert
$ make
# 确保系统信任新安装的证书,需要重启一下 taskgated 守护进程
$ sudo killall taskgated
0x02 设备环境配置(Android)
原理:手机端安装一个server程序,将手机端的端口转发到PC端,PC端通过python脚本进行交互,脚本内部又是通过javascript语言来写代码执行操作
frida-server下载地址,注意对应版本和支持平台,可以使用下面的命令查看设备abi信息
$ adb shell getprop | grep abi
[ro.product.cpu.abi]: [arm64-v8a]
[ro.product.cpu.abilist]: [arm64-v8a,armeabi-v7a,armeabi]
[ro.product.cpu.abilist32]: [armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: [arm64-v8a]
下载server程序并解压缩,然后推送到手机内部存储路径
$ curl -O https://github.com/frida/frida/releases/download/12.8.1/frida-server-12.8.1-android-arm64.xz
$ xz -d frida-server-12.8.1-android-arm64.xz
$ adb push frida-server-12.8.1-android-arm64 /data/local/tmp/frida-server
修改权限运行
blueline:/ # chmod 777 frida-server
blueline:/ # ./frida-server
跟IDA一下,需要对端口进行转发,默认使用27042端口与frida-server通信
$ adb forward tcp:27042 tcp:27042
然后可以运行ps查看手机端进程列表
$ frida-ps -RPID Name
----- ---------------------------------------------------581 ATFWD-daemon
29920 adbd586 android.hardware.biometrics.fingerprint@2.1-service398 android.hardware.cas@1.0-service399 android.hardware.configstore@1.0-service400 android.hardware.dumpstate@1.0-service.bullhead401 android.hardware.graphics.allocator@2.0-service556 audioserver557 cameraserver......
附加某个进程
$ frida -R com.demo.fridahook____/ _ | Frida 12.8.1 - A world-class dynamic instrumentation toolkit| (_| |> _ | Commands:/_/ |_| help -> Displays the help system. . . . object? -> Display information about 'object'. . . . exit/quit -> Exit. . . .. . . . More info at https://www.frida.re/docs/home/[Remote::com.demo.fridahook]->
对于非root设备,网上也给出了方法,需要反编译目标应用包,然后注入frida-gadget
,参考这里【待验证】
下载对应版本和平台的工具,frida-gadget下载地址,修改命名为libfrida-gadget.so,放到反编译包/lib/目录下
从AndroidManifest.xml文件中找到应用入口文件,在较为靠前的函数中加载动态库
const-string v0, "frida-gadget"
invoke-static {v0}, Ljava/lang/System;>loadLibrary(Ljava/lang/String;)V
另外还需要检查下网络权限,如没有需要添加
<uses-permission android:name="android.permission.INTERNET" />
回编译应用包,并签名安装,查看会显示Gadget进程
$> frida-ps -UPID Name
----- ------
16251 Gadget
执行命令进行连接
frida -U gadget
安装objection,自动完成frida gadget注入到apk中
$ pip3 install -U objection
$ objection patchapk -s target_app.apk
0x03 命令行运行
命令行附加应用进程然后敲代码进程注入
$ frida -U -f com.demo.fridahook____/ _ | Frida 12.8.1 - A world-class dynamic instrumentation toolkit| (_| |> _ | Commands:/_/ |_| help -> Displays the help system. . . . object? -> Display information about 'object'. . . . exit/quit -> Exit. . . .. . . . More info at https://www.frida.re/docs/home/
Spawned `com.demo.fridahook`. Use %resume to let the main thread start executing!
[LGE Nexus 5X::com.demo.fridahook]-> Java
{"androidVersion": "8.1.0","available": true
}
[LGE Nexus 5X::com.demo.fridahook]->
插装指定函数,会在当前目录__handlers__
文件夹下生成open.js脚本
$ frida-trace -i open -U com.demo.fridahook
或者直接加载js脚本
$ frida -U/-R -l <script_file> --no-pause -f <process_name/pid>
js脚本用法示例
// javascript
// 将线程附加到Java虚拟机,成功会回调function()
Java.perform(function() {});
// 由于js代码注入存在超时,通常在外面再包装一层
setImmediate(function() {...
});// 动态获取一个js包装的Java类
var clazz = Java.use(className);
// $new()调用类构造方法,$dispose()调用析构清空js对象
// 获取到Java类之后,直接通过<wrapper>.<method>获取方法
// 有些重载的方法需要通过.overload()传入参数类型指定方法
// 通过方法的.call(args)来调用函数,通过方法的.implementations = function(){}来实现hook
clazz.method.implementation = function(args) {......
}
clazz.method.overload(argumentsType[]).call(args);
// 将js包装的类实例转换成另一种js包装类
Java.cast(clazz1, Clazz2)
0x04 Python脚本运行
除了在命令行直接将脚本注入进程,也可以通过python来加载脚本到指定进程
这里给出一个通用模板
# python
# -*- coding: utf-8 -*-import frida
import sysdef on_message(message, data):if message['type'] == 'send':print("*****[frida hook]***** : {0}".format(message['payload']))else:print("*****[frida hook]***** : " + str(message))def get_javascript(filepath):code = ''with open(filepath, 'r') as file:code = code + file.read()return code# 连接远端设备
device = frida.get_remote_device()
# 附加到进程
session = device.attach(package_name)
# 1、直接写入 javascript 代码
javascript = """
<javascript code>
"""
# 2、从文件中加载 javascript 脚本代码
javascript = get_javascript(javascript_file)
# 基于脚本内容创建运行脚本对象
script = session.create_script(javascript)
script.on('message', on_message)
# 加载脚本并执行
script.load()
sys.stdin.read()
0x05 基本使用
Frida提供了一些工具直接操作
比如查看连接的设备
# bash
$ frida-ls-devices
Id Type Name
---------------- ------ ------------
local local Local System
0105a575959b9fa9 usb LGE Nexus 5X
tcp remote Local TCP
使用python脚本获取的也是这样
# python
import frida
print(frida.get_local_device())
print(frida.get_usb_device())
print(frida.get_remote_device())
-------------------------------------------------------------------------
Device(id="local", name="Local System", type='local')
Device(id="0105a575959b9fa9", name="LGE Nexus 5X", type='usb')
Device(id="tcp", name="Local TCP", type='remote')
其中,local device对应的就是PC本机,usb device对应连接的Android设备,remote device也是经过端口转发的Android设备
附加指定进程
$ frida-trace -i open -U <package_name/pid>
也封装了许多常用功能和函数,可以直接调用接口
查看所有进程
# python
processes = device.enumerate_processes()
for process in processes:print(process)
查看所有安装的应用
# python
applications = device.enumerate_applications()
for application in applications:print(application)
获取顶层应用进程
# python
front_app = remote_device.get_frontmost_application()
print(front_app)
-------------------------------------------------------------------------
Application(identifier="com.android.settings", name="设置", pid=6683)
获取加载的类
// javascript
Java.enumerateLoadedClasses({onMatch:function(_className){log("found instance of '" + _className + "'");},onComplete: function(){log("enumerating completed !!!");}
});
获取类声明的函数
// javascript
var clazz = Java.use(className);
var methods = clazz.class.getDeclaredMethods();
if(methods.length > 0){log("getDeclaredMethods of class '" + className + "':");methods.forEach(function(method){log(method);});
}
获取调用堆栈
// javascript
var stack = Java.use("java.lang.Thread").$new().currentThread().getStackTrace();
for(var i = 2; i < stack.length; i++){log("getStackTrace[" + (i-2) + "] : " + stack[i].toString());
}
获取加载的所有模块
// javascript
Process.enumerateModules({onMatch: function(module) {log(module.name + " : " + module.base + "\t" + module.size + "\t" + module.path);},onComplete: function() {log("enumerating completed !!!");}
});
获取所有的导出符号
// javascript
var symbols = Module.enumerateExportsSync(moduleName);
symbols.forEach(function(symbol){log(symbol.name + " address = " + symbol.address);
});
return symbols;
获取JNI注册的函数信息
// javascript
var RegisterNativesAddr = getSymbolAddr("libart.so", "_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi");
if(RegisterNativesAddr != null){log("find symbol 'RegisterNatives' in libart.so, address = " + RegisterNativesAddr);Interceptor.attach(RegisterNativesAddr, {onEnter: function(args) {var class_name = Java.vm.getEnv().getClassName(args[1]);var methods_ptr = ptr(args[2]);var module = Process.findModuleByAddress(Memory.readPointer(methods_ptr));log("RegisterNativeMethod class = " + class_name + ", module = " + module.name + ", base = " + module.base);var method_count = parseInt(args[3]);log("registered methods count = " + method_count);// get registered native method infovar offset = Process.pointerSize;for (var i = 0; i < method_count; i++) {var name = Memory.readCString(Memory.readPointer(methods_ptr.add(offset*3*i)));var sig = Memory.readCString(Memory.readPointer(methods_ptr.add(offset*3*i+offset)));var address = Memory.readPointer(methods_ptr.add(offset*(3*i+2)));log("methods name = " + name + ", sig = " + sig + ", address = " + ptr(address) + ", offset = " + ptr(address).sub(module.base));}},onLeave: function() {}});
}
导出远程方法,注意不支持大写字母和下划线
// javascript
rpc.exports = {<exportfuncname1>: function(args) {......},<exportfuncname2>: function(args) {......}
};
远程调用
# python
script.exports.exportfuncname1(args)
0x06 常用API整理
console
- log/warn/error(message) : 打印日志
- hexdump(address, options) : 打印address内存,输出格式由options定义,一般为offset、length、header、ansi
Process
- id : 返回目标进程id
- isDebuggerAttached() : 检查目标进程是否附加成功
- enumerateModules() : 枚举当前进程已加载的所有模块,并返回数组
- enumerateThreads() : 枚举当前进程的所有线程,并返回数组
- getCurrentThreadId() : 返回当前线程id
Module
- name : 返回模块名称
- base : 返回模块加载到内存中的地址,类型为 NativePointer
- size : 模块大小
- path : 模块的完整文件路径
- load() : 加载so文件,返回 Module 对象
- enumerateImports() : 枚举所有导入函数,返回数组(type/name/module/address)
- enumerateExports() : 枚举所有导出函数,返回数组(type/name/module/address)
- enumerateSymbols() : 枚举所有符号,返回数组(isGlobal/type/section/name/address)
- findExportByName()/getExportByName() : 返回指定so文件中导出函数绝对地址
- findBaseAddress()/getBaseAddress() : 返回指定so文件内存基址
Memory
- scan(address, size, pattern, callback) : 从address开始搜索size大小空间中满足pattern条件的数据回调callback,可以返回“stop”提前取消内存扫描
- scan(address, size, pattern) : 返回符合条件的数据数组
- alloc(size) : 在目标进程堆中申请size大小的内存,并按照Process.pageSize对齐,返回NativePointer的地址指针,且自动释放
- allocUtf8String(string)/allocUtf16String(string)/allocAnsiString(string) : 分配UTF-8/UTF-16/ANSI字符串
- copy(obj_addr, tar_addr, size) : 将目标地址开始size大小的数据拷贝到目的地址
- writeByteArray(addr, array) : 将数组写入目的地址
- readByteArray(addr, size) : 从目标地址读取size大小的数组
Java
- available : 判断当前进程是否加载JavaVM,Dalvik/ART虚拟机
- androidVersion : 当前设备系统版本
- enumerateLoadedClasses() : 枚举当前加载的所有类,回调onMatch(name)/onComplete()
- enumerateLoadedClassesSync() : 枚举当前加载的所有类,返回数组
- enumerateClassLoaders() : 枚举当前所有的类加载器,回调onMatch(loader)/onComplete()
- enumerateClassLoadersSync() : 枚举当前所有的类加载器,返回数组
- perform(callback) : 用于当前线程附加到Java VM,回调callback
- use(classname) : 动态获取classname对应的类定义,
$new()
调用构造函数,$dispose()
显式释放 - choose(classname) : 在堆上查找实例化对象,回调onMatch(instance)/onComplete()
- cast(instance, clazz) : 将指定变量或数据强制转化为给定类定义
- array(type, [value1, value2, …]) : 定义type类型的Java的数组
- registerClass(args) : 注册一个新的Java类并返回对象,类定义由args给出,name指定类名[superClass指定父类,implements指定实现接口数组,fields指定域字段,methods指定方法]
- vm : 虚拟机,一般用于调用getEnv()来获取JNIEnv对象
Interceptor
- attach(address, callback) : 拦截NativPointer类型的函数地址,回调onEnter/onLeave
- detach() : 取消对地址的拦截
- replace(function, callback) : 替换NativeFunction类型的原函数为回调函数
NativePointer : javascript中的指针
- new NativePointer(var) : 构造指向var的指针
- add/sub/and/or/xor(var) : 四则和逻辑运算
- shl/shr(n) : 左/右移动n位
- isNull() : 检查指针是否为空
- toString/toInt32() : 转为字符串/32位整数
- readXXX()/writeXXX(value) : 从内存读取内容/向内存写入内容
- readByteArray(length) : 从内存读取length长度字节以ArrayBuffer返回
- writeByteArray(bytes) : 将Arraybuffer类型或整数数组写入内存位置
- readCSString/readUtf8String/readUtf16String/readAnsiString() : 将内存位置的字节作为ASCII、UTF-8、UTF-16、ANSI字符串读取
- writeUtf8String/writeUtf16String/writeAnsiString(value) : 对JavaScript字符串进行编码并写入内存位置
NativeFunction : 本地函数
- new NativeFunction(address, retType, argTypes) : 构造方法,address为NativePointer类型函数地址,retType指定返回类型,argTypes数组指定参数类型,返回函数对象,可以直接调用
NativeCallback : 本地函数回调
- new NativeCallback(callback, retType, argTypes) : 构造方法,callback为回调函数,retType指定返回类型,argTypes数组指定参数类型,返回NativePointer对象
0x07 开发用例
上面所有的代码都可以在这里找到,这是我从学习frida开始写过的一些用例,包含测试应用程序、hook测试用例,还封装了一些常用功能
另外总结了一些优秀的应用案例:
appmon
objection
ssl_logger
passionfruit
r2frida = Frida + Radare2
Brida = Frida + Burp
Frida用法详解【附用例】相关推荐
- Python os.path() 模块 详解 附算例
Python os.path() 模块 函数 说明 os.path.abspath(path) 返回绝对路径 os.path.basename(path) 返回文件名 os.path.commonpr ...
- python字典的用法_Python字典的用法详解(附示例)
本篇文章给大家带来的内容是关于Python字典的用法详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 字典的表现形式为:{key:value},键和值之间用冒号分隔,每个 ...
- Django基础(11): 表单集合Formset的高级用法详解
Formset(表单集)是多个表单的集合.Formset在Web开发中应用很普遍,它可以让用户在同一个页面上提交多张表单,一键添加多个数据,比如一个页面上添加多个用户信息.今天小编我就介绍下Djang ...
- linux mount命令参数及用法详解
linux mount命令参数及用法详解 非原创,主要来自 http://www.360doc.com/content/13/0608/14/12600778_291501907.shtml. htt ...
- c++中vector的用法详解
c++中vector的用法详解 vector(向量): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间 ...
- jQuery 表单验证插件,jQuery Validation Engine用法详解
jQuery 表单验证插件,jQuery Validation Engine用法详解 功能强大的 jQuery 表单验证插件,适用于日常的 E-mail.电话号码.网址等验证及 Ajax 验证,除自身 ...
- 教程-Delphi中Spcomm使用属性及用法详解
Delphi中Spcomm使用属性及用法详解 Delphi是一种具有 功能强大.简便易用和代码执行速度快等优点的可视化快速应用开发工具,它在构架企业信息系统方面发挥着越来越重要的作用,许多程序员愿意选 ...
- string类的用法详解
//string函数用法详解!附代码,写具体的用法! #include <iostream> #include <string> #include <sstream> ...
- csh for循环_shell中的for循环用法详解_linux shell
这篇文章主要介绍了shell中的for循环用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 for 命令: for i i ...
最新文章
- Java的List排序
- 编程笔试(解析及代码实现):国内各大银行(招商银行/浦发银行等)在线笔试常见题目(猴子吃桃/字符串逆序输出/一段话输出字的个数/单词大小转换等)及其代码实现(Java/Python/C#等)之详细攻略
- Find Integer(费马大定理的使用)
- $lookup做关联表查询
- Ubuntu用户及用户组管理命令
- 依图芯片服务器,AI 芯片行业再添硬核新玩家:依图推出云端 AI 芯片 questcore™...
- python回到初始位置_python之基础
- gg product
- 基于Matlab的自适应低通滤波器设计,课程设计-低通滤波器设计(含matlab程序)
- 标准输入输出流OutputStreamWriter:将字节输出流转换为字符输出流InputStreamReader:将字节输入流转换为字符输入流打印流添加输出数据的功能ObjectInputStrea
- 光线微弯传感器matlab仿真,光纤压力与位移传感特性
- Python程序设计
- 为什么10M、20M的宽带只有大约1、2M的下载速度——网速KB/s与Kbps(Kb/s)的区别
- 聊天机器人之知识图谱 Freebase 简介
- 区块链赋能金融服务新价值
- 联发科p60和骁龙710哪个好_骁龙670、联发科P60和麒麟710哪个好 详细对比介绍
- 如何绘制流程图?绘制流程图在线网站分享
- 常见的CSS页面布局方式
- agsXMPP + Openfire 即时通讯开发(二) 【文件传输】
- Config是什么意思
热门文章
- 7-80 大炮打蚊子(C语言)
- 2,理解Verilog的四值逻辑
- 整车行业MES系统概念导入
- 永磁同步电机驱动视频教程_矢量控制_手把手教你写代码_无感FOC_有感FOC_状态观测器_卡尔曼滤波_慧驱动
- Orange Pi Zero2 (全志H616) 开箱配置
- 判断是否是对象本身的属性(hasOwnProperty)
- R语言科学计数法数据改变/丢失/失准,取消科学计数法的原因和解决方法
- 用Python实现拨打电话
- android11电视,适用于Android TV的安卓11更新:提高性能与隐私
- java+selenium3