Android 应用多开对抗实践

白天午睡梦到些以前给某行业安全峰会写了材料,醒来后把记得的部分重新整理一下,分享出来给大家,尽量写得简洁明了。

未必准确,仅供参考,欢迎纠正补充

应用多开技术总结

系统级技术多开技术方案

发行版 APP

Android 多用户功能

OEM系统自带的"手机分身"、"应用双开",和 "Island/炼妖壶" 等各种 "Android for work" 产品

chroot

暂无发现 APP

多用户功能

多用户模式主要用到 UserManager 相关类,切换不同的用户,在不同的用户下运行 App,实现多开。最直观的例子是 Android 手机上的 多用户 功能, 手机分身 功能,以及 am switch-user 命令,这种简单粗暴的用法会将 Android 服务都运行一份,如果只用于应用多开,且不说资源消耗,切换用户是在麻烦。

在 Android 5.0 在基于多用户功能 添加了 Android for work 功能,可以在同一个桌面启动器下使用受限用户启动 APP,不再需要切换界面。同时将权限开发给了非系统应用。

chroot

UNIX 的 chroot 系统调用在 Android 上也能用,需要 root 权限。在本地挂载运行精简版系统镜像,使用远程桌面软件如 VNC 等访问本地多开的系统。尚未发现发行版 APP,可能在 ARM 服务器云手机中用到。

用户级技术多开技术方案

发行版 APP

VirtualApp

VirtualXposed, DualSpace

MultiDroid

LBE平行空间, Parallel Space

DroidPlugin

分身大师

Excelliance

双开助手, MultiAccount

其它

虚拟大师

在用户级的多开技术中,还可以在按设计用途划分出三类

“容器”:VirtualApp、MultiDroid

热更新/插件化:DroidPlugin、Excelliance

虚拟系统:虚拟大师

具体实现原理大家可以翻论坛里的 精品贴,这里不多描述。

值得一提的是,某云手机团队的 "虚拟大师" 产品,实现在用户态运行了一个精简版的 Android 系统镜像,在系统库中拦截了几乎所有系统调用,使用类似前文提到的 chroot 挂载系统镜像的方法运行,有兴趣的同学可以看一看。

拆招

反系统级应用多开仅多用户方案的多开,忽略 chroot /lxc

简单粗暴的代码// --- C++ ---

#include

bool isDualApp(){return 0 != getuid()/100000;}

// --- Java ---

import android.os.Process;

static boolean isDualApp(){return 0 != Process.myUid() / 100000;}

一行代码完事了?

完事了,真的完事了。

但是为什么?

Android 系统中,如果开启了多用户模式,会存在一个主用户和若干受限用户。

把 MIUI 的 "手机分身" 和 "应用双开" 功能都打开,可以看到有三用户,0、11 和 999,分别对应主用户、"手机分身" 和 "应用双开"

# --- adb shell ---

$ ls -al /data/user/

total 52

drwx--x--x 4 system system 4096 2019-09-05 11:49 .

drwxrwx--x 42 system system 4096 2019-04-22 20:32 ..

lrwxrwxrwx 1 root root 10 1970-08-23 18:57 0 -> /data/data

drwxrwx--x 221 system system 16384 2019-09-05 11:50 11

drwxrwx--x 13 system system 16384 2019-09-12 17:53 999

使用多用户模式实现的多开,在客户端中可以通过 Android SDK 的 UserManger 类判断当前运行 APP 的用户是否为主用户和受限用户

// android.os.UserManger.java

public boolean isAdminUser() {

return isUserAdmin(UserHandle.myUserId());

}

// ...

public boolean isPrimaryUser() {

UserInfo user = getUserInfo(UserHandle.myUserId());

return user != null && user.isPrimary();

}

顺着线索,找到 UserHandler 类

// android.os.UserHandle.java

@Deprecated

/**

* @hide A user id constant to indicate the "owner" user of the device

* @deprecated Consider using either {@link UserHandle#USER_SYSTEM} constant or

* check the target user's flag {@link android.content.pm.UserInfo#isAdmin}.

*/

@Deprecated

public static final @UserIdInt int USER_OWNER = 0;

// ...

public static final @UserIdInt int USER_SYSTEM = 0;

// ...

public static final int PER_USER_RANGE = 100000;

// ...

public static @UserIdInt int getUserId(int uid) {

if (MU_ENABLED) {

return uid / PER_USER_RANGE;

} else {

return UserHandle.USER_SYSTEM;

}

}

可看到通过 uid/100000 获得 Android 的 UserId,同时通过其它 final 字段和相关注释得知 OWNER/SYSTEM/ADMIN 的 UserId 是 0,因此我们可以通过 uid/100000 为 0 判断为主用户,非主用户直接判为多开即可。

验证

使用上文提到的 MIUI 中的 "应用双开" 功能,在进程中找到 UserId 999 运行的进程,因为第一列显示成了用户名,进 /proc/${PID}/status 查看进程 uid。uid / 100000 是 999,没毛病。

# --- adb shell ---

$ ps -ef | grep u999

u999_a118 14392 905 0 14:07:52 ? 00:00:01 com.miui.analytics

u999_system 19793 905 0 19:55:24 ? 00:00:00 com.android.keychain

shell 20408 13712 3 19:58:11 pts/0 00:00:00 grep u999

$ cat /proc/14392/status

Name: .miui.analytics

Umask: 0077

State: S (sleeping)

Tgid: 14392

Ngid: 0

Pid: 14392

PPid: 905

TracerPid: 0

Uid: 99910118 99910118 99910118 99910118

Gid: 99910118 99910118 99910118 99910118

可改进区分各种系统级双开/分身模式

反用户级应用多开

仍然是简单粗暴的代码

测试代码一

// --- C++ ---

#include

#include

#include

bool isDualApp(std::string dataDir) {

return 0 == access((dataDir + "/../").c_str(), R_OK);

}

测试代码二

// --- Java ---

import java.io.File;

boolean isDualApp(String dataDir){

return new File(dataDir + File.separator + "..").canRead();

}

dataDir 目录的父级目录理论上归属 system 用户或 root 用户,除非是系统应用,否则不能访问。

补充

为什么使用 dataDir 而不是 nativeLibraryDir 或 sourceDir 呢?

看 nativeLibraryDir 和 sourceDir 目录所在的位置,base.apk 权限 rw-r-r--r--任意用户读取,lib 目录权限 rwxr-xr-x 任意用户读取和执行

# ls -al /data/app/com.example.checksandbox-ElZnrZIb5m2rbBv_3K8nZQ\=\=/

total 2420

drwxr-xr-x 3 system system 4096 2019-09-24 16:15 .

drwxrwx--x 80 system system 12288 2019-09-24 16:15 ..

-rw-r--r-- 1 system system 2440234 2019-09-24 16:15 base.apk

drwxr-xr-x 3 system system 4096 2019-09-24 16:15 lib

而 dataDir 的目录权限 rwx------,私有不公开

# ls -al /data/user/0/com.example.checksandbox/

total 40

drwx------ 5 u0_a366 u0_a366 4096 2019-09-24 16:15 .

drwxrwx--x 271 system system 20480 2019-09-24 16:15 ..

drwxrws--x 2 u0_a366 u0_a366_cache 4096 2019-09-24 16:15 cache

drwxrws--x 2 u0_a366 u0_a366_cache 4096 2019-09-24 16:15 code_cache

drwxrwxr-x 5 u0_a366 u0_a366 4096 2019-09-24 16:15 lldb

因此,对于用户级多开软件来说,仅是实现多开功能,完全可以复用原本的 base.apk 和 lib,不需要在自己目录中释放一份,而 dataDir 目录却是必须要另外准备的。

验证

代码放 APP 里跑一遍看看

测试代码一结果

技术

发行产品

结果

DroidPlugin

分身大师

阴性

VirtualApp

VirtualXposed

阳性 +

VirtualApp

DualSpace

阳性 +

MultiDroid

LBE平行空间

阳性 +

Excelliance

双开助手

阳性 +

其它

虚拟大师

阳性 +

emmm,看来三六零在文件系统上的功能做得挺完善的,access 访问 dataDir 的父目录时返回了 -1 (errno: 13 Permission denied),其它的没什么问题

测试代码二结果

技术

发行产品

结果

DroidPlugin

分身大师

阳性 +

VirtualApp

VirtualXposed

阳性 +

VirtualApp

DualSpace

阳性 +

MultiDroid

LBE平行空间

阳性 +

Excelliance

双开助手

阳性 +

其它

虚拟大师

阳性 +

到底还是我高估了三六零。。。

威力加强版

上面给的两份代码容易被绕过,稍微换个姿势,获取到真实的路径,再利用父级目录进行检测。

graph LR;

A[dataDir的fd] --> B[fd反查真实路径]

B --> C[尝试访问真实路径的父目录]

由于睡懵了忘记了细节,这里用 Java 写了份 "伪" 代码,由于没能绕过 hook,多少会存在瑕疵,但是原本的 ASM 代码绕过各种 Hook,达到通杀

// --- Java ---

import android.support.annotation.RequiresApi;

import java.io.File;

import java.io.FileDescriptor;

import java.io.FileOutputStream;

import java.lang.reflect.Field;

import java.nio.file.Files;

import java.nio.file.Paths;

@RequiresApi(api = Build.VERSION_CODES.O)

boolean isDualAppEx(String dataDir) {

try {

String simpleName = "wtf_jack";

String testPath = dataDir + File.separator + simpleName;

FileOutputStream fos = new FileOutputStream(testPath);

FileDescriptor fileDescriptor = fos.getFD();

Field fid_descriptor = fileDescriptor.getClass().getDeclaredField("descriptor");

fid_descriptor.setAccessible(true);

// 获取到 fd

int fd = (Integer) fid_descriptor.get(fileDescriptor);

// fd 反查真实路径

String fdPath = String.format("/proc/self/fd/%d", fd);

String realPath = Files.readSymbolicLink(Paths.get(fdPath)).toString();

if (!realPath.substring(realPath.lastIndexOf(File.separator))

.equals(File.separator + simpleName)){

// 文件名被修改

return true;

}

// 尝试访问真实路径的父目录

String fatherDirPath = realPath.replace(simpleName, "..");

Log.d(TAG, "isDualAppEx: " + fatherDirPath);

File fatherDir = new File(fatherDirPath);

if (fatherDir.canRead()) {

// 父目录可访问

return true;

}

} catch (Exception e) {

e.printStackTrace();

return true;

}

return false;

}

对用户级应用多开的理解

两个关键技术点:Binder IPC 拦截 、系统函数拦截

两个直观特征:沙盒破损、共享 UID

Binder IPC 拦截用于重定向 APP 与 Android 系统服务、其它应用的远程调用;

系统函数拦截主要用于重定向 I/O 操作,如果搞定所有 libc 函数,再配合修改过的 linker ,可以在一定程度上仿真 Linux 系统;

在用户级的多开中,没有系统的支持,缺少沙盒机制,应用的数据目录一般内嵌在宿主的数据目录中。同时,在同一个用户级多开软件中,内部的 APP 共享宿主的 UID,意味着他们都具有相同的权限,在系统层面上可以相互访问。因此,围绕这点,除了上文给出的方法外,还有三百六十五天不重复的姿势能够检出用户级的多开。

由于是在用户态实现的隔离,不建议用于政企办公保密场景,有此类需求的可以直接用系统自带的 "Android for work" 功能。

业务前端白名单

如果己方有用户级的多开辅助工具,需要在 前端 检测上添加白名单,通常有三种白名单:

APK 包名

APK 签名公钥

APK 内文件(文件名/CRC/HASH)

前端白名单会成为一个很好的攻击点,需要相关业务的后端一起配合来弥补

后记

不要觉得各种 “奔放稳定框架” 稳,只是厂商在权衡 KPI 之后的围三阙一,老大哥可以用各种姿势虎摸狗头。

附件提供 demo,欢迎把玩,欢迎补充。

PS: 看雪的 markdown 不支持 mermaid,又不想贴图,于是把 PDF 送上

上传的附件:

demo.apk

(1.41MB,170次下载)

android sdk多开,[原创]Android 应用多开对抗实践相关推荐

  1. AndroidStudio 集成海康威视 Android SDK,集成萤石Android SDK。

    之前以为海康威视的android sdk 就是萤石的sdk,其实不是 海康威视的: 0: 相关的手机端软件:http://www.hikvision.com/cn/download_more_391. ...

  2. android sdk集成文档,android jpush sdk 集成文档.pdf

    android jpush sdk 集成文档 使⽤提⽰ 本⽂是 Android SDK 标准的集成指南⽂档. 匹配的 SDK 版本为:v2.0.5 及以后版本. 本⽂随SDK压缩包分发.在你看到本⽂时 ...

  3. android sdk linux 离线包,android sdk离线安装

    android sdk离线安装 如果您发现本文排版有问题,可以先点击下面的链接切换至老版进行查看!!!先分别下载所需包,再安装. 一,首先下载SDK的安装包,android-sdk_r06-windo ...

  4. mac 安装Android sdk以便开展appium android自动化测试

    首先下载android sdk. 切换到用户home目录 cd ~/ 创建配置文件 touch .bash_profile 打开配置文件 open .bash_profile 添加内容: export ...

  5. android sdk eclipse没导入,Android—新的eclipse导入SDK出错解决办法

    原先系统崩溃,重装系统,加入一块内存条,从32位变成62位,原先的eclipse用不了:去官网下载64位的eclipse,安装,用一样的方法导入SDK.这时候肯定会提示错误,如下: 1.This An ...

  6. android sdk是灰的,Android Studio 2.3 sdk管理器标签灰显

    Android Studio 2.3 - >配置 - > SDK管理器. 这些选项卡呈灰色显示: SDK工具,SDK更新站点.此外,"Show Package Details&q ...

  7. android sdk版本升级,个推 Android SDK 老版本升级2.9.+

    为什么升级新的SDK? 官网2.9.0.0更新日志: 1.兼容android6.0系统: 2.安全性提升: 3.布局文件优化,通知展示效果提高: 4.增加setTag回执,返回成功与否: 动手升级 1 ...

  8. mac android sdk manager速度慢,android - SDK Manager无法在Mac上打开 - 堆栈内存溢出

    当我尝试打开sdkmanager时,它在终端上说 inconsistent location '/Users/username/Library/Android/sdk/build-tools/28.0 ...

  9. 通过 Android SDK Manager 安装面向 Android* 模拟器插件的英特尔® 凌动™ x86 系统映像...

    作者:hernan-alijo Android* x86 模拟器映像插件要求安装 Android SDK.有关 Android SDK 安装说明,请参考 Android 开发人员网站 (http:// ...

  10. 美图 android sdk,ADT中使用Android SDK Manager安装X86的System Image | 在路上

    之前折腾: 的过程中,需要去用Android SDK Manager安装X86的System Image.下面记录过程: 1.去运行Android SDK Manager: 2.打开后,很快就找到了, ...

最新文章

  1. 代理上网环境下配置TortoiseCVS
  2. 物体检测丨从R-CNN到Mask R-CNN
  3. php amqp rabbitmq 介绍和使用
  4. 用jsp实现登录界面
  5. 枚举当前环境中打开的所有IE
  6. ios和android交互差异,Android 和 iOS 主要交互区别整理
  7. Swift UISearchController
  8. torch.cuda.FloatTensor 与 torch.FloatTensor(torch.Tensor)--CPU和GPU上的数据类型
  9. Python使用pyzabbix调用Zabbix API
  10. mutable和volatile关键字
  11. C#中BackgroundWorker的介绍
  12. 【180929】仿微信飞机大战游戏源码
  13. skiplist及Java实现
  14. 浦发招聘笔试计算机部分考啥,2019浦发银行校园招聘考试题型及题量分布
  15. 蓝桥杯2020年第十一届省赛真题-走方格-java
  16. 荣耀智慧屏x1鸿蒙2.0功能,让家庭中心回归客厅-荣耀智慧屏X1评测
  17. 解决台式机前耳机插孔没有声音
  18. 七彩虹 iGame G-ONE Plus 27寸游戏一体机 评测
  19. 给你一个整数数组 nums 。 如果一组数字 (i,j) 满足 nums[i] == nums[j] 且 i < j ,就可以认为这是一组 好数对 。
  20. Trinity转录组无参组装

热门文章

  1. 网易邮箱显示服务器返回错误,企业退信的常见问题?
  2. 守望先锋app(2)
  3. 听NBA现场讲解很爽,但啥都听不懂?快来学学术语!
  4. 城建税和教育费附加怎么计算
  5. 压缩——实测rar压缩的各种选项对文件压缩的效果(包括固实压缩)
  6. APP推送系统工作原理
  7. C# NPOI Excel 合并单元格和取消单元格
  8. Mysql 免安装配置
  9. 文献检索与下载的几种方式
  10. 将汉语转换成拼音,实现拼音和中文双重登录