About six months ago, I discovered something on my smartphone that horrified me: I went to undelete a file in DiskDigger, and I stumbled upon a plethora of unexpected jpegs: screenshots of my activity. Screenshots that I didn’t take. Screenshots of my conversations. Screenshots of my GPS position. And screenshots of my bitcoin wallet.
I was perplexed. I was astonished. And, to be honest, I was scared. How did this happen? Was it a vulnerability shipped with LineageOS? Could it be some malicious binary embedded into AOSP? Or is it some exploit in one of those damned closed-source apps that I was forced to install through social pressure (*cough* whatsapp)?
This week I was honored to be accepted into a 1-week mini batch at the Recurse Center (formerly “Hacker School”) in Brooklyn, NY. And, finally, I decided to roll-up my sleeves and dig into auditing android security with the ultimate goal of finding out what was responsible for creating (and then deleting) all these screenshots. Well, with no thanks to Google, I did find the source. And the codebase is integrated into AOSP. But (spoiler), it’s not something to sweat about. Though it is a fun journey 🙂
UPDATE: to view the slides from my presentation at the RC, click here.
Context
I encountered this on a Nexus 5X (bullhead) running LineageOS 15.1 = 8.1.0 Oreo = API 26. This phone is rooted, and does not have gapps installed.
To be clear, I’m not an Android Developer–though I did write a hello world app once 🙂
Discovery
As most tech-savvy/sec-conscious readers are aware: when a file is deleted, the bits aren’t lost–they’re just marked as unused. Deleted files’ data sticks around until it happens to be overwritten by a new file’s data. Or until a Secure Wipe is performed.
This could be regarded as a bad thing for many people who are concerned about privacy–especially, for example, when they try to sell an old phone (factory reset is not enough to prevent the new owner from viewing all your photos!)
However, this could also be regarded as a good thing if you, like me, deleted a precious image by accident! And so, after I accidentally deleted an image (oh shoot!), I fired up DiskDigger–an app that can scan your disk for the remnants of deleted files and restore them. But as DiskDigger was scanning my /data/
partition for deleted image files, I stumbled upon more than I bargained for..
Here’s some of the images that I restored from DiskDigger.
There are some interesting things to note about the above images. For example,
- Many sensitive apps have not been spared, such as the encrypted messaging app Wire
- Some of the images were seemingly useless–such as a screenshot of my home screen (it’s not limited to apps)
- The homescreen screenshot was also taken when a menu item was half-transparent, which is interesting regarding the timing of the screen capture event
- Many, many of the files were just corrupt. This could be an artifact of the fact that they were marked as unused by the filesystem and have subsequently been partially overwritten
Auditing Attempt #1, via SELinux
I had exactly 1 week at RC to figure out what the source of these screenshots was. When I finally sat down on Monday to begin my investigation, I figured I would write some auditd rule to capture all file writes and file deletions, throw selinux into permissive mode, and spend a lot of time searching through logs to pinpoint the process id responsible for creating these screenshot images.
I have a strong background in RHEL System Administration. In 1998, the NSA worked with RHEL to design SELinux (Security Enhanced Linux). 15 years later, Google ported selinux into Android version 4.3, Jelly Bean.
If I were trying to do this on a RHEL server, the tools-of-choice would be auditctl
and ausearch
. So, I thought: “Android is linux. I just have to install the auditd toolset, and I’ll be able to use auditctl on my android shell.” Oh boy, how ignorant I was..
The fact is: Google goes through great lengths to prevent users and developers from manipulating the selinux rules. This intention is explicitly outlined in Android’s Compatibility Definition Document (CDD). Version 9’s Section 9.7 Security Features reads:
Device implementations MUST ensure compliance with security features in both the kernel and platform as described below.
The Android Sandbox includes features that use the Security-Enhanced Linux (SELinux) mandatory access control (MAC) system, seccomp sandboxing, and other security features in the Linux kernel. Device implementations:
…
[C-0-3] MUST NOT make SELinux or any other security features implemented below the Android framework configurable to the user or app developer.
So, in fact, there is no auditctl
command in Android world. And, it seems, there never will be an official tool for users or developers to manipulate the selinux rule set for security auditing purposes.
But Android is a community full of hackers, and I’m not the first person wanting to modify the selinux ruleset. In fact, there’s a tool called `sepolicy-inject
` (and a newer fork) that exists for this exact purpose.
While the Android selinux documentation spends a lot of time talking about how to craft selinux policies, it’s unsatisfactorily abstract. They don’t tell you which file the policies are eventually stored to on the andorid device. And (for reasons explained above), they certainly don’t tell you how to modify that file with new rules.
Well friends, the selinux policies are compiled to a binary stored in the root of the android filesystem at “/sepolicy
“. In absense of auditctl
, sepolicy-inject
exists to turn that binary back into a human-read/write-able policy file, re-compile it, and copy it back onto the android device–effectively allowing the user to update the selinux policies. There’s a problem though: at the time of writing, sepolicy-inject
hasn’t been updated in over 2 years and, in order to use it, you have to download 40-100G worth of Android source code, provision a very specific OS build environment, and hope to not spend a week fighting with hair-pulling compilation errors.
Unlike some other folks, I was not so successful at this, and I completely abandoned the attempt to use selinux as a tool to figure out what process was taking screenshots of my phone.
Auditing Attempt #2, via File & Metadata Analysis
There’s a couple interesting facts that readily jump out about the above screenshot files restored by DiskDigger. Namely:
- The files are JPGs. But when I tell android to take a screenshot, it stores the image as a PNG.
- Unlike traditional screenshots, these images don’t include [a] the top bar (where the battery/wifi/etc icons live), [b] the bottom bar (where the back/home/recent apps buttons live) or [c] the keyboard. In all cases, these are blacked-out.
I also checked for clues in the exif image metadata, and one thing jumped-out at me: the images had an attribute named “Profile Copyright” with the value “Google Inc. 2016”
michael@amy:/tmp$ exiftool 8064090112.jpg ... Profile Copyright: Google Inc. 2016
Another clue would be the directory where these images were stored before they were deleted. Unfortunately, DiskDigger doesn’t provide this info, and it wasn’t available in any metadata. So, I just did a search across the entire device for all jpg images. Excluding the files in the camera’s DCIM directory, there weren’t too many directories in question here. One that stood out was at the end of the file list in a directory named /data/system_ce/0/snapshots/
.
This information was critical.
bullhead:/ $ find / -name *.jpg 1>/sdcard/findJpgs.txt bullhead:/ # tail /sdcard/findJpgs.txt ... /data/system_ce/0/snapshots/3419_reduced.jpg /data/system_ce/0/snapshots/3419.jpg bullhead:/ #
I copied the contents of the /data/system_ce/0/snapshots/
directory to the /sdcard/
, copied that to my laptop via adb, and–lo & behold–it was a bunch of screenshots. The images matched all the observations above: format was JPG, top and bottom bars were blacked-out, and exif copyright by Google, 2016.
Root Cause
Googling for the /data/system_ce/0/snapshots/
directory yields a few results of people who experienced the same issue as me, and the root cause is clear: It’s persisted images taken by android for the “recent apps” navigation.
In iOS, there’s also an “app switcher” that caches images of the app for quickly switching between apps. Indeed, this is the “screenshot” images I discovered–they’re the Recent Task List “snapshot” images stored to disk, not requiring the top/bottom bars, copyright google, deleted when no longer needed.
It’s also worth noting that the directory /data/system_ce/
is the so-called “Credential Encrypted” directory, whoose encryption key includes the user-specific password. And these files are certainly inaccessible to most apps, except those to which I grant root access.
Take Away
Between hardware/supply chain issues, zero-days being irresponsibly held in cyberwarfare arsenals instead of responsible disclosure, and incessant attempts to backdoor critical security software, it’s easy to develop paranoia. That said, it’s always good to keep in mind that not everything is as it seems, and sometimes seemingly-sketchy behavior is actually well-designed and benign.
Also, while I understand that Google shouldn’t make it exceedingly easy for users to modify their ‘/sepolicy
‘ file, I do think that AOSP should include an off-by-default tool for powers users to be able to manipulate their selinux policy for the purposes of security auditing.
And, to the Android Developers out there: If your app has sensitive data in any way, consider setting FLAG_SECURE, which prevents android from taking snapshots of your app.
Related Posts
Hi, I’m Michael Altfield. I write articles about opsec, privacy, and devops âž¡
How/why does running as root grant a process access to `/data/system_ce/`?
I guess that’s why signal never shows message content in the switch screen menu
Hi,
Some additional informations:
Root-ing tools use sepolicy-inject a lot.
You’ll find a prebuilt in my SU https://github.com/phhusson/super-bootimg/tree/master/libs/armeabi, but it can be built (and is built) with NDK ( https://github.com/phhusson/sepolicy-inject ), no need to download full AOSP.
But Magisk also includes its own sepolicy-inject (but that’s inside magisk binary that does everything, not just sepolicy-inject, so that’s more annoying).
For your original issue, Nexus 5X originally was running Android 6.0, which per Android CDD “the full-disk encryption MUST be enabled by default at the time the user has completed the out-of-box setup experience.”
So you shouldn’t have been able to read your /data. This is tested by CTS. So any “real life” device has it.
If Lineage doesn’t have it, it’s on purpose, they explicitly chose to reduce user’s security.
Also, for the question of re-selling, the CDD is also explicit “Devices MUST provide users with a mechanism to perform a “Factory Data Reset” that allows logical and physical deletion of all data. This MUST satisfy relevant industry standards for data deletion such as NIST SP800-88.”
Though this part isn’t tested by CTS, so OEMs could have badly implemented it.
Please note that if you have Full-Disk Encryption, the key location on the disk is always the same, and will be overriden, so even if the factory data reset doesn’t work properly, even if the user has no pincode or anything, the data will be unreadable because the key will have been lost.
@Phh thanks for the info. Indeed, I’m using Lineage 15.1, and the persisted snapshots uncovered in this post are fully encrypted. The DiskDigger app only had access to break out of its sandbox and access the decrypted files in ‘/data/’ because I explicitly gave it root access.
I look forward to investigating more into sepolicy-inject in the future!
AFAIK, the issue was known for years, and above all, you should NEVER keep use your device in a rooted mode. Step back to get back security.
afaik, the use of root isn’t inherently insecure, but it could be a risk depending on the apps to which you grant the root access
It may be worth to check the FLAG_SECURE on One Plus devices (at least on the 6). The device is just ignoring the flag.
Hey Stan, to be clear, you mean the stock ROM that ships with the one plus, correct? Also, can you please link to a reference supporting this issue?
Do you know if these snapshots are still encrypted in both Full Disk Encryption and the newer File Based Encryption Android systems?
As far as I can tell from the documentation, yes. By definition everything inside the “Credential encrypted storage” (/data/system_ce/) is encrypted.
Source: https://developer.android.com/training/articles/direct-boot
Thanks for the article! I’m not a tech nerd but I’m concerned about mobile security… Since using Vigilante on an unrooted LG V30 to track camera, mic, etc usage I have detected 2 unauthorised camera uses and I’m digging about that… greetings