Reprinted from Sakura, blog: eternalsakura13.com/2020/07/04/…
title: Frida Android hook categories:
- Android reverse
Thank you
This article is based entirely and exclusively on r0ySue’s planet of Knowledge (here’s a guy who can do everything and flirt with you online).
Frida environment
github.com/frida/frida
pyenv
Random switching of all python versions, here is the configuration method on macOS
brew update
brew install pyenv
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bash_profile
Copy the code
Pyenv install 3.8.2 Pyenv versions sakura@sakuradeMacBook-Pro:~$Pyenv versions system * 3.8.2 (setBy /Users/sakura/.python-version) to switch to pyenvlocal3.8.2 Python -v PIP -v The original Python of the systemlocal system
python -V
Copy the code
Also when you need to temporarily disable PyEnv
About uninstalling a Python version
Uninstalling Python Versions
As time goes on, you will accumulate Python versions in your $(pyenv root)/versions directory.
To remove old Python versions, pyenv uninstall commandto automate the removal process. Alternatively, simply rm -rf the directory of the version you want to remove. You can find the directory of a particular Python version with the pyenv prefixcommandPyenv prefix 2.6.8.Copy the code
Frida installation
If you install frida and frida-Tools directly, you will install the latest version of Frida and Frida-Tools directly.
pip install frida-tools
frida --version
frida-ps --version
Copy the code
We are also free to install older versions of Frida, such as 12.8.0
Pyenv install 3.7.7 pyenvlocal 3.7.7
pip install frida==12.8.0
pip install frida-tools==5.3.0
Copy the code
The old version of Frida and correspondence is easy to find
Install the objection
pyenv local3.8.2 PIP install obedience -hCopy the code
pyenv local3.7.7 PIP install obedience ==1.8.4 -hCopy the code
Frida use
Download frida-server and unzip it. Download frida-server-12.8.0 here
Adb shell, switch to root, change the name of frida server to FS and run Frida
The adb push/Users/sakura/Desktop/lab/alpha/tools/android/frida - server - 12.8.0 - android - arm64 / data /local/tmp
chmod +x fs
./fs
Copy the code
If you want to listen on a port, just
./fs -l 0.0.0.0:8888
Copy the code
Frida development environment setup
- The installation
git clone https://github.com/oleavr/frida-agent-example.git
cd frida-agent-example/
npm install
Copy the code
- Use VScode to open this project, write JS under agent folder, there will be intelligent prompt.
npm run watch
Automatically generates JS files to monitor code changes- Load _agent.js in python script or CLI
frida -U -f com.example.android --no-pause -l _agent.js
Here is the test script
s1.js
function main() {
Java.perform(function x() {
console.log("sakura")
})
}
setImmediate(main)
Copy the code
loader.py
import time
import frida
device8 = frida.get_device_manager().add_remote_device("192.168.0.9:8888")
pid = device8.spawn("com.android.settings")
device8.resume(pid)
time.sleep(1)
session = device8.attach(pid)
with open("si.js") as f:
script = session.create_script(f.read())
script.load()
input() # wait for input
Copy the code
To explain, the script is to find the device via frida.get_device_manager().add_remote_device, launch the Settings in spawn mode, attach to it, and execute the Frida script.
FRIDA basis
Frida View existing processes
Frida -ps -u Views processes on an Android phone connected via USB.
sakura@sakuradeMacBook-Pro:~$ frida-ps --help
Usage: frida-ps [options]
Options:
--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 applicationsCopy the code
sakura@sakuradeMacBook-Pro:~$ frida-ps -U PID Name ----- --------------------------------------------------- 3640 ATFWD daemon adbd adsprpcd [email protected] 26041 728 707-741 android. Hardware. Biometrics. Fingerprint @Copy the code
Grep filter to find the package name we want.
Frida prints parameters and modifies return values
package myapplication.example.com.frida_demo;
import android.support.v7.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("sakura.string" , fun("LoWeRcAsE Me!!!!!!!!!")); }}void fun(int x , int y ){
Log.d("sakura.Sum" , String.valueOf(x+y));
}
String fun(String x){
total +=x;
return x.toLowerCase();
}
String secret(a){
returntotal; }}Copy the code
function main() {
console.log("Enter the Script!");
Java.perform(function x() {
console.log("Inside Java perform");
var MainActivity = Java.use("myapplication.example.com.frida_demo.MainActivity");
// Overload finds the specified function
MainActivity.fun.overload('java.lang.String').implementation = function (str) {
// Prints parameters
console.log("original call : str:" + str);
// Modify the result
var ret_value = "sakura";
return ret_value;
};
})
}
setImmediate(main);
Copy the code
sakura@sakuradeMacBook-Pro:~$ frida-ps -U | grep frida
8738 frida-helper-32
8897 myapplication.example.com.frida_demo
// -fSpawn is injected with js sakura@sakuradeMacBook-Pro:~$frida -u-f myapplication.example.com.frida_demo -lfrida_demo.js ... original call : str:LoWeRcAsE Me!!!!!!!!! 12-21 04:46:49. 875 9594-9594 /myapplication.example.com.frida_demo D/sakura. String: sakuraCopy the code
Frida looks for instance and calls it actively.
function main() {
console.log("Enter the Script!");
Java.perform(function x() {
console.log("Inside Java perform");
var MainActivity = Java.use("myapplication.example.com.frida_demo.MainActivity");
//overload selects the overloaded objects
MainActivity.fun.overload('java.lang.String').implementation = function (str) {
// Prints parameters
console.log("original call : str:" + str);
// Modify the result
var ret_value = "sakura";
return ret_value;
};
// Find an instance of type className
Java.choose("myapplication.example.com.frida_demo.MainActivity", {
onMatch: function (x) {
console.log("find instance :" + x);
console.log("result of secret func:" + x.secret());
},
onComplete: function () {
console.log("end"); }}); }); } setImmediate(main);Copy the code
frida rpc
function callFun() {
Java.perform(function fn() {
console.log("begin");
Java.choose("myapplication.example.com.frida_demo.MainActivity", {
onMatch: function (x) {
console.log("find instance :" + x);
console.log("result of fun(string) func:" + x.fun(Java.use("java.lang.String"). $new("sakura")));
},
onComplete: function () {
console.log("end");
}
})
})
}
rpc.exports = {
callfun: callFun
};
Copy the code
import time
import frida
device = frida.get_usb_device()
pid = device.spawn(["myapplication.example.com.frida_demo"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
with open("frida_demo_rpc_call.js") as f:
script = session.create_script(f.read())
def my_message_handler(message, payload):
print(message)
print(payload)
script.on("message", my_message_handler)
script.load()
script.exports.callfun()
Copy the code
sakura@sakuradeMacBook-Pro:~/gitsource/frida-agent-example/agent$ python frida_demo_rpc_loader.py
begin
find instance :myapplication.example.com.frida_demo.MainActivity@1d4b09d
result of fun(string):sakura
end
Copy the code
Frida dynamic modification
The contents of the app on the mobile phone are sent to the Frida Python program on the PC, and then processed and returned to the app, and then the app does the subsequent flow. The core is to understand the send/ RECV function
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="please input username and password"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/editText"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:hint="username"
android:maxLength="20"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.095" />
<EditText
android:id="@+id/editText2"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:hint="password"
android:maxLength="20"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.239" />
<Button
android:id="@+id/button"
android:layout_width="100dp"
android:layout_height="35dp"
android:layout_gravity="right|center_horizontal"
android:text="Submit"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.745" />
Copy the code
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)); }}); }}Copy the code
First of all, my ultimate goal is to have Message_tv. setText “send” a base64 string with username admin. That must be hook TextView.settext.
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;
send(string_to_send); // send data to python code
recv(function (received_json_object) {
string_to_recv = received_json_object.my_data
console.log("string_to_recv: " + string_to_recv);
}).wait(); //block execution till the message is received
var my_string = Java.use("java.lang.String"). $new(string_to_recv);
this.setText(my_string); }});Copy the code
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( 'message:', message)
#data = data.decode("base64")
#data = data
data = str(base64.b64decode(data))
print( 'data:',data)
user, pw = data.split(":")
print( 'pw:',pw)
#data = ("admin" + ":" + pw).encode("base64")
data = str(base64.b64encode(("admin" + ":" + pw).encode()))
print( "encoded data:", data)
script.post({"my_data": data}) # send JSON object
print( "Modified data sent")
device = frida.get_usb_device()
pid = device.spawn(["myapplication.example.com.frida_demo"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
with open("frida_demo2.js") as f:
script = session.create_script(f.read())
script.on("message", my_message_handler)
script.load()
input()
Copy the code
sakura@sakuradeMacBook-Pro:~/gitsource/frida-agent-example/agent$ python frida_demo_rpc_loader2.py
Script loaded successfully
{'type': 'send'.'payload': 'Sending to the server :c2FrdXJhOjEyMzQ1Ng==\n'}
None
Sending to the server :c2FrdXJhOjEyMzQ1Ng==
message: {'type': 'send'.'payload': 'Sending to the server :c2FrdXJhOjEyMzQ1Ng==\n'}
data: b'sakura:123456'
pw: 123456'
encoded data: b'YWRtaW46MTIzNDU2Jw=='
Modified data sent
string_to_recv: b'YWRtaW46MTIzNDU2Jw=='
Copy the code
Reference link: github.com/Mind0xP/Fri…
API List
-
Choose (className: String, Callbacks: java.Choosecallbacks): Void enumerates live Instances of className classes by scanning the Java VM heap.
-
Java.use(className: string): java.wrapper <{}> Dynamically generates a JavaScript Wrapper for className, which can be instantiated by calling $new() to invoke the constructor. Call $Dispose () on the instance to either explicitly clean it up or wait for the JavaScript object to be gc.
-
Java.perform(fn: () => void): void Function to run while attached to the VM. Ensures that the current thread is attached to the VM and calls fn. (This isn’t necessary in callbacks from Java.) Will defer calling fn if the app’s class loader is not available yet. Use Java.performNow() if access to the app’s classes is not needed.
-
send(message: any, data? : ArrayBuffer | number []) : void any JSON serializable value. Send a SERIalized JSON message to your Frida-based application, including (optionally) some raw binary data. The latter is useful if you e.g. dumped some memory using NativePointer#readByteArray().
-
recv(callback: MessageCallback): MessageRecvOperation Requests callback to be called on the next message received from your Frida-based application. This will only give you one message, so you need to call recv() again to receive the next one.
-
Wait (): void blocks until message has been received and callback has completed and returned
Frida dynamic and static analysis
Objection
- See this article for practical FRIDA advancements: memory roaming, Hook Anywhere, packet capture
- Objection pypi.org/project/obj…
Object is started and injected into memory
objection -d -g package_name explore
sakura@sakuradeMacBook-Pro:~$ objection -d-g com.android.settings explore [debug] Agent path is: / Users/sakura /. Pyenv/versions / 3.7.7 / lib/python3.7 / site - packages/objection/agent. The js/debug Injecting agent... Using USB device `Google Pixel` [debug] Attempting to attach to process: `com.android.settings` [debug] Process attached! Agent injected and responds ok! _ _ _ _ ___ | | _ | _ | ___ ___ | | _ _ - | | ___ ___. | |. | | - _ - _ | | _ | |. | | | ___ | ___ | | ___ | ___ _ - | | | _ | ___ | _ _ - | | | ___ | (object) inject (ion) v1.8.4 Runtime Mobile Exploration by: @ leonjza from @ sensepost [TAB]for commandSuggestions com.Android. Settings on (Google: 8.1.0) [USB]#
Copy the code
objection memory
View modules loaded in memorymemory list modules
Com.android.settings on (Google: 8.1.0) [USB]# memory list modules
Save the output by adding `--json modules.json` to this commandName Base Size Path ----------------------------------------------- ------------ -------------------- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- app_process64 0 x64ce143000 32768 KiB (32.0) /system/bin/app_process64 libandroid_runtime.so 0x7a90bc3000 1990656 (1.9 MiB) /system/lib64/libandroid_runtime.so Libbinder. So 0 x7a9379f000 557056 (KiB) 544.0 / system/lib64 / libbinder. SoCopy the code
View the library’s export functionsmemory list exports libssl.so
Com.android.settings on (Google: 8.1.0) [USB]# memory list exports libssl.so
Save the output by adding `--json exports.json` to this command
Type Name Address
-------- ----------------------------------------------------- ------------
function SSL_use_certificate_ASN1 0x7c8ff006f8
function SSL_CTX_set_dos_protection_cb 0x7c8ff077b8
function SSL_SESSION_set_ex_data 0x7c8ff098f4
function SSL_CTX_set_session_psk_dhe_timeout 0x7c8ff0a754
function SSL_CTX_sess_accept 0x7c8ff063b8
function SSL_select_next_proto 0x7c8ff06a74
Copy the code
Dump memory space
Memory dump all file name
Memory dump from_base start address number of bytes File name
Search memory space
Usage: memory search "<pattern eg: 41 41 41 ?? 41>" (--string) (--offsets-only)
objection android
Memory heap search instanceAndroid Heap Search Instances Class name
Searches for instances of classes on the heap
sakura@sakuradeMacBook-Pro:~$ objection -g myapplication.example.com.frida_demo explore
Using USB device `Google Pixel`
Agent injected and responds ok!
[usb] # android heap search instances myapplication.example.com.frida_demo
.MainActivity
Class instance enumeration complete for myapplication.example.com.frida_demo.MainActivity
Handle Class toString()
-------- ------------------------------------------------- ---------------------------------------------------------
0x2102 myapplication.example.com.frida_demo.MainActivity myapplication.example.com.frida_demo.MainActivity@5b1b0af
Copy the code
Method to call the instanceAndroid Heap Execute instance ID Instance method
View the currently available activities or servicesandroid hooking list activities/services
Start the activity or service directlyAndroid intent launch_activity/launch_service activity
Android intent launch_activity com. Android. Settings. DisplaySettings this command of the more interesting is used in if some design is not good, can directly to bypass the combination lock screen for directly in.
Com.android.settings on (Google: 8.1.0) [USB]# android hooking list services
com.android.settings.SettingsDumpService
com.android.settings.TetherService
com.android.settings.bluetooth.BluetoothPairingService
Copy the code
Lists all classes in memoryandroid hooking list classes
Searches all loaded classes in memory for classes that contain a particular keyword.android hooking search classes display
Com.android.settings on (Google: 8.1.0) [USB]# android hooking search classes display
[Landroid.icu.text.DisplayContext$Type;
[Landroid.icu.text.DisplayContext;
[Landroid.view.Display$Mode;
android.hardware.display.DisplayManager
android.hardware.display.DisplayManager$DisplayListener
android.hardware.display.DisplayManagerGlobal
Copy the code
Search in memory for all methods of the specified classAndroid Hooking list class_methods class name
Com.android.settings on (Google: 8.1.0) [USB]# android hooking list class_methods java.nio.charset.Charset
private static java.nio.charset.Charset java.nio.charset.Charset.lookup(java.lang.String)
private static java.nio.charset.Charset java.nio.charset.Charset.lookup2(java.lang.String)
private static java.nio.charset.Charset java.nio.charset.Charset.lookupViaProviders(java.lang.String)
Copy the code
Searches for methods that contain a particular keyword in the methods of all loaded classes in memoryandroid hooking search methods display
It’s useful to know when you start searching memory for names
Com.android.settings on (Google: 8.1.0) [USB]# android hooking search methods display
Warning, searching all classes may take some time and in some cases, crash the target application.
Continue? [y/N]: y
Found 5529 classes, searching methods (this may take some time)...
android.app.ActionBar.getDisplayOptions
android.app.ActionBar.setDefaultDisplayHomeAsUpEnabled
android.app.ActionBar.setDisplayHomeAsUpEnabled
Copy the code
Hook class methods (all methods in a hook class/specific method)
Android Hooking Watch Class class name
This will hook all the methods in the class and log them out every time they are called.Android Hooking Watch class name --dump-args --dump-backtrace --dump-return
On top of the above, additional dump parameters, stack traceback, return values
android hooking watch class xxx.MainActivity --dump-args --dump-backtrace --dump-return
Copy the code
Android Hooking Watch class_method Method name
Fun --dump-args --dump-backtrace --dump-returnCopy the code
Grep trick and save the file
Objection log cannot use grep filters by default, but can be by objection run XXX | grep yyy way, from the terminal to filter through a pipe. Use the following
sakura@sakuradeMacBook-Pro:~$ objection -g com.android.settings run memory list modules | grep libc Warning: The Output is not to a terminal (fd = 1). Libcutils. So zero x7a94a1c000 81920 (KiB) 80.0 / system/lib64 / libcutils so libc++. So 0x7a9114e000 983040 (960.0kib) /system/lib64/libc++ so libc.so 0x7a9249d000 892928 (872.0kib) /system/lib64/libc.so Libcrypto. So 0 x7a92283000 1155072 (MiB) 1.1 / system/lib64 / libcrypto. SoCopy the code
Some commands can be followed by –json logfile to save the result directly to the file. Some can be viewed by looking at the output log in the.object file.
sakura@sakuradeMacBook-Pro:~/.objection$ cat *log | grep -i display
android.hardware.display.DisplayManager
android.hardware.display.DisplayManager$DisplayListener
android.hardware.display.DisplayManagerGlobal
Copy the code
Case study
Case study case1: reverse analysis of VX database prototype forensics
Attached link to android-backup-Extractor tool link
sakura@sakuradeMacBook-Pro:~/Desktop/lab/alpha/tools/android/frida_learn$ java -version
java version "1.8.0 comes with _141"sakura@sakuradeMacBook-Pro:~/Desktop/lab/alpha/tools/android/frida_learn$ java -jar abe-all.jar unpack 1.ab 1.tar 0% 1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11% 12% 14% 15% 16% 17% 18% 19% 20% 21% 22% 23% 24% 25% 26% 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% 58% 59% 60% 61% 62% 63% 64% 65% 66% 67% 68% 69% 70% 71% 72% 73% 74% 75% 76% 77% 78% 79% 80% 81% 82% 83% 84% 86% 87% 88% 89% 90% 91% 92% 93% 94% 95% 96% 97% 98% 99% 100% 9097216 bytes written to 1.tar. ... sakura@sakuradeMacBook-Pro:~/Desktop/lab/alpha/tools/android/frida_learn/apps/com.example.yaphetshan.tencentwelcome$ ls Encryto.db _manifest a dbCopy the code
Set up a Nighthian simulator
Sakura, @ sakuradeMacBook - Pro: / Applications/NoxAppPlayer app/Contents/MacOS $. / adb connect 127.0.0.1:62001 * daemon not running. starting it now on port 5037 * adb E 5139 141210 usb_osx.cpp:138] Unable to create an interface plug-in (e00002be) * Daemon started successfully * Connected to 127.0.0.1:62001 sakura@sakuradeMacBook-Pro:/Applications/NoxAppPlayer.app/Contents/MacOS$ ./adb shell dream2qltechn:/# whoami
root
dream2qltechn:/ # uname -aLinux localhost 4.0.9 +#222 SMP PREEMPT Sat Mar 14 18:24:36 HKT 2020 i686
Copy the code
Wait a Minute,What was happend? Jadx search string
In a() code, the name and password are in plain text, AVar. Then a (a2 + aVar. B (a2, contentValues getAsString (” password “))). The substring (0, 7) do it again the complex calculation and interception of seven as a password, Pass getWritableDatabase to decrypt the demo.db database.
So let’s hook getWritableDatabase.
frida-ps -U
...
5662 com.example.yaphetshan.tencentwelcome
objection -d -g com.example.yaphetshan.tencentwelcome explore
Copy the code
Take a look at the source code
package net.sqlcipher.database; . public abstract class SQLiteOpenHelper { ... public synchronized SQLiteDatabase getWritableDatabase(char[] cArr) {Copy the code
You can also object search this method
. Mple. Yaphetshan. Tencentwelcome on (samsung: 7.1.2) (usb)# android hooking search methods getWritableDatabase
Warning, searching all classes may take some time and in some cases, crash the target application.
Continue? [y/N]: y
Found 4650 classes, searching methods (this may take some time)...
android.database.sqlite.SQLiteOpenHelper.getWritableDatabase
...
net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase
Copy the code
Hook this method
[usb] # android hooking watch class_method net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase --dump-args --dump-backtrace --dump-return
- [incoming message] ------------------
{
"payload": "Attempting to watch class \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m and method \u001b[32mgetWritableDatabase\u001b[39m."."type": "send"
}
- [./incoming message] ----------------
(agent) Attempting to watch class net.sqlcipher.database.SQLiteOpenHelper and method getWritableDatabase.
- [incoming message] ------------------
{
"payload": "Hooking \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m.\u001b[92mgetWritableDatabase\u001b[39m(\u001b[31mjava.lang. String\u001b[39m)"."type": "send"
}
- [./incoming message] ----------------
(agent) Hooking net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(java.lang.String)
- [incoming message] ------------------
{
"payload": "Hooking \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m.\u001b[92mgetWritableDatabase\u001b[39m(\u001b[31m[C\u001b[3 9m)"."type": "send"
}
- [./incoming message] ----------------
(agent) Hooking net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase([C)
- [incoming message] ------------------
{
"payload": "Registering job \u001b[94mjytq1qeyllq\u001b[39m. Type: \u001b[92mwatch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase\u001b[39m"."type": "send"
}
- [./incoming message] ----------------
(agent) Registering job jytq1qeyllq. Type: watch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase ... Mple. Yaphetshan. Tencentwelcome on (samsung: 7.1.2) (usb)#
Copy the code
Open the APK after hook
(agent) [1v488x28gcs] Called net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(java.lang.String) ... (agent) [1v488x28gcs] Backtrace: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(Native Method) com.example.yaphetshan.tencentwelcome.MainActivity.a(MainActivity.java:55) com.example.yaphetshan.tencentwelcome.MainActivity.onCreate(MainActivity.java:42) android.app.Activity.performCreate(Activity.java:6692) ... (agent) [1v488x28gcs] Arguments net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(ae56f99) ... . Mple. Yaphetshan. Tencentwelcome on (samsung: 7.1.2) (usb)# jobs list
Job ID Hooks Type
----------- ------- -----------------------------------------------------------------------------
1v488x28gcs 2 watch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase
Copy the code
Find the parameter AE56f99 and all that is left is to open the encrypted DB with this password.
Another strategy is active invocation, and analysis of active invocation based on data flow is very interesting. Call a to trigger database decryption for getWritableDatabase. First find the instance of a class, then hook getWritableDatabase, and finally actively call A. Fortunately, a doesn’t have any weird arguments that we need to pass in, and actively calling this strategy might have requirements 8 in places like circular registration.
[usb] # android heap search instances com.example.yaphetshan.tencentwelcome.MainActivity
Class instance enumeration complete for com.example.yaphetshan.tencentwelcome.MainActivity
Handle Class toString()
-------- -------------------------------------------------- ----------------------------------------------------------
0x20078a com.example.yaphetshan.tencentwelcome.MainActivity com.example.yaphetshan.tencentwelcome.MainActivity@1528f80
[usb] # android hooking watch class_method net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase --dump-args --dump-backtrace --dump-return
[usb] # android heap execute 0x20078a a
(agent) [taupgwkum4h] Arguments net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(ae56f99)
Copy the code
Case study case2: actively invoke the blasting code
The attachment link
Unfortunately,note the right PIN :(I can’t find it, maybe I hid the string in some resource file. The logic is to encode the input and compare it to the password. This must be some sort of irreversible encryption.
public static boolean verifyPassword(Context context, String input) {
if(input.length() ! =4) {
return false;
}
byte[] v = encodePassword(input);
byte[] p = "09042ec2c2c08c4cbece042681caf1d13984f24a".getBytes();
if(v.length ! = p.length) {return false;
}
for (int i = 0; i < v.length; i++) {
if(v[i] ! = p[i]) {return false; }}return true;
}
Copy the code
I’m just gonna pop the code here.
frida-ps -U | grep qualification
7660 org.teamsik.ahe17.qualification.easy
frida -U org.teamsik.ahe17.qualification.easy -l force.js
Copy the code
function main() {
Java.perform(function x() {
console.log("In Java perform")
var verify = Java.use("org.teamsik.ahe17.qualification.Verifier")
var stringClass = Java.use("java.lang.String")
var p = stringClass.$new("09042ec2c2c08c4cbece042681caf1d13984f24a")
var pSign = p.getBytes()
// var pStr = stringClass.$new(pSign)
// console.log(parseInt(pStr))
for (var i = 999; i < 10000; i++){
var v = stringClass.$new(String(i))
var vSign = verify.encodePassword(v)
if (parseInt(stringClass.$new(pSign)) == parseInt(stringClass.$new(vSign))) {
console.log("yes: " + v)
break
}
console.log("not :" + v)
}
})
}
setImmediate(main)
Copy the code
. not :9080 not :9081 not :9082 yes: 9083Copy the code
Notice parseInt here
Fundamentals of Frida Hook (I)
- Calling static and non-static functions
- Sets the (same name) member variable
- Inner class, enumeration class function and hook, trace prototype 1
- Find interface, hook dynamic load dex
- Enumeration of class, Trace prototype 2
- Object cannot switch to classLoader
Frida hook: Print parameters, return values/set return values/active calls
The demo will not post, or first locate the login failure point, and then search the string.
public class LoginActivity extends AppCompatActivity {
/* access modifiers changed from: private */
public Context mContext;
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
this.mContext = this;
setContentView((int) R.layout.activity_login);
final EditText editText = (EditText) findViewById(R.id.username);
final EditText editText2 = (EditText) findViewById(R.id.password);
((Button) findViewById(R.id.login)).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String obj = editText.getText().toString();
String obj2 = editText2.getText().toString();
if (TextUtils.isEmpty(obj) || TextUtils.isEmpty(obj2)) {
Toast.makeText(LoginActivity.this.mContext, "username or password is empty.".1).show();
} else if (LoginActivity.a(obj, obj).equals(obj2)) {
LoginActivity.this.startActivity(new Intent(LoginActivity.this.mContext, FridaActivity1.class));
LoginActivity.this.finishActivity(0);
} else {
Toast.makeText(LoginActivity.this.mContext, "Login failed.".1).show(); }}}); }Copy the code
Loginactivity.a (obj, obj).equals(obj2) obj2 is from password, obj is from username, and obj is from username. So the key here is the parameter of hook A function, the simplest script is as follows.
// Prints parameters and return values
function Login(){
Java.perform(function(){
Java.use("com.example.androiddemo.Activity.LoginActivity").a.overload('java.lang.String'.'java.lang.String').implementation = function (str, str2){
var result = this.a(str, str2);
console.log("args0:"+str+" args1:"+str2+" result:"+result);
return result;
}
})
}
setImmediate(Login)
Copy the code
Look at the inputs and outputs, which can also be called directly and actively.
function login() {
Java.perform(function () {
console.log("start")
var login = Java.use("com.example.androiddemo.Activity.LoginActivity")
var result = login.a("1234"."1234")
console.log(result)
})
}
setImmediate(login)
Copy the code
. 4 e4feaea959d426155a480dc07ef92f4754ee93edbe56d993d74f131497e66fb and then start the adb shell input text"4e4feaea959d426155a480dc07ef92f4754ee93edbe56d993d74f131497e66fb"
Copy the code
Next comes the first level
public abstract class BaseFridaActivity extends AppCompatActivity implements View.OnClickListener {
public Button mNextCheck;
public void CheckSuccess(a) {}public abstract String getNextCheckTitle(a);
public abstract void onCheck(a);
/* access modifiers changed from: protected */
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView((int) R.layout.activity_frida);
this.mNextCheck = (Button) findViewById(R.id.next_check);
this.mNextCheck.setOnClickListener(this);
Button button = this.mNextCheck;
button.setText(getNextCheckTitle() + "Click to go to the next level");
}
public void onClick(View view) {
onCheck();
}
public void CheckFailed(a) {
Toast.makeText(this."Check Failed!".1).show(); }}...public class FridaActivity1 extends BaseFridaActivity {
private static final char[] table = {'L'.'K'.'N'.'M'.'O'.'Q'.'P'.'R'.'S'.'A'.'T'.'B'.'C'.'E'.'D'.'F'.'G'.'H'.'I'.'J'.'U'.'V'.'W'.'X'.'Y'.'Z'.'a'.'b'.'c'.'o'.'d'.'p'.'q'.'r'.'s'.'t'.'u'.'v'.'w'.'x'.'e'.'f'.'g'.'h'.'j'.'i'.'k'.'l'.'m'.'n'.'y'.'z'.'0'.'1'.'2'.'3'.'4'.'6'.'5'.'7'.'8'.'9'.'+'.'/'};
public String getNextCheckTitle(a) {
return "Current level 1";
}
public void onCheck(a) {
try {
if (a(b("Please enter your password :")).equals("R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=")) {
CheckSuccess();
startActivity(new Intent(this, FridaActivity2.class));
finishActivity(0);
return;
}
super.CheckFailed();
} catch(Exception e) { e.printStackTrace(); }}public static String a(byte[] bArr) throws Exception {
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= bArr.length - 1; i += 3) {
byte[] bArr2 = new byte[4];
byte b = 0;
for (int i2 = 0; i2 <= 2; i2++) {
int i3 = i + i2;
if (i3 <= bArr.length - 1) {
bArr2[i2] = (byte) (b | ((bArr[i3] & 255) >>> ((i2 * 2) + 2)));
b = (byte) ((((bArr[i3] & 255) < < (((2 - i2) * 2) + 2)) & 255) > > >2);
} else {
bArr2[i2] = b;
b = 64;
}
}
bArr2[3] = b;
for (int i4 = 0; i4 <= 3; i4++) {
if (bArr2[i4] <= 63) {
sb.append(table[bArr2[i4]]);
} else {
sb.append('='); }}}return sb.toString();
}
public static byte[] b(String str) {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
GZIPOutputStream gZIPOutputStream = new GZIPOutputStream(byteArrayOutputStream);
gZIPOutputStream.write(str.getBytes());
gZIPOutputStream.finish();
gZIPOutputStream.close();
byte[] byteArray = byteArrayOutputStream.toByteArray();
try {
byteArrayOutputStream.close();
return byteArray;
} catch (Exception e) {
e.printStackTrace();
returnbyteArray; }}catch (Exception unused) {
return null; }}}Copy the code
Key function in the a (b) (” please enter the password: “). The equals (” R4jSLLLLLLLLLLOrLE7/5 b + Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a + P65NK44NNROl0wNOLLLL = “) should be here directly hook a, Let the return value is R4jSLLLLLLLLLLOrLE7/5 b + Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a + P65NK44NNROl0wNOLLLL = to get into the next level.
function ch1() {
Java.perform(function () {
console.log("start")
Java.use("com.example.androiddemo.Activity.FridaActivity1").a.implementation = function (x) {
return "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL="}})}Copy the code
Frida hook: Actively call static/non-static functions and set values of static/non-static member variables
Conclusion:
- Static functions use the class directly and call the method; non-static functions need to choose the instance and call it
- Sets the value of a member variable, written as
xx.value = yy
Otherwise, it’s the same as a function. - If a member variable has the same name as the member function, it is preceded by one
_
, such as_xx.value = yy
Then comes the second level
public class FridaActivity2 extends BaseFridaActivity {
private static boolean static_bool_var = false;
private boolean bool_var = false;
public String getNextCheckTitle(a) {
return "Current level 2";
}
private static void setStatic_bool_var(a) {
static_bool_var = true;
}
private void setBool_var(a) {
this.bool_var = true;
}
public void onCheck(a) {
if(! static_bool_var || !this.bool_var) {
super.CheckFailed();
return;
}
CheckSuccess();
startActivity(new Intent(this, FridaActivity3.class));
finishActivity(0); }}Copy the code
The key to this level is that for the following if judgments to be false, both static_BOOL_var and this.bool_var must be true.
if(! static_bool_var || ! this.bool_var) { super.CheckFailed();return;
}
Copy the code
This calls the setBool_var and setStatic_bool_var functions.
function ch2() {
Java.perform(function () {
console.log("start")
var FridaActivity2 = Java.use("com.example.androiddemo.Activity.FridaActivity2"SetStatic_bool_var (); // Hook dynamic function, find instance, call function java.choose ()"com.example.androiddemo.Activity.FridaActivity2", {
onMatch: function (instance) {
instance.setBool_var()
},
onComplete: function () {
console.log("end")}})})}setImmediate(ch2)
Copy the code
Next comes the third level
public class FridaActivity3 extends BaseFridaActivity {
private static boolean static_bool_var = false;
private boolean bool_var = false;
private boolean same_name_bool_var = false;
public String getNextCheckTitle() {
return "Current level 3";
}
private void same_name_bool_var() {
Log.d("Frida", static_bool_var + "" + this.bool_var + "" + this.same_name_bool_var);
}
public void onCheck() {
if(! static_bool_var || ! this.bool_var || ! this.same_name_bool_var) { super.CheckFailed();return; } CheckSuccess(); startActivity(new Intent(this, FridaActivity4.class)); finishActivity(0); }}Copy the code
The key is to let if (! static_bool_var || ! this.bool_var || ! If same_name_BOOL_var is false, all three variables must be true
function ch3() {
Java.perform(function () {
console.log("start")
var FridaActivity3 = Java.use("com.example.androiddemo.Activity.FridaActivity3")
FridaActivity3.static_bool_var.value = true
Java.choose("com.example.androiddemo.Activity.FridaActivity3", {
onMatch: function (instance) {
instance.bool_var.value = true
instance._same_name_bool_var.value = true
},
onComplete: function () {
console.log("end")}})})}Copy the code
Note that the class has a member function and variable named same_name_BOOL_var. In this case, add an _ to the member variable and change the value to xx.value = yy
Frida hook: Inner class, enumeration class function and hook, trace prototype 1
Conclusion:
- For inner classes, pass
Class name $Inner class name
Use or choose - Apply reflection to clazz derived from use, as shown in
clazz.class.getDeclaredMethods()
You can get all the methods declared in the class, that is, you can enumerate all the functions in the class.
Next is the fourth level
public class FridaActivity4 extends BaseFridaActivity {
public String getNextCheckTitle() {
return "Current level 4";
}
private static class InnerClasses {
public static boolean check1() {
return false;
}
public static boolean check2() {
return false;
}
public static boolean check3() {
return false;
}
public static boolean check4() {
return false;
}
public static boolean check5() {
return false;
}
public static boolean check6() {
return false;
}
private InnerClasses() {
}
}
public void onCheck() {
if(! InnerClasses.check1() || ! InnerClasses.check2() || ! InnerClasses.check3() || ! InnerClasses.check4() || ! InnerClasses.check5() || ! InnerClasses.check6()) { super.CheckFailed();return; } CheckSuccess(); startActivity(new Intent(this, FridaActivity5.class)); finishActivity(0); }}Copy the code
The key to this level is to make if (! InnerClasses.check1() || ! InnerClasses.check2() || ! InnerClasses.check3() || ! InnerClasses.check4() || ! InnerClasses.check5() || ! All checks in innerclasses.check6 () return true.
The only problem here is finding InnerClasses. For the inner class hook, use the class name $inner class name.
function ch4() {
Java.perform(function () {
var InnerClasses = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses")
console.log("start")
InnerClasses.check1.implementation = function () {
return true
}
InnerClasses.check2.implementation = function () {
return true
}
InnerClasses.check3.implementation = function () {
return true
}
InnerClasses.check4.implementation = function () {
return true
}
InnerClasses.check5.implementation = function () {
return true
}
InnerClasses.check6.implementation = function () {
return true}})}Copy the code
Using reflection, get all the method declarations in the class, and concatenate the string to get the method name, such as check1 below. Then you can hook them in batches instead of writing them one by one as I did above.
var inner_classes = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses")
varall_methods = inner_classes.class.getDeclaredMethods(); . publicstatic boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check1(),public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check2(),public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check3(),public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check4(),public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check5(),public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check6()
Copy the code
Frida hook: hook dynamically loaded dex, and lookup interface,
Conclusion:
- through
enumerateClassLoaders
To enumerate the classloader loaded into memory, andloader.findClass(xxx)
Find out if it includes the implementation class of the interface we want, and finally passJava.classFactory.loader = loader
To switch the Classloader to load the implementation class.
The fifth level is more interesting. Its check function is dynamically loaded. In Java, interfaces are a set of abstract interfaces that require classes to implement.
package com.example.androiddemo.Dynamic;
public interface CheckInterface {
boolean check(a); }...public class DynamicCheck implements CheckInterface {
public boolean check(a) {
return false; }}...public class FridaActivity5 extends BaseFridaActivity {
private CheckInterface DynamicDexCheck = null; .public CheckInterface getDynamicDexCheck(a) {
if (this.DynamicDexCheck == null) {
loaddex();
}
return this.DynamicDexCheck;
}
/* access modifiers changed from: protected */
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
loaddex();
//this.DynamicDexCheck = (CheckInterface) new DexClassLoader(str, filesDir.getAbsolutePath(), (String) null, getClassLoader()).loadClass("com.example.androiddemo.Dynamic.DynamicCheck").newInstance();
}
public void onCheck(a) {
if (getDynamicDexCheck() == null) {
Toast.makeText(this."onClick loaddex Failed!".1).show();
} else if (getDynamicDexCheck().check()) {
CheckSuccess();
startActivity(new Intent(this, FridaActivity6.class));
finishActivity(0);
} else {
super.CheckFailed(); }}}Copy the code
Loaddex = loadClass DynamicCheck = loadClass DynamicCheck = loadClass DynamicCheck = loadClass DynamicCheck = loadClass DynamicCheck So now we need to enumerate the class loader, find the one that can instantiate the class we want, and set it to Java’s default Class Factory loader. This class loader can now be used to import a given class.
function ch5() {
Java.perform(function () {
// Java.choose("com.example.androiddemo.Activity.FridaActivity5",{
// onMatch:function(x){
// console.log(x.getDynamicDexCheck().$className)
// },onComplete:function(){}
// })
console.log("start")
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
if(loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")) {console.log("Successfully found loader")
console.log(loader); Java.classFactory.loader = loader ; }}catch(error){
console.log("find error:" + error)
}
},
onComplete: function () {
console.log("end1")
}
})
Java.use("com.example.androiddemo.Dynamic.DynamicCheck").check.implementation = function () {
return true
}
console.log("end2")
})
}
setImmediate(ch5)
Copy the code
Todo has a question github.com/frida/frida…
Frida Hook: Enumeration class, Trace prototype 2
Conclusion: through the Java enumerateLoadedClasses to enumeration class, then the name. The indexOf (STR) to filter out the hook.
Next is the sixth level
import com.example.androiddemo.Activity.Frida6.Frida6Class0;
import com.example.androiddemo.Activity.Frida6.Frida6Class1;
import com.example.androiddemo.Activity.Frida6.Frida6Class2;
public class FridaActivity6 extends BaseFridaActivity {
public String getNextCheckTitle(a) {
return "Current level 6";
}
public void onCheck(a) {
if(! Frida6Class0.check() || ! Frida6Class1.check() || ! Frida6Class2.check()) {super.CheckFailed();
return;
}
CheckSuccess();
startActivity(new Intent(this, FridaActivity7.class));
finishActivity(0); }}Copy the code
We import some classes and then call static methods in the class, so we enumerate all the classes, filter them, and hook the result to change the return value.
function ch6() {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (name, handle){
if (name.indexOf("com.example.androiddemo.Activity.Frida6") != - 1) {
console.log("name:" + name + " handle:" + handle)
Java.use(name).check.implementation = function () {
return true}}},onComplete: function () {
console.log("end")}})})}Copy the code
Frida hook: A concrete implementation class to search for an interface
Use reflection to get the interface array implemented in the class and print it out.
function more() {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (class_name){
if (class_name.indexOf("com.example.androiddemo") < 0) {
return
}
else {
var hook_cls = Java.use(class_name)
var interfaces = hook_cls.class.getInterfaces()
if (interfaces.length > 0) {
console.log(class_name + ":")
for (var i in interfaces) {
console.log("\t", interfaces[i].toString())
}
}
}
},
onComplete: function () {
console.log("end")}})})}Copy the code
Frida Hook Foundation (II)
- spawn/attach
- Various active calls
- Hook functions and hook constructors
- Call stack/simple script
- Dynamically load your own dex
Title download address: github.com/tlamb96/kgb…
spawn/attach
Firda -f parameter to represent span start frida – U – f com. Tlamb96. Spetsnazmessenger – l frida_russian. Js – no – pause
/* access modifiers changed from: protected */
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView((int) R.layout.activity_main);
String property = System.getProperty("user.home");
String str = System.getenv("USER");
if (property == null|| property.isEmpty() || ! property.equals("Russia")) {
a("Integrity Error"."This app can only run on Russian devices.");
} else if (str == null|| str.isEmpty() || ! str.equals(getResources().getString(R.string.User))) { a("Integrity Error"."Must be on the user whitelist.");
} else {
a.a(this);
startActivity(new Intent(this, LoginActivity.class)); }}}Copy the code
This topic is relatively simple, but because the check is in onCreate, it will be automatically checked as soon as the app is started, so we need to start frida script hook in spawn mode instead of attach. There are two checks, one for the value of property and one for STR. Get from System.getProperty and System.getenv, hook them.
User
function main() {
Java.perform(function () {
Java.use("java.lang.System").getProperty.overload('java.lang.String').implementation = function (str) {
return "Russia";
}
Java.use("java.lang.System").getenv.overload('java.lang.String').implementation = function(str){
return "RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==";
}
})
}
setImmediate(main)
Copy the code
Next, go to the login function
public void onLogin(View view) {
EditText editText = (EditText) findViewById(R.id.login_username);
EditText editText2 = (EditText) findViewById(R.id.login_password);
this.n = editText.getText().toString();
this.o = editText2.getText().toString();
if (this.n ! =null && this.o ! =null&&!this.n.isEmpty() && !this.o.isEmpty()) {
if (!this.n.equals(getResources().getString(R.string.username))) {
Toast.makeText(this."User not recognized.".0).show();
editText.setText("");
editText2.setText("");
} else if(! j()) { Toast.makeText(this."Incorrect password.".0).show();
editText.setText("");
editText2.setText("");
} else {
i();
startActivity(new Intent(this, MessengerActivity.class)); }}}...private boolean j(a) {
String str = "";
for (byte b : this.m.digest(this.o.getBytes())) {
str = str + String.format("%x".new Object[]{Byte.valueOf(b)});
}
returnstr.equals(getResources().getString(R.string.password)); }...private void i(a) {
char[] cArr = {'('.'W'.'D'.') '.'T'.'P'.':'.The '#'.'? '.'T'};
cArr[0] = (char) (cArr[0] ^ this.n.charAt(1));
cArr[1] = (char) (cArr[1] ^ this.o.charAt(0));
cArr[2] = (char) (cArr[2] ^ this.o.charAt(4));
cArr[3] = (char) (cArr[3] ^ this.n.charAt(4));
cArr[4] = (char) (cArr[4] ^ this.n.charAt(7));
cArr[5] = (char) (cArr[5] ^ this.n.charAt(0));
cArr[6] = (char) (cArr[6] ^ this.o.charAt(2));
cArr[7] = (char) (cArr[7] ^ this.o.charAt(3));
cArr[8] = (char) (cArr[8] ^ this.n.charAt(6));
cArr[9] = (char) (cArr[9] ^ this.n.charAt(8));
Toast.makeText(this."FLAG{" + new String(cArr) + "}".1).show();
}
Copy the code
Java.use("com.tlamb96.kgbmessenger.LoginActivity").j.implementation = function () {
return true}... Java.use("android.widget.Toast").makeText.overload('android.content.Context'.'java.lang.CharSequence'.'int').implementation = function (x, y, z) {
var flag = Java.use("java.lang.String"). $new(y)
console.log(flag)
}
...
[Google Pixel::com.tlamb96.spetsnazmessenger]-> FLAG{G&qG13 R0}
Copy the code
Frida hook: Hook constructor/print stack traceback
Clazz.$init.implementation = callback hook constructor; clazz.$init.implementation = callback hook constructor
Let’s first learn how to hook constructors.
add(new com.tlamb96.kgbmessenger.b.a(R.string.katya, "Archer, you up?"."2:20 am".true)); .package com.tlamb96.kgbmessenger.b;
public class a {...public a(int i, String str, String str2, boolean z) {
this.f448a = i;
this.b = str;
this.c = str2;
this.d = z; }... }Copy the code
Hook the constructor with $init
Java.use("com.tlamb96.kgbmessenger.b.a").$init.implementation = function (i, str1, str2, z) {
this.$init(i, str1, str2, z)
console.log(i, str1, str2, z)
printStack("com.tlamb96.kgbmessenger.b.a")}Copy the code
Frida Hook: Print stack traceback
Print stack backtracking
function printStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if(straces ! =undefined&& straces ! =null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g."\\n");
console.log("= = = = = = = = = = = = = = = = = = = = = = = = = = = = =" + name + " Stack strat=======================");
console.log(replaceStr);
console.log("= = = = = = = = = = = = = = = = = = = = = = = = = = = = =" + name + " Stack end=======================\r\n"); Exception.$dispose(); }}); }Copy the code
So the output looks like this
[Google Pixel: : com. Tlamb96. Spetsnazmessenger] - > 2131558449 111 02:27 afternoonfalse
=============================com.tlamb96.kgbmessenger.b.a Stack strat=======================
com.tlamb96.kgbmessenger.b.a.<init>(Native Method)
com.tlamb96.kgbmessenger.MessengerActivity.onSendMessage(Unknown Source:40)
java.lang.reflect.Method.invoke(Native Method)
android.support.v7.app.m$a.onClick(Unknown Source:25)
android.view.View.performClick(View.java:6294)
android.view.View$PerformClick.run(View.java:24770)
android.os.Handler.handleCallback(Handler.java:790)
android.os.Handler.dispatchMessage(Handler.java:99)
android.os.Looper.loop(Looper.java:164)
android.app.ActivityThread.main(ActivityThread.java:6494)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
=============================com.tlamb96.kgbmessenger.b.a Stack end=======================
Copy the code
Frida hook: Manually load the dex and call
After compiling the dex, load it through java.openClassFile (“xxx.dex”).load(), so that we can call the methods in java.use normally.
Now let’s go ahead and solve this problem.
public void onSendMessage(View view) {
EditText editText = (EditText) findViewById(R.id.edittext_chatbox);
String obj = editText.getText().toString();
if(! TextUtils.isEmpty(obj)) {this.o.add(new com.tlamb96.kgbmessenger.b.a(R.string.user, obj, j(), false));
this.n.c();
if (a(obj.toString()).equals(this.p)) {
Log.d("MessengerActivity"."Successfully asked Boris for the password.");
this.q = obj.toString();
this.o.add(new com.tlamb96.kgbmessenger.b.a(R.string.boris, "Only if you ask nicely", j(), true));
this.n.c();
}
if (b(obj.toString()).equals(this.r)) {
Log.d("MessengerActivity"."Successfully asked Boris nicely for the password.");
this.s = obj.toString();
this.o.add(new com.tlamb96.kgbmessenger.b.a(R.string.boris, "Wow, no one has ever been so nice to me! Here you go friend: FLAG{" + i() + "}", j(), true));
this.n.c();
}
this.m.b(this.m.getAdapter().a() - 1);
editText.setText(""); }}Copy the code
The new level is a chat box. If we look at the code, obj is what we typed, and when we typed it, we added it to an ArrayList of this.o. The key if check is if (a(obj.tostring ()).equals(this.p)) and if (b(obj.tostring ()).equals(this.r)).
private String p = "V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003";
private String q;
private String r = "\u0000dslp}oQ\u0000 dks$|M\u0000h +AYQg\u0000P*! M$gQ\u0000";
private String s;
Copy the code
But in fact, this problem is more difficult than I expected. The logic of this problem is that if the calculation of a and B is equal to the corresponding value, the value of obj used for calculation will be assigned to Q and S, and then the final flag will be calculated according to the q and S. Therefore, if a and B are calculated by hook without reverse algorithm, the value of OBj is still wrong and the correct flag cannot be calculated.
So it is good to reverse the algorithm, first write an APK, with Java to achieve the registration machine.
classes.dex
var dex = Java.openClassFile("/data/local/tmp/classes.dex").load();
console.log("decode_P:"+Java.use("myapplication.example.com.reversea.reverseA").decode_P());
console.log("r_to_hex:"+Java.use("myapplication.example.com.reversea.reverseA").r_to_hex()); . . decode_P:Boris, give me the password r_to_hex:0064736c707d6f510020646b73247c4d0068202b4159516700502a214d24675100
Copy the code
Frida printing with parameter construction
- Array /(string) Object array /gson/ java.array
- Object/polymorphic, strong java.cast/interface java.register
- Generics, List, Map, Set, iterative printing
- Non-ascii, Child-gating, RPC upload to PC for printing
char[]/[Object Object]
Log.d("SimpleArray"."onCreate: SImpleArray");
char arr[][] = new char[4] [];// Create a 4-row two-dimensional array
arr[0] = new char[] { 'spring'.'sleep'.'no'.'sleep'.'know' }; // Assign a value to each line
arr[1] = new char[] { 'place'.'place'.'smell'.'crow'.'the birds' };
arr[2] = new char[] { 'the night'.'to'.'the wind'.'rain'.'voice' };
arr[3] = new char[] { 'spend'.'fall'.'know'.'more than'.'less' };
Log.d("SimpleArray"."-- -- -- -- - the horizontal version -- -- -- -- --");
for (int i = 0; i < 4; i++) { // Loop through 4 lines
Log.d("SimpleArraysToString", Arrays.toString(arr[i]));
Log.d("SimpleStringBytes", Arrays.toString (Arrays.toString (arr[i]).getBytes()));
for (int j = 0; j < 5; j++) { // loop through 5 columns
Log.d("SimpleArray", Character.toString(arr[i][j])); // Outputs the elements in the array
}
if (i % 2= =0) {
Log.d("SimpleArray".",");// If it is one or three sentences, print a comma
} else {
Log.d("SimpleArray"."。");// If there are two or four sentences, print a period}}Copy the code
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use('com.r0ysue.gson.Gson');
Java.use("java.lang.Character").toString.overload('char').implementation = function(char){
var result = this.toString(char);
console.log("char,result",char,result);
return result;
}
Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){
var result = this.toString(charArray);
console.log("charArray,result:",charArray,result)
console.log("charArray Object Object:",gson.$new().toJson(charArray));
return result;
}
Copy the code
Here [C is the JNI function signature
byte[]
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use('com.r0ysue.gson.Gson');
Java.use("java.util.Arrays").toString.overload('[B').implementation = function(byteArray){
var result = this.toString(byteArray);
console.log("byteArray,result):",byteArray,result)
console.log("byteArray Object Object:",gson.$new().toJson(byteArray));
return result;
}
Copy the code
Java array structure
If you want to not just print out the results, but replace the original arguments, you need to construct a charArray of your own, using the Java.arrayAPI
/**
* Creates a Java array with elements of the specified `type`, from a
* JavaScript array `elements`. The resulting Java array behaves like
* a JS array, but can be passed by reference to Java APIs in order to
* allow them to modify its contents.
*
* @param type Type name of elements.
* @param elements Array of JavaScript values to use for constructing the
* Java array.
*/
function array(type: string, elements: any[]): any[];
Copy the code
Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){
var newCharArray = Java.array('char'['一'.'to'.'二'.'三'.'in' ]);
var result = this.toString(newCharArray);
console.log("newCharArray,result:",newCharArray,result)
console.log("newCharArray Object Object:",gson.$new().toJson(newCharArray));
var newResult = Java.use('java.lang.String'). $new(Java.array('char'['smoke'.'village'.'four'.'five'.'home']))
return newResult;
}
Copy the code
It can be used to construct parametric retransmission packages for crawlers.
Class polymorphism: transformation/java.cast
You can check the type of the current instance with getClass().getName().toString(). Find an instance and cast the type of the object through java.cast.
/** * Creates a JavaScript wrapper given the existing instance at `handle` of * given class `klass` as returned from `Java.use()`. * * @param handle An existing wrapper or a JNI handle. * @param klass Class wrapper for type to cast to. * /
function cast(handle: Wrapper | NativePointerValue, klass: Wrapper) :Wrapper;
Copy the code
public class Water { / / water
public static String flow(Water W) { // Water method
// SomeSentence
Log.d("2Object"."water flow: I`m flowing");
return "water flow: I`m flowing";
}
public String still(Water W) { // Water method
// SomeSentence
Log.d("2Object"."water still: still water runs deep!");
return "water still: still water runs deep!"; }}...public class Juice extends Water { // The fruit juice class inherits the water class
public String fillEnergy(a){
Log.d("2Object"."Juice: i`m fillingEnergy!");
return "Juice: i`m fillingEnergy!";
}
Copy the code
var JuiceHandle = null ;
Java.choose("com.r0ysue.a0526printout.Juice", {onMatch:function(instance){
console.log("found juice instance",instance);
console.log("juice instance call fill",instance.fillEnergy());
JuiceHandle = instance;
},onComplete:function(){
console.log("juice handle search completed!")}})console.log("Saved juice handle :",JuiceHandle);
var WaterHandle = Java.cast(JuiceHandle,Java.use("com.r0ysue.a0526printout.Water"))
console.log("call Waterhandle still method:",WaterHandle.still(WaterHandle));
Copy the code
interface/Java.registerClass
public interface liquid {
public String flow(a);
}
Copy the code
Frida provides the ability to create a new Java class
/** * Creates a new Java class. * * @param spec Object describing the class to be created. */
function registerClass(spec: ClassSpec) :Wrapper;
Copy the code
First get the interface to implement, and then call registerClass to implement the interface.
Java.perform(function(){
var liquid = Java.use("com.r0ysue.a0526printout.liquid");
var beer = Java.registerClass({
name: 'com.r0ysue.a0526printout.beer'.implements: [liquid],
methods: {
flow: function () {
console.log("look, beer is flowing!")
return "look, beer is flowing!"; }}});console.log("beer.bubble:",beer.$new().flow())
})
}
Copy the code
Member inner class/Anonymous inner class
Look at smali or enumerated classes.
hook enum
Learn about Java enumerations from this article. www.cnblogs.com/jingmoxukon…
enum Signal {
GREEN, YELLOW, RED
}
public class TrafficLight {
public static Signal color = Signal.RED;
public static void main(a) {
Log.d("4enum"."enum "+ color.getClass().getName().toString());
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break; }}}Copy the code
Java.perform(function(){
Java.choose("com.r0ysue.a0526printout.Signal", {onMatch:function(instance){
console.log("instance.name:",instance.name());
console.log("instance.getDeclaringClass:",instance.getDeclaringClass());
},onComplete:function(){
console.log("search completed!")}})})Copy the code
Print the hash map
Java.perform(function(){
Java.choose("java.util.HashMap", {onMatch:function(instance){
if(instance.toString().indexOf("ISBN")! =- 1) {console.log("instance.toString:",instance.toString()); }},onComplete:function(){
console.log("search complete!")}})})Copy the code
Print non – ASCII
Api-caller.com/2019/03/30/… If the class name is not ASCII string, it will be encoded and printed first, and then hook with the encoded string.
// Scenario hook cls.forName finds the classloader of the target class.
cls.forName.overload('java.lang.String'.'boolean'.'java.lang.ClassLoader').implementation = function (arg1, arg2, arg3) {
var clsName = cls.forName(arg1, arg2, arg3);
console.log('oriClassName:' + arg1)
var base64Name = encodeURIComponent(arg1)
console.log('encodeName:' + base64Name);
// Check the non-ASCII character string after Base64 through logs
//clsName is the special character o.I E «
if ('o.%CE%99%C9%AB' == base64Name) {
/ / print this
console.log(arg3);
}
return clsName;
}
Copy the code
Frida Native Hook: An introduction to NDK development
www.jianshu.com/p/87ce6f565…
- Extern “C” with name modifications
- The c++filt tool can be used to restore the original function name
- zh.wikipedia.org/zh-hans/ name fix…
- Extern “C” -derived JNI functions are not mangling by name
- JNI parameters and base types
- The first NDK program
- JNI log
#define TAG "sakura1328"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
Java_myapplication_example_com_ndk_1demo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std: :string hello = "Hello from C++";
LOGD("sakura1328");
returnenv->NewStringUTF(hello.c_str()); }...public class MainActivity extends AppCompatActivity {
private static final String TAG = "sakura";
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
Log.d(TAG, stringFromJNI());
}
/** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */
public native String stringFromJNI(a);
}
Copy the code
Frida Native Hook: JNIEnv and reflection
Use jNI strings to learn basic jNI env usage
public native String stringWithJNI(String context); . extern"C"
JNIEXPORT jstring JNICALL
Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI(JNIEnv *env, jobject instance, jstring context_) {
const char *context = env->GetStringUTFChars(context_, 0);
int context_size = env->GetStringUTFLength(context_);
if (context_size > 0) {
LOGD("%s\n", context);
}
env->ReleaseStringUTFChars(context_, context);
return env->NewStringUTF("sakura1328");
}
12-26 22:30:00.548 15764-15764/myapplication.example.com.ndk_demo D/sakura1328: sakura
Copy the code
Java reflection
Bottom line: Read more about Java’s reflection API.
Java Advanced features – Reflection
- Lookup calls various API interfaces, JNI, frida/ Xposed part of the principle
- Reflection basic API
- Reflection modifies access control and property values
- JNI SO calls reflection into the Java world
- Xposed/Frida hook principle
The reason why we want to trace artMethod is because some so are very confused, and then it is difficult to statically analyze which Java functions are called in SO. Nor is it called via GetMethodID like JNI. Instead, you get the class using a method like FindClass, and then reflection calls a Java function inside your app.
So hook its execution location, every Java function is an ArtMethod structure for Android source code, and then hook gets the artMethod instance to call the class function, print the function name.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "sakura";
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringWithJNI("sakura"));
// Log.d(TAG, stringFromJNI());
// Log.d(TAG, stringWithJNI("sakura"));
try {
testClass();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public void testClass() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Test sakuraTest = new Test();
// 获得Class的方法(三种)
Class testClazz = MainActivity.class.getClassLoader().loadClass("myapplication.example.com.ndk_demo.Test");
Class testClazz2 = Class.forName("myapplication.example.com.ndk_demo.Test");
Class testClazz3 = Test.class;
Log.i(TAG, "Classloader.loadClass->" + testClazz);
Log.i(TAG, "Classloader.loadClass->" + testClazz2);
Log.i(TAG, "Classloader.loadClass->" + testClazz3.getName());
// 获得类中属性相关的方法
Field publicStaticField = testClazz3.getDeclaredField("publicStaticField");
Log.i(TAG, "testClazz3.getDeclaredField->" + publicStaticField);
Field publicField = testClazz3.getDeclaredField("publicField");
Log.i(TAG, "testClazz3.getDeclaredField->" + publicField);
//对于Field的get方法,如果是static,则传入null即可;如果不是,则需要传入一个类的实例
String valueStaticPublic = (String) publicStaticField.get(null);
Log.i(TAG, "publicStaticField.get->" + valueStaticPublic);
String valuePublic = (String) publicField.get(sakuraTest);
Log.i(TAG, "publicField.get->" + valuePublic);
//对于private属性,需要设置Accessible
Field privateStaticField = testClazz3.getDeclaredField("privateStaticField");
privateStaticField.setAccessible(true);
String valuePrivte = (String) privateStaticField.get(null);
Log.i(TAG, "modified before privateStaticField.get->" + valuePrivte);
privateStaticField.set(null, "modified");
valuePrivte = (String) privateStaticField.get(null);
Log.i(TAG, "modified after privateStaticField.get->" + valuePrivte);
Field[] fields = testClazz3.getDeclaredFields();
for (Field i : fields) {
Log.i(TAG, "testClazz3.getDeclaredFields->" + i);
}
// 获得类中method相关的方法
Method publicStaticMethod = testClazz3.getDeclaredMethod("publicStaticFunc");
Log.i(TAG, "testClazz3.getDeclaredMethod->" + publicStaticMethod);
publicStaticMethod.invoke(null);
Method publicMethod = testClazz3.getDeclaredMethod("publicFunc", java.lang.String.class);
Log.i(TAG, "testClazz3.getDeclaredMethod->" + publicMethod);
publicMethod.invoke(sakuraTest, " sakura");
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native String stringWithJNI(String context);
}
...
public class Test {
private static final String TAG = "sakura_test";
public static String publicStaticField = "i am a publicStaticField";
public String publicField = "i am a publicField";
private static String privateStaticField = "i am a privateStaticField";
private String privateField = "i am a privateField";
public static void publicStaticFunc() {
Log.d(TAG, "I`m from publicStaticFunc");
}
public void publicFunc(String str) {
Log.d(TAG, "I`m from publicFunc" + str);
}
private static void privateStaticFunc() {
Log.i(TAG, "I`m from privateFunc");
}
private void privateFunc() {
Log.i(TAG, "I`m from privateFunc");
}
}
...
...
12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->class myapplication.example.com.ndk_demo.Test
12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->class myapplication.example.com.ndk_demo.Test
12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->myapplication.example.com.ndk_demo.Test
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredField->public static java.lang.String myapplication.example.com.ndk_demo.Test.publicStaticField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredField->public java.lang.String myapplication.example.com.ndk_demo.Test.publicField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: publicStaticField.get->i am a publicStaticField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: publicField.get->i am a publicField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: modified before privateStaticField.get->i am a privateStaticField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: modified after privateStaticField.get->modified
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private java.lang.String myapplication.example.com.ndk_demo.Test.privateField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->public java.lang.String myapplication.example.com.ndk_demo.Test.publicField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private static final java.lang.String myapplication.example.com.ndk_demo.Test.TAG
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private static java.lang.String myapplication.example.com.ndk_demo.Test.privateStaticField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->public static java.lang.String myapplication.example.com.ndk_demo.Test.publicStaticField
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredMethod->public static void myapplication.example.com.ndk_demo.Test.publicStaticFunc()
12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo D/sakura_test: I`m from publicStaticFunc
12-26 23:57:11.786 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredMethod->public void myapplication.example.com.ndk_demo.Test.publicFunc(java.lang.String)
12-26 23:57:11.786 17682-17682/myapplication.example.com.ndk_demo D/sakura_test: I`m from publicFunc sakura
Copy the code
memory list modules
Frida’s debugging
The main content of this section is about the principle of reverse debugging and how to reverse debugging, important content or read the article to understand. Because I don’t need to do any work related to reverse debugging, I’ll skip some of this.
- Frida debug and reverse debug basic ideas (Java layer API, Native layer API, Syscall)
- AntiFrida
- frida-detection-demo
- Multiple feature detection Frida
- From high dimensional countervail – Reverse TinyTool homemade
- Unicorn app for Android
Frida native hook: symbol hook JNI, art&liBC
Native function of Java Hook and active call
The Java layer hook and active call of native functions are exactly the same as ordinary Java functions. Skip it.
jni.h
Header file import
Import jni.h and search for the file.
sakura@sakuradeMacBook-Pro:~/Library/Android/sdk$ find ./ -name "jni.h"
.//ndk-bundle/sysroot/usr/include/jni.h
Copy the code
Error /Users/sakura/Library/Android/sdk/ndk-bundle/sysroot/usr/include/jni.h,27: Can't open include file 'stdarg.h'
Total 1 errors
Caching 'Exports'... ok
Copy the code
Error reported, so a copy of jni.h out
Import and delete these two header files
Import success
Now you can recognize _JNIEnv, as shown
JNI function symbol hook
Take a look at what functions are exported.
extern "C" JNIEXPORT jstring JNICALL
Java_myapplication_example_com_ndk_1demo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std: :string hello = "Hello from C++";
LOGD("sakura1328");
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI(JNIEnv *env, jobject instance, jstring context_) {
const char *context = env->GetStringUTFChars(context_, 0);
int context_size = env->GetStringUTFLength(context_);
if (context_size > 0) {
LOGD("%s\n", context);
}
env->ReleaseStringUTFChars(context_, context);
return env->NewStringUTF("sakura1328");
}
Copy the code
There are several required apis.
- The first is to find out if so is loaded, through
Process.enumerateModules()
This API enumerates modules that have been loaded into memory. - Then through
Module.findBaseAddress(module name)
To find the base address of so for the function to hook, and return null if not found. - And then you can go through
findExportByName(moduleName: string, exportName: string): NativePointer
To find the absolute address of the exported function. If you don’t know what moduleName is, you can pass in a NULL, but it will take some time to traverse all modules. Returns null if it cannot be found. - Once the address is found, the function/ Instruction execution can be intercepted. through
Interceptor.attach
. See the following code for how to use it. - In addition, to print out the jString value, jenv’s function getStringUtfChars can be used, just like writing native programs normally.
Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()
I’m going to loop string_with_jni, and if I don’t loop, I’m going to call this function, or hook dlopen. The hook dlopen method can be referenced in this code.
function hook_native() {
// console.log(JSON.stringify(Process.enumerateModules()));
var libnative_addr = Module.findBaseAddress("libnative-lib.so")
console.log("libnative_addr is: " + libnative_addr)
if (libnative_addr) {
var string_with_jni_addr = Module.findExportByName("libnative-lib.so"."Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI")
console.log("string_with_jni_addr is: " + string_with_jni_addr)
}
Interceptor.attach(string_with_jni_addr, {
onEnter: function (args) {
console.log("string_with_jni args: " + args[0], args[1], args[2])
console.log(Java.vm.getEnv().getStringUtfChars(args[2].null).readCString())
},
onLeave: function (retval) {
console.log("retval:", retval)
console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString())
var newRetval = Java.vm.getEnv().newStringUtf("new retval from hook_native"); retval.replace(ptr(newRetval)); }})}Copy the code
libnative_addr is: 0x7a0842f000
string_with_jni_addr is: 0x7a08436194
[Google Pixel::myapplication.example.com.ndk_demo]-> string_with_jni args: 0x7a106cc1c0 0x7ff0b71da4 0x7ff0b71da8
sakura
retval: 0x75
sakura1328
Copy the code
Hook env (GetStringUTFChars) ¶
function hook_art(){
var addr_GetStringUTFChars = null;
//console.log( JSON.stringify(Process.enumerateModules()));
var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
for(var i = 0; i<symbols.length; i++){var symbol = symbols[i].name;
if((symbol.indexOf("CheckJNI") = =- 1)&&(symbol.indexOf("JNI") > =0)) {if(symbol.indexOf("GetStringUTFChars") > =0) {console.log(symbols[i].name);
console.log(symbols[i].address); addr_GetStringUTFChars = symbols[i].address; }}}console.log("addr_GetStringUTFChars:", addr_GetStringUTFChars);
Java.perform(function (){
Interceptor.attach(addr_GetStringUTFChars, {
onEnter: function (args) {
console.log("addr_GetStringUTFChars OnEnter args[0],args[1]",args[0],args[1]);
//console.log(hexdump(args[0].readPointer()));
//console.log(Java.vm.tryGetEnv().getStringUtfChars(args[0]).readCString());
}, onLeave: function (retval) {
console.log("addr_GetStringUTFChars OnLeave",ptr(retval).readCString()); }})})}Copy the code
JNI function arguments, return values print and replace
- The liBC function symbol hook
- Libc function parameters, return value print and replace hook libc are exactly the same as above, also need not be described. So here, the essence is to find the exported symbol and its so base address.
function hook_libc(){
var pthread_create_addr = null;
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for(var i = 0; i<symbols.length; i++){var symbol = symbols[i].name;
if(symbol.indexOf("pthread_create") > =0) {//console.log(symbols[i].name);
//console.log(symbols[i].address);pthread_create_addr = symbols[i].address; }}console.log("pthread_create_addr,",pthread_create_addr);
Interceptor.attach(pthread_create_addr,{
onEnter:function(args){
console.log("pthread_create_addr args[0],args[1],args[2],args[3]:",args[0],args[1],args[2],args[3]);
},onLeave:function(retval){
console.log("retval is:",retval)
}
})
}
Copy the code
Frida Native hook: JNI_Onload/ dynamic registration/Inline_hook /native layer call stack printing
Github.com/android/ndk…
JNI_Onload/ Dynamic registration principle
- JNI_Onload/ dynamic registration /Frida hook RegisterNative
- JNI and dynamic registration
- Dynamic registration of native methods
- Frida hook art
See my article for more details, but only chestnuts are given here.
Log.d(TAG,stringFromJNI2());
public native String stringFromJNI2(a);
Copy the code
JNIEXPORT jstring JNICALL stringFromJNI2( JNIEnv *env, jclass clazz) {
jclass testClass = env->FindClass("myapplication/example/com/ndk_demo/Test");
jfieldID publicStaticField = env->GetStaticFieldID(testClass, "publicStaticField"."Ljava/lang/String;");
jstring publicStaticFieldValue = (jstring) env->GetStaticObjectField(testClass,
publicStaticField);
const char *value_ptr = env->GetStringUTFChars(publicStaticFieldValue, NULL);
LOGD("now content is %s", value_ptr);
std: :string hello = "Hello from C++ stringFromJNI2";
returnenv->NewStringUTF(hello.c_str()); }...JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
vm->GetEnv((void **) &env, JNI_VERSION_1_6);
JNINativeMethod methods[] = {
{"stringFromJNI2"."()Ljava/lang/String;", (void *) stringFromJNI2},
};
env->RegisterNatives(env->FindClass("myapplication/example/com/ndk_demo/MainActivity"), methods,
1);
return JNI_VERSION_1_6;
}
Copy the code
Frida hook RegisterNative
Use the following script to print the RegisterNatives parameters. Note that enumerateSymbolsSync is used, which is the synchronous version of enumerateSymbols. It’s also different from the way we called env with java.vm.trygetenv ().getStringutfchars. In this case, the getStringUtfChars function is called directly by encapsulating the address and parameter information of the function. I didn’t analyze the specific principle, but remember the usage first. In fact, the principle is the same, are based on the symbol to find the address, and then hook symbol address, and then print parameters.
declare const NativeFunction: NativeFunctionConstructor;
interface NativeFunctionConstructor {
new(address: NativePointerValue, retType: NativeType, argTypes: NativeType[], abiOrOptions?: NativeABI | NativeFunctionOptions): NativeFunction;
readonly prototype: NativeFunction;
}
...
var funcGetStringUTFChars = new NativeFunction(addrGetStringUTFChars, "pointer"["pointer"."pointer"."pointer"]);
Copy the code
var ishook_libart = false;
function hook_libart() {
if (ishook_libart === true) {
return;
}
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrGetStringUTFChars = null;
var addrNewStringUTF = null;
var addrFindClass = null;
var addrGetMethodID = null;
var addrGetStaticMethodID = null;
var addrGetFieldID = null;
var addrGetStaticFieldID = null;
var addrRegisterNatives = null;
var addrAllocObject = null;
var addrCallObjectMethod = null;
var addrGetObjectClass = null;
var addrReleaseStringUTFChars = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name == "_ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh") {
addrGetStringUTFChars = symbol.address;
console.log("GetStringUTFChars is at ", symbol.address, symbol.name);
} else if (symbol.name == "_ZN3art3JNI12NewStringUTFEP7_JNIEnvPKc") {
addrNewStringUTF = symbol.address;
console.log("NewStringUTF is at ", symbol.address, symbol.name);
} else if (symbol.name == "_ZN3art3JNI9FindClassEP7_JNIEnvPKc") {
addrFindClass = symbol.address;
console.log("FindClass is at ", symbol.address, symbol.name);
} else if (symbol.name == "_ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_") {
addrGetMethodID = symbol.address;
console.log("GetMethodID is at ", symbol.address, symbol.name);
} else if (symbol.name == "_ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_") {
addrGetStaticMethodID = symbol.address;
console.log("GetStaticMethodID is at ", symbol.address, symbol.name);
} else if (symbol.name == "_ZN3art3JNI10GetFieldIDEP7_JNIEnvP7_jclassPKcS6_") {
addrGetFieldID = symbol.address;
console.log("GetFieldID is at ", symbol.address, symbol.name);
} else if (symbol.name == "_ZN3art3JNI16GetStaticFieldIDEP7_JNIEnvP7_jclassPKcS6_") {
addrGetStaticFieldID = symbol.address;
console.log("GetStaticFieldID is at ", symbol.address, symbol.name);
} else if (symbol.name == "_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi") {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("_ZN3art3JNI11AllocObjectEP7_JNIEnvP7_jclass") > =0) {
addrAllocObject = symbol.address;
console.log("AllocObject is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("_ZN3art3JNI16CallObjectMethodEP7_JNIEnvP8_jobjectP10_jmethodIDz") > =0) {
addrCallObjectMethod = symbol.address;
console.log("CallObjectMethod is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("_ZN3art3JNI14GetObjectClassEP7_JNIEnvP8_jobject") > =0) {
addrGetObjectClass = symbol.address;
console.log("GetObjectClass is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("_ZN3art3JNI21ReleaseStringUTFCharsEP7_JNIEnvP8_jstringPKc") > =0) {
addrReleaseStringUTFChars = symbol.address;
console.log("ReleaseStringUTFChars is at ", symbol.address, symbol.name); }}if(addrRegisterNatives ! =null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var funcAllocObject = new NativeFunction(addrAllocObject, "pointer"["pointer"."pointer"]);
var funcGetMethodID = new NativeFunction(addrGetMethodID, "pointer"["pointer"."pointer"."pointer"."pointer"]);
var funcCallObjectMethod = new NativeFunction(addrCallObjectMethod, "pointer"["pointer"."pointer"."pointer"]);
var funcGetObjectClass = new NativeFunction(addrGetObjectClass, "pointer"["pointer"."pointer"]);
var funcGetStringUTFChars = new NativeFunction(addrGetStringUTFChars, "pointer"["pointer"."pointer"."pointer"]);
var funcReleaseStringUTFChars = new NativeFunction(addrReleaseStringUTFChars, "void"["pointer"."pointer"."pointer"]);
var clz_obj = funcAllocObject(env, java_class);
var mid_getClass = funcGetMethodID(env, java_class, Memory.allocUtf8String("getClass"), Memory.allocUtf8String("()Ljava/lang/Class;"));
var clz_obj2 = funcCallObjectMethod(env, clz_obj, mid_getClass);
var cls = funcGetObjectClass(env, clz_obj2);
var mid_getName = funcGetMethodID(env, cls, Memory.allocUtf8String("getName"), Memory.allocUtf8String("()Ljava/lang/String;"));
var name_jstring = funcCallObjectMethod(env, clz_obj2, mid_getName);
var name_pchar = funcGetStringUTFChars(env, name_jstring, ptr(0));
var class_name = ptr(name_pchar).readCString();
funcReleaseStringUTFChars(env, name_jstring, name_pchar);
//console.log(class_name);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base)); }},onLeave: function (retval) {}}); } ishook_libart =true;
}
hook_libart();
Copy the code
The result is clearly printed, including the name of the dynamically registered function, the function signature, the loading address, and the offset in so,
[RegisterNatives] java_class: myapplication.example.com.ndk_demo.MainActivity name: stringFromJNI2 sig: ()Ljava/lang/String; fnPtr: 0x79f8698484 module_name: libnative-lib.so module_base: 0x79f8691000 offset: 0x7484
Copy the code
Finally, let’s test a hook Art script of Yang’s open source. It is very interesting and traces out a lot of information needed.
frida -U --no-pause -f package_name -l hook_art.js
...
[FindClass] name:myapplication/example/com/ndk_demo/Test
[GetStaticFieldID] name:publicStaticField, sig:Ljava/lang/String;
[GetStringUTFChars] result:i am a publicStaticField
[NewStringUTF] bytes:Hello from C++ stringFromJNI2
[GetStringUTFChars] result:sakura
Copy the code
Native layer call stack printing
Print stack tracebacks directly using the interface provided by FRIda.
Interceptor.attach(f, {
onEnter: function (args) {
console.log('RegisterNatives called from:\n' +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n'); }});Copy the code
The effect is as follows. I added the hook registerNative place.
[Google Pixel::myapplication.example.com.ndk_demo]-> RegisterNatives called from: 0x7a100be03c libart.so! 0xe103c 0x7a100be038 libart.so! 0xe1038 0x79f85699a0 libnative-lib.so! _ZN7_JNIEnv15RegisterNativesEP7_jclassPK15JNINativeMethodi+0x44 0x79f85698e0 libnative-lib.so! JNI_OnLoad+0x90 0x7a102b9fd4 libart.so! _ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobje ctP8_jstringPS9_+0x638 0x7a08e3820c libopenjdkjvm.so! JVM_NativeLoad+0x110 0x70b921c4 boot.oat! oatexec+0xa81c4Copy the code
Active calls to perform method parameter substitution
Use interceptor. replace, not to mention. The main purpose is to change the behavior of the function, not just print some information.
inline hook
An inline hook is an instruction that is executed in the middle of a hook function. The hook address is inline with the attach callback, which is then added to the “so” address.
/** * Callback to invoke when an instruction is about to be executed. */
type InstructionProbeCallback = (this: InvocationContext, args: InvocationArguments) = > void;
type InvocationContext = PortableInvocationContext | WindowsInvocationContext | UnixInvocationContext;
interface PortableInvocationContext {
/** * Return address. */
returnAddress: NativePointer;
/** * CPU registers. You may also update register values by assigning to these keys. */
context: CpuContext;
/** * OS thread ID. */
threadId: ThreadId;
/** * Call depth of relative to other invocations. */
depth: number;
/** * User-defined invocation data. Useful if you want to read an argument in `onEnter` and act on it in `onLeave`. */[x: string]: any; }... . interface Arm64CpuContext extends PortableCpuContext {x0: NativePointer;
x1: NativePointer;
x2: NativePointer;
x3: NativePointer;
x4: NativePointer;
x5: NativePointer;
x6: NativePointer;
x7: NativePointer;
x8: NativePointer;
x9: NativePointer;
x10: NativePointer;
x11: NativePointer;
x12: NativePointer;
x13: NativePointer;
x14: NativePointer;
x15: NativePointer;
x16: NativePointer;
x17: NativePointer;
x18: NativePointer;
x19: NativePointer;
x20: NativePointer;
x21: NativePointer;
x22: NativePointer;
x23: NativePointer;
x24: NativePointer;
x25: NativePointer;
x26: NativePointer;
x27: NativePointer;
x28: NativePointer;
fp: NativePointer;
lr: NativePointer;
}
Copy the code
My so was compiled by myself, and the specific assembly code is as follows. Anyway, it is clear that at 775C, x0 stored a pointer to the string “Sakura”. ReadCString (this.context.x0); readCString(this.context.x0); Print it out and the result is as follows
.text:000000000000772C ; __unwind {
.text:000000000000772C SUB SP, SP, #0x40
.text:0000000000007730 STP X29, X30, [SP,#0x30+var_s0]
.text:0000000000007734 ADD X29, SP, #0x30
.text:0000000000007738 ; 6: v6 = a1;
.text:0000000000007738 MOV X8, XZR
.text:000000000000773C STUR X0, [X29,#var_8]
.text:0000000000007740 ; 7: v5 = a3;
.text:0000000000007740 STUR X1, [X29,#var_10]
.text:0000000000007744 STR X2, [SP,#0x30+var_18]
.text:0000000000007748 ; 8: v4 = (const char *)_JNIEnv::GetStringUTFChars(a1, a3, 0LL);
.text:0000000000007748 LDUR X0, [X29,#var_8]
.text:000000000000774C LDR X1, [SP,#0x30+var_18]
.text:0000000000007750 MOV X2, X8
.text:0000000000007754 BL ._ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh ; _JNIEnv::GetStringUTFChars(_jstring *,uchar *)
.text:0000000000007758 STR X0, [SP,#0x30+var_20]
.text:000000000000775C ; 9: if((signed int)_JNIEnv::GetStringUTFLength(v6, v5) > 0 )
.text:000000000000775C LDUR X0, [X29,#var_8]
.text:0000000000007760 LDR X1, [SP,#0x30+var_18]
Copy the code
function inline_hook() {
var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
if (libnative_lib_addr) {
console.log("libnative_lib_addr:", libnative_lib_addr);
var addr_775C = libnative_lib_addr.add(0x775C);
console.log("addr_775C:", addr_775C);
Java.perform(function () {
Interceptor.attach(addr_775C, {
onEnter: function (args) {
var name = this.context.x0.readCString()
console.log("addr_775C OnEnter :".this.returnAddress, name);
},
onLeave: function (retval) {
console.log("retval is :", retval)
}
})
})
}
}
setImmediate(inline_hook())
Copy the code
Attaching...
libnative_lib_addr: 0x79fabe0000
addr_775C: 0x79fabe775c
TypeError: cannot read property 'apply'of undefined at [anon] (.. /.. /.. /frida-gum/bindings/gumjs/duktape.c:56618) at frida/runtime/core.js:55 [Google Pixel::myapplication.example.com.ndk_demo]-> addr_775C OnEnter : 0x79fabe7758 sakura addr_775C OnEnter : 0x79fabe7758 sakuraCopy the code
Here I can summarize my current learning, and I need to supplement some FRIda API learning. For example, There is a readCString in NativePointr, which needs to be looked at again.
Frida Native Hook: Frida Hook Native App
- Undo native layer debugging for Frida full-port detection
- Hook libc’s pthread_create function
- Reverse native debugging of TracePid
- Target: gtoad. Making. IO / 2017/06/25 /…
- Solve: Hook libc’s fgets function
- Native layer modifies parameters and return values
- Static analysis
JNI_Onload
- Dynamic Trace active registration & IDA traceability
- Dynamic TRACE JNI, LIBC function & IDA tracing
- Native layer actively calls and calls the call stack
- Actively call liBC to read and write files
Look at the logcat
n/u0a128 forThe activity com. Gdufs. Xman /. 12 to 28 MainActivity 05:53:26. 898, 26615, 26615 V com. Gdufs. Xman: JNI_OnLoad() 12-28 05:53:26.898 26615 26615 V com.gdufs.xman: RegisterNatives() --> nativeMethod() OK 12-28 05:53:26.898 26615 26615 D com.gdufs.xman m=: 0 12-28 05:53:26.980 26615 26615 D com.gdufs.xman m=: xmanCopy the code
sakura@sakuradeMacBook-Pro:~/gitsource/frida-agent-example/agent$ frida -U --no-pause -f com.gdufs.xman -lhook_reg.js ... [Google Pixel::com.gdufs.xman]-> [RegisterNatives] method_count: 0x3 [RegisterNatives] java_class: com.gdufs.xman.MyApp name: initSN sig: ()V fnPtr: 0xd4ddf3b1 module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x13b1 [RegisterNatives] java_class: com.gdufs.xman.MyApp name: saveSN sig: (Ljava/lang/String;) V fnPtr: 0xd4ddf1f9 module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x11f9 [RegisterNatives] java_class: com.gdufs.xman.MyApp name: work sig: ()V fnPtr: 0xd4ddf4cd module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x14cd
Copy the code
- InitSN feels that the meaning should be from
/sdcard/reg.dat
Read a value in, and then sumEoPAoY62@ElRD
Compare. And finally setValue, from the export function, the first argument is supposed to be JNIEnv *env, and then we see that we’re assigning to field M.
- SaveSN looks like it’s just going to transform the string “W3_arE_whO_we_ARE” based on the value of STR, and write to
/sdcard/reg.dat
In the
When initSN checks that /sdcard/reg.dat is EoPAoY62@ElRD, it should set m to 1. As long as m is 1, it follows the logic of the work() function.
Refer to Frida’s File API
function main() {
var file = new File("/sdcard/reg.dat".'w')
file.write("EoPAoY62@ElRD")
file.flush()
file.close()
}
setImmediate(main())
Copy the code
So let’s continue with the logic of Work
V2 is derived from getValue, which looks like the value of the m field, which should be 1.
[NewStringUTF] bytes: The input is flag in the format of xman{...... }!Copy the code
The work function is called again in callWork, and the loop is endless.
It looks like we’re back to initSN, but we’re looking at it in the wrong order. N2 executes and saves to the file, and n1 checks. Therefore, n2’s algorithm must be reversed, pass.
Frida Trace four-piece set
jni trace : trace jni
Github.com/chame1eon/j…
PIP install jnitrace Requirement already SATISFIED: FRIda >=12.5.0in/ Users/sakura /. Pyenv/versions / 3.7.7 / lib/python3.7 / site - packages (from jnitrace) (12.8.0) Requirement already satisfied: coloramain/ Users/sakura /. Pyenv/versions / 3.7.7 / lib/python3.7 / site - packages (from jnitrace) (0.4.3) Collecting the hexdump) (the from jnitrace) Downloading https://files.pythonhosted.org/packages/55/b3/279b1d57fa3681725d0db8820405cdcb4e62a9239c205e4ceac4391c78e4/hexdump-3.3.z ip Installing collected packages: hexdump, jnitrace Running setup.py installfor hexdump ... done
Running setup.py install for jnitrace ... doneSuccessfully installed hexdump) - 3.3 jnitrace - 3.0.8Copy the code
Usage: jnitrace [options] -l libname target default should be spawn run,
-m
Is to specifyspawn
orattach
-b
Specify whetherfuzzy
oraccurate
-i <regex>
Specify a regular expression to filter out method names, for example-i Get -i RegisterNatives
Only JNI Methods with Get or RegisterNatives in their name will be printed.-e <regex>
and-i
Instead, it is filtered through regular expressions, but this time the specified content is ignored.-I <string>
Jnitrace considers that the exported functions should be functions that can be called directly from the Java side, so you can include functions registered using RegisterNatives, for example-I stringFromJNI -I nativeMethod([B)V
These include stringFromJNI in the export name, and functions registered with RegisterNames and signed with nativeMethod([B)V.-o path/output.json
Export the output to a file.-p path/to/script.js
To load a Frida script specifying the path into the target process before loading the Jnitrace script. This can be used to counter anti-debugging before jnitrace starts.-a path/to/script.js
, used to load the Frida script of the specified path into the target process after the Jnitrace script is loaded--ignore-env
Do not print all JNIEnv functions--ignore-vm
Do not print all JavaVM functions
sakura@sakuradeMacBook-Pro:~/Desktop/lab/alpha/tools/android/frida_learn/0620/0620/xman/resources/lib/armeabi-v7a$ jnitrace -l libmyjni.so com.gdufs.xman
Tracing. Press any key to quit...
Traced library "libmyjni.so" loaded from path "/data/app/com.gdufs.xman-X0HkzLhbptSc0tjGZ3yQ2g==/lib/arm". /* TID 28890 */ 355 ms [+] JavaVM->GetEnv 355 ms |- JavaVM* : 0xefe99140 355 ms |- void** : 0xda13e028 355 ms |: 0xeff312a0 355 ms |- jint : 65542 355 ms |= jint : 0 355 ms ------------------------Backtrace------------------------ 355 ms |-> 0xda13a51b: JNI_OnLoad+0x12 (libmyjni.so:0xda139000) /* TID 28890 */ 529 ms [+] JNIEnv->FindClass 529 ms |- JNIEnv* : 0xeff312a0 529 ms |- char* : 0xda13bdef 529 ms |: com/gdufs/xman/MyApp 529 ms |= jclass : 0x81 { com/gdufs/xman/MyApp } 529 ms ------------------------Backtrace------------------------ 529 ms |-> 0xda13a539: JNI_OnLoad+0x30 (libmyjni.so:0xda139000) /* TID 28890 */ 584 ms [+] JNIEnv->RegisterNatives 584 ms |- JNIEnv* : 0xeff312a0 584 ms |- jclass : 0x81 { com/gdufs/xman/MyApp } 584 ms |- JNINativeMethod* : 0xda13e004 584 ms |: 0xda13a3b1 - initSN()V 584 ms |: 0xda13a1f9 - saveSN(Ljava/lang/String;) V 584 ms |: 0xda13a4cd - work()V
584 ms |- jint : 3
584 ms |= jint : 0
584 ms ------------------------Backtrace------------------------
584 ms |-> 0xda13a553: JNI_OnLoad+0x4a (libmyjni.so:0xda139000)
/* TID 28890 */
638 ms [+] JNIEnv->FindClass
638 ms |- JNIEnv* : 0xeff312a0
638 ms |- char* : 0xda13bdef
638 ms |: com/gdufs/xman/MyApp
638 ms |= jclass : 0x71 { com/gdufs/xman/MyApp }
638 ms -----------------------Backtrace-----------------------
638 ms |-> 0xda13a377: setValue+0x12 (libmyjni.so:0xda139000)
/* TID 28890 */
688 ms [+] JNIEnv->GetStaticFieldID
688 ms |- JNIEnv* : 0xeff312a0
688 ms |- jclass : 0x71 { com/gdufs/xman/MyApp }
688 ms |- char* : 0xda13be04
688 ms |: m
688 ms |- char* : 0xda13be06
688 ms |: I
688 ms |= jfieldID : 0xf1165004 { m:I }
688 ms -----------------------Backtrace-----------------------
688 ms |-> 0xda13a38d: setValue+0x28 (libmyjni.so:0xda139000)
Copy the code
strace : trace syscall
Linuxtools – RST. Readthedocs. IO/zh_CN/lates…
frida-trace : trace libc(or more)
Frida. Re/docs/frida -…
Usage:frida-trace [options] target
frida-trace -U -i "strcmp" -f com.gdufs.xman
...
5634 ms strcmp(s1="fi", s2="es-US")
5635 ms strcmp(s1="da", s2="es-US")
5635 ms strcmp(s1="es", s2="es-US")
5635 ms strcmp(s1="eu-ES", s2="es-US")
5635 ms strcmp(s1="et-EE", s2="es-US")
5635 ms strcmp(s1="et-EE", s2="es-US")
Copy the code
- art trace: hook artmethod
Hook_artmethod: Trace Java function call
Github.com/lasting-yan…
Modify AOSP source code print
Change aOSP source trace information
Frida Native Hook: init_array development and automated reverse engineering
Init_array principle
Common protection is done in init_array. Read the following article about how this works.
- IDA debug android so. Init_array array
- Android NDK. Init and. Init_array functions
- Linker study notes
IDA static analysis init_array
// Build in.init
extern "C" void _init(void) {
LOGD("Enter init......");
}
Init_array [name can be changed]
__attribute__((__constructor__)) static void sakura_init(a) {
LOGD("Enter sakura_init......"); }... .2016- 12- 29 16:51:23.017 5160- 5160./com.example.ndk_demo D/sakura1328: Enter init......
2016- 12- 29 16:51:23.017 5160- 5160./com.example.ndk_demo D/sakura1328: Enter sakura_init......
Copy the code
shift+F7
.init_array
IDA dynamically debugs SO
- Open the APK you want to debug and find the entry
sakura@sakuradeMacBook-Pro:~/.gradle/caches$ adb shell dumpsys activity top | grep TASK
TASK com.android.systemui id=29 userId=0
TASK null id=26 userId=0
TASK com.example.ndk_demo id=161 userId=0
Copy the code
-
Adb shell am start -d -n com.example.ndk_demo/.mainActivity adb shell am start -d -n com.example.ndk_demo/.mainActivity
-
Perform android_server64
sailfish:/data/local/tmp # ./android_server64IDA Android 64-bit Remote Debug Server (ST) V1.22. hx-Rays (C) 2004-2017 Listening on 0.0.0.0:23946 IDA Android 64-bit Remote Debug Server (ST) V1.22. hx-Rays (C) 2004-2017 Listening on 0.0.0.0:23946...Copy the code
- Open a new window for port forwarding using the forward program:
adb forward tcp:23946 tcp:23946
The adb forward TCP: < local machine network port number > TCP: < simulator or real machine network port number > example: adb [| | – e – d – s] forward TCP: TCP 6100:7100 When the simulator or the real machine sends data to its own port 7100, we can read the content sent from the local machine’s port 6100. This is a very important command. In the future, before we use JDB debugging APK, It is used to associate the destination process with the local port
-
Open IDA and choose From the menu Debugger -> Attach -> Remote ARM Linux/Android Debugger
-
Open IDA, select Debugger -> Process Options, fill it in, and then select Attach Process.
-
Look up the process adb JDWP for debugging
sakura@sakuradeMacBook-Pro:~$ adb jdwp
10436
Copy the code
-
Adb Forward TCP :8700 JDWP :10436, binds the debug port of this process to the host 8700.
-
JDB connection debug port, so that the program continues to run JDB – connect com. Sun. Jdi. SocketAttach: hostname = 127.0.0.1, port = 8700
-
Find and break the breakpoint.
Open the module
Eventually I can actually debug.init_array initialization. See Linker’s study notes for the code analysis.
Init_array && JNI_Onload
JNI_Onload
The goal is to find the address of a dynamically registered function, because such a function is not exported.
JNINativeMethod methods[] = {
{"stringFromJNI2"."()Ljava/lang/String;", (void *) stringFromJNI2},
};
env->RegisterNatives(env->FindClass("com/example/ndk_demo/MainActivity"), methods,
1);
Copy the code
First jnitrace -m spawn -I “RegisterNatives” -l libnative-lib.so com.example.ndk_demo
525 ms [+] JNIEnv->RegisterNatives
525 ms |- JNIEnv* : 0x7a106cc1c0
525 ms |- jclass : 0x89 { com/example/ndk_demo/MainActivity }
525 ms |- JNINativeMethod* : 0x7ff0b71120
525 ms |: 0x79f00d36b0 - stringFromJNI2()Ljava/lang/String;
Copy the code
Then objection – d – g com. Example. Ndk_demo run list memory modules explore | grep demo
sakura@sakuradeMacBook-Pro:~$ objection -d-g com.example.ndk_demo run memory list modules explore | grep demo [debug] Attempting to attach to process: `com.example.ndk_demo` Warning: Output is not to a terminal (fd=1).base. Odex 0x79F0249000 106496 (104.0kib) / data/app/com. Example. Ndk_demo - HGAFhnKyKCSIpzn227pwXw = = / oat/arm64 / base odex libnative - lib. So zero x79f00c4000 221184 (216.0) KiB) /data/app/com.example.ndk_demo-HGAFhnKyKCSIpzn227pwXw==/lib/arm64/libnative...Copy the code
offset = 0x79f00d36b0 – 0x79f00c4000 = 0xf6b0
And there you have it
init_array
Adb install –abi armeabi-v7a forces the app to run in 32-bit mode without arm64 support
This script is basically a hook callfunction, and then prints out the function address and parameters in init_array.
The first parameter indicates that this is the function registered in init_array. The second parameter indicates the address of the function stored in init_array.
template <typename F>
static void call_array(const char* array_name __unused,
F* functions,
size_t count,
bool reverse,
const char* realpath) {
if (functions == nullptr) {
return;
}
TRACE("[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);
int begin = reverse ? (count - 1) : 0;
int end = reverse ? - 1 : count;
int step = reverse ? - 1 : 1;
for (inti = begin; i ! = end; i += step) { TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
call_function("function", functions[i], realpath);
}
TRACE("[ Done calling %s for '%s' ]", array_name, realpath);
}
Copy the code
function LogPrint(log) {
var theDate = new Date(a);var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds()
hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);
}
function hooklinker() {
var linkername = "linker";
var call_function_addr = null;
var arch = Process.arch;
LogPrint("Process run in:" + arch);
if (arch.endsWith("arm")) {
linkername = "linker";
} else {
linkername = "linker64";
LogPrint("arm64 is not supported yet!");
}
var symbols = Module.enumerateSymbolsSync(linkername);
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
//LogPrint(linkername + "->" + symbol.name + "---" + symbol.address);
if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != - 1) {
call_function_addr = symbol.address;
LogPrint("linker->" + symbol.name + "-" + symbol.address)
}
}
if(call_function_addr ! =null) {
var func_call_function = new NativeFunction(call_function_addr, 'void'['pointer'.'pointer'.'pointer']);
Interceptor.replace(new NativeFunction(call_function_addr,
'void'['pointer'.'pointer'.'pointer']), new NativeCallback(function (arg0, arg1, arg2) {
var functiontype = null;
var functionaddr = null;
var sopath = null;
if(arg0 ! =null) {
functiontype = Memory.readCString(arg0);
}
if(arg1 ! =null) {
functionaddr = arg1;
}
if(arg2 ! =null) {
sopath = Memory.readCString(arg2);
}
var modulebaseaddr = Module.findBaseAddress(sopath);
LogPrint("after load:" + sopath + "--start call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
if (sopath.indexOf('libnative-lib.so') > =0 && functiontype == "DT_INIT") {
LogPrint("after load:" + sopath + "--ignore call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
} else {
func_call_function(arg0, arg1, arg2);
LogPrint("after load:" + sopath + "--end call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:"+ modulebaseaddr); }},'void'['pointer'.'pointer'.'pointer']));
}
}
setImmediate(hooklinker)
Copy the code
I debug linker64, because the address of call_function is not exported, so I cannot hook the symbol name directly, instead, I need to hook it according to the offset, and I will talk about it later. Actually depends on init_array, direct shift + F7 to find in the segment. Init_array section is ok, here mainly to reflect debugging, because may reverse debugging in init_array hook call_function can let it not loading the debugger.
Native layer unexported function calls (arbitrary symbols and addresses)
Now I want to actively call sakurA_add to print the value, either by opening IDA to find the symbol, or by offsetting, or eventually by initializing a NativeFunction with this NativePointer.
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ndk_1demo_MainActivity_sakuraWithInt(JNIEnv *env, jobject thiz, jint a, jint b) {
// TODO: implement sakuraWithInt()
returnsakura_add(a,b); }...int sakura_add(int a, int b){
int sum = a+b;
LOGD("sakura add a+b:",sum);
return sum;
}
Copy the code
function main() {
var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
console.log("libnative_lib_addr is :", libnative_lib_addr);
if (libnative_lib_addr) {
var sakura_add_addr1 = Module.findExportByName("libnative-lib.so"."_Z10sakura_addii");
var sakura_add_addr2 = libnative_lib_addr.add(0x0F56C);console.log("sakura_add_addr1 ", sakura_add_addr1);
console.log("sakura_add_addr2 ", sakura_add_addr2)
}
var sakura_add1 = new NativeFunction(sakura_add_addr1, "int"["int"."int"]);
var sakura_add2 = new NativeFunction(sakura_add_addr2, "int"["int"."int"]);
console.log("sakura_add1 result is :", sakura_add1(200.33));
console.log("sakura_add2 result is :", sakura_add2(100.133)); } setImmediate(main()) ... . libnative_lib_addr is :0x79fa1c5000
sakura_add_addr1 0x79fa1d456c
sakura_add_addr2 0x79fa1d456c
sakura_add1 result is : 233
sakura_add2 result is : 233
Copy the code
C/C++ hook
//todo
Native/JNI layer parameter printing and active call parameter construction
Jni primitive types are converted to c++ objects by calling jni-related apis before they can be printed and invoked. When jNI calls, there are two ways to construct parameters, one is java.vm. getenv, and the other is to hook the ENV and call the JNI API to construct parameters.