Skip to content

Commit

Permalink
Add first version of sources.
Browse files Browse the repository at this point in the history
  • Loading branch information
ekszz committed Sep 16, 2023
1 parent 2119f26 commit 5e49e55
Show file tree
Hide file tree
Showing 8 changed files with 1,007 additions and 0 deletions.
157 changes: 157 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# EzzPatcher

> A tiny runtime hot patch tool for JAVA
![GitHub](https://img.shields.io/github/license/ekszz/EzzPatcher)
![GitHub top language](https://img.shields.io/github/languages/top/ekszz/EzzPatcher)
![GitHub release (latest by SemVer including pre-releases)](https://img.shields.io/github/downloads-pre/ekszz/EzzPatcher/latest/total)


一款Java运行态字节码插桩工具。可以通过配置,在不重启应用的情况下,动态更新运行中的JVM里的字节码。

初衷是用于CTF比赛AWD模式下Java应用的快速修补,当然,它绝不局限于此。

- 基于JavaAgent
- 支持随应用启动和应用启动后attach两种模式
- 使用Yaml文件简便配置
- 支持不重启应用的情况下多次patch
- 支持已patch类的还原
- 支持配置文件不落地


## 0x01 构建
### Maven

使用Maven编译打包:

```
mvn clean package
```

将会生成 `target/EzzPatcher-1.x.x-jar-with-dependencies.jar`.

当然,你可以直接从releases下载打包好的jar包。

## 0x02 执行
### 通过Attach JVM来执行

如果目标JVM已经在运行中,可以通过attach的方式来执行。

每次执行时,会还原上次的修改,并根据最新的配置文件,重新对目标JVM中的类进行热补丁。

**JDK 8**
```
java -Xbootclasspath/a:<path-to-jdk>/lib/tools.jar -jar EzzPatcher-1.x.x-jar-with-dependencies.jar <java-pid>
```
or
```
java -Xbootclasspath/a:<path-to-jdk>/lib/tools.jar -jar EzzPatcher-1.x.x-jar-with-dependencies.jar <java-pid> <path-to-config>
```

**JDK 11 and newer**
```
java -jar EzzPatcher-1.x.x-jar-with-dependencies.jar <java-pid>
```
or
```
java -jar EzzPatcher-1.x.x-jar-with-dependencies.jar <java-pid> <path-to-config>
```

### 通过-javaagent参数随目标JVM启动

在目标JVM启动命令行中添加-javaagent参数来启动。
```
java -cp <class-path> -javaagent:EzzPatcher-1.x.x-jar-with-dependencies.jar <main-class> <arguments>
```
or
```
java -cp <class-path> -javaagent:EzzPatcher-1.x.x-jar-with-dependencies.jar=<path-to-config> <main-class> <arguments>
```

通过该方式启动应用后,依然可以使用上述attach JVM的方式来变更配置。

该方式主要用来变更一些应用启动时就用到的类。

## 0x03 配置文件

以下是一个示例配置文件。

> 如果在启动命令行中不带<path-to-config>参数的话,默认使用目标JVM工作目录下的config.yml文件。建议使用绝对路径指定。
```yaml
classPatchDefine: # 固定值
# 需要修改的类名
com.example.ezjava.controller.EvilController:
# 需要修改的第1个方法名
# 该类下所有需要修改的方法组成一个List
- method: cmd
# 该方法的参数列表(可选),List结构
# 如果为空,应为paramType: []
# 若无该参数,默认取同名函数中的某一个
paramType:
- java.lang.String
# 可选,目前支持:
# overwrite 覆盖(默认)
# insertBefore 在原方法前插入代码
# insertAfter 在原方法返回前插入代码
# insertAt 在指定行数插入代码
mode: overwrite
# 你的代码
code: '{ return "ok";}'

# 需要修改的第2个方法名
- method: cmd
paramType: []
mode: insertAt
# 在该行数前插入代码
# 当mode为insertAt时,此参数为必选
insertAt: 23
code: '{System.out.println("helloworld");return "yes!";}'

# 需要修改的第2个类,这是一个最简单的配置的示例
com.example.ezjava.beans.TargetClass:
- method: getFlag
code: 'return "ok";'

com.example.ezjava.controller.DownloadController:
- method: readFileCode
paramType:
- java.lang.String
mode: insertBefore
code: '{filePath="/tmp/nothing.txt";}'
```
> 注意:
>
> 1. code字段中的代码应满足Javassist要求。
>
> 2. Javassist中有一些特别的参数约定,如$0代表this,$1、$2...等代表第1、2...个参数,具体可参见Javassist文档。
如果你要还原所有变更,直接放置一个如下的配置文件后再次运行即可:
```yaml
classPatchDefine:

```
你可以将配置文件内容Base64编码后,放在<path-to-config>参数位置,以实现配置文件不落地。
例如,你要还原所有变更的话,在JDK11下,可以执行:
```
java -jar EzzPatcher-1.x.x-jar-with-dependencies.jar <java-pid> Y2xhc3NQYXRjaERlZmluZToNCg==
```

## 0x04 一些注意

Javaagent在被JVM加载之后,agent的相关类无法被卸载/更新,虽然一般没太大影响,但请谨慎使用。

仅在JDK8和JDK11上测试过。

## 0x05 免责声明

本项目仅面向软件研发调试、研究、学习,禁止任何非法用途。

如您在使用本项目的过程中存在任何非法行为,您需自行承担相应后果。

除非您已充分阅读、完全理解并接受本协议,否则,请您不要使用本项目。
40 changes: 40 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
classPatchDefine: # 固定值
# 需要修改的类名
com.example.ezjava.controller.EvilController:
# 需要修改的第1个方法名
# 该类下所有需要修改的方法组成一个List
- method: cmd
# 该方法的参数列表(可选),List结构
# 如果为空,应为paramType: []
# 若无该参数,默认取同名函数中的某一个
paramType:
- java.lang.String
# 可选,目前支持:
# overwrite 覆盖(默认)
# insertBefore 在原方法前插入代码
# insertAfter 在原方法返回前插入代码
# insertAt 在指定行数插入代码
mode: overwrite
# 你的代码
code: '{ return "ok";}'

# 需要修改的第2个方法名
- method: cmd
paramType: []
mode: insertAt
# 在该行数前插入代码
# 当mode为insertAt时,此参数为必选
insertAt: 23
code: '{System.out.println("helloworld");return "yes!";}'

# 需要修改的第2个类,这是一个最简单的配置的示例
com.example.ezjava.beans.TargetClass:
- method: getFlag
code: 'return "ok";'

com.example.ezjava.controller.DownloadController:
- method: readFileCode
paramType:
- java.lang.String
mode: insertBefore
code: '{filePath="/tmp/nothing.txt";}'
91 changes: 91 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?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>

<groupId>com.ekszz.ezzpatcher</groupId>
<artifactId>EzzPatcher</artifactId>
<version>1.0.0</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<profiles>
<profile>
<id>jdk8</id>
<activation>
<jdk>1.8</jdk>
</activation>
<dependencies>
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>jdk1.8.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
</profile>
</profiles>

<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.2-GA</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.ekszz.ezzpatcher.EzzPatcher</mainClass>
</manifest>
<manifestEntries>
<Premain-Class>com.ekszz.ezzpatcher.EzzPatcher</Premain-Class>
<Agent-Class>com.ekszz.ezzpatcher.EzzPatcher</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
61 changes: 61 additions & 0 deletions src/main/java/com/ekszz/ezzpatcher/ClassMethodDefine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.ekszz.ezzpatcher;

import java.util.List;

public class ClassMethodDefine {
private String mode;
private String method;
private List<String> paramType;
private Integer insertAt;
private String code;

public String getMode() {
return mode;
}

public void setMode(String mode) {
this.mode = mode;
}

public String getMethod() {
return method;
}

public void setMethod(String method) {
this.method = method;
}

public List<String> getParamType() {
return paramType;
}

public void setParamType(List<String> paramType) {
this.paramType = paramType;
}

public Integer getInsertAt() {
return insertAt;
}

public void setInsertAt(Integer insertAt) {
this.insertAt = insertAt;
}

public String getCode() {
return code;
}

public void setCode(String code) {
this.code = code;
}

@Override
public String toString() {
return "ClassMethodDefine{" +
"mode='" + mode + '\'' +
", method='" + method + '\'' +
", paramType=" + paramType +
", code='" + code + '\'' +
'}';
}
}
Loading

0 comments on commit 5e49e55

Please sign in to comment.