diff --git a/build.gradle b/build.gradle index a20dc66..e73b80c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'com.gradle.plugin-publish' version '1.1.0' } -version = '2.8.0' +version = '2.8.1' group = 'com.modrinth.minotaur' archivesBaseName = 'Minotaur' description = 'Modrinth plugin for publishing builds to the website!' diff --git a/src/main/java/com/modrinth/minotaur/scanner/JarInfectionScanner.java b/src/main/java/com/modrinth/minotaur/scanner/JarInfectionScanner.java index 8115c5c..9ac2fb2 100644 --- a/src/main/java/com/modrinth/minotaur/scanner/JarInfectionScanner.java +++ b/src/main/java/com/modrinth/minotaur/scanner/JarInfectionScanner.java @@ -25,7 +25,7 @@ public static void scan(Logger logger, ZipFile file) { .filter(entry -> entry.getName().endsWith(".class")) .anyMatch(entry -> { try { - return scanClass(readAllBytes(file.getInputStream(entry))); + return scanClass(getByteArray(file.getInputStream(entry))); } catch (IOException e) { throw new RuntimeException(e); } @@ -45,40 +45,121 @@ public static void scan(Logger logger, ZipFile file) { logger.info("Fractureiser not detected in {}", file.getName()); } + private static byte[] getByteArray(InputStream inputStream) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int nRead; + byte[] data = new byte[16384]; + + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + + return buffer.toByteArray(); + } + private static final AbstractInsnNode[] SIG1 = new AbstractInsnNode[] { new TypeInsnNode(NEW, "java/lang/String"), new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "", "([B)V"), new TypeInsnNode(NEW, "java/lang/String"), new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "", "([B)V"), new MethodInsnNode(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;"), - new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"), + new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Class", "getConstructor", + "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"), new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "", "([B)V"), new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "", "([B)V"), new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "", "([B)V"), - new MethodInsnNode(INVOKESPECIAL, "java/net/URL", "", "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V"), - new MethodInsnNode(INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;"), - new MethodInsnNode(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"), + new MethodInsnNode(INVOKESPECIAL, "java/net/URL", "", + "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V"), + new MethodInsnNode(INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", + "([Ljava/lang/Object;)Ljava/lang/Object;"), + new MethodInsnNode(INVOKESTATIC, "java/lang/Class", "forName", + "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"), new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "", "([B)V"), - new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"), - new MethodInsnNode(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"), + new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Class", "getMethod", + "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"), + new MethodInsnNode(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"), }; private static final AbstractInsnNode[] SIG2 = new AbstractInsnNode[] { new MethodInsnNode(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;"), new MethodInsnNode(INVOKESTATIC, "java/util/Base64", "getDecoder", "()Ljava/util/Base64$Decoder;"), - new MethodInsnNode(INVOKEVIRTUAL, "java/lang/String", "INVOKEVIRTUAL", "(Ljava/lang/String;)Ljava/lang/String;"),//TODO:FIXME: this might not be in all of them + new MethodInsnNode(INVOKEVIRTUAL, "java/lang/String", "concat", + "(Ljava/lang/String;)Ljava/lang/String;"), // TODO:FIXME: this might not be in all of them new MethodInsnNode(INVOKEVIRTUAL, "java/util/Base64$Decoder", "decode", "(Ljava/lang/String;)[B"), new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "", "([B)V"), new MethodInsnNode(INVOKEVIRTUAL, "java/io/File", "getPath", "()Ljava/lang/String;"), new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "([Ljava/lang/String;)Ljava/lang/Process;"), }; + // The IP + private static final AbstractInsnNode[] SIG3 = new AbstractInsnNode[] { + new IntInsnNode(BIPUSH, 56), + new InsnNode(BASTORE), + new InsnNode(DUP), + new InsnNode(ICONST_1), + new IntInsnNode(BIPUSH, 53), + new InsnNode(BASTORE), + new InsnNode(DUP), + new InsnNode(ICONST_2), + new IntInsnNode(BIPUSH, 46), + new InsnNode(BASTORE), + new InsnNode(DUP), + new InsnNode(ICONST_3), + new IntInsnNode(BIPUSH, 50), + new InsnNode(BASTORE), + new InsnNode(DUP), + new InsnNode(ICONST_4), + new IntInsnNode(BIPUSH, 49), + new InsnNode(BASTORE), + new InsnNode(DUP), + new InsnNode(ICONST_5), + new IntInsnNode(BIPUSH, 55), + new InsnNode(BASTORE), + new InsnNode(DUP), + new IntInsnNode(BIPUSH, 6), + new IntInsnNode(BIPUSH, 46), + new InsnNode(BASTORE), + new InsnNode(DUP), + new IntInsnNode(BIPUSH, 7), + new IntInsnNode(BIPUSH, 49), + new InsnNode(BASTORE), + new InsnNode(DUP), + new IntInsnNode(BIPUSH, 8), + new IntInsnNode(BIPUSH, 52), + new InsnNode(BASTORE), + new InsnNode(DUP), + new IntInsnNode(BIPUSH, 9), + new IntInsnNode(BIPUSH, 52), + new InsnNode(BASTORE), + new InsnNode(DUP), + new IntInsnNode(BIPUSH, 10), + new IntInsnNode(BIPUSH, 46), + new InsnNode(BASTORE), + new InsnNode(DUP), + new IntInsnNode(BIPUSH, 11), + new IntInsnNode(BIPUSH, 49), + new InsnNode(BASTORE), + new InsnNode(DUP), + new IntInsnNode(BIPUSH, 12), + new IntInsnNode(BIPUSH, 51), + new InsnNode(BASTORE), + new InsnNode(DUP), + new IntInsnNode(BIPUSH, 13), + new IntInsnNode(BIPUSH, 48) + }; + private static boolean same(AbstractInsnNode a, AbstractInsnNode b) { if (a instanceof TypeInsnNode) { - return ((TypeInsnNode)a).desc.equals(((TypeInsnNode)b).desc); + TypeInsnNode aa = (TypeInsnNode) a; + return aa.desc.equals(((TypeInsnNode) b).desc); } if (a instanceof MethodInsnNode) { - return ((MethodInsnNode)a).owner.equals(((MethodInsnNode)b).owner) && ((MethodInsnNode)a).desc.equals(((MethodInsnNode)b).desc); + MethodInsnNode aa = (MethodInsnNode) a; + return aa.owner.equals(((MethodInsnNode) b).owner) + && aa.name.equals(((MethodInsnNode) b).name) + && aa.desc.equals(((MethodInsnNode) b).desc); } if (a instanceof InsnNode) { return true; @@ -86,25 +167,27 @@ private static boolean same(AbstractInsnNode a, AbstractInsnNode b) { throw new IllegalArgumentException("TYPE NOT ADDED"); } - private static boolean scanClass(byte[] clazz) { + public static boolean scanClass(byte[] clazz) { ClassReader reader = new ClassReader(clazz); ClassNode node = new ClassNode(); try { reader.accept(node, 0); } catch (Exception e) { - return false;//Yes this is very hacky but should never happen with valid clasees + return false;// Yes this is very hacky but should never happen with valid clasees } for (MethodNode method : node.methods) { { - //Method 1, this is a hard detect, if it matches this it is 100% chance infected + // Method 1, this is a hard detect, if it matches this it is 100% chance + // infected boolean match = true; int j = 0; for (int i = 0; i < method.instructions.size() && j < SIG1.length; i++) { - if (method.instructions.get(i).getOpcode() == -1) { + AbstractInsnNode insn = method.instructions.get(i); + if (insn.getOpcode() == -1) { continue; } - if (method.instructions.get(i).getOpcode() == SIG1[j].getOpcode()) { - if (!same(method.instructions.get(i), SIG1[j++])) { + if (insn.getOpcode() == SIG1[j].getOpcode()) { + if (!same(insn, SIG1[j++])) { match = false; break; } @@ -119,18 +202,19 @@ private static boolean scanClass(byte[] clazz) { } { - //Method 2, this is a near hard detect, if it matches this it is 95% chance infected + // Method 2, this is a near hard detect, if it matches this it is 95% chance + // infected boolean match = false; - outer: - for (int q = 0; q < method.instructions.size(); q++) { + outer: for (int q = 0; q < method.instructions.size(); q++) { int j = 0; for (int i = q; i < method.instructions.size() && j < SIG2.length; i++) { - if (method.instructions.get(i).getOpcode() != SIG2[j].getOpcode()) { + AbstractInsnNode insn = method.instructions.get(i); + if (insn.getOpcode() != SIG2[j].getOpcode()) { continue; } - if (method.instructions.get(i).getOpcode() == SIG2[j].getOpcode()) { - if (!same(method.instructions.get(i), SIG2[j++])) { + if (insn.getOpcode() == SIG2[j].getOpcode()) { + if (!same(insn, SIG2[j++])) { continue outer; } } @@ -144,34 +228,48 @@ private static boolean scanClass(byte[] clazz) { return true; } } - } - return false; - } - // Java 8 equivalent of InputStream.readAllBytes() - private static byte[] readAllBytes(InputStream inputStream) throws IOException { - final int bufLen = 1024; - byte[] buf = new byte[bufLen]; - int readLen; - IOException exception = null; + // Method 3, this looks for a byte array with the IP. This is a likely match. + { + boolean match = false; + // where we're looking in the SIG3 array + int pos = 0; + for (int i = 0; i < method.instructions.size(); i++) { + if (pos == SIG3.length) { + break; + } + AbstractInsnNode insn = method.instructions.get(i); + if (insn.getOpcode() == -1) { + continue; + } + if (insn.getOpcode() == SIG3[pos].getOpcode()) { + // the opcode matches + + if (SIG3[pos].getType() == AbstractInsnNode.INT_INSN) { + // check if operand matches + IntInsnNode iInsn = (IntInsnNode) insn; + IntInsnNode sigInsn = (IntInsnNode) SIG3[pos]; + if (iInsn.operand == sigInsn.operand) { + // operands match + match = true; + pos++; + } + } else { + // this is a regular InsnNode; just match + match = true; + pos++; + } + } else { + match = false; + pos = 0; + } + } - try { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - while ((readLen = inputStream.read(buf, 0, bufLen)) != -1) - outputStream.write(buf, 0, readLen); - - return outputStream.toByteArray(); - } catch (IOException e) { - exception = e; - throw e; - } finally { - if (exception == null) inputStream.close(); - else try { - inputStream.close(); - } catch (IOException e) { - exception.addSuppressed(e); + if (match) { + return true; + } } } + return false; } }