24、模拟器检测绕过:Build特征检测、传感器检测、Frida Hook绕过

模拟器检测,这几乎是每个Android逆向工程师都会遇到的坎。说实话,我早年刚入行时,第一次碰到这玩意儿还挺懵的——明明代码逻辑都分析清楚了,一跑就闪退,后来才发现是模拟器检测在作祟。今天咱们就把这块硬骨头啃下来。

模拟器检测说白了,就是App在判断自己是不是跑在真机上。如果是模拟器,它就拒绝执行核心逻辑,或者直接退出。常见的检测手段分三类:Build特征、传感器特征,还有针对Frida的Hook检测。咱们一个一个来拆解。

24.1 Build特征检测与绕过

这是最基础也是最常见的检测方式。App会读取系统属性,比如ro.build.fingerprintro.product.modelro.hardware等。模拟器通常会有一些固定的特征值,比如genericsdk_phoneAndroid SDK built for x86

我个人习惯先拿一个模拟器跑一下,看看这些值长什么样。比如在Genymotion上,ro.product.manufacturer往往是unknown,而真机通常是Xiaomisamsung之类的。

核心思路:找到检测点,然后Hook掉对应的系统调用,返回伪造的真机值。

举个例子,App可能会这样检测:

String model = Build.MODEL;
if (model.contains("sdk") || model.contains("generic")) {
    // 检测到模拟器,退出
    System.exit(0);
}

绕过方法很简单,用Frida Hook掉Build类:

Java.perform(function() {
    var Build = Java.use("android.os.Build");
    Build.MODEL.value = "Pixel 6";
    Build.MANUFACTURER.value = "Google";
    Build.FINGERPRINT.value = "google/raven/raven:13/TQ1A.230205.002/12345678:user/release-keys";
    Build.HARDWARE.value = "raviole";
    Build.PRODUCT.value = "raven";
    Build.DEVICE.value = "raven";
    Build.BOARD.value = "raviole";
    Build.BRAND.value = "google";
});

嗯,这里要注意一点:有些App会通过反射读取Build类的字段,或者直接读取系统属性文件/system/build.prop。对于后者,我们还需要Hook SystemProperties.get()方法。

避坑指南:我曾经遇到过一个App,它不光检查Build特征,还检查/proc/version里的内核信息。模拟器的内核版本通常带有generic字样,真机则是具体的内核版本号。所以记得把内核信息也一并伪造了。

24.2 传感器检测与绕过

这个就有点意思了。模拟器通常没有真实的物理传感器,或者传感器返回的数据是固定的、不真实的。App会检查传感器列表,或者读取传感器数据的变化模式。

为什么会这样?因为真机上的传感器数据是连续变化的,比如加速度计、陀螺仪。而模拟器要么没有这些传感器,要么返回的数据是恒定值。

常见的检测点包括:

  • 传感器列表检查:检查是否存在TYPE_ACCELEROMETERTYPE_GYROSCOPE等传感器
  • 传感器数据变化:连续读取几次,看数值是否变化
  • 传感器精度:真机的传感器精度通常较高,模拟器可能返回0或固定值

绕过方法也不复杂。我们可以Hook掉SensorManager的相关方法:

Java.perform(function() {
    var SensorManager = Java.use("android.hardware.SensorManager");
    var Sensor = Java.use("android.hardware.Sensor");
    
    // Hook getDefaultSensor,返回一个伪造的传感器
    SensorManager.getDefaultSensor.overload('int').implementation = function(type) {
        var result = this.getDefaultSensor(type);
        if (result == null) {
            // 如果模拟器没有这个传感器,我们伪造一个
            console.log("伪造传感器: " + type);
            // 这里可以返回一个自定义的Sensor对象
        }
        return result;
    };
    
    // Hook传感器事件监听器,伪造数据变化
    var SensorEventListener = Java.use("android.hardware.SensorEventListener");
    SensorEventListener.onSensorChanged.implementation = function(event) {
        // 修改事件数据,模拟真实传感器变化
        event.values[0] = Math.random() * 10; // 伪造加速度计X轴
        event.values[1] = Math.random() * 10; // 伪造Y轴
        event.values[2] = Math.random() * 10; // 伪造Z轴
        this.onSensorChanged(event);
    };
});

不过说实话,传感器检测这块水挺深的。我记得有一次,一个金融类App不光检查传感器是否存在,还检查传感器数据的统计特征——比如方差、标准差。模拟器的数据太规律了,方差很小,真机的数据方差大得多。这种情况下,光伪造数值还不够,还得让数据看起来"自然"。

警告:有些App会同时检查多个传感器,并且要求传感器数据之间有一定的物理关联性。比如加速度计和陀螺仪的数据应该符合运动学规律。如果只伪造其中一个,另一个没动,很容易露馅。

24.3 Frida Hook检测与绕过

这个就比较高级了。App会检测Frida的运行痕迹,比如检查端口、检查进程名、检查Dex加载等。说白了,就是App在反制我们的Hook工具。

常见的Frida检测手段:

检测方式 原理 绕过方法
端口检测 检查27042端口是否被占用 修改Frida默认端口
进程名检测 检查/proc/self/status或/proc/self/cmdline 使用Frida的spawn模式,或修改进程名
Dex加载检测 检查DexClassLoader中是否有frida相关的类 使用Frida的Gadget模式,或编译自定义Frida
线程名检测 检查线程名是否包含"frida"、"gmain"等 使用Frida的隐藏模式
文件检测 检查/data/local/tmp/下是否有frida-server 重命名frida-server,或使用Gadget模式

我个人最常用的绕过方式是使用Frida的Gadget模式。把frida-gadget.so注入到App的lib目录下,然后通过配置文件控制。这样就没有独立的frida-server进程了,端口检测和进程名检测直接失效。

配置示例:

// libfrida-gadget.config.so
{
  "interaction": {
    "type": "listen",
    "address": "127.0.0.1:27042"
  },
  "reconnect": true,
  "timeout": 5000
}

然后启动App时,Frida Gadget会自动加载,我们再用frida -H 127.0.0.1:27042连接即可。

核心要点:Frida检测绕过的本质是让App找不到Frida的痕迹。要么隐藏Frida,要么让Frida看起来像App的一部分。

还有一种更狠的检测方式——检查maps文件。App会读取/proc/self/maps,看看有没有加载frida-agent.so或者frida-gadget.so。对于这种情况,我建议使用Frida的objc模式或者stalker模式,但更直接的办法是编译一个自定义的Frida,把所有的特征字符串都改掉。

嗯,这里要提醒一下:有些App会做多层次的检测。比如先检测端口,再检测进程名,再检测maps文件。如果只绕过了一层,后面还有坑等着你。所以我的习惯是,先静态分析App的so文件,把所有检测点都找出来,然后一次性全部Hook掉。

24.4 综合绕过策略

说了这么多,咱们来总结一下。模拟器检测绕过不是单一技巧,而是一套组合拳。我个人建议按以下步骤来:

  1. 静态分析:先反编译App,搜索关键词如"emulator"、"simulator"、"generic"、"sdk"等,找到所有检测点
  2. 动态调试:用Frida跟踪系统调用,看App到底调用了哪些API
  3. 分层绕过:先绕过Build特征,再绕过传感器,最后处理Frida检测
  4. 验证:绕过之后,确保App的核心功能正常运行,不要出现奇怪的崩溃

下面这张图展示了模拟器检测与绕过的整体流程:

模拟器检测与绕过流程 App检测层 Build特征检测 传感器检测 Frida Hook检测 绕过层(Frida Hook + 环境伪造) Hook Build类 伪造传感器数据 Gadget模式/隐藏Frida 成功绕过,App正常运行

最后说一句,模拟器检测绕过是个动态博弈的过程。App开发者会不断更新检测手段,我们也要不断更新绕过技巧。但万变不离其宗——只要理解了检测的原理,绕过就只是时间问题。

个人建议:平时多收集一些App的检测样本,建立自己的检测库。我每次遇到新的检测方式,都会记录下来,久而久之就成了一个很实用的参考手册。


公众号:蓝海资料掘金营,微信deep3321