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

  1. The installation
git clone https://github.com/oleavr/frida-agent-example.git
cd frida-agent-example/
npm install
Copy the code
  1. Use VScode to open this project, write JS under agent folder, there will be intelligent prompt.
  2. npm run watchAutomatically generates JS files to monitor code changes
  3. Load _agent.js in python script or CLIfrida -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 nameThis 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-returnOn 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 asxx.value = yyOtherwise, 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, passClass name $Inner class nameUse or choose
  • Apply reflection to clazz derived from use, as shown inclazz.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:

  • throughenumerateClassLoadersTo 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 = loaderTo 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.hHeader 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, throughProcess.enumerateModules()This API enumerates modules that have been loaded into memory.
  • Then throughModule.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 throughfindExportByName(moduleName: string, exportName: string): NativePointerTo 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. throughInterceptor.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 analysisJNI_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.datRead a value in, and then sumEoPAoY62@ElRDCompare. 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.datIn 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,

  • -mIs to specifyspawnorattach
  • -bSpecify whetherfuzzyoraccurate
  • -i <regex>Specify a regular expression to filter out method names, for example-i Get -i RegisterNativesOnly JNI Methods with Get or RegisterNatives in their name will be printed.
  • -e <regex>and-iInstead, 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)VThese include stringFromJNI in the export name, and functions registered with RegisterNames and signed with nativeMethod([B)V.
  • -o path/output.jsonExport the output to a file.
  • -p path/to/script.jsTo 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-envDo not print all JNIEnv functions
  • --ignore-vmDo 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.

C/C++ programming so and introducing Frida to call its functions