刚刚看了一些关于Detect Android Emulator的开源项目/文章/论文, 我看的这些其实都是13年14年提出的方法, 方法里大多是检测一些环境属性, 检查一些文件这样, 但实际上检测的思路并不局限于此. 有的是很直接了当去检测qemu, 而其它的方法则是旁敲侧击比如检测adb, 检测ptrace之类的. 思路也很灵活. 最后看到有提出通过利用QEMU这样的模拟CPU与物理CPU之间的实际差异(任务调度差异), 模拟传感器和物理传感器的差异, 缓存的差异等方法来检测. 相比检测环境属性, 检测效果会提升很多.
下面我就列出各个资料中所提出的一些方法/思路/代码供大家交流学习.
这些都是基于一些经验和特征来比对的属性, 这里的属性以及之后的一些文件呀属性啊之类的我就不再多作解释.
以下是对应的c++代码
这里我的描述可能并不准确, 因为并没有找到相关的资料. 我只能以自己的理解来解释一下:
SIGTRAP
是调试器设置断点时发生的信号, 在nexus5或一加手机等大多数手机都可以触发. SIGBUS
则是在一个总线错误, 指针也许访问了一个有效地址, 但总线会因为数据未对齐等原因无法使用, 在nexus4手机上可以触发. 而bkpt
则是arm的断点指令, 这是曾经qemu被提出来的一个issue, qemu会因为SIGSEGV
信号而崩溃, 作者想利用这个崩溃来检测qemu. 如果程序没有正常退出或被冻结, 那么就可以认定很可能是在模拟器里.
这个其实是用于检测当前操作到底是用户还是脚本在要求应用执行.
这个方法是用来检测调试, 判断是否有调试器连接.
这个方法是通过检查/proc/self/status
的TracerPid
项, 这个项在没有跟踪的时候默认为0, 当有程序在跟踪时会修改为对应的pid. 因此如果TracerPid
不等于0, 那么就可以认为是在模拟器环境.
这个方法是通过读取/proc/net/tcp
的信息来判断是否存在adb. 比如真机的的信息为0: 4604D20A:B512 A3D13AD8...
, 而模拟器上的对应信息就是0: 00000000:0016 00000000:0000
, 因为adb通常是反射到0.0.0.0
这个ip上, 虽然端口有可能改变, 但确实是可行的.
这个比较单纯了. 就是通过检测包名, 检测Taint
类来判断是否安装有TaintDroid
这个污点分析工具. 另外也还可以检测TaintDroid
的一些成员变量.
检测是否存在eth0
网卡.
手机上配备了各式各样的传感器, 但它们实质上都是基于从环境收集的信息输出值, 因此想要模拟传感器是非常具有挑战性的. 这些传感器为识别手机和模拟器提供了新的机会.
比如在论文Rage Against the Virtual Machine: Hindering Dynamic Analysis of Android Malware
中, 作者对Android模拟器的加速器进行测试, 作者发现Android模拟器上的传感器会在相同的时间间隔内(观测结果是0.8s, 标准偏差为0.003043)产生相同的值. 显然对于现实世界的传感器, 这是不可能的.
于是我们可以先注册一个传感器监听器, 如果注册失败, 就可能是在模拟器中(排除实际设备不支持传感器的可能性). 如果注册成功, 那么检查onSensorChanged
回调方法, 如果在连续调用这个方法的过程所观察到的传感器值或时间间隔相同, 那么就可以认定是在模拟器环境中.
出于性能优化的原因, QEMU在每次执行指令时都不会主动更新程序计数器(PC), 由于翻译指令在本地执行, 而增加PC需要额外的指令带来开销. 所以QEMU只在执行那些从线性执行过程里中断的指令(例如分支指令)时才会更新程序计数器. 这也就导致在执行一些基本块的期间如果发生了调度事件, 那么也没有办法恢复调度前的PC, 也是出于这个原因, QEMU仅在执行基本块后才发生调度事件, 绝不会执行的过程中发生.
如上图, 因为调度可能在任意时间发生, 所以在非模拟器环境下, 会观察到大量的调度点. 而在模拟器环境中, 只能看到特定的调度点.
因为QEMU会跟踪代码页的改动, 于是存在一种新颖的方法来检测QEMU--使用自修改代码(Self-Modifying Code, SMC)引起模拟器和实际设备之间的执行流变化.
ARM处理器包含有两个不同的缓冲Cache, 一个用于指令访问(I-Cache), 而另一个用于数据访问(D-Cache). 但如ARM这样的哈佛架构并不能保证I-Cache和D-Cache之间的一致性. 因此CPU有可能在新代码片已经写入主存后执行旧的代码片(也许是无效的).
这个问题可以通过强迫两个缓存一致得到解决, 这有两步:
在原生Android代码中, 可以使用cacheflush
函数, 该函数通过系统调用完成上述操作.
识别代码, 使用一个具有读写权限的内存, 其中包含两个不同函数f1和f2的代码, 这两个函数其实很简单, 只是单纯在一个全局字符串变量的末尾附加各自的函数名称, 这两个函数会在循环里交错执行, 这样就可以通过结果的字符串推断出函数调用序列.
如前所述, 我们调用cacheflush
来同步缓存. 在实际设备和模拟器上运行代码得到的结果是相同的--每次执行都会产生一致的函数调用序列.
接下来我们移除调用cacheflush
, 执行相同的操作. 那么在实际设备中, 我们每次运行都会观察到一个随机的函数调用序列, 这也如前所述的那样, 因为I-Cache可能包含一些旧指令, 每次调用的时候缓存都不同步所导致的.
而模拟器环境却不会发生这样的情况, 而且函数调用序列会跟之前没有移除cacheflush
时完全相同, 也就是每次函数调用前缓存都是一致的. 这是因为QEMU会跟踪代码页上的修改, 并确保生成的代码始终与内存中的目标指令匹配, 因此QEMU会放弃之前版本的代码翻译并重新生成新代码.
看到这里会不会已经觉得检测方法够多了. 可是我还只是看了13年14年的资料. 有关近几年的资料还未涉及.
最后我就把这些检测方法整合在一张思维导图(见附件)里供大家一览, 欢迎大家和我交流带带我
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-2-2 14:19
被kanxue编辑
,原因: