Xposed检测方法和绕过
1.检查安装目录的是否有xposedinstall
private static boolean findHookAppName(Context context) {
PackageManager packageManager = context.getPackageManager();
List<ApplicationInfo> applicationInfoList = packageManager
.getInstalledApplications(PackageManager.GET_META_DATA);
//遍历应用列表,查看是否有xposed的包名
for (ApplicationInfo applicationInfo : applicationInfoList) {
if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {
Log.wtf("HookDetection", "Xposed found on the system.");
return true;
}
}
return false;
}
反检测方法1:
关闭应用权限使其无法获取安装目录包名
反检测方法2:修改xposed的包名
反检测方法3 直接用Androidkiller干掉判断(不是很适用,毕竟有很多应用都有壳)
2.检测xposed相关的jar和so
private static boolean findHookAppFile() {
try {
Set<String> libraries = new HashSet<String>();
String mapsFilename = "/proc/" + android.os.Process.myPid() + "/maps";
BufferedReader reader = new BufferedReader(new FileReader(mapsFilename));
String line;
while ((line = reader.readLine()) != null) {
//循环遍历/proc/pid/maps,如果后缀为.so或者为.jar,就存入Map中
if (line.endsWith(".so") || line.endsWith(".jar")) {
int n = line.lastIndexOf(" ");
libraries.add(line.substring(n + 1));
}
}
reader.close();
//遍历Map检测是否包含XposedBridge.jar
for (String library : libraries) {
if (library.contains("XposedBridge.jar")) {
Log.wtf("HookDetection", "Xposed JAR found: " + library);
return true;
}
}
} catch (Exception e) {
Log.wtf("HookDetection", e.toString());
}
return false;
}
反检测方法1:
通过hook BufferedReader.class的readline()
方法,使其在读取/proc/pid/maps目录时不读取Xposed.jar
实现如下:
XposedHelpers.findAndHookMethod(BufferedReader.class, "readLine", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String result = (String) param.getResult();
if(result != null) {
//如果ReadLine方法返回的结果为Xposed.jar,就设置其返回值为空
if (result.contains("/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar")) {
param.setResult("");
new File("").lastModified();
}
}
super.afterHookedMethod(param);
}
});
反检测方法2:使用virtualXposed框架
3.查看程序运行栈内容读取判断是否有Xposed等字符串
说明:由Xposed原理可知,Xposed执行会早于Zygote进程,因此查看系统堆栈时会有Xposed的栈信息
private static boolean findHookStack() {
try {
throw new Exception("findhook");
} 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.");
return true;
}
}
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.");
return true;
}
if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge")
&& stackTraceElement.getMethodName().equals("main")) {
Log.wtf("HookDetection", "Xposed is active on the device.");
return true;
}
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.");
return true;
}
}
}
return false;
}
反检测方法:原理和方法二类似通过hook StackTraceElement.class的getClassName()
XposedHelpers.findAndHookMethod(StackTraceElement.class, "getClassName", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String result = (String) param.getResult();
if (result != null){
// 替换字符串为空
if (result.contains("de.robv.android.xposed.")) {
param.setResult("");
}else if(result.contains("com.android.internal.os.ZygoteInit")){
param.setResult("");
}
}
super.afterHookedMethod(param);
}
});
4.利用Xposed的注入方式
由于Xposed会将hook的方法修改为native方法,因此可以通过检测方法是否为native达到检测目的,该方案适用性不是很广,因为应用的方法只会在被hook时才会被转换为native方法
private static boolean findhookso(){
boolean isNative=false;
try {
Class<MainActivity> clz=MainActivity.class;
Method method=clz.getMethod("toastMessage");
isNative=Modifier.isNative(method.getModifiers());
} catch (NoSuchMethodException e) {
e.printStackTrace ();
}
return isNative;
}
反检测方法:
final int modify=0;
//获得检测时的方法
XposedHelpers.findAndHookMethod(Method.class, "getModifiers", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Method method = (Method)param.thisObject;
String[] array = new String[] { "getDeviceId" };
String method_name = method.getName();
//asList(T... a)返回一个受指定数组支持的固定大小的列表。
if(Arrays.asList(array).contains(method_name)){
modify = 0;
}else{
modify = (int)param.getResult();
}
super.afterHookedMethod(param);
}
});
//hook isNative方法,修改返回值
XposedHelpers.findAndHookMethod(Modifier.class, "isNative", int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = modify;
super.beforeHookedMethod(param);
}
});
root检测
1.查看su是否存在
我们通常使用
adb shell
su
实现获取Android系统的root权限,因此我们可以通过检测su是否存在,从而达到Android设备是否被root。
想要检测su是否存在可以通过以三个方法。
(1)检测关键的目录
public static boolean isDeviceRooted01() {
File f = null;
final String kSuSearchPaths[] = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/"};
try {
for (int i = 0; i < kSuSearchPaths.length; i++) {
f = new File (kSuSearchPaths[i] + "su");
if (f != null && f.exists ()) {
Log.i (LOG_TAG, "find su in : " + kSuSearchPaths[i]);
return true;
}
}
} catch (Exception e) {
e.printStackTrace ();
}
return false;
(2)使用which命令检测
public static boolean isDeviceRooted02() {
String[] strCmd = new String[]{"/system/xbin/which", "su"};
ArrayList<String> execResult = executeCommand (strCmd);
if (execResult != null) {
Log.i (LOG_TAG, "execResult=" + execResult.toString ());
return true;
} else {
Log.i (LOG_TAG, "execResult=null");
return false;
}
}
public static ArrayList<String> executeCommand(String[] shellCmd) {
String line = null;
ArrayList<String> fullResponse = new ArrayList<String> ();
Process localProcess = null;
try {
Log.i (LOG_TAG, "to shell exec which for find su :");
localProcess = Runtime.getRuntime ().exec (shellCmd);
} catch (Exception e) {
Log.d (LOG_TAG, e.toString ());
return null;
}
BufferedWriter out = new BufferedWriter (new OutputStreamWriter (localProcess.getOutputStream ()));
BufferedReader in = new BufferedReader (new InputStreamReader (localProcess.getInputStream ()));
try {
while ((line = in.readLine ()) != null) {
Log.i (LOG_TAG, "–> Line received: " + line);
fullResponse.add (line);
}
} catch (Exception e) {
e.printStackTrace ();
}
Log.i (LOG_TAG, "–> Full response was: " + fullResponse);
return fullResponse;
}
(3)直接构造执行su命令
public static boolean isDeviceRooted03() {
Process process = null;
DataOutputStream os = null;
try {
Log.i (LOG_TAG, "to exec su");
process = Runtime.getRuntime ().exec ("su");
os = new DataOutputStream (process.getOutputStream ());
os.writeBytes ("exit\n");
os.flush ();
int exitValue = process.waitFor ();
Log.i (LOG_TAG, "exitValue=" + exitValue);
if (exitValue == 0) {
return true;
} else {
return false;
}
} catch (Exception e) {
Log.i (LOG_TAG, "Unexpected error - Here is what I know: "
+ e.getMessage ());
return false;
} finally {
try {
if (os != null) {
os.close ();
}
process.destroy ();
} catch (Exception e) {
e.printStackTrace ();
}
}
}
2.检测Busybox是否存在
Busybox集成了一些Android系统为了安全考虑去掉的一些风险命令,一般root过的手机都有可能安装Busybox
public static synchronized boolean isDeviceRooted04() {
try {
Log.i (LOG_TAG, "to exec busybox df");
String[] strCmd = new String[]{"busybox", "df"};
ArrayList<String> execResult = executeCommand (strCmd);
if (execResult != null) {
Log.i (LOG_TAG, "execResult=" + execResult.toString ());
return true;
} else {
Log.i (LOG_TAG, "execResult=null");
return false;
}
} catch (Exception e) {
Log.i (LOG_TAG, "Unexpected error - Here is what I know: "
+ e.getMessage ());
return false;
}
}
3.访问/data目录查看读写权限
//写文件
public static Boolean writeFile(String fileName, String message) {
try {
FileOutputStream fout = new FileOutputStream (fileName);
byte[] bytes = message.getBytes ();
fout.write (bytes);
fout.close ();
return true;
} catch (Exception e) {
e.printStackTrace ();
return false;
}
}
//读文件
public static String readFile(String fileName) {
File file = new File (fileName);
try {
FileInputStream fis = new FileInputStream (file);
byte[] bytes = new byte[1024];
ByteArrayOutputStream bos = new ByteArrayOutputStream ();
int len;
while ((len = fis.read (bytes)) > 0) {
bos.write (bytes, 0, len);
}
String result = new String (bos.toByteArray ());
Log.i (LOG_TAG, result);
return result;
} catch (Exception e) {
e.printStackTrace ();
return null;
}
}
public static synchronized boolean isDeviceRooted05() {
try {
Log.i (LOG_TAG, "to write /data");
String fileContent = "test_ok";
Boolean writeFlag = writeFile ("/data/su_test", fileContent);
if (writeFlag) {
Log.i (LOG_TAG, "write ok");
} else {
Log.i (LOG_TAG, "write failed");
}
Log.i (LOG_TAG, "to read /data");
String strRead = readFile ("/data/su_test");
Log.i (LOG_TAG, "strRead=" + strRead);
if (fileContent.equals (strRead)) {
return true;
} else {
return false;
}
} catch (Exception e) {
Log.i (LOG_TAG, "Unexpected error - Here is what I know: "
+ e.getMessage ());
return false;
}
}
4.检测系统是否为测试版
一般不完全root的设备(通过找到系统漏洞提权的设备),会显示不是正版因此可以通过检测版本
public static synchronized boolean isDeviceRooted06(){
String buildTags = android.os.Build.TAGS;
if(buildTags !=null&&buildTags.contains("test-keys"))
{
Log.i (LOG_TAG, "buildTags=" + buildTags);
return true;
}
return false;
}
5.检查是否存在Superuser.apk
Superuser.apk被广泛用来root安卓设备,我们可以通过检测其是否存在达到检测是否root的目的
public static synchronized boolean isDeviceRooted07(){
try {
File file = new File("/system/app/Superuser.apk");
if (file.exists()) {
Log.i(LOG_TAG,"/system/app/Superuser.apk exist");
return true;
}
} catch (Exception e) { }
return false;
}
}