Android模拟器检测及对抗方法

参考:http://zeng9t.com/tech/2019/04/30/Android%E6%A8%A1%E6%8B%9F%E5%99%A8%E6%A3%80%E6%B5%8B%E5%8F%8A%E5%AF%B9%E6%8A%97%E6%96%B9%E6%B3%95.html

 

前言

因为做项目的原因,对目前检测模拟器环境的方法大概有了一个了解,有业界方法也有学术界的方法。如何进行对抗的方法,在后续进行说明。 做项目就是边学边做的过程中不断学习,知识不可能学完,并且计算机相关的知识和技术变化迭代很快,需要用到的时候再学再扩展,保持一颗学习的心及学习方法很重要。

检测模拟器环境的不同用途

关于emulator环境的检测,业界的应用比如阿里等公司,防止一些利益集团进行淘宝恶意刷单、支付宝注册得红包等褥羊毛操作;还有比如手游防止玩家使用模拟器进行操作造成了游戏的不公平性。而恶意应用会利用模拟器检测技术来防止在emulator环境下被动态分析,因此会检测emulator环境,然后在emulator环境下触发不同的代码路径(通常是非恶意的代码逻辑)。

检测核心思想

利用emulator和真机的区别,当然这种区别是可检测的,相对易于实现的,根据具体需求来衡量使用什么样的方法是足够的,并且可扩展、可维护、对程序运行效率影响小。

常用的实用方法

TelephonyManager类

  • getLine1Number
  • getDeviceId
  • getSubscriberId
  • getVoiceMailNumber
  • getSimSerialNumber

Build信息

  • BRAND == generic
  • DEVICE == generic
  • HARDWARE == goldfish
  • PRODUCT == sdk
  • HOST == android-test
  • TAGS == test-keys

特征文件

  • /dev/socket/qemud
  • /dev/qemu_pipe
  • /system/lib/libc_malloc_debug_qemu.so
  • /sys/qemu_trace
  • /system/bin/qemu-prop

系统属性

  • ro.hardware == goldfish
  • ro.product.device == generic
  • ro.product.model == sdk
  • ro.product.name == sdk

基于差异化信息

  • /proc/cpuinfo真机CPU一般都是基于ARM,模拟器一般为Intel或AMD
  • 模拟器通话记录、联系人、短信等通常为空
  • 检查是否存在Dev Tools等模拟器上特有的应用程序

基于硬件数据

  • 可以多次检测单个传感器数据,但部分传感器模拟器也可以模拟实现。检测传感器数量,模拟器传感器数量一般无法超过10,而一般手机传感器数量大于20;多次对传感器数据取值观察是否变化等方法来判断。常见传感器有:陀螺仪、加速度计、[心率传感器]、[光线传感器]、[压力传感器]、[计步器]、重力传感器、旋转矢量传感器等,其中括号中的传感器数据一般模拟器较难进行模拟
  • 检查电池的电压、电量、温度等是否实时变化
  • 触摸面积:真机变化且不为0,模拟器为0

基于应用层行为数据

  • 例如安装应用数量,是否具备常见应用,是否有联系人等。对设备的行为模式进行统计分析,作为风险设备画像的参考维度。

基于cache行为

  • ARM采用的哈弗架构将指令存储跟数据存储分开,ARM的一级缓存分为I-Cache(指令缓存)与D-Cache(数据缓存),而Simpled X86只有一块缓存,而模拟器可以看做是Simpled-x86架构。如果将一段代码可执行代码动态映射到内存,在执行的时候,Simpled-X86架构上动态修改这部分代码后,指令cache会被同步修改,而ARM修改的却是D-Cache中的内容,此时I-Cache中的指令并不一定被更新,因此程序就会在ARM与Simpled-x86上有不同的表现,根据计算结果便可以知道究竟是x86还是在ARM平台上运行。
  • 无论是x86还是ARM,只要是静态编译的程序,都没有修改代码段的权限,所以,首先需要将上面的汇编代码翻译成可执行文件,再需要申请一块内存,将可执行代码段映射过去,执行。 以下实现代码是测试代码的核心,主要就是将地址e2844001的指令add r4, r4, #1,在运行中动态替换为e2877001的指令add r7, r7, #1,这里目标是ARM-V7架构的,要注意它采用的是三级流水,PC值=当前程序执行位置+8。通过arm交叉编译链编译出的可执行代码如下:
8410:       e92d41f0        push    {r4, r5, r6, r7, r8, lr}
8414:       e3a07000        mov     r7, #0
8418:       e1a0800f        mov     r8, pc      // 本平台针对ARM7,三级流水  PC值=当前程序执行位置+8
841c:       e3a04000        mov     r4, #0
8420:       e2877001        add     r7, r7, #1
    ....
842c:       e1a0800f        mov     r8, pc
8430:       e248800c        sub     r8, r8, #12   // PC值=当前程序执行位置+8
8434:       e5885000        str     r5, [r8]
8438:       e354000a        cmp     r4, #10
843c:       aa000002        bge     844c <out>
.....

如果是在ARM上运行,e2844001处指令无法被覆盖,最终执行的是add r4,#1 ,而在x86平台上,执行的是add r7,#1 ,代码执行完毕, r0的值在模拟器上是1,而在真机上是10。之后,将上述可执行代码通过mmap,映射到内存并执行即可,具体做法如下,将可执行的二进制代码直接拷贝可执行代码区,去执行:

void (*asmcheck)(void);
int emulator_detect() {
        //可执行二进制代码
    char code[] =
        "\xF0\x41\x2D\xE9"
        "\x00\x70\xA0\xE3"
        "\x0F\x80\xA0\xE1"
        "\x00\x40\xA0\xE3"
        "\x01\x70\x87\xE2"
        "\x00\x50\x98\xE5"
        "\x01\x40\x84\xE2"
        ....
    // 映射一块可执行内存 PROT_EXEC
    void *exec = mmap(NULL, (size_t) getpagesize(), PROT_EXEC|PROT_WRITE|PROT_READ, MAP_ANONYMOUS | MAP_SHARED, -1, (off_t) 0);
    memcpy(exec, code, sizeof(code) + 1);
       //强制赋值到函数
    asmcheck = (void *) exec;
      //执行函数
    asmcheck();
    __asm __volatile (
    "mov %0,r0 \n"
    :"=r"(a)
    );
    munmap(exec, getpagesize());
    return a;
}

防止在真机上出现崩溃,最好单独开一个进程服务,利用Binder实现模拟器鉴别的查询。再结合其他检测方法做综合度量。

基于指令执行行为

  • 为了效率上的考虑,qemu在翻译执行ARM指令时并没有实时更新模拟的pc寄存器值,只会在一段代码翻译执行完之后再更新,而真机中pc寄存器是一直在更新的。可以设计CPU任务调度程序以检测模拟器。

对抗

针对大部分Java层检测方法和部分native层检测方法都可以被hook掉,可以考虑在native code中进行检测,并使用自定义API替换相应的系统API,防止被hook,面对一些特殊处理后的模拟器具有相对较好的检测效果。

说明

检测方法有很多,例如还有基于mac信息和蓝牙信息等,可以利用多种检测手段进行综合分析,但也要考虑到具体需求,不要过度检测以免影响应用运行效率。

检测示例

public static boolean isEmulatorAbsoluly() {
        if (Build.PRODUCT.contains("sdk") ||
                Build.PRODUCT.contains("sdk_x86") ||
                Build.PRODUCT.contains("sdk_google") ||
                Build.PRODUCT.contains("Andy") ||
                Build.PRODUCT.contains("Droid4X") ||
                Build.PRODUCT.contains("nox") ||
                Build.PRODUCT.contains("vbox86p")) {
            return true;
        }
        if (Build.MANUFACTURER.equals("Genymotion") ||
                Build.MANUFACTURER.contains("Andy") ||
                Build.MANUFACTURER.contains("nox") ||
                Build.MANUFACTURER.contains("TiantianVM")) {
            return true;
        }
        if (Build.BRAND.contains("Andy")) {
            return true;
        }
        if (Build.DEVICE.contains("Andy") ||
                Build.DEVICE.contains("Droid4X") ||
                Build.DEVICE.contains("nox") ||
                Build.DEVICE.contains("vbox86p")) {
            return true;
        }
        if (Build.MODEL.contains("Emulator") ||
                Build.MODEL.equals("google_sdk") ||
                Build.MODEL.contains("Droid4X") ||
                Build.MODEL.contains("TiantianVM") ||
                Build.MODEL.contains("Andy") ||
                Build.MODEL.equals("Android SDK built for x86_64") ||
                Build.MODEL.equals("Android SDK built for x86")) {
            return true;
        }
        if (Build.HARDWARE.equals("vbox86") ||
                Build.HARDWARE.contains("nox") ||
                Build.HARDWARE.contains("ttVM_x86")) {
            return true;
        }
        if (Build.FINGERPRINT.contains("generic/sdk/generic") ||
                Build.FINGERPRINT.contains("generic_x86/sdk_x86/generic_x86") ||
                Build.FINGERPRINT.contains("Andy") ||
                Build.FINGERPRINT.contains("ttVM_Hdragon") ||
                Build.FINGERPRINT.contains("generic/google_sdk/generic") ||
                Build.FINGERPRINT.contains("vbox86p") ||
                Build.FINGERPRINT.contains("generic/vbox86p/vbox86p")) {
            return true;
        }
        return false;
    }

  反检测

既然有检测方法,那么最常见的反检测方法可以自然地想到是针对这些检测方法,做hook,因为大部分检测方法在调用链上最终都是需要通过相应的API来获取相关的数据。有就Java层的hook也有native层的hook,Android上常见的Hook框架有Xposed和Frida等,Xposed对x86的兼容性较差,Frida的跨平台兼容性较好。部分字段是系统镜像自带属性,因此可通过修改AOSP源码自编译系统镜像再装入emulator。

Some Paper Resources

Morpheus-acsac2014

Evading Android Runtime Analysis via Sandbox Detection

Rethinking anti-emulation techniques for large-scale software deployment

posted @ 2021-04-25 21:30  狂客  阅读(2409)  评论(0编辑  收藏  举报