如何在 Android 上用 Frida Hook 進行動態調試
今天我們要來練習用 frida 在 Android 上做動態調試
逆之呼吸壹之型 - 一般函式
先來寫個簡單的範例 APP 有一個按扭和一個輸入欄,輸入名字之後,按下按鈕,就會顯示 Hello 加上你輸入的名字 不會寫 APP 的小朋友可以先去 youtube 上找教學,有一大堆
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sayButton = findViewById(R.id.sayButton);
sayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EditText somethingEditText = findViewById(R.id.somethingEditText);
TextView resultTextView = findViewById(R.id.resultTextView);
String something = somethingEditText.getText().toString();
resultTextView.setText(say(something));
}
});
}
String say (String something) {
return "Hello " + something;
}
}
python 負責呼叫 frida api 做注入,javascript 是被注入進去做事的
我們的目標是 hook 函式 say
,並在原本的輸出文字後面加上 !!!
import frida
def on_message(message, payload):
print(message)
device = frida.get_usb_device()
pid = device.spawn(["com.example.myapplication"])
session = device.attach(pid)
with open("script.js") as f:
script = session.create_script(f.read())
script.on("message", on_message)
script.load()
device.resume(pid)
input()
Java.perform(() => {
main = Java.use("com.example.myapplication.MainActivity");
main.say.implementation = function (something) {
var ret = this.say(something);
return ret + "!!!";
};
});
逆之呼吸貳之型 - 重載函式
改一下範例 APP,多加上一個接受數字做輸入的 say
函式
接收到數字後,就輸出 Hello 加上輸入的數字的平方
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sayButton = findViewById(R.id.sayButton);
sayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EditText somethingEditText = findViewById(R.id.somethingEditText);
TextView resultTextView = findViewById(R.id.resultTextView);
String something = somethingEditText.getText().toString();
try {
Integer number = Integer.parseInt(something);
resultTextView.setText(say(number));
} catch (NumberFormatException e) {
resultTextView.setText(say(something));
}
}
});
}
String say (String something) {
return "Hello " + something;
}
String say (Integer number) {
number = number * number;
return "Hello " + number.toString();
}
}
兩個 say
都是一樣的名字,所以 hook 的時候要用 overload
去區分,overload
參數放的是目標函式輸入參數的型態
這次我們在新的 say
函式的輸出文字後面加上 ???
hook.py
跟上一個例子一樣就不再貼一次了
Java.perform(() => {
main = Java.use("com.example.myapplication.MainActivity");
main.say.overload("java.lang.String").implementation = function (something) {
var ret = this.say(something);
return ret + "!!!";
};
main.say.overload("java.lang.Integer").implementation = function (number) {
var ret = this.say(number);
return ret + "???";
};
});
逆之呼吸參之型 - 隱藏函式
再改一下範例 APP,多加上一個變數 secret
和一個函式 getSecret
,在 onCreate
裡面會給 secret
一個隨機值,我們的目標就是找出這個值
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private int secret;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
secret = (int) (Math.random() * 100);
Button sayButton = findViewById(R.id.sayButton);
sayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EditText somethingEditText = findViewById(R.id.somethingEditText);
TextView resultTextView = findViewById(R.id.resultTextView);
String something = somethingEditText.getText().toString();
try {
Integer number = Integer.parseInt(something);
resultTextView.setText(say(number));
} catch (NumberFormatException e) {
resultTextView.setText(say(something));
}
}
});
}
String say (String something) {
return "Hello " + something;
}
String say (Integer number) {
number = number * number;
return "Hello " + number.toString();
}
int getSecret () {
return secret;
}
}
因為我們的目標不是自己 new 一個物件出來抓 secret
,這樣就只是一個我們自己就可以生成的隨機值而已
我們是要找出目前已經存在的那個 instance 的 secret
,在實際例子中可能就會是一組隨機生成的密碼之類的
所以我們要用到 Java.choose
去抓 instance,抓到 instance 後,可以
instance.getSecret()
呼叫函式搞定instance.secret.value
存取變數搞定
然後用 send
可以把資料傳到 python 端的 on_message
做處理
Java.perform(function () {
Java.choose("com.example.myapplication.MainActivity", {
onMatch: function (instance) {
send(instance.getSecret()); // call function
send(instance.secret.value); // access vairable
},
onComplete: function () {},
});
});
疑難雜症
Q : 有函式 a
跟變數 a
同名怎麼辦 ?
A : a
存取函式,_a
存取變數