Skip to content

Commit f56b9cd

Browse files
authored
Update jar scanner to include Stage 1 changes from original code (#52)
1 parent 69189d0 commit f56b9cd

File tree

2 files changed

+146
-48
lines changed

2 files changed

+146
-48
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ plugins {
22
id 'com.gradle.plugin-publish' version '1.1.0'
33
}
44

5-
version = '2.8.0'
5+
version = '2.8.1'
66
group = 'com.modrinth.minotaur'
77
archivesBaseName = 'Minotaur'
88
description = 'Modrinth plugin for publishing builds to the website!'

src/main/java/com/modrinth/minotaur/scanner/JarInfectionScanner.java

Lines changed: 145 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static void scan(Logger logger, ZipFile file) {
2525
.filter(entry -> entry.getName().endsWith(".class"))
2626
.anyMatch(entry -> {
2727
try {
28-
return scanClass(readAllBytes(file.getInputStream(entry)));
28+
return scanClass(getByteArray(file.getInputStream(entry)));
2929
} catch (IOException e) {
3030
throw new RuntimeException(e);
3131
}
@@ -45,66 +45,149 @@ public static void scan(Logger logger, ZipFile file) {
4545
logger.info("Fractureiser not detected in {}", file.getName());
4646
}
4747

48+
private static byte[] getByteArray(InputStream inputStream) throws IOException {
49+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
50+
51+
int nRead;
52+
byte[] data = new byte[16384];
53+
54+
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
55+
buffer.write(data, 0, nRead);
56+
}
57+
58+
return buffer.toByteArray();
59+
}
60+
4861
private static final AbstractInsnNode[] SIG1 = new AbstractInsnNode[] {
4962
new TypeInsnNode(NEW, "java/lang/String"),
5063
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
5164
new TypeInsnNode(NEW, "java/lang/String"),
5265
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
5366
new MethodInsnNode(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;"),
54-
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"),
67+
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Class", "getConstructor",
68+
"([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"),
5569
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
5670
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
5771
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
58-
new MethodInsnNode(INVOKESPECIAL, "java/net/URL", "<init>", "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V"),
59-
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;"),
60-
new MethodInsnNode(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
72+
new MethodInsnNode(INVOKESPECIAL, "java/net/URL", "<init>",
73+
"(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V"),
74+
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance",
75+
"([Ljava/lang/Object;)Ljava/lang/Object;"),
76+
new MethodInsnNode(INVOKESTATIC, "java/lang/Class", "forName",
77+
"(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
6178
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
62-
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
63-
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
79+
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Class", "getMethod",
80+
"(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
81+
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
82+
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
6483
};
6584

6685
private static final AbstractInsnNode[] SIG2 = new AbstractInsnNode[] {
6786
new MethodInsnNode(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;"),
6887
new MethodInsnNode(INVOKESTATIC, "java/util/Base64", "getDecoder", "()Ljava/util/Base64$Decoder;"),
69-
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/String", "INVOKEVIRTUAL", "(Ljava/lang/String;)Ljava/lang/String;"),//TODO:FIXME: this might not be in all of them
88+
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/String", "concat",
89+
"(Ljava/lang/String;)Ljava/lang/String;"), // TODO:FIXME: this might not be in all of them
7090
new MethodInsnNode(INVOKEVIRTUAL, "java/util/Base64$Decoder", "decode", "(Ljava/lang/String;)[B"),
7191
new MethodInsnNode(INVOKESPECIAL, "java/lang/String", "<init>", "([B)V"),
7292
new MethodInsnNode(INVOKEVIRTUAL, "java/io/File", "getPath", "()Ljava/lang/String;"),
7393
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "([Ljava/lang/String;)Ljava/lang/Process;"),
7494
};
7595

96+
// The IP
97+
private static final AbstractInsnNode[] SIG3 = new AbstractInsnNode[] {
98+
new IntInsnNode(BIPUSH, 56),
99+
new InsnNode(BASTORE),
100+
new InsnNode(DUP),
101+
new InsnNode(ICONST_1),
102+
new IntInsnNode(BIPUSH, 53),
103+
new InsnNode(BASTORE),
104+
new InsnNode(DUP),
105+
new InsnNode(ICONST_2),
106+
new IntInsnNode(BIPUSH, 46),
107+
new InsnNode(BASTORE),
108+
new InsnNode(DUP),
109+
new InsnNode(ICONST_3),
110+
new IntInsnNode(BIPUSH, 50),
111+
new InsnNode(BASTORE),
112+
new InsnNode(DUP),
113+
new InsnNode(ICONST_4),
114+
new IntInsnNode(BIPUSH, 49),
115+
new InsnNode(BASTORE),
116+
new InsnNode(DUP),
117+
new InsnNode(ICONST_5),
118+
new IntInsnNode(BIPUSH, 55),
119+
new InsnNode(BASTORE),
120+
new InsnNode(DUP),
121+
new IntInsnNode(BIPUSH, 6),
122+
new IntInsnNode(BIPUSH, 46),
123+
new InsnNode(BASTORE),
124+
new InsnNode(DUP),
125+
new IntInsnNode(BIPUSH, 7),
126+
new IntInsnNode(BIPUSH, 49),
127+
new InsnNode(BASTORE),
128+
new InsnNode(DUP),
129+
new IntInsnNode(BIPUSH, 8),
130+
new IntInsnNode(BIPUSH, 52),
131+
new InsnNode(BASTORE),
132+
new InsnNode(DUP),
133+
new IntInsnNode(BIPUSH, 9),
134+
new IntInsnNode(BIPUSH, 52),
135+
new InsnNode(BASTORE),
136+
new InsnNode(DUP),
137+
new IntInsnNode(BIPUSH, 10),
138+
new IntInsnNode(BIPUSH, 46),
139+
new InsnNode(BASTORE),
140+
new InsnNode(DUP),
141+
new IntInsnNode(BIPUSH, 11),
142+
new IntInsnNode(BIPUSH, 49),
143+
new InsnNode(BASTORE),
144+
new InsnNode(DUP),
145+
new IntInsnNode(BIPUSH, 12),
146+
new IntInsnNode(BIPUSH, 51),
147+
new InsnNode(BASTORE),
148+
new InsnNode(DUP),
149+
new IntInsnNode(BIPUSH, 13),
150+
new IntInsnNode(BIPUSH, 48)
151+
};
152+
76153
private static boolean same(AbstractInsnNode a, AbstractInsnNode b) {
77154
if (a instanceof TypeInsnNode) {
78-
return ((TypeInsnNode)a).desc.equals(((TypeInsnNode)b).desc);
155+
TypeInsnNode aa = (TypeInsnNode) a;
156+
return aa.desc.equals(((TypeInsnNode) b).desc);
79157
}
80158
if (a instanceof MethodInsnNode) {
81-
return ((MethodInsnNode)a).owner.equals(((MethodInsnNode)b).owner) && ((MethodInsnNode)a).desc.equals(((MethodInsnNode)b).desc);
159+
MethodInsnNode aa = (MethodInsnNode) a;
160+
return aa.owner.equals(((MethodInsnNode) b).owner)
161+
&& aa.name.equals(((MethodInsnNode) b).name)
162+
&& aa.desc.equals(((MethodInsnNode) b).desc);
82163
}
83164
if (a instanceof InsnNode) {
84165
return true;
85166
}
86167
throw new IllegalArgumentException("TYPE NOT ADDED");
87168
}
88169

89-
private static boolean scanClass(byte[] clazz) {
170+
public static boolean scanClass(byte[] clazz) {
90171
ClassReader reader = new ClassReader(clazz);
91172
ClassNode node = new ClassNode();
92173
try {
93174
reader.accept(node, 0);
94175
} catch (Exception e) {
95-
return false;//Yes this is very hacky but should never happen with valid clasees
176+
return false;// Yes this is very hacky but should never happen with valid clasees
96177
}
97178
for (MethodNode method : node.methods) {
98179
{
99-
//Method 1, this is a hard detect, if it matches this it is 100% chance infected
180+
// Method 1, this is a hard detect, if it matches this it is 100% chance
181+
// infected
100182
boolean match = true;
101183
int j = 0;
102184
for (int i = 0; i < method.instructions.size() && j < SIG1.length; i++) {
103-
if (method.instructions.get(i).getOpcode() == -1) {
185+
AbstractInsnNode insn = method.instructions.get(i);
186+
if (insn.getOpcode() == -1) {
104187
continue;
105188
}
106-
if (method.instructions.get(i).getOpcode() == SIG1[j].getOpcode()) {
107-
if (!same(method.instructions.get(i), SIG1[j++])) {
189+
if (insn.getOpcode() == SIG1[j].getOpcode()) {
190+
if (!same(insn, SIG1[j++])) {
108191
match = false;
109192
break;
110193
}
@@ -119,18 +202,19 @@ private static boolean scanClass(byte[] clazz) {
119202
}
120203

121204
{
122-
//Method 2, this is a near hard detect, if it matches this it is 95% chance infected
205+
// Method 2, this is a near hard detect, if it matches this it is 95% chance
206+
// infected
123207
boolean match = false;
124-
outer:
125-
for (int q = 0; q < method.instructions.size(); q++) {
208+
outer: for (int q = 0; q < method.instructions.size(); q++) {
126209
int j = 0;
127210
for (int i = q; i < method.instructions.size() && j < SIG2.length; i++) {
128-
if (method.instructions.get(i).getOpcode() != SIG2[j].getOpcode()) {
211+
AbstractInsnNode insn = method.instructions.get(i);
212+
if (insn.getOpcode() != SIG2[j].getOpcode()) {
129213
continue;
130214
}
131215

132-
if (method.instructions.get(i).getOpcode() == SIG2[j].getOpcode()) {
133-
if (!same(method.instructions.get(i), SIG2[j++])) {
216+
if (insn.getOpcode() == SIG2[j].getOpcode()) {
217+
if (!same(insn, SIG2[j++])) {
134218
continue outer;
135219
}
136220
}
@@ -144,34 +228,48 @@ private static boolean scanClass(byte[] clazz) {
144228
return true;
145229
}
146230
}
147-
}
148-
return false;
149-
}
150231

151-
// Java 8 equivalent of InputStream.readAllBytes()
152-
private static byte[] readAllBytes(InputStream inputStream) throws IOException {
153-
final int bufLen = 1024;
154-
byte[] buf = new byte[bufLen];
155-
int readLen;
156-
IOException exception = null;
232+
// Method 3, this looks for a byte array with the IP. This is a likely match.
233+
{
234+
boolean match = false;
235+
// where we're looking in the SIG3 array
236+
int pos = 0;
237+
for (int i = 0; i < method.instructions.size(); i++) {
238+
if (pos == SIG3.length) {
239+
break;
240+
}
241+
AbstractInsnNode insn = method.instructions.get(i);
242+
if (insn.getOpcode() == -1) {
243+
continue;
244+
}
245+
if (insn.getOpcode() == SIG3[pos].getOpcode()) {
246+
// the opcode matches
247+
248+
if (SIG3[pos].getType() == AbstractInsnNode.INT_INSN) {
249+
// check if operand matches
250+
IntInsnNode iInsn = (IntInsnNode) insn;
251+
IntInsnNode sigInsn = (IntInsnNode) SIG3[pos];
252+
if (iInsn.operand == sigInsn.operand) {
253+
// operands match
254+
match = true;
255+
pos++;
256+
}
257+
} else {
258+
// this is a regular InsnNode; just match
259+
match = true;
260+
pos++;
261+
}
262+
} else {
263+
match = false;
264+
pos = 0;
265+
}
266+
}
157267

158-
try {
159-
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
160-
161-
while ((readLen = inputStream.read(buf, 0, bufLen)) != -1)
162-
outputStream.write(buf, 0, readLen);
163-
164-
return outputStream.toByteArray();
165-
} catch (IOException e) {
166-
exception = e;
167-
throw e;
168-
} finally {
169-
if (exception == null) inputStream.close();
170-
else try {
171-
inputStream.close();
172-
} catch (IOException e) {
173-
exception.addSuppressed(e);
268+
if (match) {
269+
return true;
270+
}
174271
}
175272
}
273+
return false;
176274
}
177275
}

0 commit comments

Comments
 (0)