In this Sherlock players will analyze a disk image acquired from a compromised Android Device.
A user received a phishing email in the Proton Mail app that linked to an APK hosted behind a Cloudflare quick tunnel. Chrome downloaded the file, and about ten minutes after the email arrived it was installed as a fake Booking app. The package was granted a large set of runtime permissions, and its code carried an obfuscated C2 address and port. The whole chain is recoverable from the app databases and the package manager state on the data partition.
debugfs, extract the data directory with rdump to a work folder (ownership errors are harmless without root)db-mail, Chrome History) → open with sqlite3packages.xml, runtime-permissions.xml, bootstat → read as text or XMLTimestamp note: Android stores many times as epoch milliseconds, and file mtimes depend on your shell timezone. Run
TZ=UTCon everylsand convert epochs explicitly. The shelllsmay be aliased, so usecommand lswhen paths break it.
Android’s bootstat directory under data/misc holds a last_boot_time_utc file. Its content is not the answer, its modification time is. List it with the timezone forced to UTC. This was reasoned from the file’s mtime rather than read from a dedicated boot-time field.
TZ=UTC command ls -l --time-style=full-iso data/misc/bootstat/last_boot_time_utc
Look at the installed app directories under data/data. The Proton Mail package, ch.protonmail.android, has a databases/db-mail file that holds the message the user received, which makes it the vector.
command ls data/data/ch.protonmail.android/databases/
Open db-mail and read the subject column of MessageEntity. The phishing message is the one with a celebratory “thank you” subject, using an en dash that must be copied exactly.
sqlite3 db-mail "select subject from MessageEntity;"
The headers are stored in MessageBodyEntity.header. Proton’s own Received: line records when its mail server accepted the message, which is the receipt time. Convert it to UTC if the header carries an offset.
sqlite3 db-mail "select header from MessageBodyEntity;" | grep -i '^Received'
Gotcha: The
timecolumn inMessageEntityis the sender’sDateheader (18:04:40 at +0100), not when the mail arrived. The notification post time is also off. Use the ProtonReceived:header.
Chrome’s History database records downloads with the page that started them. The downloads table lists Booking.apk with its tab_url on a Cloudflare quick tunnel domain.
sqlite3 History "select tab_url, target_path from downloads;"
Hash the downloaded Booking.apk from the Download folder. The installed base.apk gives the same digest, which ties the downloaded file to the installed app.
sha256sum Download/Booking.apk
packages.xml in data/system is the package manager’s registry. Find the entry whose codePath holds the base.apk matching the Q6 hash.
python3 -I parse_packages.py data/system/packages.xml com.hostel.mount
The same packages.xml entry carries it, the first install time, as hex epoch milliseconds. Convert 0x1935f2b00f3 to UTC. The result lands about ten minutes after the Q4 email, which fits the story.
python3 -I -c "import datetime;print(datetime.datetime.fromtimestamp(0x1935f2b00f3/1000, datetime.UTC))"
Runtime grants are stored per user in data/misc_de/system/users/0/runtime-permissions.xml. Find the pkg block for the Q7 package and count the permissions with granted="true".
python3 -I count_granted.py users/0/runtime-permissions.xml com.hostel.mount
No decompiler was available, so the dex was read with Androguard and its string pool dumped. The C2 is hidden as base64 strings: My4xMjEuMTM5Ljgy decodes to the host part and MTA4MjQ= to the port. No direct code reference to the strings turned up. The port was chosen because it is the only numeric base64 value in the pool, so this answer was reasoned from the string pool rather than traced through the connection code.
python3 -I -c "from androguard.misc import get_default_session; ..."
Tip: Search the string pool for short base64 values and decode them. The IP is split, so put the pieces together before submitting.
| Phase | Action |
|---|---|
| Initial Access | Phishing email delivered to the Proton Mail app |
| Initial Access | Link opened in Chrome, Booking.apk downloaded from a Cloudflare tunnel |
| Execution | APK sideloaded as com.hostel.mount about ten minutes after the email |
| Execution | 13 runtime permissions granted to the app |
| Command and Control | App configured with a base64-encoded C2 address and port |
| Type | Value |
|---|---|
| URL | hxxps[://]provincial-consecutive-lbs-boots[.]trycloudflare[.]com/Booking.apk |
| SHA-256 | af081cd26474a6071cde7c6d5bd971e61302fb495abcf317b4a7016bdb98eae2 |
| Package | com.hostel.mount |
| C2 | 3[.]121[.]139[.]82:10824 |
| Technique | ID | Description |
|---|---|---|
| Phishing: Spearphishing Link | T1566.002 | Email in Proton Mail linking to the malicious APK |
| User Execution: Malicious File | T1204.002 | User downloaded and installed the fake Booking app |
Sideloading must be blocked or alerted on. The user installed an APK from a browser download, which needs the “install unknown apps” permission for Chrome. Enforce that setting off through MDM and flag any package installed from outside the store.
Mail links to tunnel domains deserve filtering. The payload sat behind a trycloudflare.com quick tunnel, a free and disposable host. Blocking or detonating links to such domains at the mail gateway would have cut off the delivery.
Permission grants are a cheap triage signal. The app got 13 runtime permissions right after install. Compare grants against what a booking app plausibly needs and quarantine outliers.
Hard-coded C2 values are easy to hide from strings. The address was base64-encoded in the dex. Static analysis that decodes string pools finds it without running the sample.
A single phishing email and a user who sideloaded a fake Booking app were enough to put an attacker-controlled implant on the phone. Everything needed to reconstruct it sat in ordinary app databases and package manager files on the data partition.