Hello, I’m @topjohnwu, the developer of the popular Android modding tool: Magisk. In this article I’d love to share the whole journey from the process of analyzing the root detection mechanism of a popular anime game Fate/Grand Order (US/JP), to discovering a widespread security/privacy bug that exists on potentially millions of Android devices.
Table of Contents
A massive amount of Android devices are affected by a bug that causes
/procto be mounted without
hidepid=2, which opens up the ability for unprivileged applications to read many information of other processes. Google promptly updated its Compatibility Test Suite (CTS) to prevent any future systems* containing this bug from shipping to end users. This vulnerability is not severe but still should be fixed since it is part of the Android application sandbox. The way to mitigate this issue is either through a system upgrade, or remounting
/procwith proper flags with root permission.
I created the app ProcGate to let you detect (no root) and fix this issue (only if rooted), you can download it here.
* CTS is only required for devices with Google Services, which pretty much means all Android devices other than those in China and Amazon Android devices.
I will not cover what Magisk is, what it can do, and how it works; this is for readers with decent knowledge about Android rooting, and hopefully also a proud Magisk user 😁. Some terms used in this article might sound unfamiliar, which could be Magisk internal details. You can gracefully skip those parts, but for those willing to go down the rabbit hole, check the full Magisk Documentation.
Without further ado, let’s get right into it.
I’ve been fighting with all sorts of weird device integrity detection methods over the years, but nothing is near the level of FGO. For some reason, the developers at Aniplex are extremely obsessed with detecting Magisk, as if it is an ultimate mission they are destined to accomplish. Other than the standard and common sensitive binaries, mount point, and environment variables checks, they went out the way to ban USB debugging, blacklist the package name of Magisk Manager (
com.topjohnwu.magisk), and also parse through all system services and blacklisted Magisk related services. All of them are mitigated in Magisk with various different techniques that I will spare you from the lengthy details.
OnePlus 6 became my main development phone since I switched to Pixel 3 XL (I don’t usually use my daily driver for development), so all initial tests are done on that device. FGO has become a “benchmark” for the effectiveness of MagiskHide for quite a while, and one day I found out that the app no longer launches with the standard MagiskHide + Magisk Manager repackaging treatment. For the record, all tests are done with Fate/Grand Order US version (
com.aniplex.fategrandorder.en), version 1.23.0.
To do any analysis, USB debugging (ADB) is pretty much required. The Aniplex devs are smart and sneaky: the app simply refuses to run with ADB enabled. I’ve got some tricks up my sleeves so it is possible for me to enable ADB without being detected. However, I decide not to disclose the trick publicly since I really don’t want to make my life more difficult in the future. Just keep in mind that all following steps are done in a USB debuggable environment.
The first step is to experiment using stripped down Magisk versions to isolate the issue. The process is pretty straightforward: start from nearly pure stock, and gradually add more features until we hit a wall. Let’s try nano-Magisk: root daemon is launched, but no binaries are exposed to any unprivileged process. FGO works fine, great! Now let’s try micro-Magisk:
/sbin tmpfs overlay is enabled. This does not include MagiskHide daemon, so the hiding procedures are done manually, which when applied the effect is both globally and permanently (meaning no process can ever gain root after applying the first hiding). FGO works fine, great! Next let’s try mini-Magisk: logcat monitor with MagiskHide is enabled, and no manual hiding is done. Finally FGO refuses to launch!
MagiskHide exploits the fact that Android apps’ processes are
mount_namespace isolated. Magisk modifications are only reverted and hidden in specific target processes, which is the reason why non-target processes can still use root graciously. Through experiments, the issue can theoretically be narrowed down to:
- The effectiveness of process monitoring (FGO spawns processes that are not handled by Magisk)
- MagiskHide is simply just too slow (FGO detects root before MagiskHide have a chance to hijack the process)
The 2nd reason is extremely unlikely, so let’s assume the first scenario. To monitor all new processes spawned from Zygote(
app_process)(we don’t care child process forked from target processes), we can go through
am_proc_start logs (accessed via events buffer:
logcat -b events) and check if there are any weird process launched in the background. Hmm… doesn’t seem to be the case, something is definitely fishy here. It’s about time to pull out the big guns.
We utilize the tool strace to analyze what’s going on with FGO without taking apart the APK and do reverse engineering. If you want the full strace output, here you go (the target process PID is
9184). BTW here is an interesting info within the traces, the blacklisted packages (and some files):
It also attempts to read current mount points in
/proc/self/mount, environment variables via
/proc/self/environ, tries to open
/root, the path where Magisk store overlayed sbin binaries (access denied of course, who will let them read it 💩). But to my surprise, they are traversing the whole
/proc folder and reading mount info from other processes! Thank god it seems all access are denied… oh wait a minute. WTF. It successfully read mount info of a random process (PID =
4053), and detected Magisk through it!
I then tried running FGO on a Pixel XL, and without surprise the app actually do work with a proper MagiskHide setup. What is wrong here?
I recall I once tried calling
ps -A in terminal emulators without root, and it doesn’t return anything other than the process itself and its child processes. I quickly pulled out the good old trusty Terminal Emulator for Android, ran the command
ps -A on my OnePlus 6 and… oh my god.
This is NOT expected. There is definitely an issue here.
After a few days of investigation, the issue is narrowed down to a misconfiguration when mounting
/proc. Ever since Android 7.0,
/proc should be mounted with
hidepid=2 (more info here). However, on the OnePlus 6 running stock OOS 9.0.2, it is not mounted with the flag enabled.
On Android 9.0, SELinux is strengthened to isolate every single app individually, so in theory even without
hidepid=2 enforced, all apps do not have access to any other process. There is a catch: when an app is targeting different API levels, the system will apply different SElinux rules to its process. So for an app targeting anything lower than API 28 (Android 9.0), the system will apply the old SELinux rules, and thus is capable of accessing info of other processes that are running in the same secontext.
For example the terminal emulator app
jackpal.androidterm in the screenshot targets API 22. From the screenshot above, you shall notice that not all processes are leaked; only a handful of them are compromised. What’s going on here is that all these compromised processes are assigned to the same secontext as
u:r:untrusted_app_25:s0, which means that all of these applications target API level lower or equal to 25.
What’s the Issue?
What does leaking
procfs info actually means? By using
/proc information, you can determine what apps, processes, and services are running on the device with no permissions needed, and also a variety of information can also be leaked, like in the case of FGO which abuses this bug to read mount info from other processes to detect Magisk. Although with SELinux protection the damage won’t be as severe as it could since no apps are allowed to read anything outside of its own secontext, this is still a bug that should be fixed on all devices running Android 7.0+.
Currently, Google Play Store allows new apps and updates to target API 26, which means there will still be tons of new and updated apps that are vulnerable of being monitored and at the same time capable of monitoring other apps if
/proc is not mounted correctly.
Android Platform Security Engineering Lead Nick Kralevich reached out to me and promptly pushed an update to CTS to prevent any future system to contain this particular bug.
So is OnePlus the only OEM shipping systems with this vulnerability? Short answer, NO. The whole team at XDA-Developers helped testing on all kinds of devices, and so far Google (Pixel and Nexus line-up) and Samsung are the few big players that implement this security feature properly on their devices. This means a significant number of devices out there does not have
procfs protection included, even though it should already be implemented back in the Nougat days!! The full list of tested devices and the results is covered in this article from XDA.
For custom ROM (built-from-source) users out there, you are very likely to have
/proc mounted correctly since most of the ROMs don’t do weird sh*t to the code. For those that are running a vulnerable system, the only choice is to either wait for a system update, or you can remount
/proc with the proper flags, assuming you have root access. It is quite ironic that you have to use custom ROMs or root to secure your devices 🤨.
I created a simple app, ProcGate, that is capable of detecting whether your device is vulnerable, no root is required. It also has the ability to fix a vulnerable system by remounting
/proc if you have root access. In addition, if you are rooted with Magisk or SuperSU, the app can also add a boot script with the fix included so that your
procfs will always be remounted properly on boot. The app currently only supports Android 7.0+ since remounting
/proc on lower Android versions might cause issues and is not properly tested yet.
Source code: https://github.com/topjohnwu/ProcGate
If you get all the way here, thanks for spending your precious time reading this long article. Also I have to apologize for drawing some conclusions on my Twitter account too hastily, and false information spreads out of my control. Hopefully this article can be used to straight the facts out there.