Frida主动调用与RPC

Frida RPC开发姿势

Frida是一款基于Python和JavaScript的进程级Hook框架,其中JavaScript语言承担了Hook函数的主要工作,而Python语言则相当于一个提供给外界的绑定接口,使用者可以通过Python语言将JavaScript脚本注入进程中.官方也在下方仓库中提供了通过Python远程外部调用JavaScript中函数的方式.

https://github.com/frida/frida-python

下面简单介绍下通过Python实现Frida注入的基本方式.

获取设备

1
2
3
4
5
6
7
8
import frida
# 无线连接
# /data/local/tmp/frida-server -l 0.0.0.0:6666
# Wifi ADB监听IP和端口为192.168.2.111:5555
device = frida.get_device_manager().add_remote_device("192.168.2.111:6666")

# 有线连接
device = frida.get_usb_device()

注入进程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import frida
import time

# spawn方式注入进程
pid = device.spawn(['com.example.luodemo'])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)

# attach模式注入进程
session = device.attach('com.example.luodemo')

注入脚本

1
2
3
4
5
6
7
8
9
import frida

script = session.create_script('''
setImmediate(Java.perform(function(){
    console.log('hello python frida')
}))
''') #读入Hook脚本内容

script.load() #将脚本加载进进程空间中

文件方式注入进程

1
2
3
with open("LuoHook.js", encoding="UTF-8") as f:
    script = session.create_script(f.read())
script.load()

测试Apk

下方代码示例中主要写了一个静态的Native函数method01以及动态的实例函数method02.

Java代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
57
58
59
package com.example.luodemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.example.luodemo.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "[XiaLuoHun]";

    // Used to load the 'luodemo' library on application startup.
    static {
        System.loadLibrary("luodemo");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());

        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.i(TAG, method01("LuoHun"));
            Log.i(TAG, method02(method01("LuoHun")));
        }

    }

    /**
     * A native method that is implemented by the 'luodemo' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    /**
     * AES加密, CBC, PKCS5Padding
     */
    public static native String method01(String str);

    /**
     * AES解密, CBC, PKCS5Padding
     */
    public native String method02(String str);
}

C代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
#include <jni.h>
#include <string>

#include "aes_utils.h"
#include "tools.h"
#include "junk.h"

#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_luodemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

jstring JNICALL method01(JNIEnv *env, jclass jcls, jstring str_) {
    if (str_ == nullptr) return nullptr;

    const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
    char *result = AES_128_CBC_PKCS5_Encrypt(str);

    env->ReleaseStringUTFChars(str_, str);

    jstring jResult = getJString(env, result);
    free(result);

    return jResult;
}

JNIEXPORT jstring JNICALL method02(JNIEnv *env, jobject jcls, jstring str_) {
    if (str_ == nullptr) return nullptr;

    const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
    char *result = AES_128_CBC_PKCS5_Decrypt(str);

    env->ReleaseStringUTFChars(str_, str);

    jstring jResult = getJString(env, result);
    free(result);

    return jResult;
}

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv *env;
    vm->GetEnv((void **) &env, JNI_VERSION_1_6);
    JNINativeMethod methods[] = {
            {"method01", "(Ljava/lang/String;)Ljava/lang/String;", (void *) method01},
            {"method02", "(Ljava/lang/String;)Ljava/lang/String;", (void *) method02},
    };
    env->RegisterNatives(env->FindClass("com/example/luodemo/MainActivity"), methods, NELEM(methods));
    return JNI_VERSION_1_6;
}

Java层主动调用与RPC

Js Hook

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
function hookMethod01(){
    Java.perform(function(){
        var MainActivity = Java.use('com.example.luodemo.MainActivity')
        MainActivity.method01.implementation = function(str){
            var result = this.method01(str)//Hook时的主动调用
            console.log('str =>', str)
            console.log('result =>', result)
            return result
        }
    })
}

function invokeMethod01(plainText){
    var result = ''
    Java.perform(function(){
        var MainActivity = Java.use('com.example.luodemo.MainActivity')
        var javaString = Java.use('java.lang.String')
        //var plainText = 'LuoHun'
        result = MainActivity.method01(javaString.$new(plainText))
        console.log('plainText =>', plainText)
        console.log('result =>', result)
    })
    return result
}

function invokeMethod02(cipherText){
    var result = ''
    Java.perform(function(){
        Java.choose('com.example.luodemo.MainActivity',{
            onMatch:function(instance){
                var javaString = Java.use('java.lang.String')
                //var cipherText = '64a91c1b8eedb22b9c28d4b98e16ecc3'
                result = instance.method02(javaString.$new(cipherText))
                console.log('cipherText =>', cipherText)
                console.log('result =>', result)
            },
            onComplete(){
            }
        })
    })
    return result
}

rpc.exports={
    method01:invokeMethod01,
    method02:invokeMethod02
}

// function main(){
//     //hookMethod01()
//     //invokeMethod01('LuoHun')
//     invokeMethod02('64a91c1b8eedb22b9c28d4b98e16ecc3')
// }

// setImmediate(main())

Python调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import frida
import time
import json

def on_message(message, payload):
    print(message)
    print(payload)

# 无线连接
# /data/local/tmp/frida-server -l 0.0.0.0:6666
# Wifi ADB监听IP和端口为192.168.2.111:5555
#device = frida.get_device_manager().add_remote_device("192.168.2.111:6666")

# 有线连接
device = frida.get_usb_device()

# 启动App
pid = device.spawn(['com.example.luodemo'])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)

# 加载脚本
with open("LuoHook.js", encoding="UTF-8") as f:
    script = session.create_script(f.read())
script.on('message', on_message) #调用消息处理
script.load()
api = script.exports #获取导出函数列表

print('method01 => encode_result: ' + api.method01('LuoHun'))
print('method02 => decode_result: ' + api.method02('64a91c1b8eedb22b9c28d4b98e16ecc3'))

input()

Native层函数主动调用

Hook静态注册的JNI函数

  1. 可以使用IDA或者如下的Objection命令来查看指定库文件的导出符号.
1
memory list exports libluodemo.so --json Luo.json
  1. 以Hook stringFromJNI函数为例.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
function hookMethod(){
    var stringFromJNI = Module.findExportByName('libluodemo.so','Java_com_example_luodemo_MainActivity_stringFromJNI')
    //var libluodemo_addr = Module.findBaseAddress("libluodemo.so")
    //var stringFromJNI = libluodemo_addr.add(0x1FBE0) //打开IDA看偏移

    Interceptor.attach(stringFromJNI, {
        onEnter:function(args){
            //console.log('Enter stringFromJNI')
        },
        onLeave:function(retval){
            var env = Java.vm.getEnv()
            //构造一个新字符串
            var jstrings = env.newStringUtf("Hello XiaLuoHun");
            //替换返回值
            retval.replace(jstrings);
        }
    })
}

function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("dlopen:", path);
            }
        },
        onLeave: function(retval) {
        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("android_dlopen_ext:", path);
                if (path.indexOf("libluodemo.so") >= 0) {
                    this.target = true;
                }
            }
        },
        onLeave: function(retval) {
            if (this.target) {
                hookMethod()
            }
        }
    });
}

setImmediate(hook_dlopen);

Hook动态注册的JNI函数

  1. 首先使用下方仓库中的hook_RegisterNatives.js脚本来获取动态注册后函数的所在地址.

https://github.com/lasting-yang/frida_hook_libart

动态注册函数地址

  1. 可写出Hook动态注册的Native函数method01代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
57
function hookMethod(){
    var libluodemo_addr = Module.findBaseAddress("libluodemo.so")
    var method01 = libluodemo_addr.add(0x1fd64)
    //方式一
    // Interceptor.attach(method01, {
    //     onEnter:function(args){
    //         console.log('args[2] =>', Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())
    //     },
    //     onLeave:function(retval){
    //         console.log('retval =>', Java.vm.getEnv().getStringUtfChars(retval, null).readCString())
    //     }
    // })

    //方式二
    var addr_func = new NativeFunction(method01, 'pointer', ['pointer', 'pointer', 'pointer'])
    Interceptor.replace(method01, new NativeCallback(function(arg0, arg1, arg2){
        var result = addr_func(arg0, arg1, arg2) //Hook中的主动调用
        console.log('arg2 =>', Java.vm.getEnv().getStringUtfChars(arg2, null).readCString())
        console.log('result =>', Java.vm.getEnv().getStringUtfChars(result, null).readCString())
        return result

    }, 'pointer', ['pointer', 'pointer', 'pointer']))
}

function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("dlopen:", path);
            }
        },
        onLeave: function(retval) {
        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("android_dlopen_ext:", path);
                if (path.indexOf("libluodemo.so") >= 0) {
                    this.target = true;
                }
            }
        },
        onLeave: function(retval) {
            if (this.target) {
                hookMethod()
            }
        }
    });
}

setImmediate(hook_dlopen);

主动调用

从上面Hook的第二种方式,可以得到下述的主动调用代码.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function invoke_func(addr, contents){
    var result = null
    var func = new NativeFunction(addr, 'pointer', ['pointer', 'pointer', 'pointer'])
    Java.perform(function(){
        var env = Java.vm.getEnv()
        var jstring = env.newStringUtf(contents)
        result = func(env,ptr(1), jstring)
        result = env.getStringUtfChars(result, null)
    })
    return result
}

function inmvoke_method01(){
    var base = Module.findBaseAddress("libluodemo.so")
    var method01_addr = base.add(0x1fd64)
    var result = invoke_func(method01_addr, 'LuoHun')
    console.log('result =>', result.readCString())
}

setImmediate(inmvoke_method01);
注意
在JNI函数中,第一个参数一定是JNIEnv的指针,第二个参数取决于对应JNI函数在Java层中是静态还是动态函数,分别对应jclass类型和jobject类型.若第二个参数没有使用则可以任意传递相同类型的数据,否则就需要通过env对象进行构造.

主动加载模块并调用其中的函数

  1. 解压Apk,将对应的模块推送到手机的/data/app目录下,并以Root身份赋予所有权限.
1
2
3
4
5
6
7
8
adb push .\libluodemo.so /data/local/tmp
adb shell
su
mv libluodemo.so /data/app
chmod 777 /data/app/libluodemo.so

//关闭selinux
setenforce 0
  1. 加载模块并自动调用的函数代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function invoke_func(addr, contents){
    var result = null
    var func = new NativeFunction(addr, 'pointer', ['pointer', 'pointer', 'pointer'])
    Java.perform(function(){
        var env = Java.vm.getEnv()
        var jstring = env.newStringUtf(contents)
        result = func(env,ptr(1), jstring)
        result = env.getStringUtfChars(result, null)
    })
    return result
}

function inmvoke_method01(){
    var base = Module.load('/data/app/libluodemo.so').base
    var method01_addr = base.add(0x1fd64)
    var result = invoke_func(method01_addr, 'LuoHun')
    console.log('result =>', result.readCString())
}

setImmediate(inmvoke_method01);

相关内容

0%