Frida-Java层Hook

IDE配置

  1. 安装Node.js
https://nodejs.org/zh-cn/
1
2
apt-get install nodejs
apt-get install npm
  1. 下载frida-agent-example仓库.
https://github.com/oleavr/frida-agent-example
1
git clone https://github.com/oleavr/frida-agent-example.git
  1. 执行命令.
1
2
cd frida-agent-example/
npm install

接下来使用VSCode打开此项目,在agent目录下编写代码就会有智能提示.

Frida基础

https://github.com/frida/frida

安装

  1. 本地Python模块安装.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#下述以安装特定版本为例.
#12.8.0
pip install frida==12.8.0
pip install frida-tools==5.3.0
pip install objection==1.8.4

#14.2.13
pip install frida==14.2.13
pip install frida-tools==9.2.1
pip install objection==1.11.0
  1. frida-sever下载.
https://github.com/frida/frida/releases

下载时要选择对应的版本下载.

1
2
3
4
5
6
7
8
#①frida-server的版本要和计算机上的版本一致.
#如frida版本为14.2.13,那么frida-server的版本也必须是14.2.13

#②frida-server的架构需要和测试机的系统以及架构一致.
#如本人使用的Android测试机Nexus 5X是arm64的架构,就需要下载frida-server相应的arm64的

#可使用下述命令查询系统架构.
adb shell getprop ro.product.cpu.abi

Frida-server

  1. 将下载的frida-server安装到手机并运行.
1
2
3
4
5
adb push frida-server /data/local/tmp/frida-server
adb shell
su
chmod 777 /data/local/tmp/frida-server
/data/local/tmp/frida-server
  1. 检查frida是否安装成功.
1
frida-ps -U

检查frida安装成功

注意

Windows要将frida-ps所在的路径添加到环境变量中.

如:D:\LuoHackTools\Tools\Disassemblers\IDA_Pro_v7.5\python38\Scripts

adbWifi连接

PC和手机要连接同一个Wifi

  1. 下载安装WiFiADB.apk.
1
2
#google 搜apkmirror wifi adb
https://www.apkmirror.com/apk/metactrl/wifi-adb-debug-over-air/
  1. 手机上运行上述apk,进行一些简单的设置.

wifiAdb设置

  1. 拔掉数据线,执行下述命令,进行adb连接.
1
adb connect 192.168.2.111:5555

adbWifi连接1

非标准端口连接

  1. 手机上运行FridaServer,监听指定端口.
1
2
3
4
5
6
#adb connect 192.168.2.111:5555
adb push frida-server /data/local/tmp/frida-server
adb shell
su
chmod 777 /data/local/tmp/frida-server
/data/local/tmp/frida-server -l 0.0.0.0:6666
  1. PC上执行Frida命令.
1
2
3
4
5
6
7
#frida-ps -H 192.168.2.111:6666
frida-ps -H 手机的ip:frida监听的端口

#frida -H 192.168.2.111:6666 com.example.luoanti -l .\LuoHook.js

#objection -N -h 192.168.2.111 -p 6666 -g com.android.settings explore
objection -N -h 手机的ip -p frida监听的端口 -g 安卓应用 explore

基本知识

在Android逆向过程中,Frida存在两种操作模式.

  1. CLI(命令行)模式:通过命令行直接将JavaScript脚本注入进程中,对进程进行操作.
1
2
3
frida -U LuoDst -l luoHook.js 
#-U 指定对USB设备操作.
#-l 指定加载一个Javascript脚本.
  1. RPC模式:使用Python进行JavaScript脚本的注入工作,实际对进程进行操作的还是JavaScript脚本.

Frida脚本入门

Frida脚本就是利用Frida动态插桩框架,使用Frida导出的API和方法对内存空间里的对象方法进行监视、修改和替换的一段代码.

Hook基础

  1. 准备一个测试Apk.
 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
package org.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView tv = findViewById(R.id.tv);
                tv.setText(LuoTst(1, 2));
            }
        });
    }

    String LuoTst(int n1, int n2){
        return "Luo" + String.valueOf(n1 + n2);
    }
   
}

本次我们Hook的目的是修改函数LuoTst的参数.

  1. 编写Js脚本进行Hook.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function() {
        console.log("Inside java perform function");
        //定位类
        var MainActivity = Java.use("org.example.luodst.MainActivity");
        console.log("Java.Use.Successfully!"); //定位类成功!
        //在这里更改类方法的实现(implementation)
        MainActivity.LuoTst.implementation = function (x, y) {
            //打印替换前的参数.
            console.log("original call: LuoTst(" + x + ", " + y + ")");
            //把参数替换成20和50,依旧调用原函数.
            var ret_value = this.LuoTst(20, 50);
            return ret_value;
        }
    });
}

setImmediate(main)

脚本中使用function关键字定义了一个main函数,用于存放Hook脚本,然后调用Frida的API函数java.perform()将脚本中的内容注入到Java运行库.这个API的参数是一个匿名函数,函数内容是监控和修改Java函数逻辑的主体内容.注意,这里的Java.perform()函数非常重要,任何对App中Java层的操作都必须包裹在这个函数中,否则Frida运行起来后就会报错.

在Java.perform()函数包裹的匿名函数中,首先调用了Frida的API函数Java.use(),这个函数的参数是Hook的函数所在类的类名,参数的类型是一个字符串类型.这个函数的返回值动态地为相应Java类获取一个JavaScriptWrapper,可以通俗理解为一个JavaScript对象.

在获取到对应的JavaScript对象后,通过".“符号连接LuoTst这个对应的函数名,然后加上implementation关键词表示实现MainActivity对象的LuoTst()函数,最后通过”=“这个符号连接一个匿名函数,参数内容和原Java的内容一致.不同的是,JavaScript是一个弱类型的语言,不需要指明参数类型.此时一个针对MainActivity类的LuoTst()函数的Hook框架就完成了.

在Hook一个函数时,需要注意一个地方,那就是不要修改被Hook函数的返回值类型,否则会引起程序崩溃等问题.

setImmediate(Frida的API函数)函数传递的参数是要被执行的函数,比如传入main参数,表示当Frida注入App后立即执行main函数.这个函数和setTimeout()函数类似,都是用于指定要执行的函数,不同的是setTimeout可以用于指定Frida注入App多长时间后执行函数,往往用于延时注入.如果传递的第二个参数为0或者压根没有第二个参数,就和setImmediate()函数的作用一样.

  1. 手机端执行frida-server,PC端执行Js脚本.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//手机端
adb shell
su
chmod 777 /data/local/tmp/frida-server
/data/local/tmp/frida-server

//PC端
//CLI模式
//Frida脚本是及时生效的,第一次注入使用下述命令,后续修改脚本内容,不用重新注入,只要重新保存脚本内容即可.
frida -U -l luoHook.js LuoDst
  1. Hook后,点击按钮显示 Luo:70

Hook基础Hook后

Java重载函数Hook

  1. 准备一个测试Apk.
 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
package org.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView tv = findViewById(R.id.tv);
                tv.setText(LuoTst(1, 2));
            }
        });
    }

    String LuoTst(int n1, int n2){
        return String.valueOf(n1 + n2);
    }

    String LuoTst(String str){
        return str.toLowerCase();
    }
    
}

可以看到LuoTst()方法有了重载,在参数是两个int类型的情况下,返回两个整数之和;当参数类型为String类型时,返回字符串的小写形式.

本次我们Hook的目的是修改重载函数LuoTst(int, int)的参数.

  1. 编写Js脚本进行Hook.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function() {
        console.log("Inside java perform function");
        //定位类
        var MainActivity = Java.use("org.example.luodst.MainActivity");
        console.log("Java.Use.Successfully!"); //定位类成功!
        //在这里更改类方法的实现(implementation).
        MainActivity.LuoTst.overload('int', 'int').implementation = function (x, y) {
            //打印替换前的参数.
            console.log("original call: LuoTst(" + x + ", " + y + ")");
            //把参数替换成20和50,依旧调用原函数.
            var ret_value = this.LuoTst(20, 50);
            return ret_value;
        }
    });
}

setImmediate(main)

Frida对于函数重载Hook问题,提供了解决方案(func.overload()),就是指定函数签名.如上述中在要Hook的函数名后、关键词implementation之前添加.overload(‘int’, ‘int’)来指明具体Hook的重载函数,对于String类型,可以添加.overload(‘java.lang.String’)

  1. 手机端执行frida-server,PC端执行Js脚本.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//手机端
adb shell
su
chmod 777 /data/local/tmp/frida-server
/data/local/tmp/frida-server

//PC端
//CLI模式
//Frida脚本是及时生效的,第一次注入使用下述命令,后续修改脚本内容,不用重新注入,只要重新保存脚本内容即可.
frida -U -l luoHook.js LuoDst
  1. Hook后,点击按钮显示 70

Java重载函数Hook2

主动调用

主动调用就是强制调用一个函数去执行.相应地,被动调用是由App按照正常逻辑去执行函数,函数的执行完全依靠与用户交互完成程序逻辑进而间接调用到关键函数,而主动调用则可以直接调用关键函数,主动性更强,甚至可以直接完成关键数据的"自吐”.在逆向分析过程中,如果不想分析详细的算法逻辑,可以直接通过主动传递参数来调用关键算法函数,忽略方法函数的实现过程直接得到密文或者明文,可以说是各种算法调用的"克星".

在Java中,类中的函数可分为两种:类函数和实例方法.通俗的讲,就是静态的方法和动态的方法.类函数使用关键字static修饰,和对应类是绑定的,如果类函数还被public关键字修饰,在外部就可以直接通过类去调用.实例方法没有关键字static修饰,在外部只能通过创建对应类的实例再通过这个实例去调用.

在Frida中主动调用的类型会根据方法类型区分,如果是类函数的主动调用,直接使用Java.use()函数找到类进行调用即可;如果是实例方法的主动调用,则需要在找到对应的实例后对方法进行调用.这里用到了Frida中非常重要的一个API函数Java.choose(),这个函数可以在Java的堆中寻找指定类的实例.

示例如下:

  1. 准备一个测试Apk.
 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
package org.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView tv = findViewById(R.id.tv);
                tv.setText(LuoTst(1, 2));
            }
        });
    }

    String LuoTst(int n1, int n2){
        return String.valueOf(n1 + n2);
    }

    String LuoTst(String str){
        return str.toLowerCase();
    }

    void secret(){
        Log.d("LuoHun", "this is secret func");
    }

    static void staticSecret(){
        Log.d("LuoHun", "this is static secret func");
    }

}

上述代码中新增了两个secret()函数,一个是没有static修饰的secret实例方法,一个是有static关键字修饰的staticSecret类方法.

本次我们Hook的目的是主动调用静态函数staticSecret以及动态函数secret.

  1. 编写Js脚本进行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
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function() {
        console.log("Inside java perform function");
        
        //静态函数主动调用
        var MainActivity = Java.use("org.example.luodst.MainActivity")
        MainActivity.staticSecret()

        //动态函数主动调用
        Java.choose("org.example.luodst.MainActivity",{
            onMatch: function(instance){
                console.log("instance found", instance)
                instance.secret()
            },
            onComplete: function(){
                console.log("search Complete")
            }
        })

    });
}

setImmediate(main)
  1. 手机端执行frida-server,PC端执行Js脚本.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//手机端
adb shell
su
chmod 777 /data/local/tmp/frida-server
/data/local/tmp/frida-server

//PC端
//CLI模式
//Frida脚本是及时生效的,第一次注入使用下述命令,后续修改脚本内容,不用重新注入,只要重新保存脚本内容即可.
frida -U -l luoHook.js LuoDst
  1. Hook后,可以看到新增了两条日志.

主动调用日志

如果需要主动调用动态函数,必须确保存在相应类的对象,否则无法进入Java.choose这个API的回调onMatch逻辑中.比如MainActivity类对象,由于App在打开后确实运行在MainActivity界面上,那么这个对象就一定会存在,这就是所谓的"所见即所得"思想,这个思想在主动调用的过程中非常重要.

RPC及其自动化

远程调用

在Frida中,可以使用Python完成JavaScript脚本对进程的注入以及相应的Hook.

  1. 准备一个测试Apk.
 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
package org.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private String total = "Hello";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView tv = findViewById(R.id.tv);
                tv.setText(LuoTst(1, 2));
            }
        });
    }

    String LuoTst(int n1, int n2){
        return String.valueOf(n1 + n2);
    }

    String LuoTst(String str){
        return str.toLowerCase();
    }

    void secret(){
        total +=" secretFunc";
        Log.d("LuoHun", "this is secret func");
    }

    static void staticSecret(){
        Log.d("LuoHun", "this is static secret func");
    }

}

在上述代码中,增加了一个字符串类型的实例变量total,同时每次调用secret()函数对字符串进行扩展.本次我们Hook的目的是获取total这个实例变量的值.

在主动调用时需要注意的是,Java中的变量也存在是否使用static修饰的区别.

  1. 编写Js脚本进行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
function CallSecretFunc() {
    Java.perform(function () {
        //动态函数主动调用
        Java.choose("org.example.luodst.MainActivity", {
            onMatch: function (instance) {
                instance.secret()
            },
            onComplete: function () {}
        })
    });
}

function getTotalValue() {
    Java.perform(function () {
        //动态字段主动调用
        Java.choose("org.example.luodst.MainActivity", {
            onMatch: function (instance) {
                console.log("total value = ", instance.total.value)
            },
            onComplete: function () {}
        })
    })
}

rpc.exports = {
    callsecretfunc: CallSecretFunc,
    gettotalvalue: getTotalValue
};

如果要获取变量的值,只需要在变量后加上一个.value关键词.如上述的total.value

上述代码最后的RPC代码实现的功能是将CallSecretFunc()函数和getTotalValue()函数分别导出为callsecretfunc和gettotalvalue.需要注意的是,导出名不可以有大写字母或者下划线.

  1. 编写Python代码来加载上述的Js代码.
 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

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

#无线连接
# /data/local/tmp/frida-server -l 0.0.0.0:6666
# Wifi ADB监听IP和端口为192.168.2.111:5555
#device = frida.get_device_manager().add_remote_device("192.168.2.111:6666")

#有线连接
device = frida.get_usb_device()

process = device.attach("com.example.luodst")

with open("luoHook.js",  encoding = "UTF-8") as f:
    jsCode = f.read()
script = process.create_script(jsCode)

script.on('message', on_message)
script.load()

command = ""
while 1 == 1:
    command = input("\nEnter command:\n1: Exit\n2: Call secret function\n3: Get Total Value\nchoice:")
    if command == "1":
        break
    elif command == "2":
        script.exports.callsecretfunc()
    elif command == "3":
        script.exports.gettotalvalue()

重新运行App,然后直接运行loader.py,运行结果如下图.

RPCLoader

和单纯执行JavaScript是一致的,下面对Frida相关代码进行说明.

首先通过frida.get_usb_device()获取到USB设备句柄;然后通过device.attach(“LuoDst”)对LuoDst进行注入;接着使用create_script()函数加载上述编写的JavaScript代码,并使用script.on(‘message’, on_message)注册了自己的消息对应的函数,每当JavaScript想要输出时,都会经过这里指定的on_message进行;最后,也就是最重要的RPC调用代码,即通过script.exports访问所有我们在JavaScript中定义的导出名,进而调用导出函数.这样就完成了RPC远程调用,达到了在主机上可以随意调用App代码的目的.

互联互通

本次实验主要是记录Frida中Python和JavaScript代码的互通过程,由Python发送数据到JavaScript进而影响Hook的结果.

  1. 准备一个测试Apk.
 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView tv = findViewById(R.id.tv);
                tv.setText("Hello com.example.luodst !");
            }
        });

    }
}

本次我们Hook的目标函数是android.widget.TextView.setText(CharSequence text)

  1. JavaScript代码.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//luoHook.js
Java.perform(function () {
    console.log("Inside java perform function");
    Java.use("android.widget.TextView").setText.overload('java.lang.CharSequence').implementation = function (x) {
        var strSend = x.toString();
        var strRecv;

        send(strSend);

        recv(function (received_json_objection) {
            strRecv = received_json_objection.my_data;
            console.log("strRecv ", strRecv)
        }).wait()

        var javaStr = Java.use('java.lang.String').$new(strRecv);
        var result = this.setText(javaStr)

        console.log("javaStr result", javaStr, result)
        return result
    }
})
  1. 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
#loader.py
import frida
import sys

def on_message(message, payload):
    print(message)
    if message['type'] == 'send':
        print(message["payload"])
        data = "Hello XiaLuoHun !"
        script.post({"my_data": data})
        print("Modified data send !")


#无线连接
# /data/local/tmp/frida-server -l 0.0.0.0:6666
# Wifi ADB监听IP和端口为192.168.2.111:5555
#device = frida.get_device_manager().add_remote_device("192.168.2.111:6666")

#有线连接
device = frida.get_usb_device()

process = device.attach("com.example.luodst")

with open("luoHook.js", encoding="UTF-8") as f:
    jsCode = f.read()
script = process.create_script(jsCode)

script.on('message', on_message)
script.load()
input()
  1. 运行结果如下图.

RPC互联互通

参数构造

Frida的JavaScript API
技巧
开发时如何打印,Frida中也是如何打印.

数组

Java代码如下:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("SimpleArray", "onCreate: SImpleArray");
        char arr[][] = new char[4][]; // 创建一个4行的二维数组
        arr[0] = new char[] { '春', '眠', '不', '觉', '晓' }; // 为每一行赋值
        arr[1] = new char[] { '处', '处', '闻', '啼', '鸟' };
        arr[2] = new char[] { '夜', '来', '风', '雨', '声' };
        arr[3] = new char[] { '花', '落', '知', '多', '少' };
        Log.d("SimpleArray", "-----横版-----");
        for (int i = 0; i < 4; i++) { // 循环4行
            Log.d("SimpleArraysToString", Arrays.toString(arr[i]));
            //Log.d("SimpleStringBytes", Arrays.toString (Arrays.toString (arr[i]).getBytes()));
            for (int j = 0; j < 5; j++) { // 循环5列
                //Log.d("SimpleArray", Character.toString(arr[i][j])); // 输出数组中的元素
            }
            if (i % 2 == 0) {
                Log.d("SimpleArray", ",");// 如果是一、三句,输出逗号
            } else {
                Log.d("SimpleArray", "。");// 如果是二、四句,输出句号
            }
        }

    }
}

目前想要Hook上述代码中如下语句:

1
2
//Arrays.toString(arr[i]) 参数为数组
Log.d("SimpleArraysToString", Arrays.toString(arr[i]));

JavaScript代码如下:

1
2
3
4
5
6
//Frida JavaScript API示例
//Java.array(type, elements)

const values = Java.array('int', [ 1003, 1005, 1007 ]);
const JString = Java.use('java.lang.String');
const str = JString.$new(Java.array('byte', [ 0x48, 0x65, 0x69 ]));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Hook代码
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {
        console.log("Inside java perform function");
        //定位类
        var clsArrays = Java.use("java.util.Arrays");
        console.log("Java.Use.Successfully!"); //定位类成功!
        //在这里更改类方法的实现(implementation)
        clsArrays.toString.overload('[C').implementation = function (x) {

            //这里x的类型是一个char数组
            //构造一个char数组
            var charArray = Java.array('char', [ '一','去', '二', '三', '里' ]);

            var result = this.toString(charArray)
            //关注这里打印的日志
            console.log("x, result", JSON.stringify(charArray), result)
            return result
        }
    });
}

setImmediate(main)

强制类型转换

在Frida中,将父类转换为子类是不行的,但是可以把子类转换为父类,从而调用父类的方法.

Java代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.example.luodst;

import android.util.Log;

public class Water { // 水 类
    public static String flow(Water W) { // 水 的方法
        // SomeSentence
        Log.d("2Object", "water flow: I`m flowing");
        return "water flow: I`m flowing";
    }

    public String still(Water W) { // 水 的方法
        // SomeSentence
        Log.d("2Object", "water still: still water runs deep!");
        return "water still: still water runs deep!";
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.luodst;

import android.util.Log;

public class Juice extends Water { // 果汁 类 继承了水类

    public String fillEnergy() {
        Log.d("2Object", "Juice: i`m fillingEnergy!");
        return "Juice: i`m fillingEnergy!";
    }

    public static void main() {
        Water w1 = new Water();
        flow(w1); //

        Juice J = new Juice(); // 实例化果汁类对象
        flow(J); // 调用水的方法  向上转型 J → W

        Water w2 = new Juice();
        ((Juice) w2).fillEnergy();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Juice.main();
    }
}

JavaScript代码如下:

1
2
3
4
5
//Frida JavaScript API示例
//Java.cast(handle, klass)

const Activity = Java.use('android.app.Activity');
const activity = Java.cast(ptr('0x1234'), Activity);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {

        var JuiceHandle = null ;
        Java.choose("com.example.luodst.Juice",{
            onMatch:function(instance){
                console.log("found instance :",instance);
                console.log("filling energy,",instance.fillEnergy());
                JuiceHandle= instance;
            },onComplete:function(){"Search Completed!"}
        })
        var WaterHandle = Java.cast(JuiceHandle ,Java.use("com.example.luodst.Water"));
        console.log("Water invoke still ", WaterHandle.still(WaterHandle));

    });
}

setImmediate(main)

接口

Java代码如下:

1
2
3
4
5
package com.example.luodst;

public interface liquid {
    public String flow();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.example.luodst;

import android.util.Log;

public class milk implements liquid {
    public String flow() {
        Log.d("3interface", "flowing : interface ");
        return "nihao";
    }

    public static void main() {
        milk m = new milk();
        m.flow();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        milk.main();
    }
}

JavaScript代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//Frida JavaScript API示例
//Java.registerClass(spec)

const SomeBaseClass = Java.use('com.example.SomeBaseClass');
const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');

const MyTrustManager = Java.registerClass({
  name: 'com.example.MyTrustManager',
  implements: [X509TrustManager],
  methods: {
    checkClientTrusted(chain, authType) {
    },
    checkServerTrusted(chain, authType) {
    },
    getAcceptedIssuers() {
      return [];
    },
  }
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {

        var beer = Java.registerClass({
            name: 'com.example.luodst.beer',
            implements: [Java.use('com.example.luodst.liquid')],
            methods: {
                flow: function () {
                    console.log("look I`m beer!");
                    return "taste good!";
                }
            }
        });
        console.log("beer.flow:",beer.$new().flow()); 

    });
}

setImmediate(main)

枚举

https://www.cnblogs.com/jingmoxukong/p/6098351.html

Java枚举.pdf

除了不能继承,基本上可以把枚举看做一个常规的类.

Java代码如下:

 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
package com.example.luodst;

import android.util.Log;

enum Signal {
    GREEN, YELLOW, RED
}
public class TrafficLight {
    public static Signal color = Signal.RED;
    public static void main() {
        //Log.d("4enum", "enum "+ color.getClass().getName().toString());
        Log.d("4enum", "enum "+ color);
        switch (color) {
            case RED:
                color = Signal.GREEN;
                break;
            case YELLOW:
                color = Signal.RED;
                break;
            case GREEN:
                color = Signal.YELLOW;
                break;
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TrafficLight.main();
    }
}

JavaScript代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {

        Java.choose("com.example.luodst.Signal",{
            onMatch:function(instance){
                console.log("found instance:",instance)
                console.log("invoke getDeclaringClass",instance.getDeclaringClass())
            },onComplete:function(){console.log("Search Completed!")}
        })

    });
}

setImmediate(main)

Map

Java代码如下:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Map<String, String> LuoMap = new HashMap<>(); // 创建Map集合对象
        LuoMap.put("ISBN 978-7-5677-8742-1", "Android项目开发实战入门"); // 向Map集合中添加元素
        LuoMap.put("ISBN 978-7-5677-8741-4", "C语言项目开发实战入门");
        LuoMap.put("ISBN 978-7-5677-9097-1", "PHP项目开发实战入门");
        LuoMap.put("ISBN 978-7-5677-8740-7", "Java项目开发实战入门");

        //Log.d("5map", "key值toString"+LuoMap.toString());

        Set<String> set = LuoMap.keySet(); // 构建Map集合中所有key的Set集合
        Iterator<String> it = set.iterator(); // 创建Iterator迭代器
        Log.d("5map", "key值:");
        while (it.hasNext()) { // 遍历并输出Map集合中的key值
            try {
                Thread.sleep(1000);
                Log.d("5map", it.next()+"  ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

JavaScript代码如下:

下述代码,用来打印Map中存放的值.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {

        Java.choose("java.util.HashMap", {
            onMatch: function (instance) {
                if (instance.toString().indexOf("ISBN") != -1) {
                    console.log("found instance:", instance)
                    console.log("invoke ", instance.toString())
                }
            },
            onComplete: function () {
                console.log("Search Completed!")
            }
        })

    });
}

setImmediate(main)

Non-ASCII

https://api-caller.com/2019/03/30/frida-note/

Frida-Non-ASCII.pdf

Java代码如下:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("LuoHun", "onCreate: " + ֏(10));
    }

    int ֏(int x) {
        return x + 100;
    }
}

JavaScript代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {

        var targetClass = "com.example.luodst.MainActivity";
        var hookCls = Java.use(targetClass);
        var methods = hookCls.class.getDeclaredMethods();
        for (var i in methods) {
            console.log(methods[i].toString());
            console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));
        }

        hookCls[decodeURIComponent("%D6%8F")]
            .implementation = function (x) {
                console.log("original call: fun(" + x + ")");
                var result = this[decodeURIComponent("%D6%8F")](900);
                return result;
            }

    });
}

setImmediate(main)

小技巧

打印堆栈

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function() {
        console.log("Inside java perform function");
        //定位类
        var MainActivity = Java.use("com.example.luodst.MainActivity");
        console.log("Java.Use.Successfully!"); //定位类成功!
        //在这里更改类方法的实现(implementation)
        MainActivity.LuoTst.implementation = function (x, y) {

            //打印堆栈调用
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

            //打印替换前的参数.
            console.log("original call: LuoTst(" + x + ", " + y + ")");
            //把参数替换成20和50,依旧调用原函数.
            var ret_value = this.LuoTst(20, 50);
            return ret_value;
        }
    });
}

setImmediate(main)

启动一个App并Hook

1
2
3
4
//frida -U -f com.example.luodst -l luoHook.js
//%resume
//-f选项 就是创建一个进程
frida -U -f com.example.luodst -l luoHook.js --no-pause

打印Object

可以调Gson库或者使用Frida内部的API来打印.

https://github.com/google/gson

Java代码如下:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("SimpleArray", "onCreate: SImpleArray");
        char arr[][] = new char[4][]; // 创建一个4行的二维数组
        arr[0] = new char[] { '春', '眠', '不', '觉', '晓' }; // 为每一行赋值
        arr[1] = new char[] { '处', '处', '闻', '啼', '鸟' };
        arr[2] = new char[] { '夜', '来', '风', '雨', '声' };
        arr[3] = new char[] { '花', '落', '知', '多', '少' };
        Log.d("SimpleArray", "-----横版-----");
        for (int i = 0; i < 4; i++) { // 循环4行
            Log.d("SimpleArraysToString", Arrays.toString(arr[i]));
            //Log.d("SimpleStringBytes", Arrays.toString (Arrays.toString (arr[i]).getBytes()));
            for (int j = 0; j < 5; j++) { // 循环5列
                //Log.d("SimpleArray", Character.toString(arr[i][j])); // 输出数组中的元素
            }
            if (i % 2 == 0) {
                Log.d("SimpleArray", ",");// 如果是一、三句,输出逗号
            } else {
                Log.d("SimpleArray", "。");// 如果是二、四句,输出句号
            }
        }

    }
}

目前想要Hook上述代码中如下语句:

1
2
//Arrays.toString(arr[i]) 参数为数组
Log.d("SimpleArraysToString", Arrays.toString(arr[i]));

编写的JavaScript代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {
        console.log("Inside java perform function");
        //定位类
        var clsArrays = Java.use("java.util.Arrays");
        console.log("Java.Use.Successfully!"); //定位类成功!
        //在这里更改类方法的实现(implementation)
        clsArrays.toString.overload('[C').implementation = function (x) {
            var result = this.toString(x)
            //关注这里打印的日志
            console.log("x, result", x, result)
            return result
        }
    });
}

setImmediate(main)

打印Object1

Frida内部API打印

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {
        console.log("Inside java perform function");
        //定位类
        var clsArrays = Java.use("java.util.Arrays");
        console.log("Java.Use.Successfully!"); //定位类成功!
        //在这里更改类方法的实现(implementation)
        clsArrays.toString.overload('[C').implementation = function (x) {
            var result = this.toString(x)
            //关注这里打印的日志
            console.log("x, result", JSON.stringify(x), result)
            return result
        }
    });
}

setImmediate(main)

打印Object2

Gson打印

https://bbs.pediy.com/thread-259186.htm

用法如下:

  1. adb push到fridaserver同目录下之下.
  2. 代码.
1
2
3
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use('com.r0ysue.gson.Gson');
console.log(gson.$new().toJson(xxx));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function main() {
    console.log("Script loaded successfully ");
    Java.perform(function () {
        console.log("Inside java perform function");

        Java.openClassFile("/data/local/tmp/r0gson.dex").load();
        const gson = Java.use('com.r0ysue.gson.Gson');
        //console.log(gson.$new().toJson(xxx));

        //定位类
        var clsArrays = Java.use("java.util.Arrays");
        console.log("Java.Use.Successfully!"); //定位类成功!
        //在这里更改类方法的实现(implementation)
        clsArrays.toString.overload('[C').implementation = function (x) {
            var result = this.toString(x)
            //关注这里打印的日志
            console.log("x, result", gson.$new().toJson(x), result)
            return result
        }
    });
}

setImmediate(main)

打印Object3

枚举(ClassLoaders)

  1. 准备一个测试App.

frida_example_1.1.apk

  1. 查看相关代码

FridaActivity5

FridaActivity6

  1. Hook

frida_example_1.1.js

 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
function fifth() {
    Java.perform(function () {
        Java.choose("com.example.androiddemo.Activity.FridaActivity5", {
            onMatch: function (instance) {
                console.log("found instance getDynamicDexCheck", instance.getDynamicDexCheck().$className)
            },
            onComplete: function () {
                console.log("search Complete")
            }
        })

        Java.enumerateClassLoaders({
            onMatch: function (loader) {
                try {
                    if (loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")) {
                        console.log("Succefully found loader!", loader);
                        Java.classFactory.loader = loader;
                    }
                } catch (error) {
                    console.log("found error " + error)

                }
            },
            onComplete: function () {
                console.log("enum completed!")
            }
        })
        Java.use("com.example.androiddemo.Dynamic.DynamicCheck").check.implementation = function () {
            return true
        };
    })
}

function sixth() {
    Java.perform(function () {
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class0").check.implementation = function () {
            return true
        };
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class1").check.implementation = function () {
            return true
        };
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class2").check.implementation = function () {
            return true
        };
    })
}

function sixth2() {
    Java.perform(function () {
        Java.enumerateLoadedClasses({
            onMatch: function (name, handle) {
                if (name.toString().indexOf("com.example.androiddemo.Activity.Frida6.Frida6") >= 0) {
                    console.log("name", name)
                    Java.use(name).check.implementation = function () {
                        return true
                    }
                }
            },
            onComplete: function () {
                console.log("enum Complete")
            }
        })
    })
}

setImmediate(sixth2)

相关内容

0%