Android 双开沙箱 VirtualApp 源码分析(三)App 启动

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

在这之前,我们还是要先了解一下 VA Client Framework 和 VAService 之间的通讯方式

VAService 与通讯

VAService

首先 VAService 是指 VA 仿造 Android 原生 frameworkService 实现的一套副本,举例有 VActivityManagerService ,它和系统 AMS 一样,只不过他管理的是 VA 内部 Client App 的组件会话。

VAService 统一管理

首先所有 VAService 直接继承与 XXX.Stub,也就是 Binder ,并且直接使用了一个 Map 储存在 VAService 进程空间中,并没有注册到系统 AMS 中,事实上在 VAService 进程中,每个 Service 都被当作一个普通对象 new 和初始化。

最终,他们被添加到了 ServiceCache 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ServiceCache {

private static final Map<String, IBinder> sCache = new ArrayMap<>(5);

public static void addService(String name, IBinder service) {
sCache.put(name, service);
}

public static IBinder removeService(String name) {
return sCache.remove(name);
}

public static IBinder getService(String name) {
return sCache.get(name);
}

}

这个 cache 很简单,就是一个 Map

而被添加的时机则在 BinderProvideronCreate() 回调中:

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
@Override
public boolean onCreate() {
Context context = getContext();
// 这是一个空前台服务,目的是为了保活 VAService 进程,即 :x 进程
DaemonService.startup(context);
if (!VirtualCore.get().isStartup()) {
return true;
}
VPackageManagerService.systemReady();
addService(ServiceManagerNative.PACKAGE, VPackageManagerService.get());
VActivityManagerService.systemReady(context);
addService(ServiceManagerNative.ACTIVITY, VActivityManagerService.get());
addService(ServiceManagerNative.USER, VUserManagerService.get());
VAppManagerService.systemReady();
addService(ServiceManagerNative.APP, VAppManagerService.get());
BroadcastSystem.attach(VActivityManagerService.get(), VAppManagerService.get());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addService(ServiceManagerNative.JOB, VJobSchedulerService.get());
}
VNotificationManagerService.systemReady(context);
addService(ServiceManagerNative.NOTIFICATION, VNotificationManagerService.get());
VAppManagerService.get().scanApps();
VAccountManagerService.systemReady();
addService(ServiceManagerNative.ACCOUNT, VAccountManagerService.get());
addService(ServiceManagerNative.VS, VirtualStorageService.get());
addService(ServiceManagerNative.DEVICE, VDeviceManagerService.get());
return true;
}

需要注意的是 DeamonService 是一个空前台服务,目的是为了保活 VAService 进程,即 :x 进程,因为现在后台服务很容易被杀,在 Android 8.0 以后后台服务只能在后台存活 5S ,而前台服务则不受影响。

ServiceFetcher

VA 设计了一个单独的 ServiceFetcher 服务用于向外部暴露 VAService 中的所有服务的 IBinder 句柄,而 ServiceFetcher 本身也是 Binder 服务,也就是说, ServiceFetcherIbinder 句柄是拿到其他 VAService IBinder 的钥匙。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ServiceFetcher 实现类 远程调用类
private class ServiceFetcher extends IServiceFetcher.Stub {
@Override
public IBinder getService(String name) throws RemoteException {
if (name != null) {
return ServiceCache.getService(name);
}
return null;
}

@Override
public void addService(String name, IBinder service) throws RemoteException {
if (name != null && service != null) {
ServiceCache.addService(name, service);
}
}

@Override
public void removeService(String name) throws RemoteException {
if (name != null) {
ServiceCache.removeService(name);
}
}
}

ServicecFetcher 自身的 IBnder 则通过 BinderProvicer 这个ContentProvider 暴露给其他进程:

1
2
3
4
5
6
7
8
9
@Override
public Bundle call(String method, String arg, Bundle extras) {
if ("@".equals(method)) {
Bundle bundle = new Bundle();
BundleCompat.putBinder(bundle, "_VA_|_binder_", mServiceFetcher);
return bundle;
}
return null;
}

那么在 Client App VA Client 就可以通过 IServiceFetcher 这个 IBinder 拿到其他服务的 IBinder 了:

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
// 通过 ContentProvider 传递一个
private static IServiceFetcher sFetcher;

private static IServiceFetcher getServiceFetcher() {
if (sFetcher == null || !sFetcher.asBinder().isBinderAlive()) {
synchronized (ServiceManagerNative.class) {
Context context = VirtualCore.get().getContext();
Bundle response = new ProviderCall.Builder(context, SERVICE_CP_AUTH).methodName("@").call();
/* SERVICE_CP_AUTH 对应着下面的 Provider
<provider
android:name="com.lody.virtual.server.BinderProvider"
android:authorities="${applicationId}.virtual.service.BinderProvider"
android:exported="false"
android:process="@string/engine_process_name" />
*/
if (response != null) {
IBinder binder = BundleCompat.getBinder(response, "_VA_|_binder_");
linkBinderDied(binder);
sFetcher = IServiceFetcher.Stub.asInterface(binder);
}
}
}
return sFetcher;
}

// 返回服务的 IBinder 句柄
public static IBinder getService(String name) {
// 如果是本地服务,直接本地返回
if (VirtualCore.get().isServerProcess()) {
return ServiceCache.getService(name);
}
// 通过 ServiceFetcher 的句柄找到远程 Service 的句柄
IServiceFetcher fetcher = getServiceFetcher();
if (fetcher != null) {
try {
return fetcher.getService(name);
} catch (RemoteException e) {
e.printStackTrace();
}
}
VLog.e(TAG, "GetService(%s) return null.", name);
return null;
}

启动 App

首先要了解的是 Android App 是组件化的, Apk 其实是 N 多个组件的集合,以及一些资源文件和 AssertApp 的启动有多种情况,只要在一个新的进程中调起了 apk 中任何一个组件, App 将被初始化, Application 将被初始化。

启动 Activity

我们先看启动 Activity 的情况:

Hook startActivity(重定位 Intent 到 StubActivity)

首先在 Client App 中, startActivity 方法必须被 Hook 掉,不然 Client App 调用 startActivity 就直指外部 Activity 去了。

这部分的原理其实与 DroidPlugin 大同小异,由于插件(Client App)中的 Activity 是没有在 AMS 中注册的, AMS 自然无法找到我们的插件 Activity

Hook 的目的是我们拿到用户的 Intent ,把他替换成指向 VAMenifest 中站好坑的 StubActivityIntent ,然后将原 Intent 当作 data 打包进新 Intent 以便日后流程再次进入 VA 时恢复。

Hook 的方法就是用我们动态代理生成的代理类对象替换系统原来的 ActiityManagerNative.geDefault 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//ActivityManagerStub.java
@Override
public void inject() throws Throwable {
if (BuildCompat.isOreo()) {
//Android Oreo(8.X)
Object singleton = ActivityManagerOreo.IActivityManagerSingleton.get();
Singleton.mInstance.set(singleton, getInvocationStub().getProxyInterface());
} else {
if (ActivityManagerNative.gDefault.type() == IActivityManager.TYPE) {
ActivityManagerNative.gDefault.set(getInvocationStub().getProxyInterface());
} else if (ActivityManagerNative.gDefault.type() == Singleton.TYPE) {
Object gDefault = ActivityManagerNative.gDefault.get();
Singleton.mInstance.set(gDefault, getInvocationStub().getProxyInterface());
}
}
BinderInvocationStub hookAMBinder = new BinderInvocationStub(getInvocationStub().getBaseInterface());
hookAMBinder.copyMethodProxies(getInvocationStub());
ServiceManager.sCache.get().put(Context.ACTIVITY_SERVICE, hookAMBinder);
}

好了,下面只要调用到 startActivity 就会被 Hookcall

这个函数需要注意以下几点:

  1. VA 有意将安装和卸载 APP 的请求重定向到了卸载 VA 内部 APK 的逻辑。
  2. resolveActivityInfo 调用到了 VPMresolveIntent ,最终会远程调用到 VPMSresolveIntent ,然后 VPMS 就会去查询 VPackage 找到目标 Activity 并将信息附加在 ResolveInfo 中返回 VPM
  3. 最后也是最重要的一点, startActivity 会调用到 VAM.startActivity ,同样最终会远程调用到 VAMSstartActivity
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
// Hook startActivity
static class StartActivity extends MethodProxy {

private static final String SCHEME_FILE = "file";
private static final String SCHEME_PACKAGE = "package";

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

@Override
public Object call(Object who, Method method, Object... args) throws Throwable {
int intentIndex = ArrayUtils.indexOfObject(args, Intent.class, 1);
if (intentIndex < 0) {
return ActivityManagerCompat.START_INTENT_NOT_RESOLVED;
}
int resultToIndex = ArrayUtils.indexOfObject(args, IBinder.class, 2);
String resolvedType = (String) args[intentIndex + 1];
Intent intent = (Intent) args[intentIndex];
intent.setDataAndType(intent.getData(), resolvedType);
IBinder resultTo = resultToIndex >= 0 ? (IBinder) args[resultToIndex] : null;
int userId = VUserHandle.myUserId();

if (ComponentUtils.isStubComponent(intent)) {
return method.invoke(who, args);
}

// 请求安装和卸载界面
if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())
|| (Intent.ACTION_VIEW.equals(intent.getAction())
&& "application/vnd.android.package-archive".equals(intent.getType()))) {
if (handleInstallRequest(intent)) {
return 0;
}
} else if ((Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())
|| Intent.ACTION_DELETE.equals(intent.getAction()))
&& "package".equals(intent.getScheme())) {

if (handleUninstallRequest(intent)) {
return 0;
}
}

String resultWho = null;
int requestCode = 0;
Bundle options = ArrayUtils.getFirst(args, Bundle.class);
if (resultTo != null) {
resultWho = (String) args[resultToIndex + 1];
requestCode = (int) args[resultToIndex + 2];
}
// chooser 调用选择界面
if (ChooserActivity.check(intent)) {
intent.setComponent(new ComponentName(getHostContext(), ChooserActivity.class));
intent.putExtra(Constants.EXTRA_USER_HANDLE, userId);
intent.putExtra(ChooserActivity.EXTRA_DATA, options);
intent.putExtra(ChooserActivity.EXTRA_WHO, resultWho);
intent.putExtra(ChooserActivity.EXTRA_REQUEST_CODE, requestCode);
return method.invoke(who, args);
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
args[intentIndex - 1] = getHostPkg();
}

//解析 ActivityInfo
ActivityInfo activityInfo = VirtualCore.get().resolveActivityInfo(intent, userId);
if (activityInfo == null) {
VLog.e("VActivityManager", "Unable to resolve activityInfo : " + intent);
if (intent.getPackage() != null && isAppPkg(intent.getPackage())) {
return ActivityManagerCompat.START_INTENT_NOT_RESOLVED;
}
return method.invoke(who, args);
}

// 调用远程 VAMS.startActivity
int res = VActivityManager.get().startActivity(intent, activityInfo, resultTo, options, resultWho, requestCode, VUserHandle.myUserId());
if (res != 0 && resultTo != null && requestCode > 0) {
VActivityManager.get().sendActivityResult(resultTo, resultWho, requestCode);
}

// 处理 Activity 切换动画,因为此时动画还是 Host 的 Stub Activity 默认动画,需要覆盖成子程序包的动画
if (resultTo != null) {
ActivityClientRecord r = VActivityManager.get().getActivityRecord(resultTo);
if (r != null && r.activity != null) {
try {
TypedValue out = new TypedValue();
Resources.Theme theme = r.activity.getResources().newTheme();
theme.applyStyle(activityInfo.getThemeResource(), true);
if (theme.resolveAttribute(android.R.attr.windowAnimationStyle, out, true)) {

TypedArray array = theme.obtainStyledAttributes(out.data,
new int[]{
android.R.attr.activityOpenEnterAnimation,
android.R.attr.activityOpenExitAnimation
});

r.activity.overridePendingTransition(array.getResourceId(0, 0), array.getResourceId(1, 0));
array.recycle();
}
} catch (Throwable e) {
// Ignore
}
}
}
return res;
}


private boolean handleInstallRequest(Intent intent) {
IAppRequestListener listener = VirtualCore.get().getAppRequestListener();
if (listener != null) {
Uri packageUri = intent.getData();
if (SCHEME_FILE.equals(packageUri.getScheme())) {
File sourceFile = new File(packageUri.getPath());
try {
listener.onRequestInstall(sourceFile.getPath());
return true;
} catch (RemoteException e) {
e.printStackTrace();
}
}

}
return false;
}

private boolean handleUninstallRequest(Intent intent) {
IAppRequestListener listener = VirtualCore.get().getAppRequestListener();
if (listener != null) {
Uri packageUri = intent.getData();
if (SCHEME_PACKAGE.equals(packageUri.getScheme())) {
String pkg = packageUri.getSchemeSpecificPart();
try {
listener.onRequestUninstall(pkg);
return true;
} catch (RemoteException e) {
e.printStackTrace();
}
}

}
return false;
}
}

逻辑最终走到 VAMS 后,VAMS 调用 ActivityStack.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
// 参考 framework 的实现
int startActivityLocked(int userId, Intent intent, ActivityInfo info, IBinder resultTo, Bundle options,
String resultWho, int requestCode) {
optimizeTasksLocked();

Intent destIntent;
ActivityRecord sourceRecord = findActivityByToken(userId, resultTo);
TaskRecord sourceTask = sourceRecord != null ? sourceRecord.task : null;

ReuseTarget reuseTarget = ReuseTarget.CURRENT;
ClearTarget clearTarget = ClearTarget.NOTHING;
boolean clearTop = containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TOP);
boolean clearTask = containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);

if (intent.getComponent() == null) {
intent.setComponent(new ComponentName(info.packageName, info.name));
}
if (sourceRecord != null && sourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (clearTop) {
removeFlags(intent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
clearTarget = ClearTarget.TOP;
}
if (clearTask) {
if (containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
clearTarget = ClearTarget.TASK;
} else {
removeFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (info.documentLaunchMode) {
case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
clearTarget = ClearTarget.TASK;
reuseTarget = ReuseTarget.DOCUMENT;
break;
case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
reuseTarget = ReuseTarget.MULTIPLE;
break;
}
}
boolean singleTop = false;

switch (info.launchMode) {
case LAUNCH_SINGLE_TOP: {
singleTop = true;
if (containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
reuseTarget = containFlags(intent, Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
? ReuseTarget.MULTIPLE
: ReuseTarget.AFFINITY;
}
}
break;
case LAUNCH_SINGLE_TASK: {
clearTop = false;
clearTarget = ClearTarget.TOP;
reuseTarget = containFlags(intent, Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
? ReuseTarget.MULTIPLE
: ReuseTarget.AFFINITY;
}
break;
case LAUNCH_SINGLE_INSTANCE: {
clearTop = false;
clearTarget = ClearTarget.TOP;
reuseTarget = ReuseTarget.AFFINITY;
}
break;
default: {
if (containFlags(intent, Intent.FLAG_ACTIVITY_SINGLE_TOP)) {
singleTop = true;
}
}
break;
}
if (clearTarget == ClearTarget.NOTHING) {
if (containFlags(intent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)) {
clearTarget = ClearTarget.SPEC_ACTIVITY;
}
}
if (sourceTask == null && reuseTarget == ReuseTarget.CURRENT) {
reuseTarget = ReuseTarget.AFFINITY;
}

String affinity = ComponentUtils.getTaskAffinity(info);

// 根据 Flag 寻找合适的 Task
TaskRecord reuseTask = null;
switch (reuseTarget) {
case AFFINITY:
reuseTask = findTaskByAffinityLocked(userId, affinity);
break;
case DOCUMENT:
reuseTask = findTaskByIntentLocked(userId, intent);
break;
case CURRENT:
reuseTask = sourceTask;
break;
default:
break;
}

boolean taskMarked = false;
if (reuseTask == null) {
startActivityInNewTaskLocked(userId, intent, info, options);
} else {
boolean delivered = false;
mAM.moveTaskToFront(reuseTask.taskId, 0);
boolean startTaskToFront = !clearTask && !clearTop && ComponentUtils.isSameIntent(intent, reuseTask.taskRoot);

if (clearTarget.deliverIntent || singleTop) {
taskMarked = markTaskByClearTarget(reuseTask, clearTarget, intent.getComponent());
ActivityRecord topRecord = topActivityInTask(reuseTask);
if (clearTop && !singleTop && topRecord != null && taskMarked) {
topRecord.marked = true;
}
// Target activity is on top
if (topRecord != null && !topRecord.marked && topRecord.component.equals(intent.getComponent())) {
deliverNewIntentLocked(sourceRecord, topRecord, intent);
delivered = true;
}
}
if (taskMarked) {
synchronized (mHistory) {
scheduleFinishMarkedActivityLocked();
}
}
if (!startTaskToFront) {
if (!delivered) {
destIntent = startActivityProcess(userId, sourceRecord, intent, info);
if (destIntent != null) {
startActivityFromSourceTask(reuseTask, destIntent, info, resultWho, requestCode, options);
}
}
}
}
return 0;
}

然后 call 到了 startActivityProcess ,这就是真正替换 Intent 的地方。

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
// 使用 Host Stub Activity 的 Intent 包装原 Intent 瞒天过海
private Intent startActivityProcess(int userId, ActivityRecord sourceRecord, Intent intent, ActivityInfo info) {
intent = new Intent(intent);
// 获得 Activity 对应的 ProcessRecorder,如果没有则表示这是 Process 第一个打开的组件,需要初始化 Application
ProcessRecord targetApp = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);
if (targetApp == null) {
return null;
}
Intent targetIntent = new Intent();

// 根据 Client App 的 PID 获取 StubActivity
String stubActivityPath = fetchStubActivity(targetApp.vpid, info);

Log.e("gy", "map activity:" + intent.getComponent().getClassName() + " -> " + stubActivityPath);

targetIntent.setClassName(VirtualCore.get().getHostPkg(), stubActivityPath);
ComponentName component = intent.getComponent();
if (component == null) {
component = ComponentUtils.toComponentName(info);
}
targetIntent.setType(component.flattenToString());
StubActivityRecord saveInstance = new StubActivityRecord(intent, info,
sourceRecord != null ? sourceRecord.component : null, userId);
saveInstance.saveToIntent(targetIntent);
return targetIntent;
}

fetchStubActivity 会根据相同的进程 idVAMenifest 中找到那个提前占坑的 StubActivity

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
// 获取合适的 StubActivity,返回 StubActivity 全限定名
private String fetchStubActivity(int vpid, ActivityInfo targetInfo) {

boolean isFloating = false;
boolean isTranslucent = false;
boolean showWallpaper = false;
try {
int[] R_Styleable_Window = R_Hide.styleable.Window.get();
int R_Styleable_Window_windowIsTranslucent = R_Hide.styleable.Window_windowIsTranslucent.get();
int R_Styleable_Window_windowIsFloating = R_Hide.styleable.Window_windowIsFloating.get();
int R_Styleable_Window_windowShowWallpaper = R_Hide.styleable.Window_windowShowWallpaper.get();

AttributeCache.Entry ent = AttributeCache.instance().get(targetInfo.packageName, targetInfo.theme,
R_Styleable_Window);
if (ent != null && ent.array != null) {
showWallpaper = ent.array.getBoolean(R_Styleable_Window_windowShowWallpaper, false);
isTranslucent = ent.array.getBoolean(R_Styleable_Window_windowIsTranslucent, false);
isFloating = ent.array.getBoolean(R_Styleable_Window_windowIsFloating, false);
}
} catch (Throwable e) {
e.printStackTrace();
}

boolean isDialogStyle = isFloating || isTranslucent || showWallpaper;

// 根据在 Menifest 中注册的 pid
if (isDialogStyle) {
return VASettings.getStubDialogName(vpid);
} else {
return VASettings.getStubActivityName(vpid);
}
}

这里需要特别注意, VA 占坑的方式和 DroidPlugin 有些小不同,VA 没有为每个 Process 注册多个 Activity ,也没有为不同的启动方式注册多个 Activity ,这里确实是有改进的。

这里根本原因是因为 VAVAMS 实现的更为完整,实现了原版 AMS 的基本功能,包括完整的 Recorder 管理,Task Stack 管理等,这样的话 StubActivity 的唯一作用便是携带 Client App 真正的 Intent 交给 VAMS 处理。这套机制衍生到其他的组件也是一样的。

可以简单看一下 ActivityStack,ActivityRecorderActivityRecord

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
/* package */ class ActivityStack {

private final ActivityManager mAM;
private final VActivityManagerService mService;

/**
* [Key] = TaskId [Value] = TaskRecord
*/
private final SparseArray<TaskRecord> mHistory = new SparseArray<>();
}

class TaskRecord {
public final List<ActivityRecord> activities = Collections.synchronizedList(new ArrayList<ActivityRecord>());
public int taskId;
public int userId;
public String affinity;
public Intent taskRoot;
}

/* package */ class ActivityRecord {
public TaskRecord task;
public ComponentName component;
public ComponentName caller;
// Client App 中 Activity 的句柄
public IBinder token;
public int userId;
public ProcessRecord process;
public int launchMode;
public int flags;
public boolean marked;
public String affinity;
}

StubActivityRecorder

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
public class StubActivityRecord  {
public Intent intent;
public ActivityInfo info;
public ComponentName caller;
public int userId;

public StubActivityRecord(Intent intent, ActivityInfo info, ComponentName caller, int userId) {
this.intent = intent;
this.info = info;
this.caller = caller;
this.userId = userId;
}

// 获取原版 Intent 和一些其他信息
public StubActivityRecord(Intent stub) {
this.intent = stub.getParcelableExtra("_VA_|_intent_");
this.info = stub.getParcelableExtra("_VA_|_info_");
this.caller = stub.getParcelableExtra("_VA_|_caller_");
this.userId = stub.getIntExtra("_VA_|_user_id_", 0);
}

// 将原版 Intent 塞到 Stub Intent
public void saveToIntent(Intent stub) {
stub.putExtra("_VA_|_intent_", intent);
stub.putExtra("_VA_|_info_", info);
stub.putExtra("_VA_|_caller_", caller);
stub.putExtra("_VA_|_user_id_", userId);
}
}

初始化 Application

还有一个非常重要的事情,注意到这一行 。

1
2
// 获得 Activity 对应的 ProcessRecorder,如果没有则表示这是 Process 第一个打开的组件,需要初始化 Application
ProcessRecord targetApp = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);

这里会先去找对应 Client App 进程的 ProcessRecorder , 找不到代表 Application 刚启动尚未初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private ProcessRecord performStartProcessLocked(int vuid, int vpid, ApplicationInfo info, String processName) {
ProcessRecord app = new ProcessRecord(info, processName, vuid, vpid);
Bundle extras = new Bundle();
BundleCompat.putBinder(extras, "_VA_|_binder_", app);
extras.putInt("_VA_|_vuid_", vuid);
extras.putString("_VA_|_process_", processName);
extras.putString("_VA_|_pkg_", info.packageName);

// 调用子程序包的 init_process 方法,并且得到子程序包 IBinder 句柄
Bundle res = ProviderCall.call(VASettings.getStubAuthority(vpid), "_VA_|_init_process_", null, extras);
if (res == null) {
return null;
}
int pid = res.getInt("_VA_|_pid_");
IBinder clientBinder = BundleCompat.getBinder(res, "_VA_|_client_");
// attach 到 Client 的 VAM
attachClient(pid, clientBinder);
return app;
}

ProviderCall.callClient AppStubContentProvider 发起远程调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public Bundle call(String method, String arg, Bundle extras) {
if ("_VA_|_init_process_".equals(method)) {
return initProcess(extras);
}
return null;
}

private Bundle initProcess(Bundle extras) {
ConditionVariable lock = VirtualCore.get().getInitLock();
if (lock != null) {
lock.block();
}
IBinder token = BundleCompat.getBinder(extras,"_VA_|_binder_");
int vuid = extras.getInt("_VA_|_vuid_");
VClientImpl client = VClientImpl.get();
client.initProcess(token, vuid);
Bundle res = new Bundle();
BundleCompat.putBinder(res, "_VA_|_client_", client.asBinder());
res.putInt("_VA_|_pid_", Process.myPid());
return res;
}

Client AppIBinder 句柄(VClientImpl.asBinder) 被打包在了 Bundle 中返回给 VAMS

最终 VAMS 调用原生 AMstartActivity 向真正的 AMS 发送替换成 StubActivity 的伪造 Intent

1
mirror.android.app.IActivityManager.startActivity.call(ActivityManagerNative.getDefault.call(), (Object[]) args);

恢复原 Intent 重定向到原 Activity

AMS 收到伪装的 Intent 后,就会找到 StubActivity ,这时流程回到 VA 里的主线程中的消息队列中。

Hook 过程就是用我们自己的 Handler 替换 android.os.Handler.mCallback 因为主线程在这里分发一些操作。

1
2
3
4
5
@Override
public void inject() throws Throwable {
otherCallback = getHCallback();
mirror.android.os.Handler.mCallback.set(getH(), this);
}

handlerMessage 判断是 LAUNCH_ACTIVITY Action 后直接调用了 handlerLaunchActivity 方法,和原版其实很像。

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
private boolean handleLaunchActivity(Message msg) {
Object r = msg.obj;
Intent stubIntent = ActivityThread.ActivityClientRecord.intent.get(r);
// 获取原版 Intent 信息
StubActivityRecord saveInstance = new StubActivityRecord(stubIntent);
if (saveInstance.intent == null) {
return true;
}
// 原版 Intent
Intent intent = saveInstance.intent;
ComponentName caller = saveInstance.caller;
IBinder token = ActivityThread.ActivityClientRecord.token.get(r);
ActivityInfo info = saveInstance.info;

// 如果 token 还没初始化,代表 App 刚刚启动第一个组件
if (VClientImpl.get().getToken() == null) {
VActivityManager.get().processRestarted(info.packageName, info.processName, saveInstance.userId);
getH().sendMessageAtFrontOfQueue(Message.obtain(msg));
return false;
}
// AppBindData 为空,则 App 信息不明
if (!VClientImpl.get().isBound()) {
// 初始化并绑定 Application
VClientImpl.get().bindApplication(info.packageName, info.processName);
getH().sendMessageAtFrontOfQueue(Message.obtain(msg));
return false;
}

// 获取 TaskId
int taskId = IActivityManager.getTaskForActivity.call(
ActivityManagerNative.getDefault.call(),
token,
false
);

// 1.将 ActivityRecorder 加入 mActivities 2.通知服务端 VAMS Activity 创建完成
VActivityManager.get().onActivityCreate(ComponentUtils.toComponentName(info), caller, token, info, intent, ComponentUtils.getTaskAffinity(info), taskId, info.launchMode, info.flags);
ClassLoader appClassLoader = VClientImpl.get().getClassLoader(info.applicationInfo);
intent.setExtrasClassLoader(appClassLoader);
// 将 Host Stub Activity Intent 替换为原版 Intent
ActivityThread.ActivityClientRecord.intent.set(r, intent);
// 同上
ActivityThread.ActivityClientRecord.activityInfo.set(r, info);
return true;
}

需要注意的是,如果这个 Activity 是这个 Apk 启动的第一个组件,则需要 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
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
private void bindApplicationNoCheck(String packageName, String processName, ConditionVariable lock) {
mTempLock = lock;
try {
// 设置未捕获异常的 Callback
setupUncaughtHandler();
} catch (Throwable e) {
e.printStackTrace();
}
try {
// 修复 Provider 信息
fixInstalledProviders();
} catch (Throwable e) {
e.printStackTrace();
}
mirror.android.os.Build.SERIAL.set(deviceInfo.serial);
mirror.android.os.Build.DEVICE.set(Build.DEVICE.replace(" ", "_"));
ActivityThread.mInitialApplication.set(
VirtualCore.mainThread(),
null
);
// 从 VPMS 获取 apk 信息
AppBindData data = new AppBindData();
InstalledAppInfo info = VirtualCore.get().getInstalledAppInfo(packageName, 0);
if (info == null) {
new Exception("App not exist!").printStackTrace();
Process.killProcess(0);
System.exit(0);
}
// dex 优化的开关,dalvik 和 art 处理不同
if (!info.dependSystem && info.skipDexOpt) {
VLog.d(TAG, "Dex opt skipped.");
if (VirtualRuntime.isArt()) {
ARTUtils.init(VirtualCore.get().getContext());
ARTUtils.setIsDex2oatEnabled(false);
} else {
DalvikUtils.init();
DalvikUtils.setDexOptMode(DalvikUtils.OPTIMIZE_MODE_NONE);
}
}
data.appInfo = VPackageManager.get().getApplicationInfo(packageName, 0, getUserId(vuid));
data.processName = processName;
data.providers = VPackageManager.get().queryContentProviders(processName, getVUid(), PackageManager.GET_META_DATA);
Log.i(TAG, "Binding application " + data.appInfo.packageName + " (" + data.processName + ")");
mBoundApplication = data;
// 主要设置进程的名字
VirtualRuntime.setupRuntime(data.processName, data.appInfo);
int targetSdkVersion = data.appInfo.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 (mirror.android.os.StrictMode.sVmPolicyMask != null) {
mirror.android.os.StrictMode.sVmPolicyMask.set(0);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
mirror.android.os.Message.updateCheckRecycle.call(targetSdkVersion);
}
if (VASettings.ENABLE_IO_REDIRECT) {
// IO 重定向
startIOUniformer();
}
// hook native 函数
NativeEngine.hookNative();
Object mainThread = VirtualCore.mainThread();
// 准备 dex 列表
NativeEngine.startDexOverride();
// 获得子 pkg 的 Context 前提是必须在系统中安装的(疑问?)
Context context = createPackageContext(data.appInfo.packageName);
// 设置虚拟机系统环境 临时文件夹 codeCacheDir
System.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
// oat 的 cache 目录
File codeCacheDir;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
codeCacheDir = context.getCodeCacheDir();
} else {
codeCacheDir = context.getCacheDir();
}
// 硬件加速的 cache 目录
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
if (HardwareRenderer.setupDiskCache != null) {
HardwareRenderer.setupDiskCache.call(codeCacheDir);
}
} else {
if (ThreadedRenderer.setupDiskCache != null) {
ThreadedRenderer.setupDiskCache.call(codeCacheDir);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (RenderScriptCacheDir.setupDiskCache != null) {
RenderScriptCacheDir.setupDiskCache.call(codeCacheDir);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (RenderScript.setupDiskCache != null) {
RenderScript.setupDiskCache.call(codeCacheDir);
}
}

// 修复子 App 中 ActivityThread.AppBinderData 的参数,因为之前用的是在 Host 程序中注册的 Stub 的信息
Object boundApp = fixBoundApp(mBoundApplication);
mBoundApplication.info = ContextImpl.mPackageInfo.get(context);
mirror.android.app.ActivityThread.AppBindData.info.set(boundApp, data.info);

// 同样修复 targetSdkVersion 原来也是可 Host 程序一样的
VMRuntime.setTargetSdkVersion.call(VMRuntime.getRuntime.call(), data.appInfo.targetSdkVersion);

boolean conflict = SpecialComponentList.isConflictingInstrumentation(packageName);
if (!conflict) {
InvocationStubManager.getInstance().checkEnv(AppInstrumentation.class);
}

// 开始构建子程序包的 Application 对象,并且替换原来通过 Host Stub 生成的 mInitialApplication
mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
mirror.android.app.ActivityThread.mInitialApplication.set(mainThread, mInitialApplication);
ContextFixer.fixContext(mInitialApplication);
if (data.providers != null) {
// 注册 Providers
installContentProviders(mInitialApplication, data.providers);
}
// 初始化锁开,异步调用的初始化函数可以返回了
if (lock != null) {
lock.open();
mTempLock = null;
}
try {
// 调用 Application.onCreate
mInstrumentation.callApplicationOnCreate(mInitialApplication);
InvocationStubManager.getInstance().checkEnv(HCallbackStub.class);
if (conflict) {
InvocationStubManager.getInstance().checkEnv(AppInstrumentation.class);
}
Application createdApp = ActivityThread.mInitialApplication.get(mainThread);
if (createdApp != null) {
mInitialApplication = createdApp;
}
} catch (Exception e) {
if (!mInstrumentation.onException(mInitialApplication, e)) {
throw new RuntimeException(
"Unable to create application " + mInitialApplication.getClass().getName()
+ ": " + e.toString(), e);
}
}
VActivityManager.get().appDoneExecuting();
}

private void setupUncaughtHandler() {
ThreadGroup root = Thread.currentThread().getThreadGroup();
while (root.getParent() != null) {
root = root.getParent();
}
ThreadGroup newRoot = new RootThreadGroup(root);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
final List<ThreadGroup> groups = mirror.java.lang.ThreadGroup.groups.get(root);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (groups) {
List<ThreadGroup> newGroups = new ArrayList<>(groups);
newGroups.remove(newRoot);
mirror.java.lang.ThreadGroup.groups.set(newRoot, newGroups);
groups.clear();
groups.add(newRoot);
mirror.java.lang.ThreadGroup.groups.set(root, groups);
for (ThreadGroup group : newGroups) {
mirror.java.lang.ThreadGroup.parent.set(group, newRoot);
}
}
} else {
final ThreadGroup[] groups = ThreadGroupN.groups.get(root);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (groups) {
ThreadGroup[] newGroups = groups.clone();
ThreadGroupN.groups.set(newRoot, newGroups);
ThreadGroupN.groups.set(root, new ThreadGroup[]{newRoot});
for (Object group : newGroups) {
ThreadGroupN.parent.set(group, newRoot);
}
ThreadGroupN.ngroups.set(root, 1);
}
}
}

bindApplication 主要做了以下几个事情:

  1. VPMS 获取 APK 的信息,根据设置控制 Dex 优化的开关。
  2. 调用 mirror.android.os.Process.setArgV0.call(processName); 设置进程的名称,如果不设置则还是 p0 p1 这样。
  3. nativeHook 主要 Hook 一些 native 的函数,主要是一些 IO 函数,包括文件访问重定向等等。
  4. 准备一些 cache 临时文件夹。
  5. 设置 AppBinderDataAppBinderData 内部包含了 ApplicationInfoprovider 信息等重要的 apk 信息。可以理解为 framework 所需要的关键数据结构。
  6. 安装 ContentProvider
  7. 初始化用户的 Application 对象,并通过 Instrumentation 调用其 onCreate ,代表着 Client App 的生命周期正式开始。

最后成功从 StubActivity Intent 还原出来的原版 Intent 被继续交给原生的 AM

1
2
3
4
// 将 Host Stub Activity Intent 替换为原版 Intent
ActivityThread.ActivityClientRecord.intent.set(r, intent);
// 同上
ActivityThread.ActivityClientRecord.activityInfo.set(r, info);

最后,最后一个 Hook 点在 Instrumentation.callActivityOnCreate

因为 AMS 实际上启动的是 StubActivity 的关系,真正的 Activity 的一些信息还不是其真正的信息,比如主题之类的,所以需要在这个时机修复一下,选择这个时间修复的原因也是因为 Activity 已经被 new 出来了,而且资源已经准备完毕。

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 void callActivityOnCreate(Activity activity, Bundle icicle) {
VirtualCore.get().getComponentDelegate().beforeActivityCreate(activity);
IBinder token = mirror.android.app.Activity.mToken.get(activity);
ActivityClientRecord r = VActivityManager.get().getActivityRecord(token);
// 替换 Activity 对象
if (r != null) {
r.activity = activity;
}
ContextFixer.fixContext(activity);
ActivityFixer.fixActivity(activity);
ActivityInfo info = null;
if (r != null) {
info = r.info;
}
// 设置主题和屏幕纵横控制
if (info != null) {
if (info.theme != 0) {
activity.setTheme(info.theme);
}
if (activity.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
&& info.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
activity.setRequestedOrientation(info.screenOrientation);
}
}
super.callActivityOnCreate(activity, icicle);
VirtualCore.get().getComponentDelegate().afterActivityCreate(activity);
}

下一章介绍 Service 的代理。