天天看點

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

前言:前段時間看到有朋友在問在怎麼使用frida去hook動态加載的dex之類的問題,确實關于如何hook動态加載的dex,網上的資料很少,更不用說怎麼使用frida去hook動态加載的dex了。(frida的官方文檔已經無力吐槽…)後來偶然的情況下發現了一道CTF題目可以作為很好的案例,是以花了很多心思将文章寫下來分享給大家,涉及的内容比較多,我打算用上下篇将frida hook動态加載的dex的方法将清楚,上篇主要是引導及一些前置的知識。

文章涉及内容及使用到的工具

0x00 使用到的工具

ADT(Android Developer Tools)

Jadx-gui

JEB

frida

apktool

010 editor

天天模拟器(genymotion,實體機等亦可)

0x01 涉及知識點

Robust熱修複架構原理

Java 反射

Robust類 hook

apk的安裝和分析

文章使用的是DDCTF2018的android逆向第二題Hello Baby Dex

示例位址:下載下傳

0x02 apk安裝及使用

程式功能很簡單,就是輸入密碼并驗證,錯誤會Toast出一些資訊,按這個慣性,可能得輸入某些正确的值才能擷取flag吧,廢話不多說,直接反編譯看看。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

0x03 靜态代碼分析

一般情況下,我是直接扔到jadx中去看反編譯後的代碼,可是這次jadx反編譯出來的結果有很多錯誤,這裡我就不貼圖了,大家可以嘗試一下,看看是什麼錯誤。後面放到jeb工具中,便沒有報錯,是以檢視反編譯的效果時,大家可以多嘗試幾個反編譯器對比效果,檢視AndroidManifest.xml,找到了程式的入口MainActivity。

intent-filter> activity>

在此同時,在cmd中通過apktool d [apk],再将apk檔案反編譯出來。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

接下來直接定位到MainActivity的onCreate()方法,可以看到代碼是混淆過的,閱讀上稍微有點小困難,但是在onCreate()方法中,我們還是可以發現一些可疑的方法及變量,比如PatchProxy,changeQuickRedirect等。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

繼續搜尋有用的資訊,我們可以發現在onCreate()方法的else邏輯中調用了this.runRobust(),并且我們發現在類中每一個方法裡面都會存在一些changeQuickRedirect變量,以及isSupport(),accessDispatch()方法。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

上面的先放一邊,我們來看看runRobust()方法中寫了什麼,在else條件語句中可以看到它執行個體化了一些對象。

private void runRobust() {

//固定格式的changeQuickRedirect,isSupport,accessDispatch int v4 = 4; Object[] v0 = new Object[0]; ChangeQuickRedirect v2 = MainActivity.changeQuickRedirect; Class[] v5 = new Class[0]; Class v6 = Void.TYPE; MainActivity v1 = this; boolean v0_1 = PatchProxy.isSupport(v0, v1, v2, false, v4, v5, v6); if(v0_1) { v0 = new Object[0]; v2 = MainActivity.changeQuickRedirect; v5 = new Class[0]; v6 = Void.TYPE; v1 = this; PatchProxy.accessDispatch(v0, v1, v2, false, v4, v5, v6); } else { Context v1_1 = this.getApplicationContext(); //執行個體化PatchManipulateImp類 PatchManipulateImp v2_1 = new PatchManipulateImp(); //以及執行個體化PatchExecutor類 PatchExecutor v0_2 = new PatchExecutor(v1_1, ((PatchManipulate)v2_1), new GeekTanCallBack()); v0_2.start(); } }

跟進PatchManipulateImp類,可以發現在fetchPatchList方法中,它調用了getAssets().open("GeekTan.BMP");從資源檔案夾中,加載了一個BMP的圖檔檔案,并将這個BMP檔案内容寫入GeekTan.jar中。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

具體代碼如下:

try { v10 = arg17.getAssets().open("GeekTan.BMP"); v8 = new File(arg17.getCacheDir() + File.separator + "GeekTan" + File.separator + "GeekTan.jar"); if(!v8.getParentFile().exists()) { v8.getParentFile().mkdirs(); } } catch(Exception v9) { goto label_171; } //将v8通過FileOutputStream方法指派v12 try { v12 = new FileOutputStream(v8); } catch(Throwable v0_3) { goto label_17; } int v0_4 = 1024; try { byte[] v7 = new byte[v0_4]; while(true) { //v10是GeekTan.BMP指派v11, int v11 = v10.read(v7); if(v11 <=>0) { break; } //最終寫入到v12中,而v12是new FileOutputStream(v8); ((OutputStream)v12).write(v7, 0, v11); } } catch(Throwable v0_3) { goto label_88; }=>

顯然這個BMP檔案很可疑,我們放到010 editor中看看二進制的内容,發現它真正的檔案格式是一個壓縮包,并且可以直覺的看到在壓縮包中存在一個dex檔案。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

同時我們還可以發現,方法中執行個體化了一個Patch對象。

Patch v13 = new Patch();

并在fetchPatchList()方法的最後,調用

v0_6 = "cn.chaitin.geektan.crackme.PatchesInfoImpl";

v13.setPatchesInfoImplClassFullName(v0_6);

回到runRobust(),接下來執行個體化了PatchExecutor類,可以看到第二個參數即為PatchManipulateImp類的執行個體。

Context v1_1 = this.getApplicationContext();

PatchManipulateImp v2_1 = new PatchManipulateImp();

PatchExecutor v0_2 = new PatchExecutor(v1_1, ((PatchManipulate)v2_1), new GeekTanCallBack());

這個PatchExecutor類是在com.meituan.robust包下的,這是一個第三方的包,目前官方已經将其開源,因為直接看混淆的代碼還是比較費時的,在此暫停上面的分析,簡單學習一下Robust。

0x04 Robust熱修複架構原理

我們先簡單了解一下Robust是什麼,Robust是美團出的一款熱修複架構,可以在github上面下載下傳它的最新的源碼。

Robust的基本原理,我們主要了解這4個步驟就能清晰明白了。

1. 将apk代碼中每個函數都在編譯打包階段自動的插入一段代碼。

例如,原函數:

public long getIndex() { return 100; }

它會被處理成這樣:

//在該類中聲明一個接口變量changeQuickRedirect

public static ChangeQuickRedirect changeQuickRedirect;

//在要修複的方法中添加以下邏輯代碼 public long getIndex() { if(changeQuickRedirect != null) { //PatchProxy中封裝了擷取目前className和methodName的邏輯,并在其内部最終調用了changeQuickRedirect的對應函數 if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) { return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue(); } } return 100L; }

“可以看到Robust為每個class增加了個類型為ChangeQuickRedirect的靜态成員,而在每個方法前都插入了使用changeQuickRedirect相關的邏輯,當changeQuickRedirect不為null時,會執行到accessDispatch方法進而替換掉之前老的邏輯,達到修複的目的”。

2.生成需要修複的類及方法的類檔案并打包成dex。

接下來你可能已經将需要修複的類及方法寫好了,這個時候調用Robust的autopatch檔案夾中的類及方法會生成如下主要檔案:PatchesInfoImpl.java,xxxPatchControl.java(其中xxx為原類的名字)。

PatchesInfoImpl.java的内是由PatchesInfoFactory類的createPatchesInfoClass生成的,這是它生成PatchesInfoImpl邏輯,可以看到,這個類其實是用拼接得到的。

private CtClass createPatchesInfoClass() { try { //建立PatchesInfoImpl類 CtClass ctPatchesInfoImpl = classPool.makeClass(Config.patchPackageName + ".PatchesInfoImpl"); ctPatchesInfoImpl.getClassFile().setMajorVersion(ClassFile.JAVA_7); ctPatchesInfoImpl.setInterfaces(new CtClass[]{classPool.get("com.meituan.robust.PatchesInfo")}); StringBuilder methodBody = new StringBuilder(); //拼接類中的内容 methodBody.append("public java.util.List getPatchedClassesInfo() {"); methodBody.append(" java.util.List patchedClassesInfos = new java.util.ArrayList();"); for (int i = 0; i < Config.modifiedClassNameList.size(); i++) { if (Constants.OBSCURE) { methodBody.append("com.meituan.robust.PatchedClassInfo patchedClass" + i + " = new com.meituan.robust.PatchedClassInfo(\"" + ReadMapping.getInstance().getClassMappingOrDefault(Config.modifiedClassNameList.get(i)).getValueName() + "\",\"" + NameManger.getInstance().getPatchControlName(Config.modifiedClassNameList.get(i).substring(Config.modifiedClassNameList.get(i).lastIndexOf('.') + 1)) + "\");"); } else { methodBody.append("com.meituan.robust.PatchedClassInfo patchedClass" + i + " = new com.meituan.robust.PatchedClassInfo(\"" + Config.modifiedClassNameList.get(i) + "\",\"" + NameManger.getInstance().getPatchControlName(Config.modifiedClassNameList.get(i).substring(Config.modifiedClassNameList.get(i).lastIndexOf('.') + 1)) + "\");"); } methodBody.append("patchedClassesInfos.add(patchedClass" + i + ");"); } methodBody.append(Constants.ROBUST_UTILS_FULL_NAME + ".isThrowable=!" + Config.catchReflectException + ";"); methodBody.append("return patchedClassesInfos;\n" + " }"); CtMethod m = make(methodBody.toString(), ctPatchesInfoImpl); ctPatchesInfoImpl.addMethod(m); return ctPatchesInfoImpl; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } }

生成的PatchesInfoImpl類形式如下。

public class PatchesInfoImpl implements PatchesInfo { public List getPatchedClassesInfo() { List arrayList = new ArrayList(); arrayList.add(new PatchedClassInfo("cn.chaitin.geektan.crackme.MainActivity", "cn.chaitin.geektan.crackme.MainActivityPatchControl")); arrayList.add(new PatchedClassInfo("cn.chaitin.geektan.crackme.MainActivity$1", "cn.chaitin.geektan.crackme.MainActivity$1PatchControl")); EnhancedRobustUtils.isThrowable = false; return arrayList; }

}

另外還會生成一個xxxPatchControl類,通過PatchesControlFactory的createControlClass()方法生成,具體的邏輯和生成PatchesInfoImpl類類似,大家可以自行去檢視源代碼,其中每個Control類中都存在以下靜态成員變量和方法。

public class xxxPatchControl implements ChangeQuickRedirect

{

public static final String MATCH_ALL_PARAMETER = "(\\w*\\.)*\\w*";

private static final MapkeyToValueRelation = new WeakHashMap();

//擷取函數的參數的方法

public Object getRealParameter(Object obj){..具體邏輯..}

//判斷是否支援修複

public boolean isSupport(String methodName, Object[] paramArrayOfObject)

{..具體邏輯.}

//執行到accessDispatch方法替換舊的類方法

public Object accessDispatch(String methodName, Object[] paramArrayOfObject) {.具體邏輯..}

}

//解決boolean被優化成byte的問題

private static Object fixObj(Object booleanObj) {.具體邏輯..}

}

将含有PatchesInfoImpl.java和xxxPatchControl.java,以及xxxPatch.java(具體修複的類)打包成dex檔案。

3.動态加載dex檔案,以反射的方式修改替換舊類。

在此,我們回到剛剛暫停的地方,我們跟進PatchExecutor類看看。

//可以看到PatchExecutor繼承線程類Thread

public class PatchExecutor extends Thread { protected Context context; protected PatchManipulate patchManipulate; protected RobustCallBack robustCallBack; //構造函數 public PatchExecutor(Context context, PatchManipulate patchManipulate, RobustCallBack robustCallBack) { this.context = context.getApplicationContext(); this.patchManipulate = patchManipulate; this.robustCallBack = robustCallBack; } public void run() { try { //拉取更新檔清單 Listpatches = fetchPatchList(); //應用更新檔清單 applyPatchList(patches); } catch (Throwable t) { Log.e("robust", "PatchExecutor run", t); robustCallBack.exceptionNotify(t, "class:PatchExecutor,method:run,line:36"); } } ... }

在run方法中,主要做了2件事。

1. 擷取更新檔清單。

Listpatches = fetchPatchList();

//PatchManipulateImp類的fetchPatchList方法

protected ListfetchPatchList() { return patchManipulate.fetchPatchList(context); }

2.應用更新檔。

applyPatchList(patches);

protected void applyPatchList(Listpatches) { if (null == patches || patches.isEmpty()) { return; } Log.d("robust", " patchManipulate list size is " + patches.size()); for (Patch p : patches) { if (p.isAppliedSuccess()) { Log.d("robust", "p.isAppliedSuccess() skip " + p.getLocalPath()); continue; } if (patchManipulate.ensurePatchExist(p)) { boolean currentPatchResult = false; try { //真正應用更新檔的方法patch() currentPatchResult = patch(context, p); } catch (Throwable t) { robustCallBack.exceptionNotify(t, "class:PatchExecutor method:applyPatchList line:69"); } if (currentPatchResult) { //設定patch 狀态為成功 p.setAppliedSuccess(true); //統計PATCH成功率 PATCH成功 robustCallBack.onPatchApplied(true, p); } else { //統計PATCH成功率 PATCH失敗 robustCallBack.onPatchApplied(false, p); } Log.d("robust", "patch LocalPath:" + p.getLocalPath() + ",apply result " + currentPatchResult); } } }

跟進patch()方法,我們具體分析一下。

protected boolean patch(Context context, Patch patch) { //驗證patch的hash if (!patchManipulate.verifyPatch(context, patch)) { robustCallBack.logNotify("verifyPatch failure, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:107"); return false; } //調用DexClassLoader動态加載dex DexClassLoader classLoader = new DexClassLoader(patch.getTempPath(), context.getCacheDir().getAbsolutePath(), null, PatchExecutor.class.getClassLoader()); patch.delete(patch.getTempPath()); Class patchClass, oldClass; Class patchsInfoClass; PatchesInfo patchesInfo = null; try { //動态加載PatchesInfoImpl,擷取要patch的類 patchsInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName()); patchesInfo = (PatchesInfo) patchsInfoClass.newInstance(); Log.d("robust", "PatchsInfoImpl ok"); } catch (Throwable t) { robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:108"); Log.e("robust", "PatchsInfoImpl failed,cause of" + t.toString()); t.printStackTrace(); } if (patchesInfo == null) { robustCallBack.logNotify("patchesInfo is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:114"); return false; } //classes need to patch //擷取要打更新檔的類patchedClasses ListpatchedClasses = patchesInfo.getPatchedClassesInfo(); if (null == patchedClasses || patchedClasses.isEmpty()) { robustCallBack.logNotify("patchedClasses is null or empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:122"); return false; } //循環類名,将patchedClasses中的類打更新檔 for (PatchedClassInfo patchedClassInfo : patchedClasses) { String patchedClassName = patchedClassInfo.patchedClassName; String patchClassName = patchedClassInfo.patchClassName; if (TextUtils.isEmpty(patchedClassName) || TextUtils.isEmpty(patchClassName)) { robustCallBack.logNotify("patchedClasses or patchClassName is empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:131"); continue; } Log.d("robust", "current path:" + patchedClassName); try { //将oldClass的changeQuickRedirectField的值設定為null oldClass = classLoader.loadClass(patchedClassName.trim()); Field[] fields = oldClass.getDeclaredFields(); Log.d("robust", "oldClass :" + oldClass + " fields " + fields.length); Field changeQuickRedirectField = null; for (Field field : fields) { if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), oldClass.getCanonicalName())) { changeQuickRedirectField = field; break; } } if (changeQuickRedirectField == null) { robustCallBack.logNotify("changeQuickRedirectField is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:147"); Log.d("robust", "current path:" + patchedClassName + " something wrong !! can not find:ChangeQuickRedirect in" + patchClassName); continue; } Log.d("robust", "current path:" + patchedClassName + " find:ChangeQuickRedirect " + patchClassName); try { //動态加載更新檔類 patchClass = classLoader.loadClass(patchClassName); Object patchObject = patchClass.newInstance(); changeQuickRedirectField.setAccessible(true); //将它的changeQuickRedirectField設定為patchObject執行個體。 changeQuickRedirectField.set(null, patchObject); Log.d("robust", "changeQuickRedirectField set sucess " + patchClassName); } catch (Throwable t) { Log.e("robust", "patch failed! "); t.printStackTrace(); robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:163"); } } catch (Throwable t) { Log.e("robust", "patch failed! "); t.printStackTrace(); robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:169"); } } Log.d("robust", "patch finished "); return true; }

4.isSupport和accessDispatch

接下來我們再來看看onCreate()中的代碼,雖然混淆後代碼看起來很冗長,但是通過剛剛我們對Robust原理的簡單分析,現在已經可以清晰的知道,這其實就是isSupport()和accessDispatch()。

public void onCreate(Bundle arg13) { int v4 = 3; Object[] v0 = new Object[1]; v0[0] = arg13; ChangeQuickRedirect v2 = MainActivity.changeQuickRedirect; Class[] v5 = new Class[1]; Class v1 = Bundle.class; v5[0] = v1; Class v6 = Void.TYPE; MainActivity v1_1 = this; boolean v0_1 = PatchProxy.isSupport(v0, v1_1, v2, false, v4, v5, v6); if(v0_1) { v0 = new Object[1]; v0[0] = arg13; v2 = MainActivity.changeQuickRedirect; v5 = new Class[1]; v1 = Bundle.class; v5[0] = v1; v6 = Void.TYPE; v1_1 = this; PatchProxy.accessDispatch(v0, v1_1, v2, false, v4, v5, v6); } else { ..... }

我們看看源碼中的isSupport()具體做了什麼。

public static boolean isSupport(Object[] paramsArray, Object current, ChangeQuickRedirect changeQuickRedirect, boolean isStatic, int methodNumber, Class[] paramsClassTypes, Class returnType) { //Robust更新檔優先執行,其他功能靠後 if (changeQuickRedirect == null) { //不執行更新檔,輪詢其他監聽者 if (registerExtensionList == null || registerExtensionList.isEmpty()) { return false; } for (RobustExtension robustExtension : registerExtensionList) { if (robustExtension.isSupport(new RobustArguments(paramsArray, current, isStatic, methodNumber, paramsClassTypes, returnType))) { robustExtensionThreadLocal.set(robustExtension); return true; } } return false; } //擷取 classMethod = className + ":" + methodName + ":" + isStatic + ":" + methodNumber; String classMethod = getClassMethod(isStatic, methodNumber); if (TextUtils.isEmpty(classMethod)) { return false; } Object[] objects = getObjects(paramsArray, current, isStatic); try { return changeQuickRedirect.isSupport(classMethod, objects); } catch (Throwable t) { return false; } }

通過上面的分析,可以知道隻有當存在更新檔的類changeQuickRedirect.isSupport()才會傳回值。這個時候我們把剛剛第二步打包的dex反編譯看看,我們可以看到在xxxPatchControl類中存在isSupport,它傳回的值其實就是methodNumber。

public boolean isSupport(String methodName, Object[] paramArrayOfObject) { return "3:6:".contains(methodName.split(":")[3]); }

accessDispatch()方法,方法替換。

public static Object accessDispatch(Object[] paramsArray, Object current, ChangeQuickRedirect changeQuickRedirect, boolean isStatic, int methodNumber, Class[] paramsClassTypes, Class returnType) {

//如果changeQuickRedirect為null... if (changeQuickRedirect == null) { RobustExtension robustExtension = robustExtensionThreadLocal.get(); robustExtensionThreadLocal.remove(); if (robustExtension != null) { notify(robustExtension.describeSelfFunction()); return robustExtension.accessDispatch(new RobustArguments(paramsArray, current, isStatic, methodNumber, paramsClassTypes, returnType)); } return null; } //同樣擷取 classMethod = className + ":" + methodName + ":" + isStatic + ":" + methodNumber; String classMethod = getClassMethod(isStatic, methodNumber); if (TextUtils.isEmpty(classMethod)) { return null; } notify(Constants.PATCH_EXECUTE); Object[] objects = getObjects(paramsArray, current, isStatic); //傳回changeQuickRedirect.accessDispatch。 return changeQuickRedirect.accessDispatch(classMethod, objects); }

具體看看PatchControl類中的accessDispatch。

public Object accessDispatch(String methodName, Object[] paramArrayOfObject) { try { MainActivityPatch mainActivityPatch; //判斷classMethod的isStatic是否為false,其實在調用accessDispatch傳遞的就是false。 if (methodName.split(":")[2].equals("false")) { MainActivityPatch mainActivityPatch2; if (keyToValueRelation.get(paramArrayOfObject[paramArrayOfObject.length - 1]) == null) { mainActivityPatch2 = new MainActivityPatch(paramArrayOfObject[paramArrayOfObject.length - 1]); keyToValueRelation.put(paramArrayOfObject[paramArrayOfObject.length - 1], null); } else { mainActivityPatch2 = (MainActivityPatch) keyToValueRelation.get(paramArrayOfObject[paramArrayOfObject.length - 1]); } mainActivityPatch = mainActivityPatch2; } else { mainActivityPatch = new MainActivityPatch(null); } //根據methodNumber,選取要執行的patch方法。 Object obj = methodName.split(":")[3]; if ("3".equals(obj)) { mainActivityPatch.onCreate((Bundle) paramArrayOfObject[0]); } if ("6".equals(obj)) { return mainActivityPatch.Joseph(((Integer) paramArrayOfObject[0]).intValue(), ((Integer) paramArrayOfObject[1]).intValue()); } } catch (Throwable th) { th.printStackTrace(); } return null; }

花了比較多的篇幅把Robust的基本原理給大家介紹了,接下來完全回到這個apk。

JavaScript代碼構造

0x05 hook點分析

經過我們對Robust的分析,我們現在已經比較清晰的知道了我們需要攻克的難點,它是通過Robust熱修複架構将一些方法熱修複了,是以我們這裡必須知道,它修複了哪些類及方法,當然在上面我們已經零星看到了一些細節,現在我們來具體看看。

我們先将assets檔案夾下的GeekTan.BMP改成GeekTan.rar,并解壓,得到dex檔案直接扔到jadx中分析。在PatchesInfoImpl類中可以看到2個要被修複的類資訊。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

先看MainActivityPatchControl類,我們看到在accessDispatch(),onCreate()和Joseph()方法将會通過判斷ethodNumber來選取。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

繼續檢視MainActivity$1PatchControl類,同樣發現onClick被修複了。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

是以這個時候,我們必須知道onClick真正執行的邏輯是什麼。檢視MainActivity$1Patch類中的真正的onClick方法。很明顯的兩句提示語,可以明确我們要的答案就在這裡。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

仔細分析onClick方法,可以發現很多invokeReflectStaticMethod,getFieldValue,invokeReflectMethod方法,同樣我們還能發現flag就在這裡面。

//flag是通過append将字元串以及Joseph(int,int)的傳回值拼接構成的。

String str = "DDCTF{";

str = (String) EnhancedRobustUtils.invokeReflectMethod("Joseph", obj2, getRealParameter(new Object[]{new Integer(5), new Integer(6)}), new Class[]{Integer.TYPE, Integer.TYPE}, MainActivity.class);

str = (String) EnhancedRobustUtils.invokeReflectMethod("Joseph", obj2, getRealParameter(new Object[]{new Integer(7), new Integer(8)}), new Class[]{Integer.TYPE, Integer.TYPE}, MainActivity.class);

str = "}";

//最終将我們輸入的值與上面構造的equals比較,判斷是否準确。

if (((Boolean) EnhancedRobustUtils.invokeReflectMethod("equals", obj, getRealParameter(new Object[]{str2}), new Class[]{Object.class}, String.class)).booleanValue()) {...}

通過上面的分析,可以發現hook有2個思路:

1.hook EnhancedRobustUtils類下的方法擷取方法執行的傳回值。

2.hook 動态加載的類MainActivityPatch的Joseph方法,直接調用它擷取傳回值。(下篇)

0x06 hook代碼構造

先來看看EnhancedRobustUtils類下的方法invokeReflectMethod。

public static Object invokeReflectMethod(String methodName, Object targetObject, Object[] parameters, Class[] args, Class declaringClass) { try { //可以看到這裡是通過反射的方法拿到類執行個體 Method method = getDeclaredMethod(targetObject, methodName, args, declaringClass); //代入參數,調用方法 return method.invoke(targetObject, parameters); } catch (Exception e) { e.printStackTrace(); } if (isThrowable) { throw new RuntimeException("invokeReflectMethod error " + methodName + " parameter " + parameters + " targetObject " + targetObject.toString() + " args " + args); } return null; }

我們再看看invokeReflectConstruct。

public static Object invokeReflectConstruct(String className, Object[] parameter, Class[] args) { try { //通過Class.forName(className)反射得到一個Class對象 Class clazz = Class.forName(className); //獲得構造器 Constructor constructor = clazz.getDeclaredConstructor(args); constructor.setAccessible(true); //傳回該類的執行個體 return constructor.newInstance(parameter); } catch (Exception e) { e.printStackTrace(); } if (isThrowable) { throw new RuntimeException("invokeReflectConstruct error " + className + " parameter " + parameter); } return null; }

很簡單,通過反射得到類的執行個體及方法,最終通過invoke代入參數執行方法。這裡很幸運,我們發現這個EnhancedRobustUtils 是Robust自帶的類,并不是動态加載的。

那hook就非常簡單了,我們隻需要簡單的hook invokeReflectMethod擷取Joseph的傳回值,以及equals的參數即可。

完整python腳本:下載下傳

Java.perform(function(){

//獲得EnhancedRobustUtils類的wapper var robust = Java.use("com.meituan.robust.utils.EnhancedRobustUtils");

//hook invokeReflectMethod方法 robust.invokeReflectMethod.implementation = function(v1,v2,v3,v4,v5){ //不破壞原來的邏輯,隻在原來的邏輯中列印出Joseph,equals的值 var result = this.invokeReflectMethod(v1,v2,v3,v4,v5); if(v1=="Joseph"){ console.log("functionName:"+v1); console.log("functionArg3:"+v3); console.log("functionArg4:"+v4); send(v4); console.log("return:"+result); console.log("-----------------------------------------------------") } else if(v1=="equals"){ console.log("functionName:"+v1); console.log("functionArg3:"+v3); console.log("functionArg4:"+v4); send(v4); console.log("return:"+result); } return result; }

});

0x07 執行py腳本擷取結果

打開模拟器,adb shell進入終端并啟動frida。

開啟端口轉發。

adb forward tcp:27043 tcp:27043

adb forward tcp:27043 tcp:27043

啟動應用後,執行Python腳本。

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

最終獲得結果如下:

android第三方注入dex,進階Frida--Android逆向之Hook動态加載dex(三)(上篇)

總結

上篇我們主要采用第一種方法Robust自帶的工具類來hook,得到我們想要的答案,從做題的角度來說是一種很好很快的辦法,但是從學習的角度,可能這道題用hook DexClassLoader的方式更有趣味和意義,下篇我會詳細介紹怎麼hook DexClassLoader動态加載的類及方法,來獲得最終答案。