一、环境概述
| 应用名称 | 两仪 |
|---|---|
| 应用版本 | 0.3.9-02201452 |
| 应用包名 | io.twoyi |
| 应用HASH | 7CB195B5FF5669BA99FAE2785C2C7F37 |
| 应用来源 | 酷安 |
| 测试设备 | pixel2-LineageOS 18.1(Android 11) |
二、两仪(twoyi)检测方案概述
1.1 基于进程的检测方案
1、两仪中所有进程号均是比较大的,不是像正常安卓那样init为1号进程
2、常规安卓中每一个应用都会有自己的uid,但是在两仪(以及其他的虚拟环境中),所有进程的uid都是宿主应用(即两仪)的uid
3、两仪中的所有进程的exe和cwd都指向本机系统的两仪目录,可以作为检测依据
1.2 基于设备信息的检测方案
1、两仪的设备名称为Twoyi
2、两仪的wifi列表中只有个名为twoyi的WiFi,其ip和网关也都是固定的
3、两仪环境中有一个包名位com.android.launcher3的应用,其是两仪的管理应用,和正常环境中的launcher3不同,可以作为特征
4、两仪中无法找到任何传感器,可以作为检测依据
5、两仪环境中存在/fake-libs目录,其中是libart.so,有一些修改的api,根目录下面还有rom.ini用于标记rom信息,可以作为检测依据
6、两仪中网络连接是靠wifi的,但是wifi是一个名为twoyi的固定的wifi,其属性也是固定的,可以以此为判断依据
三、检测详情及原理
1、传感器列表检测
检测原理:当前版本两仪环境中不存在任何传感器,一般来说真实环境不会出现这种情况
缺点:两仪容器更新后该方法可能失效,不过根据传感器可以改为其他检测方案
详情:
1、首先使用SensorManager sensorManager = SensorManager)context.getSystemService(Context.SENSOR_SERVICE);获取传感器服务
2、获取所有类型的传感器sensorManager.getSensorList(Sensor.TYPE_ALL);
3、下图为pixel2中获取到的所有传感器,在当前版本的两仪中获取结果为空

4、如果后续版本更新使得传感器可用,可以通过检测传感器变化频率来判定
检测效果:

2、UID检查
检测原理:在正常安卓环境中,每一个安装的应用的进程都有自己的uid,但是在虚拟环境中安装应用其进程的uid也只能是虚拟环境的uid(因为没有在系统层面安装新应用)
缺点:两仪容器后续更新可能(如替换输出或hook等)使该方法失效
详情:
1、通过执行ps -A -o pid,user 命令获取系统当前正在运行的pid和uid的映射
2、解析获得init进程的uid和当前进程的uid,两仪环境中具有root权限,可以正常获取initi进程的uid。正常环境中执行ps命令获取不到完整的进程信息,但是也说明大概率不是两仪环境
3、如果是两仪环境,init进程和当前进程的uid是一样的。如下图所示,所有进程的uid都是相同的

检测效果:

3、init进程pid检测
检测原理:正常环境中init进程的pid应该是1,而两仪则不是,两仪的init进程实际上是两仪应用的子进程
缺点:后续更新可能会被规避
详情:和uid检测方法类似,只不过只看init进程的pid
检测效果:

4、socket检测
检测原理:两仪环境与主机环境的通信方式采用本地socket的方式进行,如果应用在两仪环境中则可以顺利连接该socket,不过如果在主机中则会因为权限问题无法连接socket
缺点:socket名称容易修改造成检测失效
详情:
1、在两仪源码的app\src\main\java\io\twoyi\TwoyiSocketServer.java中开启了一个名为TWOYI_SOCK的本地socket服务用于通信

2、当本地系统装有两仪并且试图连接这个socket时,会因为权限问题而无法连接,如下图,这是因为自从android 5.1开始使用SElinux的显示,跨应用程序通信是无法使用本地socket的

3、在两仪环境中试图连接本地socket是可以正常通信的,因为都是在同一个应用程序下,如下图所示

检测效果:

5、进程信息检测
检测原理:/proc/pid下存放着系统中进程的信息,其中一些软连接指向了当前进程的执行路径和工作目录,两仪中的进程软连接指向的是主机系统路径
缺点:两仪容器后续更新可能修复这个问题
详情:
1、使用ls -lh命令查看某个进程下的目录可以看到cwd和exe都指向了主机系统的真实路径

2、通过检测当前应用程序的cwd和exe指向的路径即可判断。正常情况下,cwd应该指向根目录,而exe应该指向/system/bin/app_process64,如下图

3、所以查看当前应用的进程cwd和exe指向路径是否包含主机中两仪的data目录即可判断
检测效果:

6、特征目录检查
检测原理:两仪的rom中有一个rom.ini用于标记rom属性,通过检测该文件可以判断当前环境是否在两仪中
缺点:会有一定概率误报,且容易被规避
详情:
1、两仪的rom根目录下存在一个rom.ini用于标识rom信息,其内容如下

2、从两仪的源码中可以看到两仪应用的一部分日志来自于rom.ini


3、如果当前系统的根目录下具有rom.ini文件,并且文件内容与两仪的rom.ini内容结构相同,则可以判定其位于两仪环境下
检测效果:

7、特征活动检测
检测原理:通过检测两仪环境中特有的互动(例如应用导入活动)来检测当前环境是否是两仪环境
缺点:更改名称较为容易
详情:
1、两仪环境中的管理器应用(也就是两仪的设置)实际上是launcher3应用,其包含了导入应用、发送日志、捐赠、关于四个页面

2、由于正常安卓环境中的launcher3应用为系统应用,不可能出现这四个活动,所以可以通过检测应用所拥有的活动的方式进行识别
3、使用aapt工具解析apk中的am文件即可获取到所有的活动信息。所以可以使用ndk的交叉编译工具编译一份在安卓中可以使用的aapt工具以便解析am文件,如下图所示

4、解析aapt工具的输出结果即可获得任意给定应用的所有活动。除此之外还可以获取权限等信息。下图为在主机环境下获取到的launcher3应用的所有活动

5、两仪环境中的管理器应用主页面为com.android.launcher3.Launcher,检测launcher3是否包含该页面即可检测是否在两仪环境中
在新版本安卓中,普通目录是没有权限执行二进制文件的,为了让二进制文件释放到具有执行权限的目录下,需要通过将二进制文件打包成lib的方式(修改命名,注册库目录),将具有执行权限的二进制文件植入到应用目录中。也就是在安装时,将二进制文件释放到具有执行权限的lib目录中。(此时应有一个其他的可以被加载的正常的lib在程序中,因为经测试程序中所有的lib都未被加载,则其lib目录将被删除)
检测效果:

8、特征wifi检测
检测原理:两仪中有一个固定的wifi用于网络连接,可以通过匹配该wifi的信息来检测
缺点:wifi未连接时无法检测,容易被修改
详情:
1、通过WifiManager wifiManager = (WifiManager) this.context.getSystemService(Context.<em>WIFI_SERVICE</em>);来获取WiFi服务
2、使用wifiManager.getDhcpInfo()和wifiManager.getConnectionInfo()函数获取wifi信息和hdcp信息
3、获取当前连接wifi的ssid、NetworkId、IpAddress、gateway、netmask信息。在两仪环境中,其连接的wifi名称固定为twoyi,而获取到的其他信息例如网络id、ip地址、掩码等都是0,如图,一般真实环境中不会出现这种情况

检测效果:

9、设备属性检查
检测原理:通过检测两仪系统的prop配置信息来进行检测
缺点:容易被修改,小概率产生误报
详情:
1、通过getprop命令可以看到两仪系统内的所有prop字段,如下图

2、找到具有两仪或虚拟环境特征的字段作为特征字段进行匹配,这里选择的是ro.boot.hardware、ro.bootloader、ro.build.characteristics、ro.build.tags、ro.build.flavor、ro.build.product、ro.product.device、ro.product.model和ro.product.name
3、获取当前系统的上述prop信息,按照如下图所示值进行匹配

4、随着两仪版本的升级可能会改变部分值,这时需要进行更新
检测效果:

其他问题
1、在两仪中开发应用无法正常输出日志
2、在两仪中无法正常调试应用
四、参考文章
Android模拟器检测及对抗方法_mb60f65faac1de5的技术博客_51CTO博客
[原创]检测Android虚拟机的方法和代码实现-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com
How can I detect when an Android application is running in the emulator? – Stack Overflow
Android Emulator Detection | VerSprite

