为了快速概述我的目标,我构建了一个应用程序,它将 DLL 注入到 java 进程中,然后该进程尝试从远程主机加载并运行 jar 文件。请注意,jar 文件不会也不应该出现在磁盘上的任何位置。为了实现这一点,我编写了一个自定义类加载器和一个读取本机缓冲区的 InputStream 实现,使用 jni 从我的本机 C++ 代码传入。
由于缓冲区大于 Integer.MAX_VALUE,我不能简单地传入一个 byte[],所以我想出了这个 UnsafeBufferInputStream,来读取从代表 jar 文件的 C++ 代码分配的本机缓冲区。
内存类加载器:
public class MemoryClassLoader extends ClassLoader {
private final Map<String, byte[]> classes = new HashMap<>();
private final Map<String, byte[]> resources = new HashMap<>();
public MemoryClassLoader() {
}
// Called from my native c++ code, providing the start address and length of the buffer
public void load(long memAddress, long bufSize) {
try {
String tempFolderPath = System.getProperty("java.io.tmpdir"); // Get the system's temporary folder
String className = "net.sxlver.UnsafeBufferInputStream"; // Adjust with your class name
Class<?> unsafeStream = null;
try {
// Read the class file bytes from the temp folder
Path filePath = Paths.get(tempFolderPath, "UnsafeBufferInputStream.class");
byte[] classBytes = Files.readAllBytes(filePath);
unsafeStream = defineClass(className, classBytes, 0, classBytes.length);
} catch (IOException e) {
e.printStackTrace();
}
//UnsafeBufferInputStream stream = (UnsafeBufferInputStream) unsafeStream.getDeclaredConstructor(long.class, long.class).newInstance(memAddress, bufSize);
UnsafeBufferInputStream stream = new UnsafeBufferInputStream(memAddress, bufSize);
loadClassesAndResourcesFromJar(stream);
}catch(final Exception exception) {
JOptionPane.showMessageDialog(null, "");
}
}
private static Unsafe getUnsafe() {
try {
java.lang.reflect.Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
throw new RuntimeException("Unsafe access error: " + e.getMessage());
}
}
private void loadClassesAndResourcesFromJar(UnsafeBufferInputStream stream) throws IOException {
JarInputStream jarStream = new JarInputStream(stream);
JarEntry entry;
while ((entry = jarStream.getNextJarEntry()) != null) {
if (!entry.isDirectory()) {
byte[] buffer = new byte[1024];
int bytesRead;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((bytesRead = jarStream.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
if (entry.getName().endsWith(".class")) {
classes.put(entry.getName().replace(".class", "").replace("/", "."), baos.toByteArray());
} else {
resources.put(entry.getName(), baos.toByteArray());
}
}
}
}
// Override findClass to load classes from memory
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = classes.get(name);
if (classBytes == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, classBytes, 0, classBytes.length);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
try {
loadedClass = findClass(name);
} catch (ClassNotFoundException e) {
loadedClass = super.loadClass(name);
}
}
return loadedClass;
}
}
@Override
public InputStream getResourceAsStream(String name) {
byte[] resourceBytes = resources.get(name);
if (resourceBytes != null) {
return new ByteArrayInputStream(resourceBytes);
}
return super.getResourceAsStream(name);
}
}
不安全缓冲区输入流:
public class UnsafeBufferInputStream extends InputStream {
private Unsafe unsafe;
private long address;
private long remainingBytes;
private static final int BUFFER_SIZE = 1024; // Adjust buffer size as needed
public UnsafeBufferInputStream(long bufferAddress, long size) {
this.unsafe = getUnsafe();
this.address = bufferAddress;
this.remainingBytes = size;
}
private Unsafe getUnsafe() {
try {
java.lang.reflect.Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
throw new RuntimeException("Unsafe access error: " + e.getMessage());
}
}
@Override
public int read() throws IOException {
if (remainingBytes <= 0) {
return -1;
}
byte value = unsafe.getByte(address++);
remainingBytes--;
return value & 0xFF;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (remainingBytes <= 0) {
return -1;
}
int bytesRead = (int) Math.min(len, remainingBytes);
for (int i = 0; i < bytesRead; i++) {
b[off + i] = unsafe.getByte(address++);
}
remainingBytes -= bytesRead;
return bytesRead;
}
@Override
public void close() {
}
}
现在它有点工作了,至少加载包含入口点的类,但是,我在实际尝试运行 jar 时遇到了几个问题,我觉得这个任务变得比它可能需要的更加复杂。我什至不想想象这种方法在运行时可能导致的问题有多么难以追踪。
我想知道是否有任何预制的解决方案来运行仅存在于内存中且经过良好测试且稳定的 jar 文件。
或者也许我应该采用完全不同的方法。
如果比我聪明的人能指出我正确的方向,我将不胜感激。
另一种方法:
查找从 .jar (ZIP) 文件加载的 ClassLoader 的源代码。可能它从 FileInputStream 或类似的读取。修改它以便它可以从不同类型的InputStream 中读取。用 C 或 C++ 本机实现该 InputStream 类,使其从内存中的 .jar 数据中读取。