Android绕过授权方法实践

*郑重声明:本文内容只为学习研究之用,如有人用于非法用途,产生的后果笔者不负任何责任。

设备root过的Android7.1

上篇文章中我们跟着沿着源码追溯了Android系统授权的过程,这篇文章记录下怎么绕过系统的校验。

可以看到检查权限时的一个分支为:

1
2
3
4
// Root, system server get to do everything.
if (uid == 0 || uid == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}

也就是说,我们只需要将自己的app的uid伪装成0或者1000即可绕过授权,直接返回PackageManager.PERMISSION_GRANTED.

在Android 8之前,uid判断在ActivityManager.checkComponentPermission中。从Android 8开始,这段代码前置到了ContextImpl.java中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1659    @Override
1660 public int checkPermission(String permission, int pid, int uid) {
1661 if (permission == null) {
1662 throw new IllegalArgumentException("permission is null");
1663 }
1664
1665 final IActivityManager am = ActivityManager.getService();
1666 if (am == null) {
1667 // Well this is super awkward; we somehow don't have an active
1668 // ActivityManager instance. If we're testing a root or system
1669 // UID, then they totally have whatever permission this is.
1670 final int appId = UserHandle.getAppId(uid);
1671 if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
1672 Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
1673 return PackageManager.PERMISSION_GRANTED;
1674 }
1675 }
1676
1677 try {
1678 return am.checkPermission(permission, pid, uid);
1679 } catch (RemoteException e) {
1680 throw e.rethrowFromSystemServer();
1681 }
1682 }

我们以新版本为例进行绕过实践。

修改uid之getuid

一开始我的思路是,既然设备已经root了,直接执行su不就好了。实践证明不可行。

Runtime.exec(“su”)后 只是这个返回Process类的输出流其写入的命令行参数执行的环境才是uid=0的进程环境, 在此之外, 整个app 的uid并不是0, 而是不变。

那么,考虑利用su直接修改记录了uid的/data/system.packages.list,把对应的uid改为0。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        Process process = null;
try {
process = Runtime.getRuntime().exec("su");
DataOutputStream dataOutputStream = new DataOutputStream(process.getOutputStream());
dataOutputStream.writeBytes("chmod 777 /data/system/packages.list \n");
dataOutputStream.writeBytes("sed -i '/package.name/d' /data/system/packages.list \n");
dataOutputStream.writeBytes("sed -i '$a package.name 0 1 /data/user/0/com.android.sparrow default 3003' /data/system/packages.list");
// dataOutputStream.writeBytes("reboot \n");

dataOutputStream.flush();
dataOutputStream.close();
process.waitFor();
process.destroy();

} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}

执行成功!

但是app读取的uid依然没变,说明判断权限时的UID不是从这个文件读取的。

ActivityCompat.checkSelfPermission()中的调用语句:context.checkPermission(permission, android.os.Process.myPid(), Process.myUid())

可以看出uid是由Process.myUid()返回的:

1
2
3
public static final int myUid() {
return Os.getuid();
}

Os:

1
2
3
4
/**
* See <a href="http://man7.org/linux/man-pages/man2/getuid.2.html">getuid(2)</a>.
*/
public static int getuid() { return Libcore.os.getuid(); }

Libcore类:

1
2
3
4
5
6
7
17package libcore.io;
18
19public final class Libcore {
20 private Libcore() { }
21
22 public static Os os = new BlockGuardOs(new Posix());
23}

可知调用了BlockGuardOs对象的getUid()方法,实现在其父类ForwardingOs:

1
public int getuid() { return os.getuid(); }

这里的os即为传入的Posix对象,

1
2
3
4
5
6
 	  protected final Os os;
48
49 public ForwardingOs(Os os) {
50 this.os = os;
51 }
98 public int getuid() { return os.getuid(); }

Posix中:

1
public native int getuid();

调用了Native方法。

http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/native/libcore_io_Posix.cpp

1
2
3
	static jint Posix_getuid(JNIEnv*, jobject) {
1062 return getuid();
1063}

到这里,本该去看getuid()的实现,但是到这里就断了,没有找到。

没办法,只能回头看看uid是怎么产生的,看能不能在生成uid时动动手脚。

修改uid之生成uid

uid是应用安装时被分配的,具体过程的源码分析网上比较多,这里不具体说了。

涉及到的关键类PackageManagerService,它负责解析AndroidManifest.xml,从中得到应用程序相关信息。

http://androidxref.com/6.0.1_r10/xref/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

PackageManagerService.main()入手,它负责把PackageManagerService启动起来:

1
2
3
4
5
6
7
    public static PackageManagerService main(Context context, Installer installer,
1766 boolean factoryTest, boolean onlyCore) {
1767 PackageManagerService m = new PackageManagerService(context, installer,
1768 factoryTest, onlyCore);
1769 ServiceManager.addService("package", m);
1770 return m;
1771 }

PackageManagerService的构造方法是一个六百多行的方法,接下来我们详细分析。

首先,初始化一些保存各种信息的数据结构,其中比较关键的是Settings对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
			......
mSettings = new Settings(mPackages);
1816 mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
1817 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
1818 mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
1819 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
1820 mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
1821 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
1822 mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
1823 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
1824 mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
1825 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
1826 mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
1827 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
......

http://androidxref.com/8.1.0_r33/xref/frameworks/base/services/core/java/com/android/server/pm/Settings.java

Settings对象初始化时创建了一系列文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
428    Settings(File dataDir, Object lock) {
429 mLock = lock;
430
431 mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
432
433 mSystemDir = new File(dataDir, "system");
434 mSystemDir.mkdirs();
435 FileUtils.setPermissions(mSystemDir.toString(),
436 FileUtils.S_IRWXU|FileUtils.S_IRWXG
437 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
438 -1, -1);
439 mSettingsFilename = new File(mSystemDir, "packages.xml");
440 mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
441 mPackageListFilename = new File(mSystemDir, "packages.list");
442 FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
443
444 final File kernelDir = new File("/config/sdcardfs");
445 mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;
446
447 // Deprecated: Needed for migration
448 mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
449 mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
450 }

packages.xml:存放系统中安装的所有应用的包名、前面、缓存目录、权限等信息

packages.list:存放应用包名、uid、安装目录、gid等信息

接下来在PackageManagerService构造方法中,从SystemConfig中获取了权限配置,以及从SELinuxMMAC读物安装策略等。

然后,就该扫描系统中的apk文件了。

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
		synchronized (mInstallLock) {
1870 // writer
1871 synchronized (mPackages) {
......
// Collect vendor overlay packages.
2063 // (Do this before scanning any apps.)
2064 // For security and version matching reason, only consider
2065 // overlay packages if they reside in VENDOR_OVERLAY_DIR.
2066 File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
2067 scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
2068 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
2069
2070 // Find base frameworks (resource packages without code).
2071 scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
2072 | PackageParser.PARSE_IS_SYSTEM_DIR
2073 | PackageParser.PARSE_IS_PRIVILEGED,
2074 scanFlags | SCAN_NO_DEX, 0);
2075
2076 // Collected privileged system packages.
2077 final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
2078 scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
2079 | PackageParser.PARSE_IS_SYSTEM_DIR
2080 | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
2081
2082 // Collect ordinary system packages.
2083 final File systemAppDir = new File(Environment.getRootDirectory(), "app");
2084 scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
2085 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
2086
2087 // Collect all vendor packages.
2088 File vendorAppDir = new File("/vendor/app");
2089 try {
2090 vendorAppDir = vendorAppDir.getCanonicalFile();
2091 } catch (IOException e) {
2092 // failed to look up canonical path, continue with original one
2093 }
2094 scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
2095 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
2096
2097 // Collect all OEM packages.
2098 final File oemAppDir = new File(Environment.getOemDirectory(), "app");
2099 scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
2100 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
2101
2102 if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
2103 mInstaller.moveFiles();
}
}

进入scanDirLI:

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
5624    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
5625 final File[] files = dir.listFiles();
5626 if (ArrayUtils.isEmpty(files)) {
5627 Log.d(TAG, "No files in app dir " + dir);
5628 return;
5629 }
5630
5631 if (DEBUG_PACKAGE_SCANNING) {
5632 Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
5633 + " flags=0x" + Integer.toHexString(parseFlags));
5634 }
5635
5636 for (File file : files) {
5637 final boolean isPackage = (isApkFile(file) || file.isDirectory())
5638 && !PackageInstallerService.isStageName(file.getName());
5639 if (!isPackage) {
5640 // Ignore entries which are not packages
5641 continue;
5642 }
5643 try {
5644 scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
5645 scanFlags, currentTime, null);
5646 } catch (PackageManagerException e) {
5647 Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
5648
5649 // Delete invalid userdata apps
5650 if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
5651 e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
5652 logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
5653 if (file.isDirectory()) {
5654 mInstaller.rmPackageDir(file.getAbsolutePath());
5655 } else {
5656 file.delete();
5657 }
5658 }
5659 }
5660 }
5661 }

可以看到关键语句为:

scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,scanFlags, currentTime, null);

浏览并解析一个package:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
5735    private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
5736 long currentTime, UserHandle user) throws PackageManagerException {
......
5739 PackageParser pp = new PackageParser();
......
5749 try {
5750 pkg = pp.parsePackage(scanFile, parseFlags);
5751 } catch (PackageParserException e) {
5752 throw PackageManagerException.from(e);
5753 }
......
5931 // Set application objects path explicitly.
5932 pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
5933 pkg.applicationInfo.setCodePath(pkg.codePath);
5934 pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
5935 pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
5936 pkg.applicationInfo.setResourcePath(resourcePath);
5937 pkg.applicationInfo.setBaseResourcePath(baseResourcePath);
5938 pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths);
5939
5941 PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
5942 | SCAN_UPDATE_SIGNATURE, currentTime, user);
......
}

首先是解析APK包,进入PackageParser.parsePackage():

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/content/pm/PackageParser.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
752    public Package parsePackage(File packageFile, int flags) throws PackageParserException {
753 if (packageFile.isDirectory()) {
754 return parseClusterPackage(packageFile, flags);
755 } else {
756 return parseMonolithicPackage(packageFile, flags);
757 }
758 }

826 @Deprecated
827 public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
828 ......
836 final AssetManager assets = new AssetManager();
837 try {
838 final Package pkg = parseBaseApk(apkFile, assets, flags);
839 pkg.codePath = apkFile.getAbsolutePath();
840 return pkg;
841 } finally {
842 IoUtils.closeQuietly(assets);
843 }
844 }

进入parseBaseApk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
879        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
880
881 Resources res = null;
882 XmlResourceParser parser = null;
883 try {
884 res = new Resources(assets, mMetrics, null);
885 assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
886 Build.VERSION.RESOURCES_SDK_INT);
887 parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
888
889 final String[] outError = new String[1];
890 final Package pkg = parseBaseApk(res, parser, flags, outError);
891 if (pkg == null) {
892 throw new PackageParserException(mParseError,
893 apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
894 }
895
896 pkg.volumeUuid = volumeUuid;
897 pkg.applicationInfo.volumeUuid = volumeUuid;
898 pkg.baseCodePath = apkPath;
899 pkg.mSignatures = null;
900
901 return pkg;

可以看到又调用了一个parseBaseApk方法,但是参数变了,功能是解析Manifest:

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
1348    /**
1349 * Parse the manifest of a <em>base APK</em>.
1350 * <p>
1351 * When adding new features, carefully consider if they should also be
1352 * supported by split APKs.
1353 */
1354 private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
1355 String[] outError) throws XmlPullParserException, IOException {
......
1358 AttributeSet attrs = parser;
......
Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
1369 pkgName = packageSplit.first;
1370 splitName = packageSplit.second;
......
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1442 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
.......
if (tagName.equals("application")) {
......
} else if (tagName.equals("overlay")) {
......
}else if (tagName.equals("permission-group")) {
......
}
......
}

解析完apk包,调用了PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user);:

1
2
3
4
5
6
7
8
9
10
11
6466    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
6467 int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
6468 boolean success = false;
6469 try {
6470 final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
6471 currentTime, user);
6472 success = true;
6473 return res;
6474 }
......
6479 }

进入scanPackageDirtyLI,给app分配uid过程就在其中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
6481    private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
6482 int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
......
6591 SharedUserSetting suid = null;
6592 PackageSetting pkgSetting = null;
......
6672 // Just create the setting, don't add it yet. For already existing packages
6673 // the PkgSetting exists already and doesn't have to be created.
6674 pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
6675 destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
6676 pkg.applicationInfo.primaryCpuAbi,
6677 pkg.applicationInfo.secondaryCpuAbi,
6678 pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,
6679 user, false);
......

}

app包解析完,配置都获取到之后,就该写文件了,回到PackageManagerService构造方法:

1
2288            updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
1
2
3
4
5
8244    private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
8245 int flags) {
8246 final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
8247 updatePermissionsLPw(changingPkg, pkgInfo, volumeUuid, flags);
8248 }
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
8250    private void updatePermissionsLPw(String changingPkg,
8251 PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
......
8315 // Now update the permissions for all packages, in particular
8316 // replace the granted permissions of the system packages.
8317 if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
8318 for (PackageParser.Package pkg : mPackages.values()) {
8319 if (pkg != pkgInfo) {
8320 // Only replace for packages on requested volume
8321 final String volumeUuid = getVolumeUuidForPackage(pkg);
8322 final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
8323 && Objects.equals(replaceVolumeUuid, volumeUuid);
8324 grantPermissionsLPw(pkg, replace, changingPkg);
8325 }
8326 }
8327 }
8328
8329 if (pkgInfo != null) {
8330 // Only replace for packages on requested volume
8331 final String volumeUuid = getVolumeUuidForPackage(pkgInfo);
8332 final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
8333 && Objects.equals(replaceVolumeUuid, volumeUuid);
8334 grantPermissionsLPw(pkgInfo, replace, changingPkg);
8335 }
}

grantPermissionsLPw, 经过一系列解析最终调用:

1
2
3
8600        for (int userId : changedRuntimePermissionUserIds) {
8601 mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);
8602 }
1
2
3
4
5
6
7
5232    public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
5233 if (sync) {
5234 mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
5235 } else {
5236 mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
5237 }
5238 }

那么,修改app的uid的方法考虑两种方案:

(1)反射调用writeRuntimePermissionsForUserLPr,参数简单容易构造;

(2)修改最终写入的uid,packages.xml

目前还未实践出结果。

欲知后事如何,且听下回分解。

引用文献:

【1】https://blog.csdn.net/u014418171/article/details/103868878?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160930023616780302911830%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160930023616780302911830&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-1-103868878.first_rank_v2_pc_rank_v29&utm_term=Android%E5%AE%89%E5%85%A8%E4%B9%8B%E4%BD%BF%E7%94%A8root%E6%9D%83%E9%99%90%E7%BB%95%E8%BF%87%E6%A3%80%E6%B5%8B%E6%9C%BA%E5%88%B6,%E5%BC%BA%E8%A1%8C%E8%87%AA%E5%8A%A8%E5%85%81%E8%AE%B8%E5%BA%94%E7%94%A8%E7%9A%84%E6%82%AC%E6%B5%AE%E7%AA%97/%E5%BA%94%E7%94%A8%E5%90%8E%E5%8F%B0%E5%BC%B9%E5%87%BA%E7%95%8C%E9%9D%A2%E7%AD%89%E6%9D%83%E9%99%90

【2】https://blog.csdn.net/zhangyongfeiyong/article/details/52292347