记录frida学习的一些东西。

环境

  • kali2020
  • 小米6已ROOT刷入android10原生系统
  • python版本3.8

安装

安装库

pip install frida
pip install frida-tools

也可以直接安装对应版本

pip install frida==x.x.x
pip install frida-tools==x.x.x

安装server

下载server

直接进入 frida rlease 页面下载,这里要与安装的frida库版本对应,同时与手机架构对应。

安装server

  • adb push frida-server /data/local/tmp # 把serverpush到手机中
  • adb shell # 进入手机控制台
  • su # 获取控制权限
  • cd /data/local/tmp #进入目录
  • chmod 777 frida-server #添加权限
  • ./frida-server & #添加到后台运行

可以通过命令frida-ps -R 检查是否成功。

frida 基础

基础样例

import time
import frida

# 连接安卓机上的frida-server
device = frida.get_usb_device(10)
# 启动`demo02`这个app
pid = device.spawn(["com.minhal.demo2"])
device.resume(pid)#通过pid重新启动
time.sleep(1)
session = device.attach(pid)
# 加载a.js脚本
with open("a.js") as f:
    script = session.create_script(f.read())#上一步连接到的session 去执行js
script.load()

# 脚本会持续运行等待输入
input()

hook参数、修改结果(重载、隐藏函数的处理)

demo样例源代码。

package com.minhal.demo2;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
    private String total = "@@@###@@@";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    while (true){

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        fun(50,30);
        Log.d("Minhal.string" , fun("Fuck U!!!!!!!!!"));
    }

}
    void fun(int x , int y ){
        Log.d("Minhal.Sum" , String.valueOf(x+y));
    }
    String fun(String x){
        total +=x;
        return x.toLowerCase();
    }

    String secret(){
        return total;
    }
}

把这段代码编译成apk后安装在测试机,连接到主机通过 可以查看系统日志。

1

然后接下来是js代码

console.log("Script loaded successfully ");
Java.perform(function x() {
    console.log("Inside java perform function");
    //定位类
    console.log("begin");
    Java.choose("com.minhal.demo2.MainActivity" , {
    onMatch : function(instance){ //该类有多少个实例,该回调就会被触发多少次
        console.log("Found instance: "+instance);
        console.log("Result of secret func: " + instance.secret());
    },
    onComplete:function(){}
    });
    console.log("end");
    var my_class = Java.use("com.minhal.demo2.MainActivity");
    var string_class = Java.use("java.lang.String"); //获取String类型
    console.log("Java.Use.Successfully!");//定位类成功!
    //在这里更改类的方法的实现(implementation)
    my_class.fun.overload("int" , "int").implementation = function(x,y){
        //打印替换前的参数
        console.log( "original call: fun("+ x + ", " + y + ")");
        //把参数替换成2和5,依旧调用原函数
        var ret_value = this.fun(2, 5);
        return ret_value;
    }
    my_class.fun.overload("java.lang.String").implementation = function(x){
        console.log("*************************************");
        var my_string = string_class.$new("My TeSt String#####"); //new一个新字符串
        console.log("Original arg: " +x );
        var ret =  this.fun(my_string); // 用新的参数替换旧的参数,然后调用原函数获取结果
        console.log("Return value: "+ret);
        console.log("*************************************");
        return ret;
      };
    });

执行脚本

3

查看日志变化

2

远程调用

这个实力主要是实现在py脚本中也可以调用secret函数。这里主要是使用的frida提供的RPC功能(Remote Procedure Call)

apk文件还是上一个样例的文件。

现在修改下js脚本。

console.log("Script loaded successfully ");
function callsecretFun(){
    Java.perform(function x() {
        console.log("Inside java perform function");
        //定位类
        console.log("begin");
        Java.choose("com.minhal.demo2.MainActivity" , {
        onMatch : function(instance){ //该类有多少个实例,该回调就会被触发多少次
            console.log("Found instance: "+instance);
            console.log("Result of secret func: " + instance.secret());
        },
        onComplete:function(){}
        });
        console.log("end");
        
       
    });
}
rpc.exports = {
    callsecretfunction:callsecretFun//把callSecretFun函数导出为callsecretfunction符号,导出名不可以有大写字母或者下划线
};

然后修改对应的py代码

import time
import frida

# 连接安卓机上的frida-server
device = frida.get_usb_device(10)
# 启动`demo02`这个app
pid = device.spawn(["com.minhal.demo2"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
# 加载s1.js脚本
with open("a.js") as f:
    script = session.create_script(f.read())
script.load()

command = ""
while 1 == 1:
    command = input("Enter command:\n1: Exit\n2: Call secret function\nchoice:")
    if command == "1":
        break
    elif command == "2": #在这里调用
        script.exports.callsecretfunction()

运行python脚本得到下面内容:

4

动态修改

这里主要实现的功能不仅仅是可以用python调用app的函数。还要做到把数据从app传到python程序中,通过python代码修改传回到app里。
app代码:

package com.minhal.demo3;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    EditText username_et;
    EditText password_et;
    TextView message_tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        password_et = (EditText) this.findViewById(R.id.editText2);
        username_et = (EditText) this.findViewById(R.id.editText);
        message_tv = ((TextView) findViewById(R.id.textView));

        this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (username_et.getText().toString().compareTo("admin") == 0) {
                    message_tv.setText("You cannot login as admin");
                    return;
                }
                //hook target
                message_tv.setText("Sending to the server :" + Base64.encodeToString((username_et.getText().toString() + ":" + password_et.getText().toString()).getBytes(), Base64.DEFAULT));

            }
        });

    }
}

app界面

5

接下来操作是python代码获取输入内容,并修改输入内容然后传输到app,通过验证。(包括admin)
js代码主要实现是先截到输入内容,传输到python代码,然后等python传入新数据继续执行。
js代码

console.log("Script loaded successfully ");

Java.perform(function () {
    var tv_class = Java.use("android.widget.TextView");
    tv_class.setText.overload("java.lang.CharSequence").implementation = function (x) {
        var string_to_send = x.toString();
        var string_to_recv;
        console.log("Script loaded successfully ");
        send(string_to_send); // 将数据发送给python的python代码
        recv(function (received_json_object) {
            string_to_recv = received_json_object.my_data
            console.log("string_to_recv: " + string_to_recv);
        }).wait(); //收到数据之后,再执行下去
        var my_string = Java.use("java.lang.String").$new(string_to_recv);
        this.setText(my_string);
    }
});

python代码

import time
import frida
import base64
def my_message_handler(message, payload):
    print(message)
    print(payload)
    if message["type"] == "send":
        print (message["payload"])
        data = message["payload"].split(":")[1].strip()
        print(data)
        print ('message:', message)
        data = str(base64.b64decode(data))
        user,pw = data.split(":")
        print("user:",user)
        data =str(base64.b64encode(("admin" + ":" + pw).encode()))
        print ("encoded data:", data)
        script.post({"my_data": data})  # 将JSON对象发送回去
        print ("Modified data sent")


# 连接安卓机上的frida-server
device = frida.get_usb_device(10)
# 启动`demo02`这个app
pid = device.spawn(["com.minhal.demo3"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
# 加载a.js脚本
with open("a.js") as f:
    script = session.create_script(f.read())
script.on("message", my_message_handler)
script.load()
input()

直接运行python代码,然后输入新的用户名和密码,我们原程序是本来不可以输入admin的,我们本代码就是通过输入其他内容,通过frida更改他的用户名参数,使得输入内容用户名为admin。
执行结果如下:

6

然后就实现了动态内容的修改。

参考链接

Android Application Security Study [https://github.com/r0ysue/AndroidSecurityStudy]
Frida Android hook[https://eternalsakura13.com/2020/07/04/frida/]