记录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后安装在测试机,连接到主机通过 可以查看系统日志。
然后接下来是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;
};
});
执行脚本
查看日志变化
远程调用
这个实力主要是实现在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脚本得到下面内容:
动态修改
这里主要实现的功能不仅仅是可以用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界面
接下来操作是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。
执行结果如下:
然后就实现了动态内容的修改。
参考链接
Android Application Security Study [https://github.com/r0ysue/AndroidSecurityStudy]
Frida Android hook[https://eternalsakura13.com/2020/07/04/frida/]