当前位置: 首页 > 科技观察

AndroidJava层反hook技巧

时间:2023-03-12 17:26:04 科技观察

0x00前言最近一个检测nativehook框架的方法让我想到Android应用如何在Java层检测CydiaSubstrate或Xposed框架。免责声明:以下所有反挂机技术都可以被经验丰富的逆向工程师轻松绕过。这里只是介绍几种检测方法。最近的DexGuard和GuardIT等工具中没有这种反挂机检测,但我相信很快就会添加。0x01检测已安装应用最直接的方法是检测设备上是否安装了Substrate或Xposed框架。可以直接调用PackageManager显示所有已安装的应用,然后查看是否安装了Substrate或Xposed。#!javaPackageManagerpackageManager=context.getPackageManager();列出applicationInfoList=packageManager.getInstalledApplications(PackageManager.GET_META_DATA);for(ApplicationInfoapplicationInfo:applicationInfoList){if(applicationInfo.packageName.equals("de.robler.inspoler.inspoler.)){Log.wtf("HookDetection","Xposedfoundonthesystem.");}if(applicationInfo.packageName.equals("com.saurik.substrate")){Log.wtf("HookDetection","Substratefoundon系统。");}}0x02检查调用栈中的可疑方法想到的另一种方法是检查Java调用栈中的可疑方法,主动抛出异常,然后打印该方法的调用栈。代码如下:#!javapublicclassDoStuff{publicstaticStringgetSecret(){try{thrownewException("blah");}catch(Exceptione){for(StackTraceElementstackTraceElement:e.getStackTrace()){Log.wtf("HookDetection",stackTraceElement.getClassName()+"->"+stackTraceElement.getMethodName());}}return"ChangeMePls!!!";}}当应用没有被hook时,正常的调用栈如下如下:#!bashcom.example.hookdetection.DoStuff->getSecretcom.example.hookdetection.MainActivity->onCreateandroid.app.Activity->performCreateandroid.app.Instrumentation->callActivityOnCreateandroid.app.ActivityThread->performLaunchActivityandroid.app.ActivityThread.unchid.ActivityLaunchapp.ActivityThread->access$800android.app.ActivityThread$H->handleMessageandroid.os.Handler->dispatchMessageandroid.os.Looper->loopandroid.app.ActivityThread->mainjava.lang.reflect.Method->invokeNativejava。郎。reflect.Method->invokecom.android.internal.os.ZygoteInit$MethodAndArgsCaller->runco??m.android.internal.os.ZygoteInit->maindalvik.system.NativeStart->main但是如果有一个Xposedframeworkhookcom.example.hookdetection.DoStuff.getSecret方法,那么调用堆栈将有2个变化:de.robv.android.xposed.XposedBridge.main调用出现在dalvik.system.NativeStart.main方法之后。如果Xposed在调用栈中hook了一个方法,就会有de.robv.android.xposed。XposedBridge.handleHookedMethod和de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative调用,所以如果getSecret方法被挂钩,调用堆栈将如下所示:#!bashcom.example.hookdetection.DoStuff->getSecretde.robv.android.xposed.XposedBridge->invokeOriginalMethodNativede.robv.android.xposed.XposedBridge->handleHookedMethodcom.example.hookdetection.DoStuff->getSecretcom.example.hookdetection.MainActivity->onCreateandroid.app.Activity->performCreateandroid.app.Instrumentation->callActivityOnCreateActivity>performLaunchActivityandroid.app.ActivityThread->handleLaunchActivityandroid.app.ActivityThread->access$800android.app.ActivityThread$H->handleMessageandroid.os.Handler->dispatchMessageandroid.os.Looper->loopandroid.app.ActivityThread->mainjava.lang.reflect.Method->invokeNativejava.lang.reflect.Method->invokecom。android.internal.os.ZygoteInit$MethodAndArgsCaller->runco??m.android.internal.os.ZygoteInit->mainde.robv.android.xposed.XposedBridge->maindalvik.system.NativeStart->main看Substratehookcom.example。在hookdetection.DoStuff.getSecret方法之后,调用栈会发生什么:如果Substratehook调用栈,dalvik.system.NativeStart.main会出现两次com.android.internal.os.ZygoteInit.main而不是一次com.saurik.substrate.MS$2.invoked、com.saurik.substrate.MS$MethodPointer.invoke和与Substrate扩展相关的方法(这里调用了com.cigital.freak.Freak$1$1.)。所以如果hook了getSecret方法,调用栈就会如下:#!bashcom.example.hookdetection.DoStuff->getSecretcom.saurik.substrate._MS$MethodPointer->invokecom.saurik.substrate.MS$MethodPointer->invokecom.cigital.freak.Freak$1$1->调用com.saurik.substrate.MS$2->调用com.example.hookdetection.DoStuff->getSecretcom.example.hookdetection.MainActivity->onCreateandroid.app.Activity->performCreateandroid.app.Instrumentation->callActivityOnCreateandroid.app.ActivityThread->performLaunchActivityandroid.app.ActivityThread->handleLaunchActivityandroid.app.ActivityThread->access$800android.app.ActivityThread$H->handleMessageandroid.os.Handler->dispatchMessageandroid.os.Looper->loopandroid.app.ActivityThread->mainjava.lang.reflect.Method->invokeNativejava.lang.reflect.Method->invokecom.android.internal.os.ZygoteInit$MethodAndArgsCaller->runco??m.android.internal.os.ZygoteInit->maincom.android.internal。os.ZygoteInit->maindalvik.system.NativeStart->main知道调用栈的变化后,可以在Java层写代码检测:#!javatry{thrownewException("blah");}catch(Exceptione){intzygoteInitCallCount=0;for(StackTraceElementstackTraceElement:e.getStackTrace()){if(stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")){zygoteInitCallCount++;if(zygoteInitCallCount==2){Log.wtf("HookDetection","Substrate在设备上处于活动状态。");}}if(stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2")&&stackTraceElement.getMethodName().equals("调用")){Log.wtf("HookDetection","已使用Substrate挂钩堆栈跟踪上的方法。");}if(stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge")&&stackTraceElement.getMethodName().equals("main")){Log.wtf("HookDetection","Xposed在设备上处于活动状态。";);}if(stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge")&&stackTraceElement.getMethodName().equals("handleHookedMethod")){Log.wtf("HookDetection","A堆栈跟踪上的方法已使用Xposed挂钩。”);}}}0x03检测不应为本机的本机方法。Xposed框架会将hook的Java方法类型改为“native”,然后用自己的方法替换原来的方法(调用hookedMethodCallback)可以查看XposedBridge_hookMethodNative的实现,修改后就是app_process中的方法。利用Xposed改变hook方法的特性(Substrate也采用了类似的原理),可以用来检测是否被hook。注意这个在ART运行的时候不能用来检测Xposed,因为不需要把方法的类型改成native。假设有下面的方法:#!javapublicclassDoStuff{publicstaticStringgetSecret(){return"ChangeMePls!!!";}}如果getSecret方法被hook,它在运行时看起来像下面的定义:#!javapublicclassDoStuff{//clallshookedMethodCallbackifhookedusingXposedpublicnativestaticStringgetSecret();}基于以上原理,检测步骤如下:定位应用的DEX文件,枚举所有类。从反射机制来看,它在运行时不应该是原生的下面Java中的方法演示了这个技巧。这里假设应用本身不通过JNI调用native代码,大部分应用不需要调用native方法。但是如果有JNI调用,只需要将这些native方法加入白名单即可。理论上这个方法也可以用来检测Java库或者第三方库,但是第三方库的native方法需要加入白名单。测试代码如下:#!javafor(ApplicationInfoapplicationInfo:applicationInfoList){if(applicationInfo.processName.equals("com.example.hookdetection")){Setclasses=newHashSet();DexFiledex;try{dex=newDexFile(applicationInfo.sourceDir);Enumerationentries=dex.entries();while(entries.hasMoreElements()){Stringentry=entries.nextElement();classes.add(entry);}dex.close();}catch(IOExceptione){Log.e("HookDetection",e.toString());}for(StringclassName:classes){if(className.startsWith("com.example.hookdetection")){try{Classclazz=HookDetection.class.forName(className);for(Methodmethod:clazz.getDeclaredMethods()){if(Modifier.isNative(method.getModifiers())){Log.wtf("HookDetection","Nativefunctionfound(couldbehookby基板或Xposed):"+clazz.getCanonicalName()+"->"+method.getName());}}}catch(ClassNotFoundExceptione){Log.wtf("HookDetection",e.toString());}}}}}0x04通过/proc/[pid]/maps或JAR/proc/[pid]/maps记录内存映射区和访问权限检测可疑共享对象,首先检查Android应用Image,***列是起始地址和结束地址,第六列是映射文件的路径#!bash#cat/proc/5584/maps40027000-4002c000r-xp00000000103:062114/system/bin/app_process4002c000-4002d000r--p00004000103:062114/system/bin/rapp_process4002d00p00-400103:062114/system/bin/app_process4002e000-4003d000r-xp00000000103:06246/system/bin/linker4003d000-4003e000r--p0000e000103:06246/system/bin/linker4003e000-4003f000rw-p0000f000103:06246/System/Bin/LINKER4003F000-40042000RW-P00000000000040042000-40043000R-0000000000000000000043000-40044000RW00000000000044447000R-XP0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006117666:06117666:06117666:06117666:0611766:061176:061176:061176:061176/system/lib/libNimsWrap.so40047000-40048000r--p00002000103:061176/system/lib/libNimsWrap.so40048000-40049000rw-p00003000im/systemW/116:103:061176/system/lib/libNimsWrap.so40048000-40049000.so4000000000103:061237/system/lib/libc.so...这里有很多其他内存区域...因此您可以编写代码来检测加载到当前内存区域中的可疑文件:#!javatry{Setlibraries=newHashSet();StringmapsFilename="/proc/"+android.os.Process.myPid()+"/maps";BufferedReaderreader=newBufferedReader(newFileReader(mapsFilename));Stringline;while((line=reader.readLine())!=null){if(line.endsWith(".so")||line.endsWith(".jar")){intn=line.lastIndexOf("");libraries.add(line.substring(n+1));}}for(字符串库:库){if(library.contains("com.saurik.substrate")){Log.wtf("HookDetection","Substratesharedobjectfound:"+library);}if(library.contains("XposedBridge.jar")){Log.wtf("HookDetection","XposedJARfound:"+library);}}reader.close();}catch(Exceptione){Log.wtf("HookDetection",e.toString());}Substrate会用到几个so:#!bashSubstratesharedobjectfound:/data/app-lib/com.saurik.substrate-1/libAndroidBootstrap0.soSubstratesharedobjectfound:/data/app-lib/com.saurik.substrate-1/libAndroidCydia.cy.soSubstrate找到共享对象:/data/app-lib/com.saurik.substrate-1/libDalvikLoader.cy.soSubstrate找到共享对象:/data/app-lib/com.saurik.substrate-1/libsubstrate.soSubstrate找到共享对象:/data/app-lib/com.saurik.substrate-1/libsubstrate-dvm.soSubstrate共享对象找到:/data/app-lib/com.saurik.substrate-1/libAndroidLoader.soXposed将使用一个Jar:#!bashXposedJAR找到:/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar0x05绕过检测方法上面讨论了几种反挂钩方法,但是相信会有人提出绕过方法,这里对应各个检测方法如下:hookPackageManager的getInstalledApplications,从hookException的getStackTrace中去掉Xposed或者Substrate的包名,从hook中去掉自己的方法getModifiers,把flag改成好像不是nativehook打开的文件的操作,返回/dev/null或者修改后的map文件