Skip to content

Commit

Permalink
Update jar scanner to include Stage 1 changes from original code (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
hypherionmc committed Jun 13, 2023
1 parent 69189d0 commit f56b9cd
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 48 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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!'
Expand Down
192 changes: 145 additions & 47 deletions src/main/java/com/modrinth/minotaur/scanner/JarInfectionScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -45,66 +45,149 @@ 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", "<init>", "([B)V"),
new TypeInsnNode(NEW, "java/lang/String"),
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([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", "<init>", "([B)V"),
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
new MethodInsnNode(INVOKESPECIAL, "java/net/URL", "<init>", "(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", "<init>",
"(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", "<init>", "([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", "<init>", "([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;
}
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;
}
Expand All @@ -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;
}
}
Expand All @@ -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;
}
}

0 comments on commit f56b9cd

Please sign in to comment.