Describe the bug
Hi — we run a mid-sized network that was hit by a JAR infector we have not found documented anywhere. We know of five affected networks in Germany alone, all found independently. Full write-up with IoCs, structural detection criteria and the decrypted second stage:
https://gist.github.com/Plattio/d40f53c2e9f962746f3c01a1a244b9e4
Short version
Injects a method getTelemetryId()I with flags 0x1041 (ACC_PUBLIC|ACC_BRIDGE|ACC_SYNTHETIC) plus a field telemetryId. Loads an AES-encrypted second stage from https://s0lar.net/routes/0/d.php and starts it via PluginManager.loadPlugin(). Falls back to a local cache in ./logs if the C2 is unreachable, so blocking the domain does not stop it.
String signatures (base64 in the constant pool, plaintext never appears)
czBsYXIubmV0 s0lar.net
aHR0cHM6Ly9zMGxhci5uZXQvcm91dGVzLzAvZC5waHA= https://s0lar.net/routes/0/d.php
bmV0LnMwbGFyLlNvbGFyTG9hZGVy net.s0lar.SolarLoader
cC5Bc3luY1RpbWluZ3M= p.AsyncTimings
Structural signatures (these survive a domain change)
- method
getTelemetryId()I with access_flags == 0x1041 on a plain int getter
- field
telemetryId
- 9
nop bytes at the start of a method (javac never emits nop)
this starting at offset 9 instead of 0 in LocalVariableTable
- large method without LineNumberTable while sibling methods in the same class have debug info
Cross-checked against the JDK's own jrt-fs.jar — zero false positives.
Two things that broke our first detection attempts
- The injection is not reliably in the main class from plugin.yml. Across 13 infected JARs the host class varied (ConfigManager classes, constructors,
printSimpleLogo(), loadConfigValues()). Only 1 of 13 sat in onEnable() of the main class.
- The server core can be infected too, with a different style. Our Paperclip jar carried a hand-written
patchLibs() in io/papermc/paperclip/Main.class, called as the first instruction of main() — same base64 strings, but no getTelemetryId, no synthetic+bridge, no nop padding.
Persistence outside the plugin folder
Cache file in ./logs, named like a rotated log (<date>-<n>.log.gz) with an invisible U+200B appended. A full plugin replacement does not touch it.
find /path/to/server -name "*$(printf '\xe2\x80\x8b')" -ls
How to reproduce
Not a bug report — this is a detection request for a malware family the scanner does not currently catch.
To reproduce the detection gap: run MCAntiMalware against a plugin JAR infected with this family. It passes clean, because the telling strings are base64-encoded in the constant pool and the injected method is flagged ACC_SYNTHETIC|ACC_BRIDGE, which hides it from most analysis paths.
Details, signatures and structural criteria are in the description above.
Screenshots / Videos
No response
Server Log
No response
Describe the bug
Hi — we run a mid-sized network that was hit by a JAR infector we have not found documented anywhere. We know of five affected networks in Germany alone, all found independently. Full write-up with IoCs, structural detection criteria and the decrypted second stage:
https://gist.github.com/Plattio/d40f53c2e9f962746f3c01a1a244b9e4
Short version
Injects a method
getTelemetryId()Iwith flags0x1041(ACC_PUBLIC|ACC_BRIDGE|ACC_SYNTHETIC) plus a fieldtelemetryId. Loads an AES-encrypted second stage fromhttps://s0lar.net/routes/0/d.phpand starts it viaPluginManager.loadPlugin(). Falls back to a local cache in./logsif the C2 is unreachable, so blocking the domain does not stop it.String signatures (base64 in the constant pool, plaintext never appears)
Structural signatures (these survive a domain change)
getTelemetryId()Iwithaccess_flags == 0x1041on a plain int gettertelemetryIdnopbytes at the start of a method (javac never emits nop)thisstarting at offset 9 instead of 0 in LocalVariableTableCross-checked against the JDK's own
jrt-fs.jar— zero false positives.Two things that broke our first detection attempts
printSimpleLogo(),loadConfigValues()). Only 1 of 13 sat inonEnable()of the main class.patchLibs()inio/papermc/paperclip/Main.class, called as the first instruction ofmain()— same base64 strings, but nogetTelemetryId, no synthetic+bridge, no nop padding.Persistence outside the plugin folder
Cache file in
./logs, named like a rotated log (<date>-<n>.log.gz) with an invisible U+200B appended. A full plugin replacement does not touch it.How to reproduce
Not a bug report — this is a detection request for a malware family the scanner does not currently catch.
To reproduce the detection gap: run MCAntiMalware against a plugin JAR infected with this family. It passes clean, because the telling strings are base64-encoded in the constant pool and the injected method is flagged ACC_SYNTHETIC|ACC_BRIDGE, which hides it from most analysis paths.
Details, signatures and structural criteria are in the description above.
Screenshots / Videos
No response
Server Log
No response