在QQ浏览器的崩溃报告中,会发送大量当前设备的状态,设备的信息.硬件的信息.等等.
其中就包含了Hook检测,Root检测,模拟器检测,DexFile检测,Debug检测.这些信息.
由于这部分代码使用了名称混淆.所以我自己手工把代码还原了一遍.替换为有意义的名称.
部分逻辑修改了一下下.下面我们就依次来看.
Hook 检测
说是Hook检测,不如说是对目前比较流行的框架xposed,substrate的检测.
检测Package
这部分代码比较简单,就是检测是否安装了xposed或者substrate.
public static int checkPackage(Context context) {
int i = 0;
PackageManager packageManager = context.getPackageManager();
try {
packageManager.getInstallerPackageName("de.robv.android.xposed.installer");
i = 1;
} catch (Exception e) {
}
try {
packageManager.getInstallerPackageName("com.saurik.substrate");
return i | 2;
} catch (Exception e2) {
return i;
}
}
检测/proc/mypid/maps
这里我对Linux的proc文件系统没有研究过的童鞋非常简单的搜一下盲.
首先proc文件系统的设计目的之一就是允许更方便的对进程信息进行访问.
每当一个进程创建的时候,/proc目录下就会有和该进程id对应的目录产生.
目录名称就是进程id.里面记录该进程的各种信息.其中maps记录了进程的
内存信息,更具体的说内存分段信息.具体什么含义我就不说了,反正我们可以知道它可以记录加载了那些模块就OK了.如下图

对Xposed有研究过的人应该都知道,xposed会对进程注入这些模块:
XposedBridge.jar
libxposed_art.so
app_process32_xposed
所以我们检测这些模块是否存在也是可以检测出xposed和substrate的.
public static int checkMap() throws Throwable {
UnsupportedEncodingException unsupportedEncodingException;
BufferedReader bufferedReader;
Throwable th;
int i = 0;
BufferedReader bufferedReader2;
int i2;
int result = 0;
try {
HashSet hashSet = new HashSet();
bufferedReader2 = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/" + android.os.Process.myPid() + "/maps"), "utf-8"));
while (true) {
try {
String readLine = bufferedReader2.readLine();
if (readLine == null) {
break;
} else if (readLine.endsWith(".so") || readLine.endsWith(".jar")) {
hashSet.add(readLine.substring(readLine.lastIndexOf(" ") + 1));
}
} catch (UnsupportedEncodingException e) {
unsupportedEncodingException = e;
i2 = 0;
bufferedReader = bufferedReader2;
try {
unsupportedEncodingException.printStackTrace();
if (bufferedReader != null) {
}
} catch (Throwable th2) {
th = th2;
bufferedReader2 = bufferedReader;
if (bufferedReader2 != null) {
}
throw th;
}
}
}
Iterator it = hashSet.iterator();
while (it.hasNext()) {
int i3;
Object next = it.next();
if (((String) next).toLowerCase().contains("xposed")) {
result = result | 64;
}
if (((String) next).toLowerCase().contains("com.saurik.substrate")) {
result = result | 128;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
代码逻辑就是一行一行的读/proc/mypid/mpas.看是否包含xposed或com.saurik.substrate.
检测堆栈信息
先看图.

啊啊,我们只要检测堆栈是否包含红色圈圈de.robv.android.xposed.XposedBridge就可以了.
看代码
public static int checkStackTraceElement() {
int i = 0;
try {
throw new Exception("detect hook");
} catch (Exception e) {
int i2 = 0;
for (StackTraceElement stackTraceElement : e.getStackTrace()) {
if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && stackTraceElement.getMethodName().equals("main")) {
i2 |= 4;
}
if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && stackTraceElement.getMethodName().equals("handleHookedMethod")) {
i2 |= 8;
}
if (stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2") && stackTraceElement.getMethodName().equals("invoked")) {
i2 |= 16;
}
if (stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) {
i++;
if (i == 2) {
i2 |= 32;
}
}
}
return i2;
}
}
其实我们可以依据该方法检测自己的方法是否被Hook了.道理是一样的.就是检测堆栈是否包含有afterHookedMethod,beforeHookedMethod.
检测ActivityManagerNative
话说这个我不清楚啥原理.有懂得留言告诉我一下呗.
public static int checkActivityManagerNative() {
try {
Method method = Class.forName("android.app.ActivityManagerNative").getMethod("getDefault", new Class[0]);
method.setAccessible(true);
if (method.invoke(null, new Object[0]).getClass().getName().startsWith("$Proxy")) {
return 256;
}
return 0;
} catch (Exception e) {
return 256;
}
}
模拟器检测
public static String getVMDesc()
{
StringBuilder stringBuilder = new StringBuilder();
String VM = AdbShell.getprop("ro.genymotion.version");//判断genymotion模拟器
if (VM != null) {
stringBuilder.append("ro.genymotion.version");
stringBuilder.append("|");
stringBuilder.append(VM);
stringBuilder.append("\n");
}
VM = AdbShell.getprop("androVM.vbox_dpi");//判断使用了vbox的模拟器,目前很多市面上的安卓模拟器都是基于vbox的
if (VM != null) {
stringBuilder.append("androVM.vbox_dpi");
stringBuilder.append("|");
stringBuilder.append(VM);
stringBuilder.append("\n");
}
VM = AdbShell.getprop("qemu.sf.fake_camera");//检测安卓自身的模拟器
if (VM != null) {
stringBuilder.append("qemu.sf.fake_camera");
stringBuilder.append("|");
stringBuilder.append(VM);
}
return stringBuilder.toString();
}
模拟器都具有一些特殊的属性.查找这个特殊的属性就可以判断是否是模拟器.
其中AdbShell.getprop 等效于 Systemproperties.get(name).
检测Debug
一个是检测debuggable标志.一个是检测TracerPid.
//检测是否拥有调试属性
public static String HaveDebugProp()
{
//ro.debuggable表示调试权限,默认为0,1表示可以调试
StringBuilder builder = new StringBuilder();
builder.append("ro.debuggable");
builder.append(AdbShell.getprop("ro.debuggable"));
return builder.toString();
}
//如果进程被调试TracerPid不为0
public static String getTracerPid()
{
BufferedReader bufferedReader;
String readLine = "";
try {
bufferedReader = new BufferedReader(new FileReader("/proc/self/status"));
do{
readLine = bufferedReader.readLine();
if (readLine == null) {
break;
}
}while (!readLine.startsWith("TracerPid:"));
readLine = readLine.substring(10).trim();
} catch (Exception e) {
e.printStackTrace();
}
return readLine;
}
Root检测
一个是检测是否有su文件.一个是检测属性.
private static final String[] suFiles = new String[]{"/su", "/su/bin/su", "/sbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/data/local/su", "/system/xbin/su", "/system/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/system/bin/cufsdosck", "/system/xbin/cufsdosck", "/system/bin/cufsmgr", "/system/xbin/cufsmgr", "/system/bin/cufaevdd", "/system/xbin/cufaevdd", "/system/bin/conbb", "/system/xbin/conbb"};
public static boolean haveSu()
{
boolean z = false;
boolean z2 = false;
for (String file : suFiles) {
if (new File(file).exists()) {
z = true;
break;
}
}
if (Build.TAGS == null || !Build.TAGS.contains("test-keys")) {
z2 = false;
} else {
z2 = true;
}
return z2 || z;
}
public static String RootCheckProp()
{
//ro.secure表示root权限,如果为0则表示启用root权限,1则相反
//这个只能检测ROM被刷入时的默认属性.
StringBuilder builder = new StringBuilder();
builder.append("ro.secure:");
builder.append(AdbShell.getprop("ro.secure"));
builder.append("\n");
builder.append("ro.adb.secure:");
builder.append(AdbShell.getprop("ro.adb.secure"));
builder.append("\n");
return builder.toString();
}
据说对一些比较难Root的手机厂商,修改rom里面的default.prop文件里的ro.secure为0,然后重签名再刷进去可以获得永久root.不过我没试过...,比较懒.
DexFile文件检测
根据ClassLoad检测加载了那些Dex文件
//查询所有加载的dex,jar,apk文件,看一下是否有其他异己的模块加载
public static void allDex()
{
Object pathList = getDeclaredFieldValue(DexFileCheck.class.getClassLoader(),"pathList");
Object [] dexElements = (Object [])getDeclaredFieldValue(pathList,"dexElements");
for(Object dex:dexElements)
{
DexFile dexFile = (DexFile)getDeclaredFieldValue(dex,"dexFile");
if(dexFile == null)continue;
Log.d(TAG, "allDex: found dexfile "+ dexFile.getName());
}
}
总结
其中这都是最简单最直接的检测方法,还有很多检测方法.但道理无非就一个,求同排异.
像不像人体的淋巴细胞.攻击者是病毒,而守护者就是淋巴系统.其实就是为了说明一个道理,
学会类比.
安卓应用层抓包通杀脚本发布!《高研班》2021年3月班开始招生!