前言
在了解了正常环境下 app 的 activity 是如何被启动的以后,接下來我们希望能够了解一下 VirtualApp 中是如何启动目标 app 的。不过整个流程涉及到了对部分 Service 的 Hook,但这些内容却不是本节我们重点关心的内容,因此会有相应的介绍,但或许并不全面。
容器内 APP 启动流程
点击启动应用时发生了啥
当用户点击了视图上目标应用的图标后,触发点击事件并向下调用,在 blackbox 中将来到 launchApk
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public boolean launchApk(String packageName, int userId) { Intent launchIntentForPackage = getBPackageManager().getLaunchIntentForPackage(packageName, userId); if (launchIntentForPackage == null) { return false; } startActivity(launchIntentForPackage, userId); return true; }
public void startActivity(Intent intent, int userId) { if (mClientConfiguration.isEnableLauncherActivity()) { LauncherActivity.launch(intent, userId); } else { getBActivityManager().startActivity(intent, userId); } }
public static void launch(Intent intent, int userId) { Intent splash = new Intent(); splash.setClass(SandBoxCore.getContext(), LauncherActivity.class); splash.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); splash.putExtra(LauncherActivity.KEY_INTENT, intent); splash.putExtra(LauncherActivity.KEY_PKG, intent.getPackage()); splash.putExtra(LauncherActivity.KEY_USER_ID, userId); SandBoxCore.getContext().startActivity(splash); }
|
mClientConfiguration.isEnableLauncherActivity
是恒真的,因此最终会调用 LauncherActivity.launch
,在该函数中,blackbox
初始化了一个 Intent
,然后调用原生的 startActivity
函数来进入 LauncherActivity
,在进入时,会调用该对象的 onCreate
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); if (intent == null) { finish(); return; } Intent launchIntent = intent.getParcelableExtra(KEY_INTENT); String packageName = intent.getStringExtra(KEY_PKG); int userId = intent.getIntExtra(KEY_USER_ID, 0); PackageInfo packageInfo = SandBoxCore.getBPackageManager().getPackageInfo(packageName, 0, userId); if (packageInfo == null) { Slog.e(TAG, packageName + " not installed!"); finish(); return; } Drawable drawable = packageInfo.applicationInfo.loadIcon(SandBoxCore.getPackageManager()); setContentView(R.layout.activity_launcher); findViewById(R.id.iv_icon).setBackgroundDrawable(drawable); new Thread(() -> SandBoxCore.getBActivityManager().startActivity(launchIntent, userId)).start(); }
|
函数首先获取之前那个 splash ,然后从中读取出需要启动的目标应用的包名和用户 ID,并通过包名来读取包的相关信息,最后调用 BActivityManager.startActivity
来在 Blackbox 中启动应用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public void startActivity(Intent intent, int userId) { try { getService().startActivity(intent, userId); } catch (RemoteException e) { e.printStackTrace(); } }
public Service getService() { if (mService != null && mService.asBinder().pingBinder() && mService.asBinder().isBinderAlive()) { return mService; } try { mService = Reflector.on(getTClass().getName() + "$Stub") .method("asInterface", IBinder.class) .call(SandBoxCore.get().getService(getServiceName())); mService .asBinder() .linkToDeath( new IBinder.DeathRecipient() { @Override public void binderDied() { mService.asBinder().unlinkToDeath(this, 0); mService = null; } }, 0); return getService(); } catch (Throwable e) { e.printStackTrace(); return null; } }
|
getService
函数会返回对应的 Service ,在这个函数中将会返回 BActivityManagerService
,这里涉及到了一个我们尚且没有关注过的问题,VirtualApp 是如何伪造各种系统 Service 的?
那些系统服务如何被 Hook
在应用的 Manifest
里声明了这么一段:
1 2 3 4 5
| <provider android:name=".core.system.SystemCallProvider" android:authorities="${applicationId}.blackbox.SystemCallProvider" android:exported="false" android:process="@string/black_box_service_name" />
|
在启动 Blackbox 的时候,handleBindApplication
中会主动调用对应 ContentProvider
下的 onCreate
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| @Override public boolean onCreate() { return initSystem(); } private boolean initSystem() { BlackBoxSystem.getSystem().startup(); return true; }
public void startup() { if (isStartup.getAndSet(true)) return; BEnvironment.load();
mServices.add(BPackageManagerService.get()); mServices.add(BUserManagerService.get()); mServices.add(BActivityManagerService.get()); mServices.add(BJobManagerService.get()); mServices.add(BStorageManagerService.get()); mServices.add(BPackageInstallerService.get()); mServices.add(BXposedManagerService.get()); mServices.add(BProcessManagerService.get()); mServices.add(BAccountManagerService.get()); mServices.add(BLocationManagerService.get()); mServices.add(BNotificationManagerService.get()); for (ISystemService service : mServices) { service.systemReady(); } List<String> preInstallPackages = AppSystemEnv.getPreInstallPackages(); for (String preInstallPackage : preInstallPackages) { try { if (!BPackageManagerService.get().isInstalled(preInstallPackage, BUserHandle.USER_ALL)) { PackageInfo packageInfo = SandBoxCore.getPackageManager().getPackageInfo(preInstallPackage, 0); BPackageManagerService.get() .installPackageAsUser( packageInfo.applicationInfo.sourceDir, InstallOption.installBySystem(), BUserHandle.USER_ALL); } } catch (PackageManager.NameNotFoundException ignored) { } } initJarEnv(); }
|
我们重点关注的是 mServices
这个成员,在注意到它将 BActivityManagerService
放入了数组,并调用对应的 systemReady
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public BActivityManagerService() { mBroadcastManager = BroadcastManager.startSystem(this, mPms); }
@Override public void systemReady() { mBroadcastManager.startup(); } public void startup() { mPms.addPackageMonitor(this); List<BPackageSettings> bPackageSettings = mPms.getBPackageSettings(); for (BPackageSettings bPackageSetting : bPackageSettings) { BPackage bPackage = bPackageSetting.pkg; registerPackage(bPackage); } }
|
最终会为每个包注册一个 BroadcastReceiver
:
1 2 3 4 5 6 7 8
| private void addReceiver(String packageName, BroadcastReceiver receiver) { List<BroadcastReceiver> broadcastReceivers = mReceivers.get(packageName); if (broadcastReceivers == null) { broadcastReceivers = new ArrayList<>(); mReceivers.put(packageName, broadcastReceivers); } broadcastReceivers.add(receiver); }
|
而这个 SystemCallProvider
本身也作为一个 IBinder,将它管理的这些 Service 暴露给其他应用使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Nullable @Override public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) { Slog.d(TAG, "call: " + method + ", " + extras); if ("VM".equals(method)) { Bundle bundle = new Bundle(); if (extras != null) { String name = extras.getString("_B_|_server_name_"); BundleCompat.putBinder(bundle, "_B_|_server_", ServiceManager.getService(name)); } return bundle; } return super.call(method, arg, extras); }
|
ServiceManager.getService
可以能够根据参数来返回对应的 Service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public static IBinder getService(String name) { return get().getServiceInternal(name); } public static ServiceManager get() { if (sServiceManager == null) { synchronized (ServiceManager.class) { if (sServiceManager == null) { sServiceManager = new ServiceManager(); } } } return sServiceManager; } private ServiceManager() { mCaches.put(ACTIVITY_MANAGER, BActivityManagerService.get()); mCaches.put(JOB_MANAGER, BJobManagerService.get()); mCaches.put(PACKAGE_MANAGER, BPackageManagerService.get()); mCaches.put(STORAGE_MANAGER, BStorageManagerService.get()); mCaches.put(USER_MANAGER, BUserManagerService.get()); mCaches.put(XPOSED_MANAGER, BXposedManagerService.get()); mCaches.put(ACCOUNT_MANAGER, BAccountManagerService.get()); mCaches.put(LOCATION_MANAGER, BLocationManagerService.get()); mCaches.put(NOTIFICATION_MANAGER, BNotificationManagerService.get()); } public IBinder getServiceInternal(String name) { return mCaches.get(name); }
|
如果 ServiceManager
没初始化的话就先创建并初始化,将所有的 Service 都放入 mCaches
,并在需要的时候返回该对象。最终其他需要使用这些服务的应用就都能够通过 Binder 拿到这些对应的对象了。
对这些获取 Service 的对象来说,他们本该获取到原生的 ActivityManagerService
,却被 BActivityManagerService
替换掉了,对应的去调用那些本该调用的方法时,自然这些方法也就一起被 Hook 掉了。
一般来说我们都是通过 getSystemService
来获取对应的服务的:
1 2 3 4 5
| @Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
|
而在 Blackbox 的 HookManager 中注册了对各种对象的钩子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| public void init() { if (SandBoxCore.get().isBlackProcess() || SandBoxCore.get().isServerProcess()) { addInjector(new IDisplayManagerProxy()); addInjector(new OsStub()); addInjector(new IActivityManagerProxy()); addInjector(new IPackageManagerProxy()); addInjector(new ITelephonyManagerProxy()); addInjector(new HCallbackProxy()); addInjector(new IAppOpsManagerProxy()); addInjector(new INotificationManagerProxy()); addInjector(new IAlarmManagerProxy()); addInjector(new IAppWidgetManagerProxy()); addInjector(new ContentServiceStub()); addInjector(new IWindowManagerProxy()); addInjector(new IUserManagerProxy()); addInjector(new RestrictionsManagerStub()); addInjector(new IMediaSessionManagerProxy()); addInjector(new ILocationManagerProxy()); addInjector(new IStorageManagerProxy()); addInjector(new ILauncherAppsProxy()); addInjector(new IJobServiceProxy()); addInjector(new IAccessibilityManagerProxy()); addInjector(new ITelephonyRegistryProxy()); addInjector(new IDevicePolicyManagerProxy()); addInjector(new IAccountManagerProxy()); addInjector(new IConnectivityManagerProxy()); addInjector(new IClipboardManagerProxy()); addInjector(new IPhoneSubInfoProxy()); addInjector(new IMediaRouterServiceProxy()); addInjector(new IPowerManagerProxy()); addInjector(new IContextHubServiceProxy()); addInjector(new IVibratorServiceProxy()); addInjector(new IPersistentDataBlockServiceProxy()); addInjector(AppInstrumentation.get()); addInjector(new IWifiManagerProxy()); addInjector(new IWifiScannerProxy()); if (BuildCompat.isS()) { addInjector(new IActivityClientProxy(null)); addInjector(new IVpnManagerProxy()); } if (BuildCompat.isR()) { addInjector(new IPermissionManagerProxy()); } if (BuildCompat.isQ()) { addInjector(new IActivityTaskManagerProxy()); } if (BuildCompat.isPie()) { addInjector(new ISystemUpdateProxy()); } if (BuildCompat.isOreo()) { addInjector(new IAutofillManagerProxy()); addInjector(new IDeviceIdentifiersPolicyProxy()); addInjector(new IStorageStatsManagerProxy()); } if (BuildCompat.isN_MR1()) { addInjector(new IShortcutManagerProxy()); } if (BuildCompat.isN()) { addInjector(new INetworkManagementServiceProxy()); } if (BuildCompat.isM()) { addInjector(new IFingerprintManagerProxy()); addInjector(new IGraphicsStatsProxy()); } if (BuildCompat.isL()) { addInjector(new IJobServiceProxy()); } } injectAll(); }
|
我们主要看 IActivityManagerProxy
是如何对 ActivityManager
进行 hook 的:
1 2 3 4 5 6 7 8 9 10
| @Override protected void inject(Object base, Object proxy) { Object iActivityManager = null; if (BuildCompat.isOreo()) { iActivityManager = BRActivityManagerOreo.get().IActivityManagerSingleton(); } else if (BuildCompat.isL()) { iActivityManager = BRActivityManagerNative.get().gDefault(); } BRSingleton.get(iActivityManager)._set_mInstance(proxy); }
|
这里有一个 _set_mInstance
实际上是 blackreflection
的语法糖,它通过反射的方式来修改 gDefault().mInstance
。我们在上一节中提到过启动应用时会通过 ActivityManagerNative.getDefault
来得到 ActivityManagerProxy
,这里会将结果给改成 Proxy
,也就是用 IActivityManagerProxy
来代理原本的返回对象。
比如说 getServices
函数会被 hook 为:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @ProxyMethod("getServices") public static class GetServices extends MethodHook { @Override protected Object hook(Object who, Method method, Object[] args) throws Throwable { RunningServiceInfo runningServices = BActivityManager.get() .getRunningServices(BActivityThread.getAppPackageName(), BActivityThread.getUserId()); if (runningServices == null) { return new ArrayList<>(); } return runningServices.mRunningServiceInfoList; } }
|
可以注意到,在注入 Service Hook 的时候是有做进程判断的,因为主进程肯定还是需要和 Service 进行正常沟通的,如果全都 Hook 掉的话,主进程也无法正常通信了。所以在满足isBlackProcess
或 isServerProcess
时才会注入那些代理,也就是那些需要启动的内部应用或是服务进程才会注入。
顺带一提,ServerProcess 中包含了这么几个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <provider android:name=".core.system.SystemCallProvider" android:authorities="${applicationId}.blackbox.SystemCallProvider" android:exported="false" android:process="@string/black_box_service_name" /> <receiver android:name=".proxy.ProxyBroadcastReceiver" android:enabled="true" android:exported="true" android:process="@string/black_box_service_name"> <intent-filter> <action android:name="${applicationId}.stub_receiver" /> </intent-filter></receiver> <service android:name=".core.system.DaemonService" android:exported="false" android:process="@string/black_box_service_name" /> <service android:name=".core.system.DaemonService$DaemonInnerService" android:exported="false" android:process="@string/black_box_service_name" />
|
BActivityManagerService.startActivity 如何启动应用
接下来我们回到 BActivityManagerService.startActivity
来看看它如何启动应用。
1 2 3 4 5 6 7
| @Override public void startActivity(Intent intent, int userId) { UserSpace userSpace = getOrCreateSpaceLocked(userId); synchronized (userSpace.mStack) { userSpace.mStack.startActivityLocked(userId, intent, null, null, null, -1, -1, null); } }
|
这里向下继续调用 startActivityLocked
,不过这个函数有点长,这里主要关注两个几个关键步骤即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
| public int startActivityLocked( int userId, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, Bundle options) { synchronized (mTasks) { synchronizeTasks(); } ResolveInfo resolveInfo = BPackageManagerService.get().resolveActivity(intent, GET_ACTIVITIES, resolvedType, userId); if (resolveInfo == null || resolveInfo.activityInfo == null) { return 0; } Log.d(TAG, "startActivityLocked : " + resolveInfo.activityInfo); ActivityInfo activityInfo = resolveInfo.activityInfo; ActivityRecord sourceRecord = findActivityRecordByToken(userId, resultTo); if (sourceRecord == null) { resultTo = null; } TaskRecord sourceTask = null; if (sourceRecord != null) { sourceTask = sourceRecord.task; } String taskAffinity = ComponentUtils.getTaskAffinity(activityInfo); int launchModeFlags = 0; boolean singleTop = containsFlag(intent, Intent.FLAG_ACTIVITY_SINGLE_TOP) || activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP; boolean newTask = containsFlag(intent, Intent.FLAG_ACTIVITY_NEW_TASK); boolean clearTop = containsFlag(intent, Intent.FLAG_ACTIVITY_CLEAR_TOP); boolean clearTask = containsFlag(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK); TaskRecord taskRecord = null; switch (activityInfo.launchMode) { case ActivityInfo.LAUNCH_SINGLE_TOP: case ActivityInfo.LAUNCH_MULTIPLE: case ActivityInfo.LAUNCH_SINGLE_TASK: taskRecord = findTaskRecordByTaskAffinityLocked(userId, taskAffinity); if (taskRecord == null && !newTask) { taskRecord = sourceTask; } break; case ActivityInfo.LAUNCH_SINGLE_INSTANCE: taskRecord = findTaskRecordByTaskAffinityLocked(userId, taskAffinity); break; } if (taskRecord == null || taskRecord.needNewTask()) { return startActivityInNewTaskLocked(userId, intent, activityInfo, resultTo, launchModeFlags); } mAms.moveTaskToFront(taskRecord.id, 0); boolean notStartToFront = false; if (clearTop || singleTop || clearTask) { notStartToFront = true; } boolean startTaskToFront = !notStartToFront && ComponentUtils.intentFilterEquals(taskRecord.rootIntent, intent) && taskRecord.rootIntent.getFlags() == intent.getFlags(); if (startTaskToFront) return 0; ActivityRecord topActivityRecord = taskRecord.getTopActivityRecord(); ActivityRecord targetActivityRecord = findActivityRecordByComponentName(userId, ComponentUtils.toComponentName(activityInfo)); ActivityRecord newIntentRecord = null; boolean ignore = false; if (clearTop) { if (targetActivityRecord != null) { synchronized (targetActivityRecord.task.activities) { for (int i = targetActivityRecord.task.activities.size() - 1; i >= 0; i--) { ActivityRecord next = targetActivityRecord.task.activities.get(i); if (next != targetActivityRecord) { next.finished = true; Log.d(TAG, "makerFinish: " + next.component.toString()); } else { if (singleTop) { newIntentRecord = targetActivityRecord; } else { targetActivityRecord.finished = true; } break; } } } } } if (singleTop && !clearTop) { if (ComponentUtils.intentFilterEquals(topActivityRecord.intent, intent)) { newIntentRecord = topActivityRecord; } else { synchronized (mLaunchingActivities) { for (ActivityRecord launchingActivity : mLaunchingActivities) { if (!launchingActivity.finished && launchingActivity.component.equals(intent.getComponent())) { ignore = true; } } } } } if (activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK && !clearTop) { if (ComponentUtils.intentFilterEquals(topActivityRecord.intent, intent)) { newIntentRecord = topActivityRecord; } else { ActivityRecord record = findActivityRecordByComponentName(userId, ComponentUtils.toComponentName(activityInfo)); if (record != null) { newIntentRecord = record; synchronized (taskRecord.activities) { for (int i = taskRecord.activities.size() - 1; i >= 0; i--) { ActivityRecord next = taskRecord.activities.get(i); if (next != record) { next.finished = true; } else { break; } } } } } } if (activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { newIntentRecord = topActivityRecord; } if (clearTask && newTask) { for (ActivityRecord activity : taskRecord.activities) { activity.finished = true; } } finishAllActivity(userId); if (newIntentRecord != null) { deliverNewIntentLocked(newIntentRecord, intent); return 0; } else if (ignore) { return 0; } if (resultTo == null) { ActivityRecord top = taskRecord.getTopActivityRecord(); if (top != null) { resultTo = top.token; } } else if (sourceTask != null) { ActivityRecord top = sourceTask.getTopActivityRecord(); if (top != null) { resultTo = top.token; } } return startActivityInSourceTask( intent, resolvedType, resultTo, resultWho, requestCode, flags, options, userId, topActivityRecord, activityInfo, launchModeFlags); }
|
首先我们关注 startActivityInNewTaskLocked
,对于那些需要新启动的情况,使用该函数创建对应的任务:
1 2 3 4 5 6 7 8 9 10 11 12 13
| private int startActivityInNewTaskLocked( int userId, Intent intent, ActivityInfo activityInfo, IBinder resultTo, int launchMode) { ActivityRecord record = newActivityRecord(intent, activityInfo, resultTo, userId); Intent shadow = startActivityProcess(userId, intent, activityInfo, record); shadow.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); shadow.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); shadow.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); shadow.addFlags(launchMode); SandBoxCore.getContext().startActivity(shadow); return 0; }
|
该函数创建了一个 shadow
,它实际上是用来创建一个虚假的 Intent
的,我们往下跟踪 startActivityProcess
:
1 2 3 4 5 6 7 8 9 10 11 12
| private Intent startActivityProcess( int userId, Intent intent, ActivityInfo info, ActivityRecord record) { ProxyActivityRecord stubRecord = new ProxyActivityRecord(userId, info, intent, record); ProcessRecord targetApp = BProcessManagerService.get() .startProcessLocked( info.packageName, info.processName, userId, -1, Binder.getCallingPid()); if (targetApp == null) { throw new RuntimeException("Unable to create process, name:" + info.name); } return getStartStubActivityIntentInner(intent, targetApp.bpid, userId, stubRecord, info); }
|
targetApp
初始化了我们将要启动的目标应用的相关信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public ProcessRecord startProcessLocked( String packageName, String processName, int userId, int bpid, int callingPid) { ApplicationInfo info = BPackageManagerService.get().getApplicationInfo(packageName, 0, userId); if (info == null) return null; ProcessRecord app; int buid = BUserHandle.getUid(userId, BPackageManagerService.get().getAppId(packageName)); synchronized (mProcessLock) { Map<String, ProcessRecord> bProcess = mProcessMap.get(buid); if (bProcess == null) { bProcess = new HashMap<>(); } if (bpid == -1) { app = bProcess.get(processName); if (app != null) { if (app.initLock != null) { app.initLock.block(); } if (app.bActivityThread != null) { return app; } } bpid = getUsingBPidL(); Slog.d(TAG, "init bUid = " + buid + ", bPid = " + bpid); } if (bpid == -1) { throw new RuntimeException("No processes available"); } app = new ProcessRecord(info, processName); app.uid = Process.myUid(); app.bpid = bpid; app.buid = BPackageManagerService.get().getAppId(packageName); app.callingBUid = getBUidByPidOrPackageName(callingPid, packageName); app.userId = userId; bProcess.put(processName, app); mPidsSelfLocked.add(app); synchronized (mProcessMap) { mProcessMap.put(buid, bProcess); } if (!initAppProcessL(app)) { bProcess.remove(processName); mPidsSelfLocked.remove(app); app = null; } else { app.pid = getPid(SandBoxCore.getContext(), ProxyManifest.getProcessName(app.bpid)); } } return app; }
|
可以看到主要就是一些 ID 的初始化,不过注意,其中 bpid 指的其实是对 Blackbox 来说的进程 ID ,因为对系统来说只有 Blackbox 这一个进程,但是对 Blackbox 来说却需要管理其中启动的不同应用。
其中还包括了一个 initAppProcessL
用来初始化 app:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private boolean initAppProcessL(ProcessRecord record) { Log.d(TAG, "initProcess: " + record.processName); AppConfig appConfig = record.getClientConfig(); Bundle bundle = new Bundle(); bundle.putParcelable(AppConfig.KEY, appConfig); Bundle init = ProviderCall.callSafely( record.getProviderAuthority(), "_Black_|_init_process_", null, bundle); IBinder appThread = BundleCompat.getBinder(init, "_Black_|_client_"); if (appThread == null || !appThread.isBinderAlive()) { return false; } attachClientL(record, appThread); createProc(record); return true; }
|
这是一个通过 Binder 来调用 initprocess
函数的接口函数,对应调用为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Nullable @Override public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) { if (method.equals("_Black_|_init_process_")) { assert extras != null; extras.setClassLoader(AppConfig.class.getClassLoader()); AppConfig appConfig = extras.getParcelable(AppConfig.KEY); BActivityThread.currentActivityThread().initProcess(appConfig); Bundle bundle = new Bundle(); BundleCompat.putBinder(bundle, "_Black_|_client_", BActivityThread.currentActivityThread()); return bundle; } return super.call(method, arg, extras); }
|
向下调用 initProcess
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public void initProcess(AppConfig appConfig) { synchronized (mConfigLock) { if (this.mAppConfig != null && !this.mAppConfig.packageName.equals(appConfig.packageName)) { throw new RuntimeException( "reject init process: " + appConfig.processName + ", this process is : " + this.mAppConfig.processName); } this.mAppConfig = appConfig; IBinder iBinder = asBinder(); try { iBinder.linkToDeath( new DeathRecipient() { @Override public void binderDied() { synchronized (mConfigLock) { try { iBinder.linkToDeath(this, 0); } catch (RemoteException ignored) { } mAppConfig = null; } } }, 0); } catch (RemoteException e) { e.printStackTrace(); } } }
|
这里将 appConfig
设置到了 BActivityThread
对象里去。
然后再调用 getStartStubActivityIntentInner
,不过参数其实只有刚才的 bpid
,对应参数中的 vpid
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| private Intent getStartStubActivityIntentInner( Intent intent, int vpid, int userId, ProxyActivityRecord target, ActivityInfo activityInfo) { Intent shadow = new Intent(); TypedArray typedArray = null; try { Resources resources = PackageManagerCompat.getResources(SandBoxCore.getContext(), activityInfo.applicationInfo); int id; if (activityInfo.theme != 0) { id = activityInfo.theme; } else { id = activityInfo.applicationInfo.theme; } assert resources != null; typedArray = resources.newTheme().obtainStyledAttributes(id, BRRstyleable.get().Window()); boolean windowIsTranslucent = typedArray.getBoolean(BRRstyleable.get().Window_windowIsTranslucent(), false); if (windowIsTranslucent) { shadow.setComponent( new ComponentName( SandBoxCore.getHostPkg(), ProxyManifest.TransparentProxyActivity(vpid))); } else { shadow.setComponent( new ComponentName(SandBoxCore.getHostPkg(), ProxyManifest.getProxyActivity(vpid))); } Slog.d(TAG, activityInfo + ", windowIsTranslucent: " + windowIsTranslucent); } catch (Throwable e) { e.printStackTrace(); shadow.setComponent( new ComponentName(SandBoxCore.getHostPkg(), ProxyManifest.getProxyActivity(vpid))); } finally { if (typedArray != null) { typedArray.recycle(); } } ProxyActivityRecord.saveStub( shadow, intent, target.mActivityInfo, target.mActivityRecord, target.mUserId); return shadow; }
|
这个 vpid 参数会用来查找 Blackbox 提前在 Manifest 中占坑的 Activity :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <activity android:name=".proxy.ProxyActivity$P0" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" android:exported="true" android:process=":p0" android:supportsPictureInPicture="true" android:taskAffinity="com.hello.sandbox.task_affinity" android:theme="@style/BTheme" /> <activity android:name=".proxy.ProxyActivity$P1" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" android:exported="true" android:process=":p1" android:supportsPictureInPicture="true" android:taskAffinity="com.hello.sandbox.task_affinity" android:theme="@style/BTheme" />
|
这样的 Activity 总共有 50 个,相当于 Blackbox 最多能支持同时启动 50 个内部应用。
这个操作相当于构造了一个用于启动 ProxyActivity
的 Intent
,最终再将这个对象传给系统 AMS 来启动它:
1
| SandBoxCore.getContext().startActivity(shadow);
|
AMS 收到这个请求后自然是正常启动这个 Activity 了,因为所有行为都合法。但是当 AMS 完成了相关启动后,在前文我们提到过,会给这个新的 Activity 发一个 H.EXECUTE_TRANSACTION
命令,而这个命令会被 handleLaunchActivity
处理,但是这个函数其实在之前是被 Hook 掉了的:
1
| addInjector(new HCallbackProxy());
|
这个 HCallbackProxy 中是这样注入的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private Handler getH() { Object currentActivityThread = SandBoxCore.mainThread(); return BRActivityThread.get(currentActivityThread).mH(); } private Handler.Callback getHCallback() { return BRHandler.get(getH()).mCallback(); }
@Override public void injectHook() { mOtherCallback = getHCallback(); if (mOtherCallback != null && (mOtherCallback == this || mOtherCallback.getClass().getName().equals(this.getClass().getName()))) { mOtherCallback = null; } BRHandler.get(getH())._set_mCallback(this); }
|
最终是把 mCallback
对象用 HCallbackProxy
给替换掉了,从而把下面的消息处理函数 handleMessage
给换掉了。不过如果不是我们需要处理的消息,会重新调用原本的函数来处理。
最终调用自己实现的 handleLaunchActivity
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| public synchronized void handleBindApplication(String packageName, String processName) { if (isInit()) return; try { CrashHandler.create(); } catch (Throwable ignored) { } PackageInfo packageInfo = SandBoxCore.getBPackageManager() .getPackageInfo(packageName, PackageManager.GET_PROVIDERS, BActivityThread.getUserId()); ApplicationInfo applicationInfo = packageInfo.applicationInfo; if (packageInfo.providers == null) { packageInfo.providers = new ProviderInfo[] {}; } mProviders.addAll(Arrays.asList(packageInfo.providers)); Slog.d(TAG, "handleBindApplication mProviders=" + mProviders); Object boundApplication = BRActivityThread.get(SandBoxCore.mainThread()).mBoundApplication(); Context packageContext = createPackageContext(applicationInfo); Object loadedApk = BRContextImpl.get(packageContext).mPackageInfo(); BRLoadedApk.get(loadedApk)._set_mSecurityViolation(false); BRLoadedApk.get(loadedApk)._set_mApplicationInfo(applicationInfo); int targetSdkVersion = applicationInfo.targetSdkVersion; if (targetSdkVersion < Build.VERSION_CODES.GINGERBREAD) { StrictMode.ThreadPolicy newPolicy = new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy()).permitNetwork().build(); StrictMode.setThreadPolicy(newPolicy); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (targetSdkVersion < Build.VERSION_CODES.N) { StrictModeCompat.disableDeathOnFileUriExposure(); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WebView.setDataDirectorySuffix(getUserId() + ":" + packageName + ":" + processName); } VirtualRuntime.setupRuntime(processName, applicationInfo); BRVMRuntime.get(BRVMRuntime.get().getRuntime()) .setTargetSdkVersion(applicationInfo.targetSdkVersion); if (BuildCompat.isS()) { BRCompatibility.get().setTargetSdkVersion(applicationInfo.targetSdkVersion); } NativeCore.init(Build.VERSION.SDK_INT); assert packageContext != null; IOCore.get().enableRedirect(packageContext); AppBindData bindData = new AppBindData(); bindData.appInfo = applicationInfo; bindData.processName = processName; bindData.info = loadedApk; bindData.providers = mProviders; ActivityThreadAppBindDataContext activityThreadAppBindData = BRActivityThreadAppBindData.get(boundApplication); activityThreadAppBindData._set_instrumentationName( new ComponentName(bindData.appInfo.packageName, Instrumentation.class.getName())); activityThreadAppBindData._set_appInfo(bindData.appInfo); activityThreadAppBindData._set_info(bindData.info); activityThreadAppBindData._set_processName(bindData.processName); activityThreadAppBindData._set_providers(bindData.providers); mBoundApplication = bindData; if (BRNetworkSecurityConfigProvider.getRealClass() != null) { Security.removeProvider("AndroidNSSP"); BRNetworkSecurityConfigProvider.get().install(packageContext); } Application application; try { onBeforeCreateApplication(packageName, processName, packageContext); if (BuildCompat.isT()){ BEnvironment.getAllDex(packageName).forEach(new Consumer<String>() { @Override public void accept(String s) { new File(s).setReadOnly(); } }); } application = BRLoadedApk.get(loadedApk).makeApplication(false, null); mInitialApplication = application; BRActivityThread.get(SandBoxCore.mainThread())._set_mInitialApplication(mInitialApplication); ContextCompat.fix( (Context) BRActivityThread.get(SandBoxCore.mainThread()).getSystemContext()); ContextCompat.fix(mInitialApplication);
installProviders(mInitialApplication, bindData.processName, bindData.providers); try { fixAiLiaoPhoto(mInitialApplication); } catch (Throwable e) { e.printStackTrace(); } onBeforeApplicationOnCreate(packageName, processName, application); AppInstrumentation.get().callApplicationOnCreate(application); onAfterApplicationOnCreate(packageName, processName, application); NativeCore.init_seccomp(); HookManager.get().checkEnv(HCallbackProxy.class);
if (BuildConfig.DEBUG) { Log.d( TAG, "Instrumentation class name " + AppInstrumentation.get().getCurrInstrumentation().getClass().getName()); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Unable to makeApplication", e); } }
|
看起来似乎有些复杂,这里稍微总结一下。
首先 handleLaunchActivity
这个函数会有多次调用,不只是收到 LAUNCH_ACTIVITY
时,还有 EXECUTE_TRANSACTION
的时候也一样会调用(似乎是兼容版本),因此看着流程里会有多次提前返回,是因为时机还没到。
以及我们知道,一个 APP 在启动时有可能会创建多个 Activity,第一个创建的 Activity
需要额外的调用 bindApplication
去绑定 Application
对象,这个也是我们前文正常流程里提到过的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| public synchronized void handleBindApplication(String packageName, String processName) { if (isInit()) return; try { CrashHandler.create(); } catch (Throwable ignored) { } PackageInfo packageInfo = SandBoxCore.getBPackageManager() .getPackageInfo(packageName, PackageManager.GET_PROVIDERS, BActivityThread.getUserId()); ApplicationInfo applicationInfo = packageInfo.applicationInfo; if (packageInfo.providers == null) { packageInfo.providers = new ProviderInfo[] {}; } mProviders.addAll(Arrays.asList(packageInfo.providers)); Slog.d(TAG, "handleBindApplication mProviders=" + mProviders); Object boundApplication = BRActivityThread.get(SandBoxCore.mainThread()).mBoundApplication(); Context packageContext = createPackageContext(applicationInfo); Object loadedApk = BRContextImpl.get(packageContext).mPackageInfo(); BRLoadedApk.get(loadedApk)._set_mSecurityViolation(false); BRLoadedApk.get(loadedApk)._set_mApplicationInfo(applicationInfo); int targetSdkVersion = applicationInfo.targetSdkVersion; if (targetSdkVersion < Build.VERSION_CODES.GINGERBREAD) { StrictMode.ThreadPolicy newPolicy = new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy()).permitNetwork().build(); StrictMode.setThreadPolicy(newPolicy); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (targetSdkVersion < Build.VERSION_CODES.N) { StrictModeCompat.disableDeathOnFileUriExposure(); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WebView.setDataDirectorySuffix(getUserId() + ":" + packageName + ":" + processName); } VirtualRuntime.setupRuntime(processName, applicationInfo); BRVMRuntime.get(BRVMRuntime.get().getRuntime()) .setTargetSdkVersion(applicationInfo.targetSdkVersion); if (BuildCompat.isS()) { BRCompatibility.get().setTargetSdkVersion(applicationInfo.targetSdkVersion); } NativeCore.init(Build.VERSION.SDK_INT); assert packageContext != null; IOCore.get().enableRedirect(packageContext); AppBindData bindData = new AppBindData(); bindData.appInfo = applicationInfo; bindData.processName = processName; bindData.info = loadedApk; bindData.providers = mProviders; ActivityThreadAppBindDataContext activityThreadAppBindData = BRActivityThreadAppBindData.get(boundApplication); activityThreadAppBindData._set_instrumentationName( new ComponentName(bindData.appInfo.packageName, Instrumentation.class.getName())); activityThreadAppBindData._set_appInfo(bindData.appInfo); activityThreadAppBindData._set_info(bindData.info); activityThreadAppBindData._set_processName(bindData.processName); activityThreadAppBindData._set_providers(bindData.providers); mBoundApplication = bindData; if (BRNetworkSecurityConfigProvider.getRealClass() != null) { Security.removeProvider("AndroidNSSP"); BRNetworkSecurityConfigProvider.get().install(packageContext); } Application application; try { onBeforeCreateApplication(packageName, processName, packageContext); if (BuildCompat.isT()){ BEnvironment.getAllDex(packageName).forEach(new Consumer<String>() { @Override public void accept(String s) { new File(s).setReadOnly(); } }); } application = BRLoadedApk.get(loadedApk).makeApplication(false, null); mInitialApplication = application; BRActivityThread.get(SandBoxCore.mainThread())._set_mInitialApplication(mInitialApplication); ContextCompat.fix( (Context) BRActivityThread.get(SandBoxCore.mainThread()).getSystemContext()); ContextCompat.fix(mInitialApplication);
|
函数一样很长,总结一下内容:
- 获取 APK 信息
packageInfo
- 修改
LoadedApk
中的 mSecurityViolation
和 mApplicationInfo
为目标应用
- 设置进程名和命令行中的参数名为目标函数
VirtualRuntime.setupRuntime
- 设置
TargetSdkVersion
- 初始化 Blackbox 自己的 sdk 动态库
NativeCore.init
- 路径重定向
IOCore.get().enableRedirect
- 调用
makeApplication
以构建子程序包的 Application
对象,并且替换原来通过 Host Stub 生成的 mInitialApplication
。注意,这个时候新生成的 LoadedApk
代表了目标应用,其中的很多资源路径全都被替换为目标应用的路径了,加载资源时将会从被替换后的路径去查找。
- 注册 Providers
- 通过
callApplicationOnCreate
调用 Application
下的 OnCreate
,这会创建或初始化对应的上下文、Instrumentation
、Application
,目标应用生命周期开始
- 初始化 seccomp,这是 Blackbox 后续提供的新功能,Virtualbox 是没有这个的
NativeCore.init_seccomp
在 handleBindApplication
完成后我们回到 handleLaunchActivity
继续往下:
1 2 3 4 5
| int taskId = BRIActivityManager.get(BRActivityManagerNative.get().getDefault()) .getTaskForActivity(token, false); SandBoxCore.getBActivityManager() .onActivityCreated(taskId, token, stubRecord.mActivityRecord);
|
这里有一个 onActivityCreated
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public void onActivityCreated( ProcessRecord processRecord, int taskId, IBinder token, ActivityRecord record) { synchronized (mLaunchingActivities) { mLaunchingActivities.remove(record); mHandler.removeMessages(LAUNCH_TIME_OUT, record); } synchronized (mTasks) { synchronizeTasks(); TaskRecord taskRecord = mTasks.get(taskId); if (taskRecord == null) { taskRecord = new TaskRecord(taskId, record.userId, ComponentUtils.getTaskAffinity(record.info)); taskRecord.rootIntent = record.intent; mTasks.put(taskId, taskRecord); } record.token = token; record.processRecord = processRecord; record.task = taskRecord; taskRecord.addTopActivity(record); Log.d(TAG, "onActivityCreated : " + record.component.toString()); } }
|
将 Activity 指定。
1 2 3
| LaunchActivityItemContext launchActivityItemContext = BRLaunchActivityItem.get(r); launchActivityItemContext._set_mIntent(stubRecord.mTarget); launchActivityItemContext._set_mInfo(activityInfo);
|
最后将 mIntent
和 mInfo
替换成目标应用。
这个函数从这一步结束后会返回一个 false,之前一直没注意到,但实际上当其返回 false 的时候,会回到原函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Override public boolean handleMessage(@NonNull Message msg) { if (!mBeing.getAndSet(true)) { try { if (BuildCompat.isPie()) { if (msg.what == BRActivityThreadH.get().EXECUTE_TRANSACTION()) { final Boolean a = handleLaunchActivity(msg); if (a != null && a) { getH().sendMessageAtFrontOfQueue(Message.obtain(msg)); return true; } } } else { if (msg.what == BRActivityThreadH.get().LAUNCH_ACTIVITY()) { final Boolean a = handleLaunchActivity(msg); if (a != null && a) { getH().sendMessageAtFrontOfQueue(Message.obtain(msg)); return true; } } } if (msg.what == BRActivityThreadH.get().CREATE_SERVICE()) { return handleCreateService(msg.obj); } if (mOtherCallback != null) { return mOtherCallback.handleMessage(msg); } return false; } finally { mBeing.set(false); } } return false; }
|
当 handleLaunchActivity
返回 false 后,程序继续往下执行 mOtherCallback.handleMessage
,这个 mOtherCallback
就是原本的那个处理对象,通过它来调用原本的那个 handleLaunchActivity
。
在原先的那个处理函数中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) { if (ThreadedRenderer.sRendererEnabled && (r.activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) { HardwareRenderer.preload(); } WindowManagerGlobal.initialize(); GraphicsEnvironment.hintActivityLaunch(); final Activity a = performLaunchActivity(r, customIntent); return a; }
|
注意这里的 ActivityClientRecord
被传进了 performLaunchActivity
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); ...... } try { ...... activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.activityConfigCallback,r.assistToken, r.shareableActivityToken); if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { 重点* 3. OnCreate流程 mInstrumentation.callActivityOnCreate(activity, r.state); } r.setState(ON_CREATE); } ...... ...... }
|
.
由于我们预先已经把相关的资源路径全部替换成目标应用了,这里会创建目标应用的内存实例对象,获取的 classloader 也都是指向目标应用的路径,使用它们创建 Activity
并最终调用 callActivityOnCreate
。以及目标的相关 dex 和动态库也都在这里被加载进内存。
1 2 3 4 5 6 7 8 9 10
| public class Instrumentation { public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { String pkg = intent != null && intent.getComponent() != null ? intent.getComponent().getPackageName() : null; return getFactory(pkg).instantiateActivity(cl, className, intent); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class AppComponentFactory extends android.app.AppComponentFactory { public @NonNull Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className, @Nullable Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { return (Activity) cl.loadClass(className).getDeclaredConstructor().newInstance(); } catch (InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException("Couldn't call constructor", e); } } }
|
另外,AppInstrumentation
把这个函数做了个 Hook:
1 2 3 4 5 6 7 8
| @Override public void callActivityOnCreate( Activity activity, Bundle icicle, PersistableBundle persistentState) { mBaseInstrumentation.callActivityOnCreate(activity, icicle, persistentState); for (AppLifecycleCallback appLifecycleCallback : SandBoxCore.get().getAppLifecycleCallbacks()) { appLifecycleCallback.onActivityCreated(activity, icicle); } }
|
mBaseInstrumentation.callActivityOnCreate
会调用原生的 callActivityOnCreate
,这个里面会去调用 Activity
的 OnCreate
。
流程图
最后贴一份流程图,来自于 alen17

参考文章
https://blog.csdn.net/ganyao939543405/article/details/76177392
https://zhuanlan.zhihu.com/p/151010577
https://www.cnblogs.com/revercc/p/16813435.html
https://www.jianshu.com/p/f95fd575a57c