转载:使用 Frida 来 hook 加固的 Android 应用的 java 层
?
Android 加固應用Hook方式 --- Frida:https://github.com/xiaokanghub/Android
轉載:使用 frida 來 hook 加固的 Android 應用的 java 層:https://bbs.pediy.com/thread-246767.htm
使用 Frida 給 apk 脫殼并穿透加固 Hook 函數:https://xz.aliyun.com/t/7670
[推薦] 『Android安全』版2018年優秀和精華帖分類索引:https://bbs.pediy.com/thread-249602.htm
?
?
1. Frida?Hook Android 加固應用 方法
?
1、Android 加固應用 Hook 方式?
Java.perform(function () {var application = Java.use('android.app.Application');application.attach.overload('android.content.Context').implementation = function (context) {var result = this.attach(context);var classloader = context.getClassLoader();Java.classFactory.loader = classloader;var yeyoulogin = Java.classFactory.use('com.zcm.主窗口');console.log("yeyoulogin:" + yeyoulogin);yeyoulogin.按鈕_用戶登錄$被單擊.implementation = function (arg) {console.log("retval:" + this.返回值);}} });列出加載的類
Java.enumerateLoadedClasses({"onMatch": function (className) { console.log(className); },"onComplete": function () { }} )?
2、Hook 動態加載類
獲取構造函數的參數
Java.perform(function () {//創建一個DexClassLoader的wappervar dexclassLoader = Java.use("dalvik.system.DexClassLoader");//hook 它的構造函數$init,我們將它的四個參數打印出來看看。dexclassLoader.$init.implementation = function (dexPath, optimizedDirectory, librarySearchPath, parent) {console.log("dexPath:" + dexPath);console.log("optimizedDirectory:" + optimizedDirectory);console.log("librarySearchPath:" + librarySearchPath);console.log("parent:" + parent);//不破換它原本的邏輯,我們調用它原本的構造函數。this.$init(dexPath, optimizedDirectory, librarySearchPath, parent);}console.log("down!"); });?
獲取動態加載的類
Java.perform(function () {var dexclassLoader = Java.use("dalvik.system.DexClassLoader");var hookClass = undefined;var ClassUse = Java.use("java.lang.Class");dexclassLoader.loadClass.overload('java.lang.String').implementation = function (name) {//定義一個String變量,指定我們需要的類var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";//直接調用第二個重載方法,跟原本的邏輯相同。var result = this.loadClass(name, false);//如果loadClass的name參數和我們想要hook的類名相同if (name === hookname) {//則拿到它的值hookClass = result;//打印hookClass變量的值console.log(hookClass);send(hookClass);return result;}return result;} });?
通過 Java.cast 處理泛型方法(JAVA中Class<?>表示泛型),再調用動態加載方法
Java.perform(function(){var hookClass = undefined;var ClassUse = Java.use("java.lang.Class");var dexclassLoader = Java.use("dalvik.system.DexClassLoader");var constructorclass = Java.use("java.lang.reflect.Constructor");var objectclass= Java.use("java.lang.Object");dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";var result = this.loadClass(name,false);if(name == hookname){var hookClass = result;console.log("------------------------------CAST--------------------------------")//類型轉換var hookClassCast = Java.cast(hookClass,ClassUse);//調用getMethods()獲取類下的所有方法var methods = hookClassCast.getMethods();console.log(methods);console.log("-----------------------------NOT CAST-----------------------------")//未進行類型轉換,看看能否調用getMethods()方法var methodtest = hookClass.getMethods();console.log(methodtest);console.log("---------------------OVER------------------------")return result;}return result;}});?
利用 getDeclaredConstructor() 獲取具有指定參數列表構造函數的Constructor 并實例化
Java.perform(function () {var hookClass = undefined;var ClassUse = Java.use("java.lang.Class");var objectclass = Java.use("java.lang.Object");var dexclassLoader = Java.use("dalvik.system.DexClassLoader");var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");var Integerclass = Java.use("java.lang.Integer");//實例化MainActivity對象var mainAc = orininclass.$new();dexclassLoader.loadClass.overload('java.lang.String').implementation = function (name) {var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";var result = this.loadClass(name, false);if (name == hookname) {var hookClass = result;var hookClassCast = Java.cast(hookClass, ClassUse);console.log("-----------------------------BEGIN-------------------------------------");//獲取構造器var ConstructorParam = Java.array('Ljava.lang.Object;', [objectclass.class]);var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);console.log("Constructor:" + Constructor);console.log("orinin:" + mainAc);//實例化,newInstance的參數也是Ljava.lang.Object;var instance = Constructor.newInstance([mainAc]);console.log("patchAc:" + instance);send(instance);console.log("--------------------------------------------------------------------");return result;}return result;} });?
利用 getDeclaredMethods(),獲取本類中的所有方法
Java.perform(function(){var hookClass = undefined;var ClassUse = Java.use("java.lang.Class");var objectclass= Java.use("java.lang.Object");var dexclassLoader = Java.use("dalvik.system.DexClassLoader");var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");var Integerclass = Java.use("java.lang.Integer");//實例化MainActivity對象var mainAc = orininclass.$new();dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";var result = this.loadClass(name,false);if(name == hookname){var hookClass = result;var hookClassCast = Java.cast(hookClass,ClassUse);console.log("-----------------------------BEGIN-------------------------------------");//獲取構造器var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);console.log("Constructor:"+Constructor);console.log("orinin:"+mainAc);//實例化,newInstance的參數也是Ljava.lang.Object;var instance = Constructor.newInstance([mainAc]);console.log("MainActivityPatchInstance:"+instance);send(instance);console.log("----------------------------Methods---------------------------------");var func = hookClassCast.getDeclaredMethods();console.log(func);console.log("--------------------------Need Method---------------------------------");console.log(func[0]);var f = func[0];console.log("---------------------------- OVER---------------------------------");return result;}return result;} });?
調用 Method.invoke() 去執行方法
invoke 方法的參數
- 第一個參數:是執行這個方法的對象實例,
- 第二個參數:是帶入的實際值數組,
- 返回值:是 Object,也既是該方法執行后的返回值
?
read-std-string
/** Note: Only compatible with libc++, though libstdc++'s std::string is a lot simpler.*/function readStdString(str) {const isTiny = (str.readU8() & 1) === 0;if (isTiny) {return str.add(1).readUtf8String();}return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); }?
where_is_native
Java.perform(function () {var SystemDef = Java.use('java.lang.System');var RuntimeDef = Java.use('java.lang.Runtime');var exceptionClass = Java.use('java.lang.Exception');var SystemLoad_1 = SystemDef.load.overload('java.lang.String');var SystemLoad_2 = SystemDef.loadLibrary.overload('java.lang.String');var RuntimeLoad_1 = RuntimeDef.load.overload('java.lang.String');var RuntimeLoad_2 = RuntimeDef.loadLibrary.overload('java.lang.String');var ThreadDef = Java.use('java.lang.Thread');var ThreadObj = ThreadDef.$new();SystemLoad_1.implementation = function (library) {send("Loading dynamic library => " + library);stackTrace();return SystemLoad_1.call(this, library);}SystemLoad_2.implementation = function (library) {send("Loading dynamic library => " + library);stackTrace();SystemLoad_2.call(this, library);return;}RuntimeLoad_1.implementation = function (library) {send("Loading dynamic library => " + library);stackTrace();RuntimeLoad_1.call(this, library);return;}RuntimeLoad_2.implementation = function (library) {send("Loading dynamic library => " + library);stackTrace();RuntimeLoad_2.call(this, library);return;}function stackTrace() {var stack = ThreadObj.currentThread().getStackTrace();for (var i = 0; i < stack.length; i++) {send(i + " => " + stack[i].toString());}send("--------------------------------------------------------------------------");} });?
Non --- ASCII ( 不可見字符 )
如果代碼進行了混淆,一些函數、方法會變成 非ASCII、甚至有一些不可見的字符,所以可以先編碼打印出來,再用編碼后的字符串去 hook
int ?(int x) {return x + 100;}JavaScript 代碼:
Java.perform(function x() {var targetClass = "com.example.hooktest.MainActivity";var hookCls = Java.use(targetClass);var methods = hookCls.class.getDeclaredMethods();for (var i in methods) {console.log(methods[i].toString());console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));}hookCls[decodeURIComponent("%D6%8F")].implementation = function (x) {console.log("original call: fun(" + x + ")");var result = this[decodeURIComponent("%D6%8F")](900);return result;}} )?
使用 objection 打印混淆的方法名
使用 objection 打印混淆的方法名,然后再 hook 打印的方法名即可 hook 對應的函數
objection 是基于 frida 的命令行 hook 工具,可以讓你不寫代碼,?敲幾句命令就可以對 java 函數的高顆粒度 hook,?還支持 RPC 調用。
objection?目前只支持 Java層的 hook,但是 objection 有提供插件接口,可以自己寫 frida 腳本去定義接口,
比如葫蘆娃大佬的脫殼插件,實名推薦:?FRIDA-DEXDump
官方倉庫:?objection
這里以 騰訊新聞.apk 為例:
通過 抓包分析可知,騰訊新聞 app 有三個參數需要破解:qn-rid、qn-sig、qn-newsig,通過 jadx-gui 分析源碼可知,
- qn-rid :是一個 uuid。
- qn-sig :通過 qn-rid 加上一些別的參數,然后?md5 得到。
- qn-newsig :通過請求中的一些參數組合,然后 ?sha-256 得到
這里分析??qn-newsig 參數:
雙擊,定位到 qn-newsig
找到真正的 加密方法
使用 objection 注入 com.tencent.news,命令:objection -g com.tencent.news explore
列出 類 中的所有方法:
命令:android hooking list class_methods com.tencent.news.utils.n.b
在結合反編譯后的源碼,根據 函數返回值、反編譯后的注釋、函數參數的類型和個數 找出 真正要hook的函數。
public static java.lang.String com.tencent.news.utils.n.b.?(byte[]) public static java.lang.String com.tencent.news.utils.n.b.?(java.lang.String) public static java.lang.String com.tencent.news.utils.n.b.?(java.lang.String,int) public static java.lang.String com.tencent.news.utils.n.b.?(java.lang.String,java.lang.String) public static java.lang.String com.tencent.news.utils.n.b.?(long) public static java.lang.String com.tencent.news.utils.n.b.?(long,int)public static java.lang.String com.tencent.news.utils.n.b.?(java.lang.String) 這個函數就是真正要 hook 的函數,直接 hook,打印參數、返回值。即可
?
Hook 數據庫
var SQLiteDatabase = Java.use('com.tencent.wcdb.database.SQLiteDatabase'); var Set = Java.use("java.util.Set"); var ContentValues = Java.use("android.content.ContentValues"); SQLiteDatabase.insert.implementation = function (arg1, arg2, arg3) {this.insert.call(this, arg1, arg2, arg3);console.log("[insert] -> arg1:" + arg1 + "\t arg2:" + arg2);var values = Java.cast(arg3, ContentValues);var sets = Java.cast(values.keySet(), Set);var arr = sets.toArray().toString().split(",");for (var i = 0; i < arr.length; i++) {console.log("[insert] -> key:" + arr[i] + "\t value:" + values.get(arr[i]));} };?
?
2.?使用 Frida - DEXDump 進行 apk 脫殼
?
From:https://mp.weixin.qq.com/s/x8_aa762wpsvA4nhSLoppQ
github 地址:https://github.com/hluwa/FRIDA-DEXDump
示例:使用 Frida 給 apk 脫殼并穿透加固 Hook 函數:https://xz.aliyun.com/t/7670
?
脫殼的需求
APP 加固發展到現在已經好幾代了,從整體加固到代碼抽取到虛擬機保護,加固和脫殼的方案也逐漸趨于穩定。隨著保護越來越強,脫殼機們也變得越來越費勁,繁瑣。
不過對于我來說,很多時候其實并不需要那些被強保護起來的代碼,我只是想單純的看看這個地方的業務邏輯。所以,我追求的是更加快速、簡便的脫殼方法。
?
實現
得益于FRIDA, 在 PC 上面進行內存搜索、轉儲都變得十分方便,再也不需要考慮什么Xposed、什么Android開發、什么代碼注入,只需要關注如何去搜索想要的東西,于是依賴一個幾十行代碼的小腳本,就可以將大部分內存中的 dex 脫下來。在過去的一年,我幾乎所有脫殼的工作都是由此腳本來完成,目前已經隨手開源:https://github.com/hluwa/FRIDA-DEXDump
對于完整的 dex,采用暴力搜索 dex035 即可找到。而對于抹頭的 dex,通過匹配一些特征來找到。
?
使用
?
不會安裝使用 FRIDA 的,請先自行百度學會。。。然后,默念一聲 "我想脫個殼"。
- 啟動 APP。
- 啟動 frida-server。
- python main.py。默數三秒,脫好了。
或者可以將腳本封裝成命令:
?
?
3. 示 例
?
獲取完整的demo
完整的代碼已經上傳github,https://github.com/smartdone/Frida-Scripts/tree/master/shell
?
需求
在對一些加固的Android應用做測試的時候,脫殼二次打包是一個相當相當復雜的工作。所以一般是脫殼分析代碼,然后用hook的方式來動態劫持代碼。使用xposed來hook加固的應用大家可能已經很熟悉了,但是使用frida大概沒有多少人嘗試,今天就給大家分享下如何使用xposed來hook加固之后的Android應用。
?
基本原理
要hook加固的應用分為三步,第一步是拿到加載應用本事dex的classloader;第二步是通過這個classloader去找到被加固的類;第三步是通過這個類去hook需要hook的方法
?
得到第一步的classloader之后的hook操作和hook未加固的應用基本類似。
如何獲取classloader
我們看Android的android.app.Application的源碼http://androidxref.com/7.1.2_r36/xref/frameworks/base/core/java/android/app/Application.java#188可以發現,自己定義的Application的attachBaseContext方法是在Application的attach方法里面被調用的。而基本上所有的殼都是在attachBaseContext里面完成的代碼解密并且內存加載dex,在attachBaseContext執行完之后就可以去拿classloader,此時的classloader就已經是加載過加固dex的classloader了。
?
開始 hook 加固應用
以 "i春秋 app (?https://www.wandoujia.com/apps/7456953 )" 為例,此應用使用的360加固,我們的目標是hook他的flytv.run.monitor.fragment.user.AyWelcome?的?onCreate?方法,然后彈出一個?Toast。
直接使用?Java.use
我們直接使用Java.use來獲取這個Activity,代碼如下:
if(Java.available) {Java.perform(function(){var AyWelcome = Java.use("flytv.run.monitor.fragment.user.AyWelcome");if(AyWelcome != undefined) {console.log("AyWelcome: " + AyWelcome.toString());} else {console.log("AyWelcome: undefined");}}); }使用如下命令來注入這個js:
frida -R -f com.ni.ichunqiu -l hook_java.js運行之后會報如下的錯誤:
也就是找不到這個類,也就是我們現在這個默認的 classloader 找不到flytv.run.monitor.fragment.user.AyWelcome這個類。
獲取 classloader
代碼如下:
if(Java.available) {Java.perform(function(){var application = Java.use("android.app.Application");application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先執行原來的attach方法var classloader = context.getClassLoader();return result;}}); }現在我們在attach方法執行之后拿到了Context,并且通過context獲取了classloader,我們看現在的classloader是否加載了被加固的dex。我們使用classloader的loadClass方法去加載flytv.run.monitor.fragment.user.AyWelcome這個類,看是否成功:
if(Java.available) {Java.perform(function(){var application = Java.use("android.app.Application");var reflectClass = Java.use("java.lang.Class");application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先執行原來的attach方法var classloader = context.getClassLoader(); // 獲取classloadervar AyWelcome = classloader.loadClass("flytv.run.monitor.fragment.user.AyWelcome"); // 使用classloader加載類AyWelcome = Java.cast(AyWelcome, reflectClass); // 因為loadClass得到的是一個Object對象,我們需要把它強制轉換成Classconsole.log("AyWelcome class name: " + AyWelcome.getName());return result;}}); }注入這個 js,可以正確的打印出flytv.run.monitor.fragment.user.AyWelcome類名,說明我們拿到這個這個classloader是加載了加固過的dex的。
轉換成Java.use獲取到的js對象
在上一步我們雖然可以通過frida來獲取到加固之后的class,但是你如果直接使用這個{class}.{fuction}依然會失敗,因為class沒有這個成員變量,所以我們需要來實現獲取到與Java.use一樣的js對象,那么如何解決呢?當然是read the fuking source code。
我們看frida-java的use方法的實現,代碼在https://github.com/frida/frida-java/blob/9becc27091576fc198dc2a719c0fedb30a270b28/lib/class-factory.js#L139
代碼如下:
this.use = function (className) {let C = classes[className];if (!C) {const env = vm.getEnv();if (loader !== null) {const usedLoader = loader;if (cachedLoaderMethod === null) {cachedLoaderInvoke = env.vaMethod('pointer', ['pointer']);cachedLoaderMethod = loader.loadClass.overload('java.lang.String').handle;}const getClassHandle = function (env) {const classNameValue = env.newStringUtf(className);const tid = Process.getCurrentThreadId();ignore(tid);try {return cachedLoaderInvoke(env.handle, usedLoader.$handle, cachedLoaderMethod, classNameValue);} finally {unignore(tid);env.deleteLocalRef(classNameValue);}};C = ensureClass(getClassHandle, className);} else {const canonicalClassName = className.replace(/\./g, '/');const getClassHandle = function (env) {const tid = Process.getCurrentThreadId();ignore(tid);try {return env.findClass(canonicalClassName);} finally {unignore(tid);}};C = ensureClass(getClassHandle, className);}}return new C(null);};從代碼中我們可以看出來,他會先到他存class的一個列表里面去找,如果找不到,就會判斷loader是不是null,loader不為null,就會使用loader加載class,loader為null就會使用JNIEnv的findClass方法去找類,也就是使用默認的classloader。所以現在目標明確了,我們只需要讓這個loader是我們從Applicaiton的attach方法獲取到的classloader即可,那么怎么替換呢?
很顯然直接Java.loader會說undefined,我們看最終導出的是index.js這個腳本https://github.com/frida/frida-java/blob/022bc7d95c00d627091d4edc0ff87b67de5a9739/index.js#L22,有下面幾個成員變量:
let initialized = false; let api = null; let apiError = null; let vm = null; let classFactory = null; let pending = []; let threadsInPerform = 0; let cachedIsAppProcess = null;我們看到了,這個classFactory不就是我們剛剛上面看到的那個loader所在的地方嗎,那么要引用這個loader就很簡單了,直接Java.classFactory.loader就可以引用了,你可以使用console.log("classloader: " + Java.classFactory.loader);來獲取這個loader的值,后面我們直接將這個值替換為我們獲取的classloader就行了,代碼如下:
if(Java.available) {Java.perform(function(){var application = Java.use("android.app.Application");var reflectClass = Java.use("java.lang.Class");console.log("application: " + application);application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先執行原來的attach方法var classloader = context.getClassLoader(); // 獲取classloaderJava.classFactory.loader = classloader;var AyWelcome = Java.classFactory.use("flytv.run.monitor.fragment.user.AyWelcome"); //這里能直接使用Java.use,因為java.use會檢查在不在perform里面,不在就會失敗console.log("AyWelcome: " + AyWelcome);return result;}}); }寫 hook 加固的類的代碼,彈出 toast
if(Java.available) {Java.perform(function(){var application = Java.use("android.app.Application");var Toast = Java.use('android.widget.Toast');application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先執行原來的attach方法var classloader = context.getClassLoader(); // 獲取classloaderJava.classFactory.loader = classloader;var AyWelcome = Java.classFactory.use("flytv.run.monitor.fragment.user.AyWelcome"); //這里不能直接使用Java.use,因為java.use會檢查在不在perform里面,不在就會失敗console.log("AyWelcome: " + AyWelcome);// 然后下面的代碼就和寫正常的hook一樣啦AyWelcome.onCreate.overload('android.os.Bundle').implementation = function(bundle) {var ret = this.onCreate(bundle);Toast.makeText(context, "onCreate called", 1).show(); //彈出Toastreturn ret;}return result;}}); }最后效果如下:
?
?
?
?
總結
以上是生活随笔為你收集整理的转载:使用 Frida 来 hook 加固的 Android 应用的 java 层的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Grafana 使用教程 --- 开源的
- 下一篇: C 和 C++ 文件操作详解