diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/InjectorConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/InjectorConfig.java index a5151b3e..5a85c011 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/InjectorConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/InjectorConfig.java @@ -46,4 +46,9 @@ public class InjectorConfig { * 内存马类字节 */ private byte[] shellClassBytes; + + /** + * 添加静态代码块调用构造方法初始化 + */ + private boolean staticInitialize; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java index 480b15a4..cd197dd8 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java @@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.generator; import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.asm.InnerClassDiscovery; import com.reajason.javaweb.buddy.*; +import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.config.InjectorConfig; import com.reajason.javaweb.memshell.config.ShellConfig; import com.reajason.javaweb.utils.CommonUtil; @@ -59,6 +60,10 @@ public class InjectorGenerator { if (shellConfig.isDebugOff()) { builder = LogRemoveMethodVisitor.extend(builder); } + + if (injectorConfig.isStaticInitialize() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) { + builder = StaticBlockSelfConstructorCall.extend(builder); + } return builder; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/ValveGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/ValveGenerator.java index fbd2355b..97ab8e7b 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/ValveGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/ValveGenerator.java @@ -32,13 +32,20 @@ public class ValveGenerator { public static DynamicType.Builder build(DynamicType.Builder builder, AbstractServer shell, String serverVersion) { String packageName = null; - if (serverVersion.equals("6")) { - packageName = TONGWEB6_VALVE_PACKAGE; - } else if (serverVersion.equals("7")) { - packageName = TONGWEB7_VALVE_PACKAGE; - } else if (serverVersion.equals("8")) { - packageName = TONGWEB8_VALVE_PACKAGE; - } else if (shell instanceof Bes) { + if (serverVersion != null) { + switch (serverVersion) { + case "6": + packageName = TONGWEB6_VALVE_PACKAGE; + break; + case "7": + packageName = TONGWEB7_VALVE_PACKAGE; + break; + case "8": + packageName = TONGWEB8_VALVE_PACKAGE; + break; + } + } + if (shell instanceof Bes) { packageName = BES_VALVE_PACKAGE; } if (StringUtils.isEmpty(packageName)) { diff --git a/generator/src/main/java/com/reajason/javaweb/probe/config/ProbeConfig.java b/generator/src/main/java/com/reajason/javaweb/probe/config/ProbeConfig.java index 9983f308..7d817d6a 100644 --- a/generator/src/main/java/com/reajason/javaweb/probe/config/ProbeConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/probe/config/ProbeConfig.java @@ -47,6 +47,12 @@ public class ProbeConfig { @Builder.Default private boolean shrink = false; + /** + * 是否添加静态代码块调用构造方法 + */ + @Builder.Default + private boolean staticInitialize = false; + public boolean isDebugOff() { return !debug; } diff --git a/generator/src/main/java/com/reajason/javaweb/probe/generator/ByteBuddyShellGenerator.java b/generator/src/main/java/com/reajason/javaweb/probe/generator/ByteBuddyShellGenerator.java index 2fc04fc7..b86c72b4 100644 --- a/generator/src/main/java/com/reajason/javaweb/probe/generator/ByteBuddyShellGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/probe/generator/ByteBuddyShellGenerator.java @@ -4,6 +4,7 @@ import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.ShellGenerator; import com.reajason.javaweb.buddy.ByPassJavaModuleInterceptor; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; +import com.reajason.javaweb.buddy.StaticBlockSelfConstructorCall; import com.reajason.javaweb.probe.config.ProbeConfig; import com.reajason.javaweb.probe.config.ProbeContentConfig; import net.bytebuddy.ByteBuddy; @@ -27,13 +28,19 @@ public abstract class ByteBuddyShellGenerator impl @Override public byte[] getBytes() { DynamicType.Builder builder = build(new ByteBuddy()); + if (probeConfig.needByPassJavaModule()) { builder = ByPassJavaModuleInterceptor.extend(builder); } + if (probeConfig.isDebugOff()) { builder = LogRemoveMethodVisitor.extend(builder); } + if (probeConfig.isStaticInitialize()) { + builder = StaticBlockSelfConstructorCall.extend(builder); + } + try (DynamicType.Unloaded unloaded = builder.make()) { return ClassBytesShrink.shrink(unloaded.getBytes(), probeConfig.isShrink()); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ProbeAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ProbeAssertion.java index 60945738..8f6036bb 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ProbeAssertion.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ProbeAssertion.java @@ -29,6 +29,7 @@ public class ProbeAssertion { .targetJreVersion(targetJreVersion) .debug(true) .shrink(true) + .staticInitialize(true) .build(); String reqParamName = "payload"; ResponseBodyConfig responseBodyConfig = ResponseBodyConfig.builder() @@ -57,6 +58,7 @@ public class ProbeAssertion { .targetJreVersion(targetJreVersion) .debug(true) .shrink(true) + .staticInitialize(true) .build(); String reqParamName = "payload"; ResponseBodyConfig responseBodyConfig = ResponseBodyConfig.builder() @@ -84,6 +86,7 @@ public class ProbeAssertion { .probeContent(ProbeContent.Command) .debug(true) .shrink(true) + .staticInitialize(true) .targetJreVersion(targetJreVersion) .build(); String headerName = "X-Header"; diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java index 202d421a..7c07cae3 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java @@ -84,13 +84,20 @@ public class ShellAssertion { @SneakyThrows public static void shellInjectIsOk(String url, String server, String shellType, String shellTool, int targetJdkVersion, Packers packer, GenericContainer appContainer, GenericContainer pythonContainer) { + shellInjectIsOk(url, server, null, shellType, shellTool, targetJdkVersion, packer, appContainer, pythonContainer); + } + + @SneakyThrows + public static void shellInjectIsOk(String url, String server, String serverVersion, String shellType, String shellTool, + int targetJdkVersion, Packers packer, + GenericContainer appContainer, GenericContainer pythonContainer) { Pair urls = getUrls(url, shellType, shellTool, packer); String shellUrl = urls.getLeft(); String urlPattern = urls.getRight(); ShellToolConfig shellToolConfig = getShellToolConfig(shellType, shellTool, packer); - MemShellResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, shellToolConfig, packer); + MemShellResult generateResult = generate(urlPattern, server, serverVersion, shellType, shellTool, targetJdkVersion, shellToolConfig, packer); packerResultAndInject(generateResult, url, shellTool, shellType, packer, appContainer); @@ -107,7 +114,7 @@ public class ShellAssertion { String jarPath = "/" + shellTool + shellType + packer.name() + ".jar"; appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath); FileUtils.deleteQuietly(tempJar.toFile()); - String pidInContainer = appContainer.execInContainer("bash", "/fetch_pid.sh").getStdout(); + String pidInContainer = appContainer.execInContainer("bash", "/fetch_pid.sh").getStdout().trim(); assertDoesNotThrow(() -> Long.parseLong(pidInContainer)); String stdout = appContainer.execInContainer("/jattach", pidInContainer, "load", "instrument", "false", jarPath).getStdout(); log.info("attach result: {}", stdout); @@ -291,8 +298,8 @@ public class ShellAssertion { return shellToolConfig; } - public static MemShellResult generate(String urlPattern, String server, String shellType, String shellTool, int targetJdkVersion, ShellToolConfig shellToolConfig, Packers packer) { - InjectorConfig injectorConfig = new InjectorConfig(); + public static MemShellResult generate(String urlPattern, String server, String serverVersin, String shellType, String shellTool, int targetJdkVersion, ShellToolConfig shellToolConfig, Packers packer) { + InjectorConfig injectorConfig = InjectorConfig.builder().staticInitialize(true).build(); if (StringUtils.isNotBlank(urlPattern)) { injectorConfig.setUrlPattern(urlPattern); } @@ -304,6 +311,7 @@ public class ShellAssertion { ShellConfig shellConfig = ShellConfig.builder() .server(server) + .serverVersion(serverVersin) .shellTool(shellTool) .shellType(shellType) .targetJreVersion(targetJdkVersion) diff --git a/web/bun.lockb b/web/bun.lockb index 9b05fd07..726e1352 100755 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/package.json b/web/package.json index ca1f9282..78d869ee 100644 --- a/web/package.json +++ b/web/package.json @@ -15,27 +15,27 @@ }, "devDependencies": { "@biomejs/biome": "2.1.4", - "@react-router/dev": "^7.9.5", - "@types/node": "^24.10.0", - "@types/react": "^19.2.2", + "@react-router/dev": "^7.9.6", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", "@types/react-copy-to-clipboard": "^5.0.7", - "@types/react-dom": "^19.2.2", + "@types/react-dom": "^19.2.3", "@types/react-syntax-highlighter": "^15.5.13", - "@vitejs/plugin-react": "^5.1.0", + "@vitejs/plugin-react": "^5.1.1", "rimraf": "^6.1.0", - "tailwindcss": "^4.1.16", + "tailwindcss": "^4.1.17", "typescript": "^5.9.3", - "vite": "^7.1.12", + "vite": "^7.2.2", "vite-bundle-visualizer": "^1.2.1" }, "dependencies": { "@hookform/resolvers": "^5.2.2", - "@tailwindcss/vite": "^4.1.16", - "@tanstack/react-query": "^5.90.6", + "@tailwindcss/vite": "^4.1.17", + "@tanstack/react-query": "^5.90.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^12.23.24", - "i18next": "^25.6.0", + "i18next": "^25.6.2", "lucide-react": "^0.539.0", "motion": "^12.23.24", "radix-ui": "^1.4.3", @@ -44,11 +44,11 @@ "react-dom": "^19.2.0", "react-hook-form": "^7.66.0", "react-i18next": "^15.7.4", - "react-router": "^7.9.5", - "react-router-dom": "^7.9.5", + "react-router": "^7.9.6", + "react-router-dom": "^7.9.6", "react-syntax-highlighter": "^15.6.6", "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1", + "tailwind-merge": "^3.4.0", "tw-animate-css": "^1.4.0", "yup": "^1.7.1" }, diff --git a/web/src/components/memshell/main-config-card.tsx b/web/src/components/memshell/main-config-card.tsx index 4fcc9b32..b2fe2541 100644 --- a/web/src/components/memshell/main-config-card.tsx +++ b/web/src/components/memshell/main-config-card.tsx @@ -383,6 +383,22 @@ export default function MainConfigCard({ )} /> + ( + + + + + + + )} + /> diff --git a/web/src/components/probeshell/main-config-card.tsx b/web/src/components/probeshell/main-config-card.tsx index 168615b8..b7f98545 100644 --- a/web/src/components/probeshell/main-config-card.tsx +++ b/web/src/components/probeshell/main-config-card.tsx @@ -324,6 +324,24 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) { )} /> + ( + + + + + + + )} + /> ), [form.control, t], diff --git a/web/src/i18n/common/en.json b/web/src/i18n/common/en.json index ea8f1db6..6d6477c8 100644 --- a/web/src/i18n/common/en.json +++ b/web/src/i18n/common/en.json @@ -28,6 +28,7 @@ "server": "Server", "serverVersion": "Server Version", "shrink": "Shrink", + "staticInitialize": "StaticInitialize", "toast.generateError": "Generation failed, {{error}}", "toast.generateSuccess": "Generation successful", "urlPattern": "URL Pattern", diff --git a/web/src/i18n/common/zh-CN.json b/web/src/i18n/common/zh-CN.json index 934c2023..0ff9c890 100644 --- a/web/src/i18n/common/zh-CN.json +++ b/web/src/i18n/common/zh-CN.json @@ -28,6 +28,7 @@ "server": "服务类型", "serverVersion": "服务版本", "shrink": "缩小字节码", + "staticInitialize": "静态初始化", "toast.generateError": "生成失败,{{error}}", "toast.generateSuccess": "生成成功", "urlPattern": "请求路径", diff --git a/web/src/pages/memshell.tsx b/web/src/pages/memshell.tsx index 5178ab4f..d15dfd7c 100644 --- a/web/src/pages/memshell.tsx +++ b/web/src/pages/memshell.tsx @@ -77,6 +77,7 @@ export default function MemShellPage() { injectorClassName: urlParams.injectorClassName ?? "", packingMethod: urlParams.packingMethod ?? "", shrink: urlParams.shrink ?? true, + staticInitialize: true, shellClassBase64: urlParams.shellClassBase64 ?? "", }, }); diff --git a/web/src/pages/probeshell.tsx b/web/src/pages/probeshell.tsx index d001d2ba..98dc8c3d 100644 --- a/web/src/pages/probeshell.tsx +++ b/web/src/pages/probeshell.tsx @@ -57,6 +57,7 @@ export default function ProbeShellGenerator() { seconds: 5, sleepServer: "Tomcat", shrink: true, + staticInitialize: true }, }); diff --git a/web/src/types/memshell.ts b/web/src/types/memshell.ts index a0dc5bfc..00349497 100644 --- a/web/src/types/memshell.ts +++ b/web/src/types/memshell.ts @@ -67,6 +67,7 @@ export interface InjectorConfig { injectorClassName?: string; classInheritance?: string; urlPattern?: string; + staticInitialize?: boolean; } export interface ConfigResponseType { diff --git a/web/src/types/probeshell.ts b/web/src/types/probeshell.ts index 850eedbd..94a37f5b 100644 --- a/web/src/types/probeshell.ts +++ b/web/src/types/probeshell.ts @@ -16,6 +16,7 @@ export interface ProbeConfig { debug?: boolean; byPassJavaModule?: boolean; shrink?: boolean; + staticInitialize?: boolean; } export interface ProbeContentConfig { diff --git a/web/src/types/schema.ts b/web/src/types/schema.ts index c15d7771..dca9c3d7 100644 --- a/web/src/types/schema.ts +++ b/web/src/types/schema.ts @@ -10,6 +10,7 @@ export const memShellFormSchema = yup.object({ targetJdkVersion: yup.string().optional(), debug: yup.boolean().optional(), byPassJavaModule: yup.boolean().optional(), + staticInitialize: yup.boolean().optional(), shellClassName: yup.string().optional(), shellTool: yup.string().required().min(1), shellType: yup.string().required().min(1), @@ -152,6 +153,7 @@ export const probeShellFormSchema = yup.object().shape({ debug: yup.boolean().optional(), byPassJavaModule: yup.boolean().optional(), shrink: yup.boolean().optional(), + staticInitialize: yup.boolean().optional() }); type ProbeValidationResult = ResolverResult; diff --git a/web/src/utils/transformer.ts b/web/src/utils/transformer.ts index b22b0170..817cc7bb 100644 --- a/web/src/utils/transformer.ts +++ b/web/src/utils/transformer.ts @@ -37,6 +37,7 @@ export function transformToPostData(formValue: MemShellFormSchema) { const injectorConfig: InjectorConfig = { urlPattern: formValue.urlPattern, injectorClassName: formValue.injectorClassName, + staticInitialize: formValue.staticInitialize }; return { shellConfig, @@ -54,6 +55,7 @@ export function transformToProbePostData(formValue: ProbeShellFormSchema) { shrink: formValue.shrink, debug: formValue.debug, byPassJavaModule: formValue.byPassJavaModule, + staticInitialize: formValue.staticInitialize }; const probeContentConfig: ProbeContentConfig = { host: formValue.host,