換一個帥一點姿勢實現DexHunter
DexHunter 需要刷機,感覺太麻煩了,所以用xposed弄了一份。然後大部分解析邏輯都放到了java層,這樣也有另一個好處,就是殼總不能修改java正常的api。所以 define class 之類的都可以直接呼叫 java 層的classloader 來初始化 class。
注意:目前我只在dalvik上面實現,art還沒有看。
1. 尋找兩個dalvik虛擬機器函式,dvmDecodeIndirectRef 和dvmThreadSelf
/*** 函式名稱表根據4.4的Android版本設定的,不同Android版本對映可能存在差異,可以直接用ida檢視維護*/void initDvmFunctionTables() {void *libVMhandle = dlopen("libdvm.so", RTLD_GLOBAL | RTLD_LAZY);initDvmFunctionItem("_Z20dvmDecodeIndirectRefP6ThreadP8_jobject",(void **) (&dvmFunctionTables.dvmDecodeIndirectRef), libVMhandle);initDvmFunctionItem("_Z13dvmThreadSelfv", (void **) (&dvmFunctionTables.dvmThreadSelf),libVMhandle);dlclose(libVMhandle);}
2. 定位dex檔案
其實我現在還不知道為啥dump記憶體需要那麼麻煩,直接就能在記憶體中找到啊。看程式碼
extern "C"JNIEXPORT jobject JNICALLJava_com_virjar_xposedhooktool_unshell_Dumper_originDex(JNIEnv *env, jclass type,jclass loader) {//TODO check & throw exceptionClassObject *clazz = (ClassObject *) dvmFunctionTables.dvmDecodeIndirectRef(dvmFunctionTables.dvmThreadSelf(),loader);DvmDex *dvm_dex = clazz->pDvmDex;return env->NewDirectByteBuffer(dvm_dex->memMap.addr, dvm_dex->memMap.length);}
這不就直接找到dex檔案了嘛。
3. 建立dexFile模型,使用baksmaliAPI
private static DexBackedDexFile createMemoryDexFile(Class loader) {ByteBuffer byteBuffer = originDex(loader);byteBuffer.order(ByteOrder.LITTLE_ENDIAN);byte[] buffer = new byte[byteBuffer.capacity()];byteBuffer.get(buffer, 0, byteBuffer.capacity());if (HeaderItem.verifyMagic(buffer, 0)) {return new DexBackedDexFile(Opcodes.forApi(apiLevel()), buffer);//a normal dex file}if (OdexHeaderItem.verifyMagic(buffer, 0)) {//this is a odex filetry {ByteArrayInputStream is = new ByteArrayInputStream(buffer);DexUtil.verifyOdexHeader(is);is.reset();byte[] odexBuf = new byte[OdexHeaderItem.ITEM_SIZE];ByteStreams.readFully(is, odexBuf);int dexOffset = OdexHeaderItem.getDexOffset(odexBuf);if (dexOffset > OdexHeaderItem.ITEM_SIZE) {ByteStreams.skipFully(is, dexOffset - OdexHeaderItem.ITEM_SIZE);}return new DexBackedOdexFile(Opcodes.forApi(Dumper.apiLevel()), odexBuf, ByteStreams.toByteArray(is));} catch (IOException e) {//while not happenthrow new RuntimeException(e);}}throw new IllegalStateException("can not find out dex image in vm memory");}
4. 使用smali APi rewrite功能,重構Method的指令資料
@Nonnull@Overridepublic Method rewrite(@Nonnull final Method value) {if (!(value instanceof DexBackedMethod)) {return super.rewrite(value);}Class<?> definingClass;try {definingClass = classLoader.loadClass(value.getDefiningClass());} catch (ClassNotFoundException e) {return super.rewrite(value);}if (definingClass.getClassLoader() != classLoader) {return super.rewrite(value);}final Class<?> searchClass = definingClass;final String methodDescriptor = ReferenceUtil.getMethodDescriptor(value);//覆蓋 getImplementationreturn new RewrittenMethod(value) {@Nullable@Overridepublic MethodImplementation getImplementation() {ByteBuffer byteBuffer = methodDataWithDescriptor(methodDescriptor, value.getName(), searchClass);if (byteBuffer == null) {return super.getImplementation();}byteBuffer.order(ByteOrder.LITTLE_ENDIAN);byte[] buffer = new byte[byteBuffer.capacity()];byteBuffer.get(buffer, 0, byteBuffer.capacity());DexBackedMethodImplementation dexBackedMethodImplementation = new DexBackedMethodImplementation(new MethodSegmentDexFile(buffer, dexFile), (DexBackedMethod) value, 0);return rewriters.getMethodImplementationRewriter().rewrite(dexBackedMethodImplementation);}@Overridepublic int getAccessFlags() {int accessFlags = getMethodAccessFlagsWithDescriptor(methodDescriptor, value.getName(), searchClass);if (accessFlags < 0) {//證明沒有找到這個方法return super.getAccessFlags();}return accessFlags;}};}
5. 使用 dalvik dvmFindXXXMethodByDescriptor 功能
尋找對應的 method 物件,進而扣出真實的指令,這裡指令長度計算,借用了 DexHunter 的程式碼
extern "C"JNIEXPORT jobject JNICALLJava_com_virjar_xposedhooktool_unshell_Dumper_methodDataWithDescriptor(JNIEnv *env, jclass type,jstring methodDescriptor_,jstring methodName_,jclass searchClass) {const char *methodDescriptor = env->GetStringUTFChars(methodDescriptor_, 0);const char *methodName = env->GetStringUTFChars(methodName_, 0);ClassObject *clazz = (ClassObject *) dvmFunctionTables.dvmDecodeIndirectRef(dvmFunctionTables.dvmThreadSelf(),searchClass);jobject ret = NULL;Method *method = dvmFindDirectMethodByDescriptor(clazz, methodName, methodDescriptor);if (method == NULL) {method = dvmFindVirtualMethodByDescriptor(clazz, methodName, methodDescriptor);}if (method == NULL) {goto tail;}//check for nativeuint32_t ac = (method->accessFlags) & accessFlagsMask;if (method->insns == NULL || ac & ACC_NATIVE) {goto tail;}//why 16// 2 byte for registersSize// 2 byte for insSize// 2 byte for outsSize// 2 byte for triesSize// 4 byte for debugInfoOff// 4 byte for insnsSize// and then ,the insns addressDexCode *code = (DexCode *) ((const u1 *) method->insns - 16);uint8_t *item = (uint8_t *) code;int code_item_len = 0;if (code->triesSize) {const u1 *handler_data = dexGetCatchHandlerData(code);const u1 **phandler = &handler_data;uint8_t *tail = codeitem_end(phandler);code_item_len = (int) (tail - item);} else {//正確的DexCode的大小code_item_len = 16 + code->insnsSize * 2;}ret = env->NewDirectByteBuffer(item, code_item_len);tail:env->ReleaseStringUTFChars(methodDescriptor_, methodDescriptor);env->ReleaseStringUTFChars(methodName_, methodName);return ret;}
6. binggo,使用smali api,直接解碼
/*** 將指定loader的smali全部dump到硬碟,請非同步執行該函式** @param loader loader*/public static void dissembleAllDex(Object loader) {Class loaderClass = resolveLoaderClass(loader);DexBackedDexFile memoryMethodDexFile = createMemoryDexFile(loaderClass);File dumpDir = resolveDumpDir(memoryMethodDexFile);DexFile reWritedDexFile = rewrite(memoryMethodDexFile, loaderClass.getClassLoader());XposedBridge.log("脫殼目錄:" + dumpDir.getAbsolutePath());int jobs = Runtime.getRuntime().availableProcessors();if (jobs > 6) {jobs = 6;}if (memoryMethodDexFile instanceof DexBackedOdexFile) {baksmaliOptions.inlineResolver = InlineMethodResolver.createInlineMethodResolver(((DexBackedOdexFile) memoryMethodDexFile).getOdexVersion());}Log.i("weijia", "開始進行脫殼");if (Baksmali.disassembleDexFile(reWritedDexFile, dumpDir, jobs, baksmaliOptions)) {Log.i("weijia", "脫殼完成,但是存在錯誤");} else {Log.i("weijia", "脫殼成功,請在" + dumpDir + "中檢視smali檔案");}Toast.makeText(SharedObject.context, "脫殼完成,請在" + dumpDir + "中檢視smali檔案", Toast.LENGTH_LONG).show();}
7.binggo,使用smali API,輸出dex檔案
/*** 將對應class對應的dex檔案的二進位制dump出來** @param loader 該dex檔案定義的任何一個class,或者class定義的object* @return 一個byteBuffer,包含了二進位制資料*/public static ByteBuffer dumpDex(Object loader) {Class loaderClass = resolveLoaderClass(loader);DexBackedDexFile memoryDexFile = createMemoryDexFile(loaderClass);byte[] buf = (byte[]) XposedHelpers.getObjectField(memoryDexFile, "buf");DexFile dexFile = rewrite(memoryDexFile, loaderClass.getClassLoader());final DexBuilder builder = new DexBuilder(Opcodes.forApi(apiLevel()));MemoryDataStore memoryDataStore = new MemoryDataStore(buf.length);for (ClassDef classDef : dexFile.getClasses()) {try {buildClassDef(classDef, builder);} catch (Exception e) {Log.i("weijia", "error when define class:" + classDef.getType() + " skipped for rebuild it");}}try {builder.writeTo(memoryDataStore);} catch (IOException ioe) {//the memory writer,no ioe happendthrow new RuntimeException(ioe);}return ByteBuffer.wrap(memoryDataStore.getData());}
7. 如何呼叫
XposedHelpers.findAndHookConstructor(Activity.class, new XC_MethodHook() {@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable {Object activity = param.thisObject;if (activity == null) {return;}XposedBridge.log("hook class " + activity.getClass());if (StringUtils.equalsIgnoreCase(activity.getClass().getName(), "com.xxx.xxx.MainActivity")) {Dumper.dumpDex(activity);}}});
8. 對了你需要移植dalvik的dexlib模組程式碼,還有/vm/oo包下面的程式碼,到你的jni環境下。要不然沒有dex的相關資料結構,也沒有ClassObject的資料結構。
然後,不願意放整個工程,不要求程式碼。論文放出來,還是自己實現一遍才能有收穫。
其他:
相比原始DexHunter的優點:
1. 定位dex更加精準,DexHunter用過filename來確定當前dex是不是需要處理,很容易被加殼平臺識別到這個特徵。而且每次都要在放Android系統push一些檔案,比較麻煩。我這個,直接通過class物件尋找,想找按個找那個。
2. 併發,可能沒有寫過Linux c語言程式,看那個pthread哪裡看不懂。DexHunter為了防止多次重複處理同一個dex檔案,寫了比較複雜的加鎖邏輯。這個放到java層,很簡單實現吧。
3. 多dex,如果一個apk多個dex都需要處理。DexHunter不好處理,因為他輸出就是whole.dex
4. dvmDefineClass失敗,就算正常情況,一個classLoader下面的class,也不是全部可以正常load成功。DexHunter的流程是刪除這些bad class,我還可以儘可能的使用原始dex資料進行解碼。
5. Dalvik_dalvik_system_DexFile_defineClassNative被替換,這可能導致defineClassNative函式不被呼叫,這樣DexHunter無法攔截到。我用classLoader.loadeClass,java標準介面,這個他永遠沒法替換。
6.其他資料結構加密調整,如果未來不光光是method的資料變化了。由於使用baksmali建立的模型,任何資料結構都很容易重構替換,畢竟是java,抽象封裝很好用。
7.脫殼時機,脫殼輸出控制更加方便。提供的是api,你呼叫就脫殼不呼叫就不脫殼。脫殼結果是java的二進位制流,你想怎麼編碼、加密、轉儲都很方便。java層的api太多了。
8.可移植性,是一個普通的xposed專案,任何一個有xposed環境的Android機器都可以(當然現在還沒有實現art,不過理論上沒問題)。也不需要編譯系統映象。等幾天再把xposed包裝一下,免root脫殼。
當然,我對dex檔案格式,並不是非常熟,反正沒有DexHunter玩兒的那麼溜,所以只有多用別人的api了。
關注看雪學院公眾號:ikanxue,更多幹貨等你來拿~
本文由看雪論壇 virjar 原創 轉載請註明來自看雪社群
相關文章
- 如何擺出帥氣姿勢:人物動態設計的一些研究2019-11-13
- 換個姿勢上傳?el-upload + qiniu-js 的實現2018-03-24JS
- redis應用系列一:分散式鎖正確實現姿勢2021-05-16Redis分散式
- Goland 開啟一個專案的正確姿勢2021-06-08GoLand
- 實現一個切換配方的功能2024-08-30
- 一個快速切換一個底層實現的思路分享2022-06-26
- 寫一個 iOS 複雜表單的正確姿勢2017-01-26iOS
- 一個問號,點選給一個提示view的實現。2018-01-19View
- 實現Flutter彈窗的正確姿勢..2019-04-15Flutter
- 小程式各種姿勢實現登入2018-08-12
- 簡單實現一個全面屏切換效果2018-09-18
- 如何點選一個按鈕實現列印2017-03-13
- one_gadget的一些姿勢2021-08-22
- 用Flutter實現一個精美的點單功能2020-05-25Flutter
- 這才是實現分散式鎖的正確姿勢!2019-03-27分散式
- 「分散式」實現分散式鎖的正確姿勢?!2018-09-21分散式
- 實現同比、環比計算的N種姿勢2022-03-11
- 開啟Flutter動畫的另一種姿勢——Flare2019-07-02Flutter動畫
- 入門快應用的另一種姿勢2018-03-28
- 如何實現一個TCC分散式事務框架的一點思考2019-03-01分散式框架
- 設計模式總是學不會?是時候換個姿勢了2019-06-27設計模式
- Web安全的三個攻防姿勢2018-08-27Web
- 實現一個promise2018-12-11Promise
- 實現一個 Swiper2017-07-17
- 純js實現點選一個事件後,觸發另外一個事件的方法2014-07-31JS事件
- 多種姿勢花式實現薄荷Loading動畫(上)2017-08-23動畫
- android實現登入,Login姿勢對不對?2016-05-10Android
- 用Flutter實現一個仿Twitter的點贊效果2019-03-03Flutter
- 一個簡單的 Laravel 站點設定實現2017-10-04Laravel
- 帶你漲姿勢的認識一下 Kafka2019-10-17Kafka
- 扒一扒Bean注入到Spring的那些姿勢2023-04-14BeanSpring
- Spring學習之事務的使用姿勢一覽2018-05-14Spring
- 強大的CSS:3種姿勢實現26個英文字母的案例2019-05-23CSS
- SourceGenerator 使用姿勢(1):生成代理類,實現簡單的AOP2022-12-08
- 用Vue實現一個掘金沸點圖片展示元件2019-03-04Vue元件
- 基於SpringBoot如何實現一個點贊功能?2022-01-21Spring Boot
- 如何實現一個 滑鼠點選特效的 chrome外掛2018-11-13特效Chrome
- javascript如何實現複製克隆一個dom元素節點2017-03-31JavaScript