首页
社区
课程
招聘
[原创]android端手机虚拟机实现过程
发表于: 2025-4-18 19:14 25631

[原创]android端手机虚拟机实现过程

2025-4-18 19:14
25631

以下是VirtualAPP的执行流程图

我们通过这样的一个开源的virtualApp来了解一下,一个应用程序是怎么去实现在虚拟机里面运行的。

首先是应用程序的下载安装。实际上,在虚拟机中运行的应用程序是下载到的虚拟机空间(virtual space)中的。

虚拟机中的 App(比如 App1、App2)原本会直接调用系统 Framework,但是在虚拟机中的APP需要的就不同

Android Framework 是 Android 系统提供的一整套 API,它是 系统级服务(如 AMS、PMS、LocationService) 的接口,比如:

这些都属于 系统原生的 Android Framework,运行在系统进程(如 system_server)中,不是用户 App 能随意改动的。而在虚拟机中的Framework,则是自开发的可改动的Framework。

而在Virtual Framework中,需要做的事情就是让APP觉得自己是在原生系统下执行的,根据Android Frameword中的应用程序的执行流程,我们可以知道的是

应用的真正启动是 ActivityThread 执行的,但整个过程是由 AMS 控制调度的,双方通过 Binder 建立通信桥梁。

那么实际上在虚拟机下应用程序的执行流程也应该是这样的,但是不同的是,这里的Server服务不是Android 原生下的framework,而是虚拟机的framework

而这里就应用到的是APP HOOK的主要实现了

VA 会通过 反射、代理(比如 Binder 代理)或动态注入,替换系统服务对象。

举个例子:

细节的说一下可能出现的具体实现:

而在APP HOOK拦截完成之后,VA Server就需要开始去提供对应的服务来实现拦截之后的服务请求。

所以:APP Hook 是用来“拦截”App 调用系统服务行为的;而 VA Server 是用来**“虚拟提供”这些服务的实现端。**

在这一层主要为了完成2个工作,IO重定向VA APP与Android系统交互的请求修改。

比如在一些APP中,会把一些配置、缓存、数据库等文件的路径写死在代码里

这种情况下的,用虚拟机打开的程序就无法获取适配对应路径的文件和配置信息

所以说出现了 VA Native的存在的意义:

不过这里应该算得上是Native层的HOOK了。

以上的APP HOOK 都是基于Java层进行的,但是相应的Android 有大量的系统行为是通过 Native 实现的。这时候对应的应用程序适配就需要Native HOOK来实现

这里举例的程序应用就是需要通过HOOK so层来实现分析和获取适配对应路径的文件和配置信息。

在虚拟机中很多的细节其实是通过HOOK,或者通过反射的操作调用实现的。比如拦截真实的AMS,PMS等服务,返回自写的各种服务。而在虚拟机框架中为了使得调用拦截等更加方便则出来对于反射等的封装。而在这里的封装就叫mirror

正常执行下去拦截改定位:

mirror拦截改定位

这里实际上就去做了对应的函数封装,从而更加快速实现的代码逻辑

举例应用调用AMS执行流程的过程来看看Java层 HOOK

Android 系统中,所有四大组件的管理都要通过系统的 ActivityManagerService(AMS) 完成,客户端通过 IActivityManager 接口 调用 AMS。

所以 Hook AMS = 控制所有组件的启动流程!得到IActivityManager 接口就可以拦截执行流程

这里会走ActivityManager.getService() ,而在android8.0的位置

所以在

用反射将其 mInstance 替换成我们刚才构造的 代理对象,应用对 AMS 的所有调用(比如 startActivity()),都会经过我们这个 Hook 代理对象

在这里去调用 int res = VActivityManager.get().startActivity(args); 此处直接将调用转发到 VActivityManager 中(VirtualApp 的虚拟 AMS)。

“拦截 AMS 服务” 的核心目的,实际上就是为了拿到 IActivityManager 的实例,并用我们构造的 代理对象替换掉它。因为 Android 应用中的 组件调度(包括启动 Activity、Service、广播等)全都需要通过这个接口来向系统发送请求。

这些都是 IActivityManager 的方法。

上面也已经说过了Native层HOOK的原理,这里就执行来看看 io重定向,实现修改的文件读写的路径的操作

可以看到这里利用redirectDirectory函数,去重定向了对应可能读取的位置,替换成了虚拟机对应位置的内容。

该宏会生成一个以 new_ 为前缀的函数,例如:

举个例子:
pathname = "/data/data/com.test/files/a.txt"
如果这个 app 被 VA 管控了,那路径会被转向
redirect_path = "/data/user/0/com.va.wrapper/files/a.txt"

从这里就对于开头的那个图片进行了总体的大致讲解,但是其中虚拟机下运行应用的很多问题以及配置环境更多的是需要在实践运用上去体现。

参考资料:
675K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2M7@1I4G2k6s2W2Q4x3V1k6h3K9i4u0@1N6h3q4D9b7i4m8H3
99cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6B7N6h3g2B7K9h3&6Q4x3X3g2U0L8W2)9J5c8Y4m8G2M7%4c8Q4x3V1j5%4x3o6t1^5x3e0t1@1z5e0f1%4x3e0b7I4z5o6V1K6x3e0f1H3i4K6t1K6K9r3g2S2k6r3W2F1k6#2)9J5k6o6p5I4
490K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4N6W2K9i4S2A6L8W2)9#2k6U0x3K6z5o6p5$3x3K6l9H3i4K6u0r3j5i4u0@1K9h3y4D9k6g2)9J5c8X3c8W2N6r3q4A6L8s2y4Q4x3V1j5^5z5o6M7%4x3U0b7$3x3R3`.`.
195K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8X3N6S2L8Y4W2S2L8K6V1K6z5e0f1@1x3K6b7H3y4g2)9J5c8X3q4J5N6r3W2U0L8r3g2Q4x3V1k6V1k6i4c8S2K9h3I4K6i4K6u0r3y4K6j5I4y4o6j5%4y4U0l9`.

文章PDF:通过网盘分享的文件:
通过网盘分享的文件:Android虚拟机原理总.pdf
链接: d20K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1j5I4L8r3u0H3e0p5N6Q4y4h3k6e0j5Y4y4r3x3U0q4&6P5X3#2v1f1@1Z5$3d9p5y4Y4 提取码: chen
--来自百度网盘超级会员v4的分享

方面 虚拟机中的 App(VA App) 系统安装的 App(原生 App)
安装方式 并未真正通过系统 PMS 安装,仅在 VA 虚拟空间中注册 通过系统 PMS 安装,注册了 Activity、Service 等
运行路径 安装路径、数据路径等都是被 重定向/虚拟的(如 /data/data/com.xxx -> /data/data/io.virtual.app/space/0/... 使用 Android 系统真实的 /data/data/com.xxx 路径
访问系统服务 所有访问系统服务的调用(如 Location、Clipboard)都被 VA Hook 拦截和“伪造” 调用系统原生 ServiceManager 提供的服务,直接连接 system_server
AMS / PMS 管理 由 VA 模拟的 AMS/PMS 进行调度(Activity 启动、Service 调用等) 由系统的 ActivityManagerServicePackageManagerService 控制
权限模型 权限请求和判断被 VA 接管(可以伪造授予,也可以强制拦截) 权限由 Android 系统控制,用户手动授权
多开与隔离能力 可以任意多开,同一 App 多个实例之间相互隔离 系统默认一个包名只允许一个实例,数据也无法隔离
文件/IO 控制 VA Hook 底层文件系统访问,可实现只读/禁止/伪造/隔离等策略 系统文件访问直接作用于真实文件系统
运行时感知 可以“欺骗” App,使其以为自己运行在正常系统中(比如感知不到虚拟环境) 真实环境,App 可以访问所有支持的设备资源
安全性控制 更灵活,可以模拟环境、劫持函数、限制行为 安全性高但开放度小,无法灵活控制运行环境
new File("/data/data/com.example.app/shared_prefs/config.xml")
fopen("/data/data/com.example.app/files/config.dat", "r");
new File("/data/data/com.example.app/shared_prefs/config.xml")
fopen("/data/data/com.example.app/files/config.dat", "r");
情况 举例 解决方法
直接访问文件 open, fopen, access hook libc
直接调用系统服务 getuid(), getpid() hook syscall
访问特殊设备 /dev/*, /proc/* 重定向路径
ART Runtime 优化 libart.so 调用 hook libart
动态链接时 resolve dlsym 取函数地址 hook linker
private void hookInstrumentation() {
    try {
        // 加载 ActivityThread 类
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
         
        // 获取 'currentActivityThread' 方法
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
         
        // 调用该方法获取当前的 ActivityThread 实例
        Object currentThread = currentActivityThreadMethod.invoke(null);
         
        // 获取 'mInstrumentation' 字段
        Field instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
        instrumentationField.setAccessible(true);
         
        // 获取原始的 Instrumentation 实例
        Instrumentation originalInstrumentation = (Instrumentation) instrumentationField.get(currentThread);
         
        // 创建 InstrumentationDelegate 并替换原始的 Instrumentation
        InstrumentationDelegate instrumentationDelegate = new InstrumentationDelegate(originalInstrumentation);
        instrumentationField.set(currentThread, instrumentationDelegate);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
private void hookInstrumentation() {
    try {
        // 加载 ActivityThread 类
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
         
        // 获取 'currentActivityThread' 方法
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
         
        // 调用该方法获取当前的 ActivityThread 实例
        Object currentThread = currentActivityThreadMethod.invoke(null);
         
        // 获取 'mInstrumentation' 字段
        Field instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
        instrumentationField.setAccessible(true);
         
        // 获取原始的 Instrumentation 实例
        Instrumentation originalInstrumentation = (Instrumentation) instrumentationField.get(currentThread);
         
        // 创建 InstrumentationDelegate 并替换原始的 Instrumentation
        InstrumentationDelegate instrumentationDelegate = new InstrumentationDelegate(originalInstrumentation);
        instrumentationField.set(currentThread, instrumentationDelegate);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
private void hookInstrumentation(){
    Object currentThread ActivityThread.currentActivityThread.call();
    Instrumentation originInstrumentation   ActivityThread.mInstrumentation.get(currentThread);
    ActivityThread.mInstrumentation.set(currentThread,new InstrumentationDelegate(originInstrumentation));
private void hookInstrumentation(){
    Object currentThread ActivityThread.currentActivityThread.call();
    Instrumentation originInstrumentation   ActivityThread.mInstrumentation.get(currentThread);
    ActivityThread.mInstrumentation.set(currentThread,new InstrumentationDelegate(originInstrumentation));
// frameworks/base/core/java/android/app/ActivityManager.java
public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}
 
// frameworks/base/core/java/android/util/Singleton.java
private static final Singleton<IActivityManager> IActivityManagerSingleton =
    new Singleton<IActivityManager>() {
        @Override
        protected IActivityManager create() {
            // 注意这行
            return ActivityManagerNative.getDefault();
        }
    };
// frameworks/base/core/java/android/app/ActivityManager.java
public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}
 
// frameworks/base/core/java/android/util/Singleton.java
private static final Singleton<IActivityManager> IActivityManagerSingleton =
    new Singleton<IActivityManager>() {
        @Override
        protected IActivityManager create() {
            // 注意这行
            return ActivityManagerNative.getDefault();
        }
    };
public class ActivityManagerStub extends MethodInvocationProxy<MethodInvocationStub<IInterface>> {
    public ActivityManagerStub() {
        // 基于 mirror 获取 AMS 的 IActivityManager 接口对象,并创建其动态代理对象
        super(new MethodInvocationStub<>(ActivityManagerNative.getDefault.call()));//这里通过反射去获取系统 IActivityManager 实例。
    
 
public void inject() throws Throwable {
        if (BuildCompat.isOreo()) {
            // Android 8.x 及以上版本
            Object singleton = ActivityManager.IActivityManagerSingleton.get();
            // 替换原 AMS 对象为我们的动态代理对象
            Singleton.mInstance.set(singleton, getInvocationStub().getProxyInterface());
        } else {
            // Android 8.x 以下的实现(略)
        }
    }
public class ActivityManagerStub extends MethodInvocationProxy<MethodInvocationStub<IInterface>> {
    public ActivityManagerStub() {
        // 基于 mirror 获取 AMS 的 IActivityManager 接口对象,并创建其动态代理对象
        super(new MethodInvocationStub<>(ActivityManagerNative.getDefault.call()));//这里通过反射去获取系统 IActivityManager 实例。
    
 
public void inject() throws Throwable {
        if (BuildCompat.isOreo()) {
            // Android 8.x 及以上版本
            Object singleton = ActivityManager.IActivityManagerSingleton.get();
            // 替换原 AMS 对象为我们的动态代理对象
            Singleton.mInstance.set(singleton, getInvocationStub().getProxyInterface());
        } else {
            // Android 8.x 以下的实现(略)
        }
    }
    // 方法 hook 实现
    static class StartActivity extends MethodProxy {
        @Override
        public String getMethodName() {
            return "startActivity";
        }
        @Override
        public Object call(Object who, Method method, Object... args) throws Throwable {
            // 可以对参数进行处理
            int res = VActivityManager.get().startActivity(args);
            return res;
        }
    }
}
    // 方法 hook 实现
    static class StartActivity extends MethodProxy {
        @Override
        public String getMethodName() {
            return "startActivity";
        }
        @Override
        public Object call(Object who, Method method, Object... args) throws Throwable {
            // 可以对参数进行处理

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2025-4-21 18:00 被ovo_帮我不c编辑 ,原因:
收藏
免费 63
支持
分享
最新回复 (36)
雪    币: 467
活跃值: (634)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢楼主的
2025-4-19 12:31
0
雪    币: 1449
活跃值: (2716)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
VA原理哈~
2025-4-19 13:55
0
雪    币:
活跃值: (935)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
看看
2025-4-21 11:41
0
雪    币: 721
活跃值: (1462)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
我来看你
2025-4-21 16:33
0
雪    币: 104
活跃值: (5701)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
6666666
2025-4-21 18:01
0
雪    币: 181
活跃值: (903)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
111
2025-4-21 19:01
0
雪    币: 524
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
感谢分享
2025-4-21 22:03
0
雪    币: 1837
活跃值: (3680)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
666
2025-4-21 23:27
0
雪    币: 3065
活跃值: (4422)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
感谢分享
2025-4-22 00:22
0
雪    币: 160
活跃值: (1763)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
11
666
2025-4-22 08:52
0
雪    币: 10
活跃值: (1873)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
666
2025-4-22 09:46
0
雪    币: 84
活跃值: (2563)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
666
2025-4-22 11:18
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
66666666
2025-4-22 11:51
0
雪    币: 22
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
66666666
2025-4-22 16:50
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
6666
2025-4-22 16:56
0
雪    币: 2682
活跃值: (2889)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
66666666
2025-4-22 17:32
0
雪    币: 23
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
学习学习
2025-4-23 00:35
0
雪    币: 240
活跃值: (1635)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
66666
2025-4-23 08:37
0
雪    币: 30
活跃值: (1735)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
666
2025-4-23 08:45
0
雪    币: 34
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
666
2025-4-23 08:54
0
雪    币: 163
活跃值: (1874)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
22
666
2025-4-23 09:02
0
雪    币: 28
活跃值: (919)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
看看
2025-4-23 09:21
0
雪    币: 2690
活跃值: (7734)
能力值: ( LV12,RANK:459 )
在线值:
发帖
回帖
粉丝
24
这篇文章似乎来自52pojie,原链接b57K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7J5x3o6t1#2y4o6t1%4i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1@1i4@1u0p5i4K6W2o6i4@1f1^5i4K6R3H3i4K6R3#2i4@1f1$3i4K6V1^5i4@1q4r3i4@1f1#2i4K6V1H3i4K6S2o6i4@1f1@1i4@1t1^5i4K6R3H3i4@1f1@1i4@1t1^5i4@1q4m8i4@1f1@1i4@1u0m8i4@1u0m8i4@1f1@1i4@1t1&6i4K6R3^5i4@1g2r3i4@1u0o6i4K6W2r3i4@1f1$3i4K6R3@1i4K6W2r3i4@1f1^5i4@1p5%4i4K6R3&6i4@1f1^5i4@1u0r3i4K6V1&6i4@1f1#2i4@1p5@1i4K6S2p5i4@1f1#2i4K6R3^5i4@1t1$3i4@1f1%4i4@1t1J5i4K6V1^5i4@1f1^5i4@1t1@1i4@1t1@1i4@1f1@1i4@1u0o6i4@1u0o6i4@1f1@1i4@1t1&6i4K6S2q4i4@1f1$3i4@1p5H3i4@1u0o6i4@1f1#2i4@1u0o6i4K6S2r3i4@1f1#2i4K6R3#2i4@1p5^5i4@1f1&6i4K6R3K6i4@1u0p5i4@1f1$3i4K6V1^5i4@1q4r3i4@1f1$3i4@1t1%4i4@1t1%4i4@1f1@1i4@1t1&6i4@1t1I4i4@1f1%4i4K6W2m8i4K6R3@1i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1#2i4@1p5$3i4K6R3J5i4@1f1$3i4K6W2q4i4K6W2o6i4@1f1$3i4K6V1^5i4@1q4r3i4@1f1#2i4K6S2q4i4K6W2r3i4@1f1@1i4@1u0p5i4K6W2o6i4@1f1^5i4K6R3H3i4K6R3#2i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1^5i4@1q4r3i4K6W2p5i4@1f1%4i4K6W2n7i4@1t1@1i4@1f1$3i4K6S2q4i4@1p5#2i4@1f1^5i4@1t1@1i4@1t1@1L8h3c8Q4c8e0W2Q4z5o6y4Q4b7V1c8Q4c8e0c8Q4b7U0S2Q4z5p5c8Q4c8e0S2Q4z5o6N6Q4b7U0y4Q4c8e0c8Q4b7V1q4Q4z5p5g2Q4c8e0S2Q4b7V1k6Q4z5e0W2Q4c8e0k6Q4b7e0m8Q4b7U0N6Q4c8e0g2Q4z5e0m8Q4b7e0N6Q4c8f1k6Q4b7V1y4Q4z5f1j5`.
2025-4-23 16:23
1
雪    币: 1116
活跃值: (1121)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
25
666
2025-4-23 17:20
0
游客
登录 | 注册 方可回帖
返回