Native层主动调用

demo

demo主要基于如下这篇文章实现:

Android NDK AES 加解密 - 简书

两个方法:method01 和 method02 实现AES加解密,但是为了防止 AES的key和iv直接硬编码存储在代码中,可以做混淆&花指令和加固等操作。

Hook

1、使用Objection查看Native中有哪些函数:

objection -g com.example.demoso1 -d  explore
memory list exports libnative-lib.so 查看导出函数,静态函数直接搜索

image.png

2、找到了函数名:”_Z8method01P7_JNIEnvP7_jclassP8_jstring “

可以利用如下网站转换,得到原函数名:

GCC and MSVC C++ Demangler

image.png

或者这块使用 https://github.com/lasting-yang/frida_hook_libart.git 这个工具也可以。

frida -U -f com.example.demoso1 -l hook_RegisterNatives.js –no-pause 查看原始方法名

image.png

3、修改hook_RegisterNatives文件

写一个主动调用之前都要先进行Hook,本质来讲Hook就是一次简单的主动调用。。。

(1)原本hook_RegisterNatives.js文件内容如下:


function find_RegisterNatives(params) {
let symbols = Module.enumerateSymbolsSync("libart.so");
let addrRegisterNatives = null;
for (let i = 0; i < symbols.length; i++) {
let symbol = symbols[i];

//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
hook_RegisterNatives(addrRegisterNatives)
}
}

}

function hook_RegisterNatives(addrRegisterNatives) {

if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
let java_class = args[1];
let class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);

let methods_ptr = ptr(args[2]);

let method_count = parseInt(args[3]);
for (let i = 0; i < method_count; i++) {
let name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
let sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
let fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

let name = Memory.readCString(name_ptr);
let sig = Memory.readCString(sig_ptr);
let symbol = DebugSymbol.fromAddress(fnPtr_ptr)
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress));
}
}
});
}
}

setImmediate(find_RegisterNatives);

(2)添加如下方法进行hook

function hookmethod(addr){
Interceptor.attach(addr, {
onEnter: function (args) { //在Java层只有一个参数,到Native层就有三个参数
console.log("args[0]-->", args[0]); //JNIEnv
console.log("args[1]-->", args[1]); //jclass
console.log("args[2]-->", args[2]); //arg_ptr
},onLeave: function (retval) {
console.log("[onLeave] retval:", retval);
}
});

}

(3)这块只需要对 method1 和 method2 进行hook,所以在hook_RegisterNatives方法中添加判断:

if (name.indexOf("method") >= 0){hookmethod(fnPtr_ptr);}

image.png

(4)运行代码,发现成功hook上了,打印出来了对应的地址。

img

(5)传进去的参数喝返回值都是字符串,所以这块需要打印出来具体的内容:Java.vm.getEnv().getStringUtfChars(result, null).readCString()

function hookmethod(addr){
Interceptor.attach(addr, {
onEnter: function (args) { //在Java层只有一个参数,到Native层就有三个参数
console.log("args[0]-->", args[0]); //JNIEnv
console.log("args[1]-->", args[1]); //jclass
//Java.vm.getEnv().getStringUtfChars(result, null).readCString();
console.log("args[2]-->", Java.vm.getEnv().getStringUtfChars(arg[2], null).readCString()); //arg_ptr
},onLeave: function (retval) {
console.log("[onLeave] retval:", Java.vm.getEnv().getStringUtfChars(result, null).readCString());
}
});

}

这样就可以得到加密前的参数和加密后的返回值了:

img

主动调用

前面Hook已经没问题,接下来就是主动调用。。。但是 Interceptor.attach是不能用来做主动调用的,要做主动调用需要使用这个API Interceptor.replace

function replacehook(addr){
var addrfunc = new NativeFunction(addr, 'pointer',['pointer','pointer','pointer']);
Interceptor.replace(addr, new NativeCallback(function(arg1,arg2,arg3){
var result = addrfunc(arg1,arg2,arg3);
console.log("replacehook",result);
return result;
},'pointer',['pointer','pointer']));
}

可以成功打印出地址:

image.png

调用Java.vm.getEnv().getStringUtfChars(result, null).readCString()打印一下具体字符串:

function replacehook(addr){
var addrfunc = new NativeFunction(addr, 'pointer',['pointer','pointer','pointer']);
Interceptor.replace(addr, new NativeCallback(function(arg1,arg2,arg3){
var result = addrfunc(arg1,arg2,arg3);
console.log("replacehook",Java.vm.getEnv().getStringUtfChars(result, null).readCString());
return result;
},'pointer',['pointer','pointer']));
}

image.png

最终代码如下:

使用下面的方法来实习主动调用:修改hook_RegisterNatives,添加hookMethod01和invokemethod02

在hook_RegisterNatives方法中添加
if(name.indexOf("method01")>=0){
//hookmethod(fnPtr_ptr);
//replacehook(fnPtr_ptr);
method01addr = fnPtr_ptr;
}else if (name.indexOf("method02")>=0){
method02addr = fnPtr_ptr;
method02 = new NativeFunction(method02addr,'pointer',['pointer','pointer','pointer']);
}else{
continue;
}


新增invokemethod01
function invokemethod01(contents){
//这块的三个变量是定义三个全局变量,然后在hook_RegisterNatives方法进行赋值
console.log("ENV=>",ENV)
console.log("JCLZ=>",JCLZ);
console.log("method01_addr is =>",method01addr)
var method01 = new NativeFunction(method01addr,'pointer',['pointer','pointer','pointer']);
var NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
var result = null;
Java.perform(function(){
console.log("Java.vm.getEnv()",Java.vm.getEnv())
var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
result = method01(Java.vm.getEnv(),JSTRING,JSTRING);
console.log("result is =>",result)
console.log("result is ",Java.vm.getEnv().getStringUtfChars(result, null).readCString())
result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();
})
return result;
}
新增invokemethod02
function invokemethod02(contents){
var result = null;
Java.perform(function(){
var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
result = method02(Java.vm.getEnv(),JSTRING,JSTRING);
result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();
})
return result;
}

image.png

RPC

rpc.exports = {
invoke1:invokemethod01, // 导出名不可以有大写字母或者下划线
invoke2:invokemethod02
};

init.py

import time
import frida
from flask import Flask, jsonify, request
import json

def my_message_handler(message , payload): #定义错误处理
print(message)
print(payload)


# 连接安卓机上的frida-server
#device = frida.get_device_manager().add_remote_device("192.168.0.3:8888")
#device = frida.get_device_manager().add_remote_device("118.126.66.193:58888")
device = frida.get_device_manager().add_remote_device("192.168.0.105:8888")
#device = frida.get_usb_device()

# 启动`demo01`这个app
pid = device.spawn(["com.example.demoso1"])
session = device.attach(pid)

# 加载脚本
with open("hook_RegisterNatives.js") as f:
script = session.create_script(f.read())
script.on("message" , my_message_handler) #调用错误处理
script.load()

time.sleep(3)
device.resume(pid)

time.sleep(3)


print(script.exports.invoke1("onejane"))
print(script.exports.invoke2("c6138f96658ce0cb845bdab0f9616273"))

# 脚本会持续运行等待输入
#input()


app = Flask(__name__)


@app.route('/encrypt', methods=['POST'])#url加密
def encrypt_class():
data = request.get_data()
json_data = json.loads(data.decode("utf-8"))
postdata = json_data.get("data")
#print(postdata)
res = script.exports.invoke1(postdata)
return res


@app.route('/decrypt', methods=['POST'])#data解密
def decrypt_class():
data = request.get_data()
json_data = json.loads(data.decode("utf-8"))
postdata = json_data.get("data")
res = script.exports.invoke2(postdata)
return res



if __name__ == '__main__':
app.run()

端口usb连接,主机连接adb, ./fs128arm64 -l 0.0.0.0:8888 启动frida server

img

img

脱离APK

前提:函数的整个实现逻辑都是在Native层中的,不涉及到Java层的回调。

1、首先,解压APK,找到对应so文件,将其传到指定目录下,这块以/data/local/tmp为例

adb push libnative-lib.so /data/local/tmp

chmod 777 libnative-lib.so //赋予执行权限

2、修改上面的hook脚本如下:

var modulelibnative =  Module.load("/data/app/libnative-lib.so")  // 加载so

3、Frida加载,将so文件引用到设置中

frida -U -f com.android.settings -l hook_RegisterNatives.js --no-pause

4、Objection查看,可以看到libnative-lib.so已经被加载

objection -g com.android.settings explore 
memory list modules 可以看到libnative-lib.so已经被加载
memory list exports libnative-lib.so 找到两个函数名_Z8method01P7_JNIEnvP7_jclassP8_jstring,_Z8method02P7_JNIEnvP8_jobjectP8_jstring

5、主动调用

method01addr = modulelibnative.findExportByName("_Z8method01P7_JNIEnvP7_jclassP8_jstring")
method02addr = modulelibnative.findExportByName("_Z8method02P7_JNIEnvP8_jobjectP8_jstring")

修改init.py中pid = device.spawn(["com.android.settings"])

img