原文: http://d3adend.org/blog/?p=589
翻译: Zhiwei(http://zhiwei.li/text/)
最近一个在native代码层(C/C++)检测 挂钩框架(hooking frameworks)的内部讨论,引发了我的思考:一个纯Java的Android应用,有什么方法来检测
Cydia Substrate或者Xposed framework的存在。
免责声明:
所有的这些反挂钩技术是 很容易被有经验的逆向工程师绕过的。我只是在探索人们可能会怎么去检测他们的Java应用程序已经被Substrate或者Xposed框架挂钩,因为在某些时候,我们需要能够绕过这些技术,来完成我们的工作,就像我们如何绕过root检测那样。我最后一次看着DexGuard和Arxan的Java保护产品(GuardIT),他们不支持任何挂钩框架的检测。我希望类似的反钩技术将来被添加到这些Java混淆/保护产品中。
1. 检查什么软件已经安装在设备上
浮现在脑海的第一个念头就是简单地检测Substrate或Xposed框架是否安装在设备上。我们可以向清楚包管理器查询安装的软件包,标记任何可疑的包,这是root检测中使用的一个常用技术。
PackageManager packageManager = context.getPackageManager(); List applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); for(ApplicationInfo applicationInfo : applicationInfoList) { if(applicationInfo.packageName.equals("de.robv.android.xposed.installer")) { Log.wtf("HookDetection", "Xposed found on the system."); } if(applicationInfo.packageName.equals("com.saurik.substrate")) { Log.wtf("HookDetection", "Substrate found on the system."); } }
2.检查可疑的方法调用栈跟踪(stack trace)。
接下来的技术是, 检查可疑的方法调用栈跟踪。下面的Java类,含有一个抛出异常的方法,捕获它,然后打印出栈跟踪。
public class DoStuff { public static String getSecret() { try { throw new Exception("blah"); } catch(Exception e) { for(StackTraceElement stackTraceElement : e.getStackTrace()) { Log.wtf("HookDetection", stackTraceElement.getClassName() + "->" + stackTraceElement.getMethodName()); } } return "ChangeMePls!!!"; } }
进一步完善成
try { throw new Exception("blah"); } catch(Exception e) { int zygoteInitCallCount = 0; for(StackTraceElement stackTraceElement : e.getStackTrace()) { if(stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) { zygoteInitCallCount++; if(zygoteInitCallCount == 2) { Log.wtf("HookDetection", "Substrate is active on the device."); } } if(stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2") && stackTraceElement.getMethodName().equals("invoked")) { Log.wtf("HookDetection", "A method on the stack trace has been hooked using Substrate."); } if(stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && stackTraceElement.getMethodName().equals("main")) { Log.wtf("HookDetection", "Xposed is active on the device."); } if(stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && stackTraceElement.getMethodName().equals("handleHookedMethod")) { Log.wtf("HookDetection", "A method on the stack trace has been hooked using Xposed."); } } }
3. 检查那些不应该是native的native方法 (在ART版本的Xposed中不适用)
Xposed框架,将要hooked的方法,改成native, 并用它自己的方法替代(调用hookedMethodCallback)
for (ApplicationInfo applicationInfo : applicationInfoList) { if (applicationInfo.processName.equals("com.example.hookdetection")) { Set classes = new HashSet(); DexFile dex; try { dex = new DexFile(applicationInfo.sourceDir); Enumeration entries = dex.entries(); while(entries.hasMoreElements()) { String entry = entries.nextElement(); classes.add(entry); } dex.close(); } catch (IOException e) { Log.e("HookDetection", e.toString()); } for(String className : classes) { if(className.startsWith("com.example.hookdetection")) { try { Class clazz = HookDetection.class.forName(className); for(Method method : clazz.getDeclaredMethods()) { if(Modifier.isNative(method.getModifiers())){ Log.wtf("HookDetection", "Native function found (could be hooked by Substrate or Xposed): " + clazz.getCanonicalName() + "->" + method.getName()); } } } catch(ClassNotFoundException e) { Log.wtf("HookDetection", e.toString()); } } } } }
4. 用/proc/[pid]/maps 来检测 夹在到内存里的 恶意的共享对象 或者JARs
try { Set libraries = new HashSet(); String mapsFilename = "/proc/" + android.os.Process.myPid() + "/maps"; BufferedReader reader = new BufferedReader(new FileReader(mapsFilename)); String line; while((line = reader.readLine()) != null) { if (line.endsWith(".so") || line.endsWith(".jar")) { int n = line.lastIndexOf(" "); libraries.add(line.substring(n + 1)); } } for (String library : libraries) { if(library.contains("com.saurik.substrate")) { Log.wtf("HookDetection", "Substrate shared object found: " + library); } if(library.contains("XposedBridge.jar")) { Log.wtf("HookDetection", "Xposed JAR found: " + library); } } reader.close(); } catch (Exception e) { Log.wtf("HookDetection", e.toString()); }
反-反挂钩
1)勾住 PackageManager的 getInstalledApplications 方法, 从列表里 删除恶意包 ,也就是说把它们隐藏起来
2)勾住 Exception的 getStackTrace 方法, 将恶意的 StackTraceElement 对象删除
3) 勾住 Method的 getModifiers, 调整标志,让方法看起来不是native的
4) 勾住 任何文件打开操作, 将 /proc/[PID]/maps 用 /dev/null 或者一个 bogus maps文件代替
http://zhiwei.li/text/2016/02/20/android%E5%8F%8D%E6%8C%82%E9%92%A9%E6%8A%80%E6%9C%AF-java%E5%B1%82/