Android 双开沙箱 VirtualApp 源码分析(五)BroadcastReceiver

Android 双开沙箱 VirtualApp 源码分析(五)BroadcastReceiver

方案猜测

同 Activity 一样,Client App 在 Menifest 中注册的静态广播外部 AMS 是无法知晓的,经过前几章的分析,相信大家已经是老司机了,我们可以先尝试提出自己的观点。

  1. 和 Activity 一样使用 Stub 组件占坑?仔细想一想是无法实现的,因为你无法预先确定 Client App 中广播的 Intent Filter。
  2. 使用动态注册即 context.registerBroadcastReceiver 代替静态注册,这确实是个好主意,但是重点在于注册的时机。我们需要在 VAService 启动时就预先把 VA 中所有安装的 Client App 的静态 Receiver 提前注册好,事实上外部 AMS 也是这么做的。不然的话,没有打开的 App 就无法收到广播了。

VA 静态广播注册

从前面我们知道,VAService 的启动时机实在 BinderProvider.onCreate():

@Override
    public boolean onCreate() {
        .....................
        VAppManagerService.get().scanApps();
        .....................
        return true;
    }

看到 VAppManagerService.get().scanApps()————>PersistenceLayer.read()——————->PackagePersistenceLayer.readPersistenceData()——————>VAppManagerService.loadPackage()———->VAppManagerService.loadPackageInnerLocked()————–>BroadcastSystem.startUp();

// 静态 Receiver 的注册
    public void startApp(VPackage p) {
        PackageSetting setting = (PackageSetting) p.mExtras;
        // 遍历 Client App 的 Receiver
        for (VPackage.ActivityComponent receiver : p.receivers) {
            ActivityInfo info = receiver.info;
            // 得到对应 Client App 在 VAService 中的记录列表
            List<BroadcastReceiver> receivers = mReceivers.get(p.packageName);
            if (receivers == null) {
                receivers = new ArrayList<>();
                mReceivers.put(p.packageName, receivers);
            }
            // 注册显式意图
            String componentAction = String.format("_VA_%s_%s", info.packageName, info.name);
            IntentFilter componentFilter = new IntentFilter(componentAction);
            BroadcastReceiver r = new StaticBroadcastReceiver(setting.appId, info, componentFilter);
            mContext.registerReceiver(r, componentFilter, null, mScheduler);
            // 推入记录
            receivers.add(r);
            // 遍历注册隐式意图
            for (VPackage.ActivityIntentInfo ci : receiver.intents) {
                IntentFilter cloneFilter = new IntentFilter(ci.filter);
                SpecialComponentList.protectIntentFilter(cloneFilter);
                r = new StaticBroadcastReceiver(setting.appId, info, cloneFilter);
                mContext.registerReceiver(r, cloneFilter, null, mScheduler);
                // 推入记录
                receivers.add(r);
            }
        }
    }

这里对每个 Client App 静态 Receiver 的信息使用统一的代理 StaticBroadcastReceiver 注册。

  1. 首先注册 Receiver 的显式意图,每个显式意图被重定向成格式为 “_VA_PKGNAME_CLASSNAME”的 componentAction 这么做的理由实际是真正注册是 VAService 进程空间的 StaticBroadcastReceiver 代理 Receiver,而不是 VA Client App 进程空间,所以直接注册 VA Client App 中的真实类名是没有意义的,这样通过 VAService 代理然后再从 Intent 中取出的 “_VA_PKGNAME_CLASSNAME”到 VA Client 中找到真正的 Receiver,这个逻辑和 Activity 的处理有些相似。
  2. 然后就是遍历 Intent-Filter,每个 Intent-Filter 注册一个 StaticBroadcastReceiver 代理。

这样我们的代理 Receiver 注册完毕了。

下面当代理 Receiver 收到广播时:

 @Override
        public void onReceive(Context context, Intent intent) {
            if (mApp.isBooting()) {
                return;
            }
            if ((intent.getFlags() & FLAG_RECEIVER_REGISTERED_ONLY) != 0 || isInitialStickyBroadcast()) {
                return;
            }
            String privilegePkg = intent.getStringExtra("_VA_|_privilege_pkg_");
            if (privilegePkg != null && !info.packageName.equals(privilegePkg)) {
                return;
            }
            PendingResult result = goAsync();
            if (!mAMS.handleStaticBroadcast(appId, info, intent, new PendingResultData(result))) {
                result.finish();
            }
        }

然后看到 handleStaticBroadcast

boolean handleStaticBroadcast(int appId, ActivityInfo info, Intent intent,
                                  PendingResultData result) {
        // 这里是取出真正的目标 Intent
        Intent realIntent = intent.getParcelableExtra("_VA_|_intent_");
        // 取出真正的目标 component
        ComponentName component = intent.getParcelableExtra("_VA_|_component_");
        // 用户 id
        int userId = intent.getIntExtra("_VA_|_user_id_", VUserHandle.USER_NULL);
        if (realIntent == null) {
            return false;
        }
        if (userId < 0) {
            VLog.w(TAG, "Sent a broadcast without userId " + realIntent);
            return false;
        }
        int vuid = VUserHandle.getUid(userId, appId);
        return handleUserBroadcast(vuid, info, component, realIntent, result);
    }

注意这里取出了真正的 Intent,和 Activity 类似,但是和 Activity 处理不同的是现在的逻辑还在 VAService 中:

然后 handleUserBroadcast()———–>handleStaticBroadcastAsUser()————>performScheduleReceiver():

private void performScheduleReceiver(IVClient client, int vuid, ActivityInfo info, Intent intent,
                                         PendingResultData result) {

        ComponentName componentName = ComponentUtils.toComponentName(info);
        BroadcastSystem.get().broadcastSent(vuid, info, result);
        try {
            // 远程调用 client app 的 scheduleReceiver
            client.scheduleReceiver(info.processName, componentName, intent, result);
        } catch (Throwable e) {
            if (result != null) {
                result.finish();
            }
        }
    }

client.scheduleReceiver() 这时候远程调用了 Client App 的 scheduleReceiver。这样我们回到了 Client App 进程空间:

@Override
    public void scheduleReceiver(String processName, ComponentName component, Intent intent, PendingResultData resultData) {
        ReceiverData receiverData = new ReceiverData();
        receiverData.resultData = resultData;
        receiverData.intent = intent;
        receiverData.component = component;
        receiverData.processName = processName;
        sendMessage(RECEIVER, receiverData);
    }

跟到消息队列中:

case RECEIVER: {
     handleReceiver((ReceiverData) msg.obj);
}
private void handleReceiver(ReceiverData data) {
        BroadcastReceiver.PendingResult result = data.resultData.build();
        try {
            // 依然是检测 Application 是否初始化,没有则初始化
            if (!isBound()) {
                bindApplication(data.component.getPackageName(), data.processName);
            }
            // 获取 Receiver 的 Context,这个context是一个ReceiverRestrictedContext实例,它有两个主要函数被禁掉:registerReceiver()和 bindService()。这两个函数在BroadcastReceiver.onReceive()不允许调用。每次Receiver处理一个广播,传递进来的context都是一个新的实例。
            Context context = mInitialApplication.getBaseContext();
            Context receiverContext = ContextImpl.getReceiverRestrictedContext.call(context);
            String className = data.component.getClassName();
            // 实例化目标 Receiver
            BroadcastReceiver receiver = (BroadcastReceiver) context.getClassLoader().loadClass(className).newInstance();
            mirror.android.content.BroadcastReceiver.setPendingResult.call(receiver, result);
            data.intent.setExtrasClassLoader(context.getClassLoader());
            // 手动调用 onCreate
            receiver.onReceive(receiverContext, data.intent);
            // 通知 Pending 结束
            if (mirror.android.content.BroadcastReceiver.getPendingResult.call(receiver) != null) {
                result.finish();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(
                    "Unable to start receiver " + data.component
                            + ": " + e.toString(), e);
        }
        // 这里需要远程通知 VAService 广播已送到
        VActivityManager.get().broadcastFinish(data.resultData);
    }

这里就是最关键的地方了,简单点概括就是 new 了真正的 Receiver 然后调用 onCreate 而已。Receiver 生命周期真的非常简单。

需要注意的是,broadCast 发送有个超时机制:

void broadcastFinish(PendingResultData res) {
        synchronized (mBroadcastRecords) {
            BroadcastRecord record = mBroadcastRecords.remove(res.mToken);
            if (record == null) {
                VLog.e(TAG, "Unable to find the BroadcastRecord by token: " + res.mToken);
            }
        }
        mTimeoutHandler.removeMessages(0, res.mToken);
        res.finish();
    }
private final class TimeoutHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            IBinder token = (IBinder) msg.obj;
            BroadcastRecord r = mBroadcastRecords.remove(token);
            if (r != null) {
                VLog.w(TAG, "Broadcast timeout, cancel to dispatch it.");
                r.pendingResult.finish();
            }
        }
    }

这里如果广播超时则会通知 PendingResult 结束,告诉发送方广播结束了。

发送广播的处理

其实上一部分已经讲了很多发送广播的处理了。
这里 Hook 了 broacastIntent 方法:

static class BroadcastIntent extends MethodProxy {

        @Override
        public String getMethodName() {
            return "broadcastIntent";
        }

        @Override
        public Object call(Object who, Method method, Object... args) throws Throwable {
            Intent intent = (Intent) args[1];
            String type = (String) args[2];
            intent.setDataAndType(intent.getData(), type);
            if (VirtualCore.get().getComponentDelegate() != null) {
                VirtualCore.get().getComponentDelegate().onSendBroadcast(intent);
            }
            Intent newIntent = handleIntent(intent);
            if (newIntent != null) {
                args[1] = newIntent;
            } else {
                return 0;
            }

            if (args[7] instanceof String || args[7] instanceof String[]) {
                // clear the permission
                args[7] = null;
            }
            return method.invoke(who, args);
        }


        private Intent handleIntent(final Intent intent) {
            final String action = intent.getAction();
            if ("android.intent.action.CREATE_SHORTCUT".equals(action)
                    || "com.android.launcher.action.INSTALL_SHORTCUT".equals(action)) {

                return VASettings.ENABLE_INNER_SHORTCUT ? handleInstallShortcutIntent(intent) : null;

            } else if ("com.android.launcher.action.UNINSTALL_SHORTCUT".equals(action)) {

                handleUninstallShortcutIntent(intent);

            } else {
                return ComponentUtils.redirectBroadcastIntent(intent, VUserHandle.myUserId());
            }
            return intent;
        }

        private Intent handleInstallShortcutIntent(Intent intent) {
            Intent shortcut = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
            if (shortcut != null) {
                ComponentName component = shortcut.resolveActivity(VirtualCore.getPM());
                if (component != null) {
                    String pkg = component.getPackageName();
                    Intent newShortcutIntent = new Intent();
                    newShortcutIntent.setClassName(getHostPkg(), Constants.SHORTCUT_PROXY_ACTIVITY_NAME);
                    newShortcutIntent.addCategory(Intent.CATEGORY_DEFAULT);
                    newShortcutIntent.putExtra("_VA_|_intent_", shortcut);
                    newShortcutIntent.putExtra("_VA_|_uri_", shortcut.toUri(0));
                    newShortcutIntent.putExtra("_VA_|_user_id_", VUserHandle.myUserId());
                    intent.removeExtra(Intent.EXTRA_SHORTCUT_INTENT);
                    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, newShortcutIntent);

                    Intent.ShortcutIconResource icon = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
                    if (icon != null && !TextUtils.equals(icon.packageName, getHostPkg())) {
                        try {
                            Resources resources = VirtualCore.get().getResources(pkg);
                            int resId = resources.getIdentifier(icon.resourceName, "drawable", pkg);
                            if (resId > 0) {
                                //noinspection deprecation
                                Drawable iconDrawable = resources.getDrawable(resId);
                                Bitmap newIcon = BitmapUtils.drawableToBitmap(iconDrawable);
                                if (newIcon != null) {
                                    intent.removeExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
                                    intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, newIcon);
                                }
                            }
                        } catch (Throwable e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return intent;
        }
  1. 这里拦截了创建快捷图标的 Intent,这是一个发给 Launcher 的隐式广播,VA 把这个请求拦截下来因为如果不拦截这个快捷方式就会指向外部的 App,并且如果外部 App 没有安装,此广播也不会发生作用。VA 把这个广播换成了自己的逻辑。
  2. 注意 ComponentUtils.redirectBroadcastIntent(), 类似 Activity 用代理 Intent 包裹真正的 Intent:
public static Intent redirectBroadcastIntent(Intent intent, int userId) {
        Intent newIntent = intent.cloneFilter();
        newIntent.setComponent(null);
        newIntent.setPackage(null);
        ComponentName component = intent.getComponent();
        String pkg = intent.getPackage();
        if (component != null) {
            newIntent.putExtra("_VA_|_user_id_", userId);
            // 这里显式意图被重定位成 _VA_PKGNAME_CLASSNAME 的格式,与前面注册的时候对应
            newIntent.setAction(String.format("_VA_%s_%s", component.getPackageName(), component.getClassName()));
            newIntent.putExtra("_VA_|_component_", component);
            newIntent.putExtra("_VA_|_intent_", new Intent(intent));
        } else if (pkg != null) {
            newIntent.putExtra("_VA_|_user_id_", userId);
            newIntent.putExtra("_VA_|_creator_", pkg);
            newIntent.putExtra("_VA_|_intent_", new Intent(intent));
            String protectedAction = SpecialComponentList.protectAction(intent.getAction());
            if (protectedAction != null) {
                newIntent.setAction(protectedAction);
            }
        } else {
            newIntent.putExtra("_VA_|_user_id_", userId);
            newIntent.putExtra("_VA_|_intent_", new Intent(intent));
            String protectedAction = SpecialComponentList.protectAction(intent.getAction());
            if (protectedAction != null) {
                newIntent.setAction(protectedAction);
            }
        }
        return newIntent;
    }

Ok, BroadcastReceiver 完毕,下一张分析最后一个组件 ContentProvider。

转载:https://blog.csdn.net/ganyao939543405/article/details/76229480

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android 12沙箱源代码分析,主要涉及到Android操作系统中的隔离与安全机制。 Android沙箱机制是为了保护应用程序之间的隔离,确保用户数据安全和应用程序的稳定性。在源代码分析中,我们可以深入理解Android 12中沙箱的实现方式。 首先,Android 12引入了新的隔离特性,名为"沙箱"。这意味着每个应用程序都运行在独立的安全环境中,并与其他应用程序隔离。源代码揭示了沙箱的实现原理,包括进程间通信的限制、文件系统的隔离以及权限控制的改进。 其次,在源代码分析中,我们会发现Android 12利用命名空间(namespace)技术来实现文件系统的隔离。每个应用程序被分配了私有的文件系统根目录,应用程序之间无法访问其他应用程序的私有目录。这种隔离保证了用户数据的安全性,避免了不同应用程序之间的数据冲突。 此外,Android 12还使用沙箱来限制应用程序之间的进程间通信。通过源代码分析,我们可以了解到沙箱机制对进程间通信进行了限制和权限控制。这样可以防止恶意应用程序滥用进程间通信机制,从而保护系统的稳定性和用户隐私。 综上所述,Android 12的沙箱源代码分析可以帮助我们深入理解系统的安全机制和隔离特性。通过分析源代码,我们可以更好地了解Android 12是如何实现沙箱的,从而提供更好的数据隔离和安全性保护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值