侧边栏壁纸
  • 累计撰写 38 篇文章
  • 累计创建 23 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

【Android】设备指纹检测与Hook框架检测(施工中)

Fup1p1
2023-10-09 / 0 评论 / 0 点赞 / 1,013 阅读 / 35,583 字 / 正在检测是否收录...

ROOT检测

Java层检测

遍历文件名检测

	public class checkroot {
    	static String [] paths={
            "/system/app/Superuser.apk",
            "/sbin/su", "/sbin/magisk32",
            "/system/bin/su", "/system/xbin/su",
            "/data/local/xbin/su", "/data/local/bin/su",
            "/system/sd/xbin/su","/sbin/magisk64",
            "/system/bin/failsafe/su", "/data/local/su",
            "/su/bin/su","/data/adb/magisk",
            "/sbin/magisk","/sbin/magiskinit",
            "/sbin/magiskpolicy","/sbin/riru.prop","/bin/magisk","/bin/magiskpolicy",
            "/sbin/supolicy","/data/user/0/com.topjohnwu.magisk",
            "system/lib/libriru_edxp.so.s","system/lib/libriru_edxp.so",
            "system/lib/libsandhook.edxp.so","system/lib64/libriru_edxp.so",
            "system/lib/libsandhook.edxp.so.s","system/lib64/libriru_edxp.so",
            "system/lib64/libsandhook.edxp.so.s","lib/armeabi-v7a/libriru.so",
            "lib/armeabi-v7a/libriruhide.so","lib/armeabi-v7a/libriruloader.so",
            "lib/arm64-v8a/libriru.so","lib/arm64-v8a/libriruhide.so",
            "lib/arm64-v8a/libriruloader.so","lib/arm64-v8a/librirud.so",
            "/data/adb/modules/riru-core/allow_install_app",
            "/data/misc/riru/api_version","/data/misc/riru/version_code",
            "/data/misc/riru/version_name",
    };
    private static boolean checkrootfile(){
        //系统调用的过程中最终会调用一个方法,faccessat()
        for(String path:paths){
            boolean flag=new File(path).exists();
            if(flag) Log.i("Fup1p1:","Detected Root!!!(Java) :"+path);
        }
        return false;
    }
}

对抗思路

对于简单的直接hook,然后返回false即可。

因为其检测的文件名可能有很多,而且可能会对字符串进行加密,那么我们有什么办法去知道它检测了哪些文件名。

对于 File.exists() 方法,它的目的是检查某个文件是否存在。在 Linux 层面,判断文件是否存在常常是通过 access 或 faccessat 系统调用来完成的。access 用于确定调用进程是否有权限访问文件的路径,而不是实际打开它。
使用strace工具(一开始没有编译进去,是通过termux 的pkg进行下载的)

image

image-1696560930001
strace 显示的是真实的系统调用,可见libc.so最后调用内核的函数是faccessat。
我使用frida hook libc时,hook faccessat和access都行,也都能打印出文件路径。

抽空会添加aosp源码的分析,

function hook_libc(){

   var access_addr = Process.findModuleByName("libc.so").findExportByName("access");
   var access_create_addr = Module.findExportByName("libc.so", "faccessat");
   console.log("pthread_create_addr =>", access_create_addr);
   Interceptor.attach(access_create_addr, {
       onEnter: function(args){
        console.log("onEnter");
        console.log("faccessat args =>", args[0], args[1].readCString(), args[2], args[4]);
       },onLeave: function(retval){
           console.log(retval);
       }
   })

   Interceptor.attach(access_addr, {
    onEnter: function(args){
     console.log("onEnter");
     console.log("access args =>", args[0].readCString(), args[1], args[2], args[4]);
    },onLeave: function(retval){
        console.log(retval);
    }
})
}
hook_libc();

通过执行which su命令

private static boolean checkroot2(){
        //保存命令行执行后的结果
        Process process=null;
            try {
                process=Runtime.getRuntime().exec("which su");
                //process=Runtime.getRuntime().exec(new String[]{"which","su"});//不能将命令和参数放在一起,即不能 "which su",这样会被看做一个整体命令,会找不到   效果同上
                BufferedReader inp=new BufferedReader(new InputStreamReader(process.getInputStream()));
                StringBuilder output=new StringBuilder();
                String line=inp.readLine();
                inp.close();
                if(line!=null){
                    Log.i("Fup1p1", "checkroot2: "+ line);
                    return true;
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(process!=null){
                    process.destroy();
                }
        }
        return false;
    };

通过检查Build类中信息

当Android操作系统的源代码进行编译时,它会使用不同的密钥来签名。
这些密钥分为“test-keys”和“release-keys”。
通常,官方的、经过OEM验证的固件/ROM都是使用“release-keys”进行签名的,许多自定义ROM或某些root工具可能会使用“test-keys”进行签名。这是因为它们是基于Android Open Source Project (AOSP)编译的,并使用默认的test-keys进行签名。

如果你编译过Android就知道一开始可以选择系统的版本。userdebug版本则是包含了额外的调试和日志记录功能的版本。通常,这个版本是为开发者和测试人员提供的。

如果一个设备运行的是userdebug版本,那么该设备可能已经被root或者至少更容易被root。
如果想要查看build类的信息,可以在system目录下以root权限执行 cat build.prop 或者 getprop
image-1696562036188

    private static boolean checkroot1(){
        String buildtag= Build.TAGS;
        String buildfinger=Build.FINGERPRINT;
        if(buildtag=="test-keys"||buildfinger.contains("userdebug"))
            return true;
        Log.i("Fup1p1:","build.TAGS:"+buildtag+" build.FINGERPRINT: "+buildfinger);
        return false;
    }

so层检测

extern "C" JNIEXPORT jboolean JNICALL Java_com_anti_root_checkroot_nativeDetect(JNIEnv* env,
                                                                                  jobject /* this */clazz,jobjectArray paths){

    int count=env->GetArrayLength(paths);
    bool flag=false;
    bool ret=false;
    for(int i=0;i<count;i++){
        auto j_filepath=env->GetObjectArrayElement(paths,i);
        auto filepath=env->GetStringUTFChars(static_cast<jstring>(j_filepath), 0);
        //LOGI("%s",filepath);
        ret=File::NativeDetected(filepath,true);
        if(ret)flag=true;
    }
    return flag;
}

bool File::NativeDetected(const char * path,bool usesyscall){
    if(usesyscall){//使用系统调用的方式,查找文件是否存在
        long ret= syscall(SYS_faccessat,AT_FDCWD,path,0);
        if(ret==0){
            LOGI("JNI syscall:%s",path);
            return true;
        }else{
            return false;
        }
        
    }else{
        struct stat buf{};
        //使用linux内核提供的方法
        if(access(path,F_OK)==0){
            LOGI("JNI access :%s",path);
            return true;
        };
        if(stat(path,&buf)==0){//linux 文件属性结构体
            LOGI("JNI stat :%s",path);
            return true;
        }
        if(fstat(open(path,O_PATH),&buf)==0){
            LOGI("JNI fstat :%s",path);
            return true;
        }
    };
    return false;
}

Xposed检测

Java层检测

通过遍历内存去查找敏感类(Android 9+)

参考珍惜大佬的博客
blog

主要通过系统的API(VMDebug.java中的getInstancesOfClasses)去获得获取ClassLoader所有的实例,包括其子类的实例,然后遍历这些ClassLoader,通过Class.forName去查看特定的类是否存在。

public class chooseutils {
    private static final Method startMethodTracingMethod;
    private static final Method stopMethodTracingMethod;
    private static final Method getMethodTracingModeMethod;
    private static final Method getRuntimeStatMethod;
    private static final Method getRuntimeStatsMethod;
    private static final Method countInstancesOfClassMethod;
    private static final Method countInstancesOfClassesMethod;

    private static  Method getInstancesOfClassesMethod ;

    static {
        try {
            Class<?> c = Class.forName("dalvik.system.VMDebug");
            //启动方法跟踪器  Integer.TYPE 和 int.class 是等价的
            startMethodTracingMethod = c.getDeclaredMethod("startMethodTracing", String.class, Integer.TYPE, Integer.TYPE, Boolean.TYPE, Integer.TYPE);
            //停止方法跟踪器
            stopMethodTracingMethod = c.getDeclaredMethod("stopMethodTracing");
            //获取方法跟踪器状态
            getMethodTracingModeMethod = c.getDeclaredMethod("getMethodTracingMode");
            getRuntimeStatMethod = c.getDeclaredMethod("getRuntimeStat", String.class);
            getRuntimeStatsMethod = c.getDeclaredMethod("getRuntimeStats");
            countInstancesOfClassMethod = c.getDeclaredMethod("countInstancesOfClass",Class.class, Boolean.TYPE);
            countInstancesOfClassesMethod = c.getDeclaredMethod("countInstancesOfClasses", Class[].class, Boolean.TYPE);

            if(android.os.Build.VERSION.SDK_INT>=28) {
                getInstancesOfClassesMethod = c.getDeclaredMethod("getInstancesOfClasses", Class[].class, Boolean.TYPE);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据Class获取当前进程全部的实例
     *
     * @param clazz 需要查找的Class
     * @return 当前进程的全部实例。
     */
    @TargetApi(28)
    public static<T> ArrayList<T> choose(Class<T> clazz) {
        return choose(clazz, false);
    }

    /**
     * 根据Class获取当前进程全部的实例
     *
     * @param clazz      需要查找的Class
     * @param assignable 是否包含子类的实例
     * @return 当前进程的全部实例。
     */
    @TargetApi(28)
    public static synchronized <T>  ArrayList<T> choose(Class<T> clazz, boolean assignable) {
        ArrayList<T> resut = null;
        try {
            //从类加载器  ClassLoader.class  中查找他的子实例
            Object[][] instancesOfClasses = getInstancesOfClasses(new Class[]{clazz}, assignable);
            if (instancesOfClasses != null) {
                resut = new ArrayList<>();
                for (Object[] instancesOfClass : instancesOfClasses) {
                    List<T> objects = (List<T>)Arrays.asList(instancesOfClass);
                    resut.addAll(objects);
                }
            }
        } catch (Throwable e) {
            Log.e("Fup1p1","ChooseUtils choose error ", e);
            e.printStackTrace();
        }
        return resut;
    }

    /**
     *
     * @param classes   ClassLoader 类对象
     * @param assignable 是否获取所有子类的实例
     * @return
     * @throws Exception
     */
    @TargetApi(28)
    private static Object[][] getInstancesOfClasses(Class<?>[] classes, boolean assignable)
            throws Exception {
        return (Object[][]) getInstancesOfClassesMethod.invoke(null, classes, assignable);
    }




    //下面是 VMdebug.java 类其他的方法调用
    public static void startMethodTracing(String filename, int bufferSize, int flags,
                                          boolean samplingEnabled, int intervalUs) throws Exception {
        startMethodTracingMethod.invoke(null, filename, bufferSize, flags, samplingEnabled,
                intervalUs);
    }

    public static void stopMethodTracing() throws Exception {
        stopMethodTracingMethod.invoke(null);
    }

    public static int getMethodTracingMode() throws Exception {
        return (int) getMethodTracingModeMethod.invoke(null);
    }

    /**
     *  String gc_count = VMDebug.getRuntimeStat("art.gc.gc-count");
     *  String gc_time = VMDebug.getRuntimeStat("art.gc.gc-time");
     *  String bytes_allocated = VMDebug.getRuntimeStat("art.gc.bytes-allocated");
     *  String bytes_freed = VMDebug.getRuntimeStat("art.gc.bytes-freed");
     *  String blocking_gc_count = VMDebug.getRuntimeStat("art.gc.blocking-gc-count");
     *  String blocking_gc_time = VMDebug.getRuntimeStat("art.gc.blocking-gc-time");
     *  String gc_count_rate_histogram = VMDebug.getRuntimeStat("art.gc.gc-count-rate-histogram");
     *  String blocking_gc_count_rate_histogram =VMDebug.getRuntimeStat("art.gc.gc-count-rate-histogram");
     */
    public static String getRuntimeStat(String statName) throws Exception {
        return (String) getRuntimeStatMethod.invoke(null, statName);
    }

    /**
     * 获取当前进程的状态信息
     */
    public static Map<String, String> getRuntimeStats() throws Exception {
        return (Map<String, String>) getRuntimeStatsMethod.invoke(null);
    }

    public static long countInstancesofClass(Class<?> c, boolean assignable) throws Exception {
        return (long) countInstancesOfClassMethod.invoke(null, new Object[]{c, assignable});
    }

    public static long[] countInstancesofClasses(Class<?>[] classes, boolean assignable)
            throws Exception {
        return (long[]) countInstancesOfClassesMethod.invoke(
                null, new Object[]{classes, assignable});
    }

}
public static boolean check(){
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                ArrayList<ClassLoader>choose=chooseutils.choose(ClassLoader.class,true);//true的话,会获取ClassLoader所有的实例  包含其子类
                Log.i("Fup1p1","对象数量"+choose.size());
                for(ClassLoader classLoader:choose){
                    Class <?> clazz=null;

                    try {
                        clazz=Class.forName("de.robv.android.xposed.XposedBridge",false,classLoader);
                        /**
                        de.robv.android.xposed.XposedHelpers
                        de.robv.android.xposed.XposedBridge
                        xposed 框架加载 jar包的类加载器 PathClassLoader  直接通过 context 获取即可
                        edXposed 框架加载 jar包的类加载器 InMemoryDexClassLoader 需要通过内存漫游方式获取
                        **/
                        if(clazz!=null){
                            Log.i("Fup1p1","类加载器: "+classLoader+"   类:"+clazz);

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        return false;
    }

检测lsposed结果

类加载器: dalvik.system.InMemoryDexClassLoader[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433538928]]"],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /product/lib64]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: LspModuleClassLoader[module=/data/app/~~hNYaAHiorVwigr4njmWJlA==/com.hhvvg.anytext-ax_F5ZvkSinxbbrD3qNZhw==/base.apk, J[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433428272]]"],nativeLibraryDirectories=[/data/app/~~hNYaAHiorVwigr4njmWJlA==/com.hhvvg.anytext-ax_F5ZvkSinxbbrD3qNZhw==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64, /product/lib64]]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: LspModuleClassLoader[module=/data/app/~~9fTDxZuTlm-EjiD5_kw-cw==/name.caiyao.sporteditor-tPqHFcfm1ac1n3jBaXSiXA==/base.apk, J[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433424912]]"],nativeLibraryDirectories=[/data/app/~~9fTDxZuTlm-EjiD5_kw-cw==/name.caiyao.sporteditor-tPqHFcfm1ac1n3jBaXSiXA==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64, /product/lib64]]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: LspModuleClassLoader[module=/data/app/~~rmNIJCpJAn8sTQ-wfzxxAA==/com.gqghj.vb2345qw-VmGVuvSAx7-uecDIFTmhGQ==/base.apk, J[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433429840, 486433424016]]"],nativeLibraryDirectories=[/data/app/~~rmNIJCpJAn8sTQ-wfzxxAA==/com.gqghj.vb2345qw-VmGVuvSAx7-uecDIFTmhGQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64, /product/lib64]]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: LspModuleClassLoader[module=/data/app/~~e0w5JQTXcWygOaNPsJf4ng==/com.example.xposed01-1JodEz8RnWM0F8FLV_lavw==/base.apk, J[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433419984, 486433427824, 486433422000]]"],nativeLibraryDirectories=[/data/app/~~e0w5JQTXcWygOaNPsJf4ng==/com.example.xposed01-1JodEz8RnWM0F8FLV_lavw==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64, /product/lib64]]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: dalvik.system.InMemoryDexClassLoader[DexPathList[[dex file "InMemoryDexFile[cookie=[486970569168, 486433590224]]"],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /product/lib64]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: dalvik.system.InMemoryDexClassLoader[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433433200]]"],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /product/lib64]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: dalvik.system.InMemoryDexClassLoader[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433429616]]"],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /product/lib64]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: dalvik.system.InMemoryDexClassLoader[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433442160]]"],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /product/lib64]]]   类:class de.robv.android.xposed.XposedBridge
类加载器: dalvik.system.InMemoryDexClassLoader[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 486433444176]]"],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /product/lib64]]]   类:class de.robv.android.xposed.XposedBridge

proc/self/maps 检测

就是通过打开文件,然后扫描关键字,通过扫描maps目录,去检查当前进程的内存映射关系,。

public class xpcheck {
    public static boolean checkmaps(){
        Boolean retval=false;
        try {
            int pid=android.os.Process.myPid();
            FileInputStream file1=new FileInputStream("/proc/self/maps");
            FileInputStream file2=new FileInputStream("/proc/self/smaps");
            FileInputStream file3=new FileInputStream("/proc/"+pid+"/maps");
            List<InputStream> list=new ArrayList<>();
            list.add(file1);
            list.add(file2);
            list.add(file3);
            for(int i=0;i< list.size();i++){
                InputStreamReader inputStreamReader=new InputStreamReader(list.get(i), StandardCharsets.UTF_8);
                BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
                while(true){
                    String line= bufferedReader.readLine();
                    if(line==null)break;

                    if(line.contains("Hook")||line.contains("zygisk")||line.contains("magisk")||line.contains("edxp")||line.contains("lsp")){
                        Log.i("Fup1p1","CheckMaps : "+line);
                        retval=true;
                    }
                }
                bufferedReader.close();
                inputStreamReader.close();
            }
            file1.close();
            file2.close();
            file3.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return retval;
    }

通过调用栈去检查

当一个方法被Xposed钩子时,调用堆栈可能会显示一些与Xposed相关的类和方法。因此,一个常见的检测Xposed是否在运行的方法是检查调用堆栈。xposed和一些模块的开发者知道这种检测方式,因此他们可能会使用一些技巧来避免被检测,比如修改或隐藏相关的类和方法。我实际使用lsposed测试是检测不到的。

    public static boolean checkstacktrace(){
        StackTraceElement[] elements=Thread.currentThread().getStackTrace();
        for(StackTraceElement element:elements){
//            Log.i("Fup1p1","checkstacktrace: "+element.getClassName());
            if(element.getClassName().contains("xposed")||element.getClassName().contains("EdHooker")){
                Log.i("Fup1p1","checkstacktrace :"+element.getClassName());
                return true;
            }
        };
        return false;
    };

so层检测

proc/self/maps 检测,改为JNI层

extern "C" JNIEXPORT jboolean JNICALL Java_com_anti_utils_xpcheck_nativecheckmaps(JNIEnv* env,
                                                                            jobject /* this */clazz){
    jboolean flag=false;
    jboolean ret=false;
    string pid=std::to_string(getpid());
    string file1="/proc/"+pid+"/maps";
    string file2="/proc/self/maps";
    string file3="/proc/self/smaps";

    std::vector<string>list{file1,file2,file3};

    for(int i=0;i<list.size();i++){
        flag=File::getmaps(const_cast<char *>(list.at(i).c_str()));;//要转成char *
        if(flag){
            ret=flag;
        }
    }

    return ret;
}

bool File::getmaps(char * path){
    b