Android反挂钩技术-Java层

原文: 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/

你可能感兴趣的:(android)