0


安全风险 - 检测Android设备系统是否已Root

在很多app中都禁止

root

后的手机使用相关app功能,这种场景在金融app、银行app更为常见一些;当然针对

root

后的手机,我们也可以做出风险提示,告知用户当前设备已

root

,谨防风险!

最近在安全检测中提出了一项 **

风险bug

:当设备已处于 root 状态时,未提示用户风险!**

那么我们要做的就是 **检测当前

Android

设备是否已

Root

,然后

根据业务方的述求,给出风险提示或者禁用app

**

基础认知

Android

设备被

root

后,会多出

su

文件,同时也可能获取超级用户权限,就有可能存在

Superuser.apk

文件 ,所以我们主要从以下几个方面去判断设备被

root
  • 检查系统中是否存在 su 文件
  • 检查系统是否可执行 su 文件
  • 检查系统中是否 /system/app/Superuser.apk 文件(当 root 后会将 Superuser.apk 文件放于 /system/app/中)

细节分析

看了几篇 Blog 后,主要还是借鉴了 Android如何判断系统是否已经被Root + EasyProtector框架 ,希望可以更好的兼容处理方案

判断系统内是否包含 su

/**
  * 是否存在su命令,并且有执行权限
  *
  * @return 存在su命令,并且有执行权限返回true
  */publicstaticbooleanisSuEnable(){File file =null;String[] paths ={"/system/bin/","/system/xbin/","/system/sbin/","/sbin/","/vendor/bin/","/su/bin/"};try{for(String path : paths){
             file =newFile(path +"su");if(file.exists()&& file.canExecute()){Log.i(TAG,"find su in : "+ path);returntrue;}}}catch(Exception x){
         x.printStackTrace();}returnfalse;}

判断系统内是否包含 busybox

BusyBox

是一个集成了多个常用

Linux

命令和工具的软件,它的主要用途是提供一个基础但全面的

Linux

操作系统环境,适用于各种嵌入式系统和资源受限的环境

/**
  * 是否存在busybox命令,并且有执行权限
  *
  * @return 存在busybox命令,并且有执行权限返回true
  */publicstaticbooleanisSuEnable(){File file =null;String[] paths ={"/system/bin/","/system/xbin/","/system/sbin/","/sbin/","/vendor/bin/","/su/bin/"};try{for(String path : paths){
             file =newFile(path +"busybox");if(file.exists()&& file.canExecute()){Log.i(TAG,"find su in : "+ path);returntrue;}}}catch(Exception x){
         x.printStackTrace();}returnfalse;}

检测系统内是否安装了Superuser.apk之类的App

publicstaticbooleancheckSuperuserApk(){try{File file =newFile("/system/app/Superuser.apk");if(file.exists()){Log.i(LOG_TAG,"/system/app/Superuser.apk exist");returntrue;}}catch(Exception e){}returnfalse;}

判断 ro.debuggable 属性和 ro.secure 属性

默认手机出厂后

ro.debuggable

属性应该为0,

ro.secure

应该为1;意思就是系统版本要为

user

版本

privateintgetroDebugProp(){int debugProp;String roDebugObj =CommandUtil.getSingleInstance().getProperty("ro.debuggable");if(roDebugObj ==null) debugProp =1;else{if("0".equals(roDebugObj)) debugProp =0;else debugProp =1;}return debugProp;}privateintgetroSecureProp(){int secureProp;String roSecureObj =CommandUtil.getSingleInstance().getProperty("ro.secure");if(roSecureObj ==null) secureProp =1;else{if("0".equals(roSecureObj)) secureProp =0;else secureProp =1;}return secureProp;}

检测系统是否为测试版

Tips

  • 这种验证方式比较依赖在设备中通过命令进行验证,并不是很适合在软件中直接判断root场景
  • 若是非官方发布版,很可能是完全root的版本,存在使用风险

在系统

adb shell

中执行

# cat /system/build.prop | grep ro.build.tagsro.build.tags=release-keys

还有一种检测方式是

检测系统挂载目录权限

,主要是检测

Android

沙盒目录文件或文件夹读取权限(在

Android

系统中,有些目录是普通用户不能访问的,例如

/data

/system

/etc

等;比如微信沙盒目录下的文件或文件夹权限是否正常)


合并实践

有兴趣的话也可以把

CommandUtil

getProperty方法

SecurityCheckUtil

root

相关方法 合并到

RootTool

中,因为我还用到了 EasyProtector框架 的模拟器检测功能,故此处就先不进行二次封装了

封装 RootTool

importandroid.util.Log;importjava.io.File;publicclassRootTool{privatestaticfinalStringTAG="root";/**
     * 是否存在su命令,并且有执行权限
     *
     * @return 存在su命令,并且有执行权限返回true
     */publicstaticbooleanisSuEnable(){File file =null;String[] paths ={"/system/bin/","/system/xbin/","/system/sbin/","/sbin/","/vendor/bin/","/su/bin/"};try{for(String path : paths){
                file =newFile(path +"su");if(file.exists()&& file.canExecute()){Log.i(TAG,"find su in : "+ path);returntrue;}}}catch(Exception x){
            x.printStackTrace();}returnfalse;}/**
     * 是否存在busybox命令,并且有执行权限
     *
     * @return 存在busybox命令,并且有执行权限返回true
     */publicstaticbooleanisSuBusyEnable(){File file =null;String[] paths ={"/system/bin/","/system/xbin/","/system/sbin/","/sbin/","/vendor/bin/","/su/bin/"};try{for(String path : paths){
                file =newFile(path +"busybox");if(file.exists()&& file.canExecute()){Log.i(TAG,"find su in : "+ path);returntrue;}}}catch(Exception x){
            x.printStackTrace();}returnfalse;}/**
     * 检测系统内是否安装了Superuser.apk之类的App
     */publicstaticbooleancheckSuperuserApk(){try{File file =newFile("/system/app/Superuser.apk");if(file.exists()){Log.i(TAG,"/system/app/Superuser.apk exist");returntrue;}}catch(Exception e){}returnfalse;}/**
    *  检测系统是否为测试版:若是非官方发布版,很可能是完全root的版本,存在使用风险
    * */publicstaticbooleancheckDeviceDebuggable(){String buildTags =android.os.Build.TAGS;if(buildTags !=null&& buildTags.contains("test-keys")){Log.i(TAG,"buildTags="+buildTags);returntrue;}returnfalse;}}

EasyProtector Root检测剥离

为了方便朋友们进行二次封装,在后面我会将核心方法进行图示标明

CommandUtil

importjava.io.BufferedInputStream;importjava.io.BufferedOutputStream;importjava.io.IOException;/**
 * Project Name:EasyProtector
 * Package Name:com.lahm.library
 * Created by lahm on 2018/6/8 16:23 .
 */publicclassCommandUtil{privateCommandUtil(){}privatestaticclassSingletonHolder{privatestaticfinalCommandUtilINSTANCE=newCommandUtil();}publicstaticfinalCommandUtilgetSingleInstance(){returnSingletonHolder.INSTANCE;}publicStringgetProperty(String propName){String value =null;Object roSecureObj;try{
            roSecureObj =Class.forName("android.os.SystemProperties").getMethod("get",String.class).invoke(null, propName);if(roSecureObj !=null) value =(String) roSecureObj;}catch(Exception e){
            value =null;}finally{return value;}}publicStringexec(String command){BufferedOutputStream bufferedOutputStream =null;BufferedInputStream bufferedInputStream =null;Process process =null;try{
            process =Runtime.getRuntime().exec("sh");
            bufferedOutputStream =newBufferedOutputStream(process.getOutputStream());

            bufferedInputStream =newBufferedInputStream(process.getInputStream());
            bufferedOutputStream.write(command.getBytes());
            bufferedOutputStream.write('\n');
            bufferedOutputStream.flush();
            bufferedOutputStream.close();

            process.waitFor();String outputStr =getStrFromBufferInputSteam(bufferedInputStream);return outputStr;}catch(Exception e){returnnull;}finally{if(bufferedOutputStream !=null){try{
                    bufferedOutputStream.close();}catch(IOException e){
                    e.printStackTrace();}}if(bufferedInputStream !=null){try{
                    bufferedInputStream.close();}catch(IOException e){
                    e.printStackTrace();}}if(process !=null){
                process.destroy();}}}privatestaticStringgetStrFromBufferInputSteam(BufferedInputStream bufferedInputStream){if(null== bufferedInputStream){return"";}intBUFFER_SIZE=512;byte[] buffer =newbyte[BUFFER_SIZE];StringBuilder result =newStringBuilder();try{while(true){int read = bufferedInputStream.read(buffer);if(read >0){
                    result.append(newString(buffer,0, read));}if(read <BUFFER_SIZE){break;}}}catch(Exception e){
            e.printStackTrace();}return result.toString();}}

SecurityCheckUtil

importandroid.content.Context;importandroid.content.Intent;importandroid.content.IntentFilter;importandroid.content.pm.ApplicationInfo;importandroid.content.pm.PackageInfo;importandroid.content.pm.PackageManager;importandroid.content.pm.Signature;importandroid.os.BatteryManager;importandroid.os.Process;importjava.io.BufferedReader;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileReader;importjava.io.IOException;importjava.lang.reflect.Field;importjava.net.InetAddress;importjava.net.Socket;importjava.net.UnknownHostException;importjava.util.HashSet;importjava.util.Iterator;importjava.util.Set;/**
 * Project Name:EasyProtector
 * Package Name:com.lahm.library
 * Created by lahm on 2018/5/14 下午10:31 .
 */publicclassSecurityCheckUtil{privatestaticclassSingletonHolder{privatestaticfinalSecurityCheckUtil singleInstance =newSecurityCheckUtil();}privateSecurityCheckUtil(){}publicstaticfinalSecurityCheckUtilgetSingleInstance(){returnSingletonHolder.singleInstance;}/**
     * 获取签名信息
     *
     * @param context
     * @return
     */publicStringgetSignature(Context context){try{PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),PackageManager.GET_SIGNATURES);// 通过返回的包信息获得签名数组Signature[] signatures = packageInfo.signatures;// 循环遍历签名数组拼接应用签名StringBuilder builder =newStringBuilder();for(Signature signature : signatures){
                builder.append(signature.toCharsString());}// 得到应用签名return builder.toString();}catch(PackageManager.NameNotFoundException e){
            e.printStackTrace();}return"";}/**
     * 检测app是否为debug版本
     *
     * @param context
     * @return
     */publicbooleancheckIsDebugVersion(Context context){return(context.getApplicationInfo().flags
                &ApplicationInfo.FLAG_DEBUGGABLE)!=0;}/**
     * java法检测是否连上调试器
     *
     * @return
     */publicbooleancheckIsDebuggerConnected(){returnandroid.os.Debug.isDebuggerConnected();}/**
     * usb充电辅助判断
     *
     * @param context
     * @return
     */publicbooleancheckIsUsbCharging(Context context){IntentFilter filter =newIntentFilter(Intent.ACTION_BATTERY_CHANGED);Intent batteryStatus = context.registerReceiver(null, filter);if(batteryStatus ==null)returnfalse;int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1);return chargePlug ==BatteryManager.BATTERY_PLUGGED_USB;}/**
     * 拿清单值
     *
     * @param context
     * @param name
     * @return
     */publicStringgetApplicationMetaValue(Context context,String name){ApplicationInfo appInfo = context.getApplicationInfo();return appInfo.metaData.getString(name);}/**
     * 检测本地端口是否被占用
     *
     * @param port
     * @return
     */publicbooleanisLocalPortUsing(int port){boolean flag =true;try{
            flag =isPortUsing("127.0.0.1", port);}catch(Exception e){}return flag;}/**
     * 检测任一端口是否被占用
     *
     * @param host
     * @param port
     * @return
     * @throws UnknownHostException
     */publicbooleanisPortUsing(String host,int port)throwsUnknownHostException{boolean flag =false;InetAddress theAddress =InetAddress.getByName(host);try{Socket socket =newSocket(theAddress, port);
            flag =true;}catch(IOException e){}return flag;}/**
     * 检查root权限
     *
     * @return
     */publicbooleanisRoot(){int secureProp =getroSecureProp();if(secureProp ==0)//eng/userdebug版本,自带root权限returntrue;elsereturnisSUExist();//user版本,继续查su文件}privateintgetroSecureProp(){int secureProp;String roSecureObj =CommandUtil.getSingleInstance().getProperty("ro.secure");if(roSecureObj ==null) secureProp =1;else{if("0".equals(roSecureObj)) secureProp =0;else secureProp =1;}return secureProp;}privateintgetroDebugProp(){int debugProp;String roDebugObj =CommandUtil.getSingleInstance().getProperty("ro.debuggable");if(roDebugObj ==null) debugProp =1;else{if("0".equals(roDebugObj)) debugProp =0;else debugProp =1;}return debugProp;}privatebooleanisSUExist(){File file =null;String[] paths ={"/sbin/su","/system/bin/su","/system/xbin/su","/data/local/xbin/su","/data/local/bin/su","/system/sd/xbin/su","/system/bin/failsafe/su","/data/local/su"};for(String path : paths){
            file =newFile(path);if(file.exists())returntrue;}returnfalse;}privatestaticfinalStringXPOSED_HELPERS="de.robv.android.xposed.XposedHelpers";privatestaticfinalStringXPOSED_BRIDGE="de.robv.android.xposed.XposedBridge";/**
     * 通过检查是否已经加载了XP类来检测
     *
     * @return
     */@DeprecatedpublicbooleanisXposedExists(){try{Object xpHelperObj =ClassLoader.getSystemClassLoader().loadClass(XPOSED_HELPERS).newInstance();}catch(InstantiationException e){
            e.printStackTrace();returntrue;}catch(IllegalAccessException e){
            e.printStackTrace();returntrue;}catch(ClassNotFoundException e){
            e.printStackTrace();returnfalse;}try{Object xpBridgeObj =ClassLoader.getSystemClassLoader().loadClass(XPOSED_BRIDGE).newInstance();}catch(InstantiationException e){
            e.printStackTrace();returntrue;}catch(IllegalAccessException e){
            e.printStackTrace();returntrue;}catch(ClassNotFoundException e){
            e.printStackTrace();returnfalse;}returntrue;}/**
     * 通过主动抛出异常,检查堆栈信息来判断是否存在XP框架
     *
     * @return
     */publicbooleanisXposedExistByThrow(){try{thrownewException("gg");}catch(Exception e){for(StackTraceElement stackTraceElement : e.getStackTrace()){if(stackTraceElement.getClassName().contains(XPOSED_BRIDGE))returntrue;}returnfalse;}}/**
     * 尝试关闭XP框架
     * 先通过isXposedExistByThrow判断有没有XP框架
     * 有的话先hookXP框架的全局变量disableHooks
     * <p>
     * 漏洞在,如果XP框架先hook了isXposedExistByThrow的返回值,那么后续就没法走了
     * 现在直接先hookXP框架的全局变量disableHooks
     *
     * @return 是否关闭成功的结果
     */publicbooleantryShutdownXposed(){Field xpdisabledHooks =null;try{
            xpdisabledHooks =ClassLoader.getSystemClassLoader().loadClass(XPOSED_BRIDGE).getDeclaredField("disableHooks");
            xpdisabledHooks.setAccessible(true);
            xpdisabledHooks.set(null,Boolean.TRUE);returntrue;}catch(NoSuchFieldException e){
            e.printStackTrace();returnfalse;}catch(ClassNotFoundException e){
            e.printStackTrace();returnfalse;}catch(IllegalAccessException e){
            e.printStackTrace();returnfalse;}}/**
     * 检测有么有加载so库
     *
     * @param paramString
     * @return
     */publicbooleanhasReadProcMaps(String paramString){try{Object localObject =newHashSet();BufferedReader localBufferedReader =newBufferedReader(newFileReader("/proc/"+Process.myPid()+"/maps"));for(;;){String str = localBufferedReader.readLine();if(str ==null){break;}if((str.endsWith(".so"))||(str.endsWith(".jar"))){((Set) localObject).add(str.substring(str.lastIndexOf(" ")+1));}}
            localBufferedReader.close();
            localObject =((Set) localObject).iterator();while(((Iterator) localObject).hasNext()){boolean bool =((String)((Iterator) localObject).next()).contains(paramString);if(bool){returntrue;}}}catch(Exception fuck){}returnfalse;}/**
     * java读取/proc/uid/status文件里TracerPid的方式来检测是否被调试
     *
     * @return
     */publicbooleanreadProcStatus(){try{BufferedReader localBufferedReader =newBufferedReader(newFileReader("/proc/"+Process.myPid()+"/status"));String tracerPid ="";for(;;){String str = localBufferedReader.readLine();if(str.contains("TracerPid")){
                    tracerPid = str.substring(str.indexOf(":")+1, str.length()).trim();break;}if(str ==null){break;}}
            localBufferedReader.close();if("0".equals(tracerPid))returnfalse;elsereturntrue;}catch(Exception fuck){returnfalse;}}/**
     * 获取当前进程名
     *
     * @return
     */publicStringgetCurrentProcessName(){FileInputStream fis =null;try{
            fis =newFileInputStream("/proc/self/cmdline");byte[] buffer =newbyte[256];// 修改长度为256,在做中大精简版时发现包名长度大于32读取到的包名会少字符,导致常驻进程下的初始化操作有问题int len =0;int b;while((b = fis.read())>0&& len < buffer.length){
                buffer[len++]=(byte) b;}if(len >0){String s =newString(buffer,0, len,"UTF-8");return s;}}catch(Exception e){}finally{if(fis !=null){try{
                    fis.close();}catch(Exception e){}}}returnnull;}}

调用实践

if(RootTool.checkDeviceDebuggable()|| RootTool.checkSuperuserApk()|| RootTool.isSuBusyEnable()|| RootTool.isSuEnable()||SecurityCheckUtil.getSingleInstance().isRoot){//根据需要进行风险提示等相关业务
     ToastUtils.showToast("您当前设备可能已root,请谨防安全风险!")}

封装建议(可忽略)

有兴趣的话,可以将下方这些图示方法

copy

RootTool

,这样调用时仅使用

RootTool

即可

CommandUtil

getProperty

反射方法

在这里插入图片描述

SecurityCheckUtil

root

核心方法

在这里插入图片描述


本文转载自: https://blog.csdn.net/qq_20451879/article/details/139174609
版权归原作者 远方那座山 所有, 如有侵权,请联系我们删除。

“安全风险 - 检测Android设备系统是否已Root”的评论:

还没有评论