frida教程

2020-12-21
1277

Frida教程

 

一 简介

Frida是一款基于python + javascript 的hook框架,适用于android/ios/linux/win/osx等平台。Frida的动态代码执行功能,主要是在它的核心引擎Gum中用C语言来实现的。
注入模式:大部分情况下,我们都是附加到一个已经运行到进程,或者是在程序启动到时候进行劫持,然后再在目标进程中运行我们的代码逻辑。这种方式是Frida最常用的使用方式。注入模式的大致实现思路是这样的,带有GumJS的Frida核心引擎被打包成一个动态连接库,然后把这个动态连接库注入到目标进程中,同时提供了一个双向通信通道,这样控制端就可以和注入的模块进行通信了,在不需要的时候,还可以在目标进程中把这个注入的模块给卸载掉。
嵌入模式:针对没root过的设备Frida提供了一个动态连接库组件 frida-gadget, 可以把这个动态库集成到程序里面来使用Frida的动态执行功能。一旦集成了gadget,就可以和程序使用Frida进行交互。
预加载模式:自动加载Js文件。

这里写图片描述
二 环境搭建-root设备

1.下载FRIDA

pip install frida		
  • 1

2.下载frida-server
frida-server 是一个守护进程,通过TCP和Frida核心引擎通信,默认的监听端口是27042
地址: https://github.com/frida/frida/releases
注意:版本和类型对应,框架和设备对应
3.连接设备并确认连接

	adb devices											
  • 1

4.将frida-server压缩包解压并push到设备中

adb push /Users/用户名/Desktop/frida-server-10.7.7-android-arm  data/local/tmp/frida-server						
  • 1

5.运行frida-server

$adb shell
$su
$cd  /data/local/tmp
$chmod  755  frida-server
$./frida-server                          
  • 1
  • 2
  • 3
  • 4
  • 5

6.检查是否正常
转发android TCP端口到本地

adb forward tcp:27042 tcp:27043
adb forward tcp:27043 tcp:27043
Frida-ps -U       
  • 1
  • 2
  • 3

这里写图片描述
-U 代表着 USB,并且让 Frida 检查 USB-Device真机,出现图上这样就是成功了。

三 常用API
1.JAVA
(1)Java.perform(fn)
frida的main,注意:所有的脚本必须放在这里面,示例:

Java.perform(function () {                                                                                     
    var Activity = Java.use(“android.app.Activity”);//获得类包相当于js的new()                                                       
    Activity.onResume.implementation = function () {//改变onResume函数     	
         	send("onResume() got called! Let's call the original implementation”);                              
         this.onResume();											
    };														
});							
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(2)Java.use(className)
动态获取一个类的对象,为以后改变对象方法的实现,或者用$new()实例化对象, $dispose()销毁对象,示例:

Java.perform(function () {										
    var Activity = Java.use("android.app.Activity");						
    var Exception = Java.use("java.lang.Exception");						
    Activity.onResume.implementation = function () {						
        throw Exception.$new(“Hello World!");							
    };														
});														
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(3)Java.enumerateLoadedClasses(callbacks)
列出当前已经加载的类,用回调函数处理
回调函数onMatch:function(className){ }
找到加载的每个类的时候被调用,参数就是类的名字,这个参数可以传给java.use()来获得一个js类包
回调函数onComplete: function ()
列出所有类之后被调用
2.JavaScript
(1)Interceptor.attach(target, callbacks)
在target指定的位置进行函数调用拦截,target是一个NativePointer参数,用来指定你想要拦截的函数的地址。callbacks参数是一个对象:
onEnter: function(args): 被拦截函数调用之前回调,其中原始函数的 参数使用args数组(NativePointer对象数组)来表示,可以在这里修改函数的调用参数。
onLeave: function(retval): 被拦截函数调用之后回调,其中retval表示原始函数的返回值,retval是从NativePointer继承来的,是对原始返回值的一个封装,可以使用retval.replace(1337)调用来修改返回值的内容。注意:retval对象只在 onLeave函数作用域范围内有效,因此如果你要保存这个对象以备后续使用的话,一定要使用深拷贝来保存对象,比如:ptr(retval.toString())
示例:

Interceptor.attach(Module.findExportByName(“libc.so”,”read”),{				
	onEnter:function(args){										
		this.fileDescriptor = args[0].toInt32();						
	},													
	onLeave:function(retval){									
		if(retval.toInt32( ) > 0){									
			/* do something with this.fileDescriptor */				
		}												
	}													
});										
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

此外,this 对象还包含了一些额外的比较有用的属性:

returnAddress: 返回NativePointer类型的 address 对象
context: 包含 pc,sp,以及相关寄存器比如 eax, ebx等,可以在回调函数中直接修改
errno: (UNIX)当前线程的错误值
lastError: (Windows) 当前线程的错误值
threadId: 操作系统线程Id
depth: 函数调用层次深度
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(2)Interceptor.detachAll()
取消之前所有的拦截调用
(3)Interceptor.replace(target, replacement):
函数实现代码替换,注意:这种情况主要用于想要完全替换掉一个原有函数的实现,replacement参数使用JavaScript形式的一个NativeCallback来实现,后续如果想要取消这个替换效果,可以使用 Interceptor.revert调用来实现

四 工具
(1)frida-ps
用于枚举进程

--version             show program's version number and exit					
-h, --help            show this help message and exit						
-D ID, --device=ID    connect to device with the given ID					
-U, --usb             connect to USB device							
-R, --remote          connect to remote frida-server						
-H HOST, --host=HOST  connect to remote frida-server on HOST			
-a, --applications    list only applications							
-i, --installed       include all installed applications						
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

(2)frida-trace
动态跟踪

 --version             show program's version number and exit				
 -h, --help            show this help message and exit						
 -D ID, --device=ID    connect to device with the given ID					
 -U, --usb             connect to USB device							
 -R, --remote          connect to remote frida-server						
 -H HOST, --host=HOST  connect to remote frida-server on HOST			
 -f FILE, --file=FILE  spawn FILE									
 -n NAME, --attach-name=NAME                   attach to NAME				
 -p PID, --attach-pid=PID                         attach to PID					
 --debug               enable the Node.js compatible script debugger			
 --disable-jit         disable JIT									
 -I MODULE, --include-module=MODULE     include MODULE				
 -X MODULE, --exclude-module=MODULE   exclude MODULE				
 -i FUNCTION, --include=FUNCTION               include FUNCTION			
 -x FUNCTION, --exclude=FUNCTION             exclude FUNCTION			
 -a MODULE!OFFSET, --add=MODULE!OFFSET     add MODULE!OFFSET		
 -T, --include-imports                                          include program's imports		
 -t MODULE, --include-module-imports=MODULE       include MODULE 		imports													
 -m OBJC_METHOD, --include-objc-method=OBJC_METHOD     include 		OBJC_METHOD												
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

示例:
启动手机中的Chrome浏览器

	frida-trace -i "open" -U com.android.chrome	
  • 1

可以看到终端中出现:

open:Loaded handler at :”/用户名/__handlers__/libc.so/open.js”	
  • 1

frida-trace会生成一个javascript文件,然后Frida会将其注入到进程中,并跟踪特定的调用。生成的open.js脚本将钩住libc.so中的open函数并输出参数.
open.js:

/*														
 * Auto-generated by Frida. Please modify to match the signature of open.		
 * This stub is currently auto-generated from manpages when available.		
 *														
 * For full API reference, see: http://www.frida.re/docs/javascript-api/			
 */														

{														
  /**														
   * Called synchronously when about to call open.						
   *														
   * @this {object} - Object allowing you to store state for use in onLeave.		
   * @param {function} log - Call this function with a string to be presented to 	the user.													
   * @param {array} args - Function arguments represented as an array of 		NativePointer objects.											
   * For example use Memory.readUtf8String(args[0]) if the first argument is a 	pointer to a C string encoded as UTF-8.								
   * It is also possible to modify arguments by assigning a NativePointer 		object to an element of this array.									
   * @param {object} state - Object allowing you to keep state across function calls.														
   * Only one JavaScript function will execute at a time, so do not worry 		about race-conditions.											
   * However, do not use this to store function arguments across onEnter/		onLeave, but instead											
   * use "this" which is an object for keeping state local to an invocation.		
   */														
  onEnter: function (log, args, state) {								
    log("open(" +												
      "path=\"" + Memory.readUtf8String(args[0]) + "\"" +					
      ", oflag=" + args[1] +										
    ")");													
  },														
														
  /**														
   * Called synchronously when about to return from open.				
   *														
   * See onEnter for details.										
   *														
   * @this {object} - Object allowing you to access state stored in onEnter.		
   * @param {function} log - Call this function with a string to be presented to	 the user.													
   * @param {NativePointer} retval - Return value represented as a 			NativePointer object.											
   * @param {object} state - Object allowing you to keep state across function calls.														
   */														
  onLeave: function (log, retval, state) {								
  }														
}													
  • 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

这里写图片描述
可以实时修改open.js进行操作,终端输出如图。

(3)Python绑定
示例:
从Python注入chrome.js脚本
注意:从命令行加载脚本然后生成一个命令的进程时,Frida容易崩溃。所以先生成进程,再让Frida注入脚本

#!/usr/bin/python             										
import frida												
# js														
jscode= """													
console.log("[*] Starting script");									
Java.perform(function() {										
   var Activity = Java.use("android.app.Activity");						
    Activity.onResume.implementation = function () {						
        console.log("[*] onResume() got called!");							
        this.onResume();											
    };														
});														
"""														
# startup frida and attach to com.android.chrome process on a usb device		
session = frida.get_usb_device().attach("com.android.chrome")				
# create a script for frida of jsccode								
script = process.create_script(jscode)								
# and load the script											
script.load()												
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

五 使用
(1)挂钩libc.so中的函数

frida-trace -i "xxx" -U com.android.xxxxx	
  • 1

启动FRIDA和应用,在应用程序启动主进程之前注入FRIDA代码。—no-pause不会中断应用程序,并将生成的进程的任务留给FRIDA,这会打开一个shell,在里边使用Javascript API向Frida写命令

frida -U -f com.android.xxxxx —no-pause	
  • 1

例如:重写onResume函数,如图,一旦调用onResume()就会得到如下结果。
这里写图片描述
这里写图片描述

(2)Hook函数
FRIDA可以动态改变Android应用的行为,比如可以绕过检测Android设备是否处于root状态的函数。对于在ART(Android Runtime,Android运行时)环境中运行的应用来说,可以使用Java.perform来hook函数。
需要工具:
dex2jar:将apk使用压缩工具打开提出classes.dex放入dex2jar文件夹中,为d2j_invoke.sh和d2j-dex2jar.sh增加执行权限 ,执行sh d2j-dex2jar.sh classes.dex得到classes-dex2jar.jar。
JD-GUI:阅读classes-dex2jar.jar

示例:
JavaScript

setImmediate(function() {         //包裹在setImmediate中以防超时				
	Java.perform(function(){	 //所有脚本必须在这里面				
    		classObject = Java.use("com.android.MainActivity");			
  		classObject .targetFunction.implementation = function(v){			
        			send("argetFunction hooked");						
   		 }													});        												
});									
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(3)Hook native函数
使用Frida中的Interceptor函数,深入到设备的底层内存中,hook特定的库或者内存地址
可能需要工具:
Radare2(或者其他的反汇编工具):可以使用r2frida将Radare2连接到Frida,然后对进程的内存进行静态分析和反汇编处理。使用Radare2的数据包管理程序来安装r2frida,注意先安装Radare2。

	r2pm install r2frida						
  • 1

访问进程以及让r2frida执行注入操作的语法如下所示:

	r2 frida://DEVICE-ID/PROCESS	
  • 1

示例:
JavaScript

setImmediate(function() {										
    Java.perform(function() {										
	var targetFunction = undefined;								
	imports = Module.enumerateImportsSync("libfoo.so");//通过导入函数表	获取 targetFunction函数的地址									
	for(i = 0; i < imports.length; i++) {								
		if(imports[i].name == "targetFunction") {						
      			targetFunction = imports[i].address;					
        			break;										
    		}												
	}													
	Interceptor.attach(targetFunction, {							
    		onEnter: function (args) {								
      			//do something									
     		},												
	});													
	console.log("[*] Intercepting targetFunction”);						
    });														
});														

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

六 嵌入模式-unroot设备
需要的工具:
apktool下载地址:https://bitbucket.org/iBotPeaches/apktool/downloads/
1.下载frida-Gadget
2.解码app

      apktool d targetapp.apk -o targetappFolder
  • 1

3.将对应的frida-Gadget复制到targetappFolder/lib/或者targetappFolder/lib/armeabi下,注意版本对应。
4.打开manifest.xml找到MainActivity,因为我们希望在执行任何其他代码之前注入frida-Gadget,所以将以下smali代码复制到MainActivity的onCreate()方法中

const-string v0, "frida-gadget"	
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
  • 1
  • 2

5.如果manifest.xml没有Internet permission需要进行添加

<uses-permission android:name="android.permission.INTERNET" />
  • 1

6.将apk重新打包

	apktool b -o repackaged.apk targetappFolder/
  • 1

7.签名
创建keystore(如果没有的话)

keytool -genkey -v -keystore custom.keystore -alias mykeyaliasname -	keyalg RSA -keysize 2048 -validity 10000				
  • 1

签名

jarsigner -sigalg SHA256withRSA -digestalg SHA1 -keystore custom.keystore -storepass 你的密码 repackaged.apk mykeyaliasname
  • 1

验证

jarsigner -verify repackaged.apk	
  • 1

zipalign(在sdk的build-tools里面,注意路径)

zipalign 4 repackaged.apk final.apk
  • 1

8.安装apk

adb install final.apk	
  • 1

七 frida-Gadget使用
经过第六节重打包的apk在启动时会进入等待页面,这时使用frida-ps -U只显示Gadget一个进程,使用frida -U Gadget进行连接(依旧可以使用frida-trace等工具)。
这里写图片描述
我们可以通过%load 脚本.js进行操作(脚本依旧要包裹在Java.perform()中)。

八 目前存在的一些检测FRIDA的方法
(1)方法:frida-server通过 TCP 对外与 frida 通信, Java 遍历运行的进程列表能够检查到 frida-server 是否在运行
措施:重命名 frida-server
(2)方法:frida-server 默认的TCP 端口是 27047,检查端口是否开放
措施:命令行指定参数改变frida-server的监听端口
(3)方法:frida-server 使用 D-Bus 协议通信,为每个开放的端口发送 D-Bus 的认证消息,回复的端口是 frida-server
措施:Frida 提供了不需要 frida-server 运行的模式
(4)方法:利用frida 运行时映射到内存的库,直接逐一检查加载的库。在内存中扫描 frida 的库特征 “gadgets”。例如:字符串 “LIBFRIDA”,它在所有 frida-gadget 和 frida-agent 的版本中都有出现
措施:打补丁,对修改的apk进行重建和签名(例如:apktool)

转载时必须以链接形式注明原始出处及本声明

扫描关注公众号