背景
接口信息泄漏的问题在中国大陆OEM上已经不是什么新鲜漏洞,主要也是喜欢做多屏协同这类对Window和Activity“下手”的特性,但是这次遇到的这两个同样的漏洞,还是让我觉得很有趣,在这里跟大家分享一下。
第一个漏洞
最早是在看到了一个名为getActivityConfigs的接口,逻辑大概是这样的(隐去部分信息):
@Override // com.android.server.wm./* hide */
public Bundle getActivityConfigs(IBinder token, String pkgName) {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_SMART_WINDOW, isNeedSmartWindow(pkgName) || isNeedTvSmartWindow(pkgName));
ActivityRecord activityRecord = null;
synchronized (this.mIAtmsInner.getATMS().getGlobalLock()) {
if (token != null) { // [1]
try {
activityRecord = ActivityRecord.isInRootTaskLocked(token);
} finally {
}
}
if (token == null && pkgName != null) {
activityRecord = this.mIAtmsInner.getLastResumedActivityRecord(); // [2]
}
// [3]
if (activityRecord != null && (TextUtils.equals(activityRecord.packageName, pkgName) ||
/* hide */)) {
/* hide */
Task task = activityRecord.getTask();
if (task != null) { // [4]
bundle.putBoolean(KEY_FIXED_SIZE, task.isFixedSize());
bundle.putBoolean("hasCaption", task.isHwStackShowCaption);
bundle.putBoolean(HwActivityTaskManager./* hide */, task.isAlwaysOnTop());
/* hide */
int policyMode = task.getWindowingMode();
bundle.putInt("android.activity.windowingMode", policyMode);
if (policyMode == 101) {
policyMode = 100;
}
if (policyMode == 102) {
policyMode = 102;
}
int displayId = activityRecord.getDisplayId();
bundle.putFloat(KEY_CORNER_RADIUS, getCornerRadius(policyMode, displayId));
bundle.putBoolean(KEY_CAPTION_BAR, isNeedCaptionBar(policyMode, displayId));
}
return bundle; // [5]
}
return bundle;
}
}
首先是没有权限检查,但是这个接口能带来什么呢?在标记1的地方会取我们传入的IBinder token,并且传入isInRootTaskLocked,这里其实是没办法做什么的,因为正常情况下也没办法知道别人的IBinder token。但是到标记2这里给了一个逃生通道,如果token为null也没关系,只要pkgName不是null就会获取getLastResumedActivityRecord,这个的LastResumedActivity在AOSP代码中的解释是:
The last resumed activity. This is identical to the current resumed activity most of the time but could be different when we’re pausing one activity before we resume another activity.
其实简单点来说,就是用户当前前台的Activity。后面就比较有趣了,在标记3的地方会判断LastResumedActivity.packageName是否和入参pkgName相同(后面或的条件成立与否不影响),若相同并且activityRecord.getTask()不为空,会在标记4那里写入很多内容到bundle,并在标记5的地方返回。这里就可以通过遍历所有的包名,传入这个函数,去轮询判断哪个包名才是当前LastResumedActivity所在的,从而知道哪个应用位于前台显示,造成信息泄漏。
复制粘帖的“漏洞”
如果只是第一个漏洞,那其实没什么意思,仅仅是一个无聊的信息泄漏漏洞而已。但是我在查看另一个中国大陆OEM厂商的代码时,居然发现了几乎一模一样的代码,而且从影响版本来看出现的比前者要晚一些,也就是说很有可能是复制粘帖的“漏洞。”具体有多“一模一样”,我贴出来大家自己评判(隐去部分信息):
public Bundle getActivityConfigs(IBinder token, String packageName) {
Task task;
Bundle bundle = new Bundle();
ActivityRecord activityRecord = null;
synchronized (this.mAtms.mGlobalLock) {
if (token != null) {
try {
activityRecord = ActivityRecord.isInRootTaskLocked(token);
} catch (Throwable th) {
throw th;
}
}
if (token == null && packageName != null) {
activityRecord = this.mAtms.mLastResumedActivity;
}
if (activityRecord != null && TextUtils.equals(activityRecord.packageName, packageName) && (task = activityRecord.getTask()) != null) {
/* hide */
bundle.putBoolean(/* hide */.KEY_SUPPORT_SPLIT_SCREEN_WINDOWING_MODE, isSupportMultiWindowMode);
bundle.putBoolean(/* hide */.KEY_LAUNCH_TASK_EMBEDDED, task.getWrapper().getExtImpl().isTaskEmbedded());
bundle.putInt(/* hide */.KEY_LAUNCH_TASK_SCENARIO, task.getWrapper().getExtImpl().getLaunchScenario());
bundle.putBoolean(/* hide */.KEY_SUPER_LOCK, task.getWrapper().getExtImpl().isSmartBackend());
}
}
return bundle;
}
同样可以通过包名轮询去获取前台显示应用。
后记
本文这两个漏洞实在是一则“抄代码抄走一模一样漏洞”的“悲伤”故事,后续将这两个漏洞都上报给了对应的厂商,而获得的奖励也大有不同,前者达到了5位数,后者却只有3位数。具体品牌不点名,有兴趣的研究人员通过自己动手很容易即可知道是哪两家厂商。从漏洞的“复制粘帖”到奖金的如此差距,可以看出不同厂商对创新和安全的态度,最终反映在市场也是品牌形象的巨大差距。