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 查看导出函数,静态函数直接搜索
|
2、找到了函数名:”_Z8method01P7_JNIEnvP7_jclassP8_jstring “
可以利用如下网站转换,得到原函数名:
GCC and MSVC C++ Demangler
或者这块使用 https://github.com/lasting-yang/frida_hook_libart.git 这个工具也可以。
frida -U -f com.example.demoso1 -l hook_RegisterNatives.js –no-pause 查看原始方法名
|
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]; 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);
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) { console.log("args[0]-->", args[0]); console.log("args[1]-->", args[1]); console.log("args[2]-->", args[2]); },onLeave: function (retval) { console.log("[onLeave] retval:", retval); } });
}
|
(3)这块只需要对 method1 和 method2 进行hook,所以在hook_RegisterNatives方法中添加判断:
if (name.indexOf("method") >= 0){hookmethod(fnPtr_ptr);}
|
(4)运行代码,发现成功hook上了,打印出来了对应的地址。
(5)传进去的参数喝返回值都是字符串,所以这块需要打印出来具体的内容:Java.vm.getEnv().getStringUtfChars(result, null).readCString()
function hookmethod(addr){ Interceptor.attach(addr, { onEnter: function (args) { console.log("args[0]-->", args[0]); console.log("args[1]-->", args[1]); console.log("args[2]-->", Java.vm.getEnv().getStringUtfChars(arg[2], null).readCString()); },onLeave: function (retval) { console.log("[onLeave] retval:", Java.vm.getEnv().getStringUtfChars(result, null).readCString()); } });
}
|
这样就可以得到加密前的参数和加密后的返回值了:
主动调用
前面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'])); }
|
可以成功打印出地址:
调用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'])); }
|
最终代码如下:
使用下面的方法来实习主动调用:修改hook_RegisterNatives,添加hookMethod01和invokemethod02
在hook_RegisterNatives方法中添加 if(name.indexOf("method01")>=0){ 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){ 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; }
|
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
脱离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")
|
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"])
|