进阶Frida--Android逆向之动态加载dex Hook(三)
前段時間看到有朋友在問在怎么使用frida去hook動態加載的dex之類的問題,確實關于如何hook動態加載的dex,網上的資料很少,更不用說怎么使用frida去hook動態加載的dex了。(frida的官方文檔已經無力吐槽...)后來偶然的情況下發現了一道CTF題目可以作為很好的案例,所以花了很多心思將文章寫下來分享給大家,涉及的內容比較多,我打算用上下篇將frida hook動態加載的dex的方法將清楚,上篇主要是引導及一些前置的知識。
?
?
目錄
- ?????????文章涉及內容及使用到的工具
- ?????????????????0x00 使用到的工具
- ?????????????????0x01 涉及知識點
- ?????????apk的安裝和分析
- ?????????????????0x00 apk安裝及使用
- ?????????????????0x01 靜態代碼分析
- ?????????????????0x02 Robust熱修復框架原理
- ?????????JavaScript代碼構造
- ?????????????????0x00 hook點分析
- ?????????????????0x01 hook代碼構造
- ?????????????????0x02 執行py腳本獲取結果
- ?????????總結
?
?
博客同步:訪問?(一些web安全的研究會直接發布到這上面)
文章涉及內容及使用到的工具
0x00 使用到的工具
- ADT(Android Developer Tools)
- Jadx-gui
- JEB
- frida
- apktool
- 010 editor
- 天天模擬器(genymotion,實體機等亦可)
0x01 涉及知識點
- Robust熱修復框架原理
- Java 反射
- Robust類 hook
- frida基礎hook
apk的安裝和分析
文章使用的是DDCTF2018的android逆向第二題Hello Baby Dex
?
示例地址:下載
0x00 apk安裝及使用
程序功能很簡單,就是輸入密碼并驗證,錯誤會Toast出一些信息,按這個慣性,可能得輸入某些正確的值才能獲取flag吧,廢話不多說,直接反編譯看看。
0x01 靜態代碼分析
一般情況下,我是直接扔到jadx中去看反編譯后的代碼,可是這次jadx反編譯出來的結果有很多錯誤,這里我就不貼圖了,大家可以嘗試一下,看看是什么錯誤。后面放到jeb工具中,便沒有報錯,所以查看反編譯的效果時,大家可以多嘗試幾個反編譯器對比效果,查看AndroidManifest.xml,找到了程序的入口MainActivity。
| 1 2 3 4 5 6 | <activity android:name="cn.chaitin.geektan.crackme.MainActivity"> ???????????<intent-filter> ???????????????<action android:name="android.intent.action.MAIN"?/> ???????????????<category android:name="android.intent.category.LAUNCHER"?/> ???????????</intent-filter> ???????</activity> |
在此同時,在cmd中通過apktool d [apk],再將apk文件反編譯出來。
接下來直接定位到MainActivity的onCreate()方法,可以看到代碼是混淆過的,閱讀上稍微有點小困難,但是在onCreate()方法中,我們還是可以發現一些可疑的方法及變量,比如PatchProxy,changeQuickRedirect等。
?
繼續搜索有用的信息,我們可以發現在onCreate()方法的else邏輯中調用了this.runRobust(),并且我們發現在類中每一個方法里面都會存在一些changeQuickRedirect變量,以及isSupport(),accessDispatch()方法。
上面的先放一邊,我們來看看runRobust()方法中寫了什么,在else條件語句中可以看到它實例化了一些對象。
| 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 | 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中。
?
具體代碼如下:
| 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 | ????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文件。
同時我們還可以發現,fetchPatchList()方法中實例化了一個Patch對象。
| 1 | Patch v13?=?new Patch(); |
并在fetchPatchList()方法的最后,調用
| 1 2 | v0_6?=?"cn.chaitin.geektan.crackme.PatchesInfoImpl"; v13.setPatchesInfoImplClassFullName(v0_6); |
回到runRobust(),接下來實例化了PatchExecutor類,可以看到第二個參數即為PatchManipulateImp類的實例。
| 1 2 3 | 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。
0x02 Robust熱修復框架原理
我們先簡單了解一下Robust是什么,Robust是美團出的一款熱修復框架,可以在github上面下載它的最新的源碼。
?
Robust的基本原理,我們主要了解這4個步驟就能清晰明白了。
1. 將apk代碼中每個函數都在編譯打包階段自動的插入一段代碼。
例如,原函數:
| 1 2 3 | public?long?getIndex() { ????????return?100; ????} |
它會被處理成這樣:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | //在該類中聲明一個接口變量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邏輯,可以看到,這個類其實是用拼接得到的。
| 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 | 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類型及類內容如下。
| 1 2 3 4 5 6 7 8 9 10 | public?class?PatchesInfoImpl implements PatchesInfo { ????public?List?getPatchedClassesInfo() { ????????List?arrayList?=?new ArrayList(); ????????//PatchedClassInfo("原來的類","修復后的類control"); ????????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類中都存在以下靜態成員變量和方法。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public?class?xxxPatchControl implements ChangeQuickRedirect { ? public static final String MATCH_ALL_PARAMETER?=?"(\\w*\\.)*\\w*"; ? private static final?Map<Object,?Object> keyToValueRelation?=?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類看看。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //可以看到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?{ ????????????//拉取補丁列表 ????????????List<Patch> patches?=?fetchPatchList(); ????????????//應用補丁列表 ????????????applyPatchList(patches); ????????} catch (Throwable t) { ????????????Log.e("robust",?"PatchExecutor run", t); ????????????robustCallBack.exceptionNotify(t,?"class:PatchExecutor,method:run,line:36"); ????????} ????} ????... ????} |
在run方法中,主要做了2件事。
?
1.獲取補丁列表。
| 1 2 3 4 5 | List<Patch> patches?=?fetchPatchList(); //PatchManipulateImp類的fetchPatchList方法 protected?List<Patch> fetchPatchList() { ????????return?patchManipulate.fetchPatchList(context); ????} |
2.應用補丁。
| 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 | applyPatchList(patches); ? protected void applyPatchList(List<Patch> patches) { ???????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()方法,我們具體分析一下。
| 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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? ????????List<PatchedClassInfo> patchedClasses?=?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()。
| 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 | 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()具體做了什么。
| 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 | 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?{ ????????/*調用changeQuickRedirect.isSupport,還記得這個changeQuickRedirect ????????嗎,他是在第3步中changeQuickRedirectField.set(null, patchObject); ????????得到的補丁類的實例。*/ ????????????return?changeQuickRedirect.isSupport(classMethod, objects); ????????} catch (Throwable t) { ????????????return?false; ????????} ????} |
通過上面的分析,可以知道只有當存在補丁的類changeQuickRedirect.isSupport()才會返回值。這個時候我們把剛剛第二步打包的dex反編譯看看,我們可以看到在xxxPatchControl類中存在isSupport,它返回的值其實就是methodNumber。
| 1 2 3 | public boolean isSupport(String methodName,?Object[] paramArrayOfObject) { ????????return?"3:6:".contains(methodName.split(":")[3]); ????} |
accessDispatch()方法,替換原方法。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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。
| 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 | 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代碼構造
0x00 hook點分析
經過我們對Robust的分析,我們現在已經比較清晰的知道了我們需要攻克的難點,它是通過Robust熱修復框架將一些方法熱修復了,所以我們這里必須知道,它修復了哪些類及方法,當然在上面我們已經零星看到了一些細節,現在我們來具體看看。
?
我們先將assets文件夾下的GeekTan.BMP改成GeekTan.rar,并解壓,得到dex文件直接扔到jadx中分析。在PatchesInfoImpl類中可以看到2個要被修復的類信息。
先看MainActivityPatchControl類,我們看到在accessDispatch(),onCreate()和Joseph()方法將會通過判斷ethodNumber來選取。
繼續查看MainActivity$1PatchControl類,同樣發現onClick被修復了。
所以這個時候,我們必須知道onClick真正執行的邏輯是什么。查看MainActivity$1Patch類中的真正的onClick方法。很明顯的兩句提示語,可以明確我們要的答案就在這里。
仔細分析onClick方法,可以發現很多invokeReflectStaticMethod,getFieldValue,invokeReflectMethod方法,同樣我們還能發現flag就在這里面。
| 1 2 3 4 5 6 7 | //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方法,直接調用它獲取返回值。(下篇)
0x01 hook代碼構造
先來看看EnhancedRobustUtils類下的方法invokeReflectMethod。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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腳本:下載
| 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 | 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; ????????} }); |
0x02 執行py腳本獲取結果
| 1 2 3 4 5 | 1.?打開模擬器,adb shell進入終端并啟動frida。 2.?開啟端口轉發。 ???adb forward tcp:27043?tcp:27043 ???adb forward tcp:27043?tcp:27043 3.?啟動應用后,執行Python腳本。 |
?
最終獲得結果如下:
總結
上篇我們主要講了robust的原理,并采用第一種方法Robust自帶的EnhancedRobustUtils工具類來hook,得到我們想要的答案,從做題的角度來說是一種很好很快的辦法,但是從學習的角度,可能這道題用hook DexClassLoader的方式更有趣味和意義,下篇我會詳細介紹怎么hook DexClassLoader動態加載的類及方法,來獲得最終答案:D
?
https://bbs.pediy.com/thread-229597.htm
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的进阶Frida--Android逆向之动态加载dex Hook(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 初识Frida--Android逆向之J
- 下一篇: CVE-2015-3636(pingpo