Skip to content

Commit

Permalink
Add GenericPrint to codebase
Browse files Browse the repository at this point in the history
The GenericPrint plugin is now included into rmg per default and can be
activated by using the --return-value option.
  • Loading branch information
qtc-de committed Nov 27, 2023
1 parent 3e223f2 commit b09e9a5
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 17 deletions.
3 changes: 2 additions & 1 deletion src/de/qtc/rmg/internal/RMGOption.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ public enum RMGOption {
SOCKET_FACTORY_SSL("--socket-factory-ssl", "enforce SSL connections from dynamically created socket factories", Arguments.storeTrue(), RMGOptionGroup.CONNECTION),
SOCKET_FACTORY("--socket-factory", "dynamically create a socket factory class with the specified name", Arguments.store(), RMGOptionGroup.CONNECTION, "classname"),

SPRING_REMOTING("--spring-remoting", "enforce method calls to be dispatched via spring remoting", Arguments.storeTrue(), RMGOptionGroup.CONNECTION);
SPRING_REMOTING("--spring-remoting", "enforce method calls to be dispatched via spring remoting", Arguments.storeTrue(), RMGOptionGroup.CONNECTION),
GENERIC_PRINT("--return-value", "attempt to output the return value using GenericPrint", Arguments.storeTrue(), RMGOptionGroup.ACTION);

public final String name;
public final String description;
Expand Down
1 change: 1 addition & 0 deletions src/de/qtc/rmg/operations/Operation.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public enum Operation {
RMGOption.SOCKET_FACTORY,
RMGOption.SOCKET_FACTORY_SSL,
RMGOption.SOCKET_FACTORY_PLAIN,
RMGOption.GENERIC_PRINT,
}),

CODEBASE("dispatchCodebase", "<classname> <url>", "Perform remote class loading attacks", new RMGOption[] {
Expand Down
237 changes: 237 additions & 0 deletions src/de/qtc/rmg/plugin/GenericPrint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package de.qtc.rmg.plugin;

import java.io.File;
import java.lang.reflect.Array;
import java.rmi.Remote;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;

import de.qtc.rmg.internal.ExceptionHandler;
import de.qtc.rmg.io.Logger;
import de.qtc.rmg.utils.ActivatableWrapper;
import de.qtc.rmg.utils.RemoteObjectWrapper;
import de.qtc.rmg.utils.SpringRemotingWrapper;
import de.qtc.rmg.utils.UnicastWrapper;

/**
* GenericPrint is an rmg ResponseHandler plugin that attempts to print all incoming
* server responses. It compares the incoming object to some known data types and chooses
* reasonable defaults to visualize them.
*
* From remote-method-guesser v5.0.0, GenericPrint is included per default and does not need
* to be compiled separately. It can be enabled by using the --generic-print option.
*
* @author Tobias Neitzel (@qtc_de)
*/
public class GenericPrint implements IResponseHandler
{
/**
* The handleResponse function is called with the incoming responseObject from the
* RMI server. Depending on the corresponding class, a different print action is
* chosen.
*
* @param responseObject Incoming object from an RMI server response
*/
public void handleResponse(Object responseObject)
{
Class<?> responseClass = responseObject.getClass();

if(responseObject instanceof Collection<?>)
{
handleCollection(responseObject);
}

else if(responseObject instanceof Map<?,?>)
{
handleMap(responseObject);
}

else if(responseClass.isArray())
{
handleArray(responseObject);
}

else if(Remote.class.isAssignableFrom(responseClass))
{
handleRemote((Remote)responseObject);
}

else if(responseObject instanceof File)
{
handleFile((File)responseObject);
}

else if(responseObject instanceof Byte)
{
handleByte((byte)responseObject);
}

else
{
handleDefault(responseObject);
}
}

/**
* For each item within an collection, call handleResponse on the corresponding
* item value.
*
* @param o Object of the Collection type
*/
public void handleCollection(Object o)
{
for(Object item: (Collection<?>)o)
{
handleResponse(item);
}
}

/**
* For each entry within a map, handleResponse is called on the entry key and value.
* Furthermore, an arrow is printed in an attempt to visualize their relationship.
*
* @param o Object of the Map type
*/
public void handleMap(Object o)
{
Map<?,?> map = (Map<?,?>)o;

for(Entry<?,?> item: map.entrySet())
{
handleResponse(item.getKey());
System.out.print(" --> ");
handleResponse(item.getValue());
}
}

/**
* For each item within an array, call the handleResponse function.
*
* @param o Object of the Array type
*/
public void handleArray(Object o)
{
Object[] objectArray = null;
Class<?> type = o.getClass().getComponentType();

if(type.isPrimitive())
{
int length = Array.getLength(o);
objectArray = new Object[length];

for(int ctr = 0; ctr < length; ctr++)
{
objectArray[ctr] = Array.get(o, ctr);
}
}

else
{
objectArray = (Object[])o;
}

for(Object item: objectArray)
{
handleResponse(item);
}
}

/**
* For all objects that extend Remote, the details of the remote reference are printed.
* This includes the class name, the remote TCP endpoint, the assigned ObjID and the
* configured socket factories.
*
* @param o Object that extends the Remote type
*/
public void handleRemote(Remote o)
{
try
{
RemoteObjectWrapper objectWrapper = RemoteObjectWrapper.getInstance(o);

if ((objectWrapper instanceof UnicastWrapper) || objectWrapper instanceof SpringRemotingWrapper)
{
UnicastWrapper wrapper = (UnicastWrapper)objectWrapper;

String csf = "default";
String ssf = "default";

if (wrapper.csf != null)
{
csf = wrapper.csf.getClass().getName();
}

if (wrapper.ssf != null)
{
ssf = wrapper.ssf.getClass().getName();
}

Logger.printlnYellow("Printing unicast RemoteObject:");
Logger.increaseIndent();
Logger.printlnMixedBlue("Remote Class:\t\t", wrapper.getInterfaceName());
Logger.printlnMixedBlue("Endpoint:\t\t", wrapper.getTarget());
Logger.printlnMixedBlue("ObjID:\t\t\t", wrapper.objID.toString());
Logger.printlnMixedBlue("ClientSocketFactory:\t", csf);
Logger.printlnMixedBlue("ServerSocketFactory:\t", ssf);
}

else if (objectWrapper instanceof ActivatableWrapper)
{
ActivatableWrapper wrapper = (ActivatableWrapper)objectWrapper;

Logger.printlnYellow("Printing activatable RemoteObject:");
Logger.increaseIndent();
Logger.printlnMixedBlue("Remote Class:\t\t", wrapper.getInterfaceName());
Logger.printlnMixedBlue("Activator:\t\t", wrapper.getActivatorEndpoint());
Logger.printlnMixedBlue("ActivationID:\t\t", wrapper.activationUID.toString());
}

else
{
Logger.eprintlnYellow("Unsupported object type.");
}
}

catch (Exception e)
{
ExceptionHandler.unexpectedException(e, "constructing", "RemoteObjectWrapper", true);
}

finally
{
Logger.decreaseIndent();
}
}

/**
* For File objects, print their absolute path.
*
* @param o File object
*/
public void handleFile(File o)
{
Logger.println(o.getAbsolutePath());
}

/**
* Byte objects are converted to hex and printed. As a single byte is most likely part of a
* sequence, we print without a newline.
*
* @param o File object
*/
public void handleByte(byte o)
{
Logger.printPlain(String.format("%02x", o));
}

/**
* The default action for each object is to print it using it's toString method.
*
* @param o Object that did not matched one of the previously mentioned types.
*/
public void handleDefault(Object o)
{
Logger.println(o.toString());
}
}
58 changes: 42 additions & 16 deletions src/de/qtc/rmg/plugin/PluginSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import de.qtc.rmg.exceptions.MalformedPluginException;
import de.qtc.rmg.internal.ExceptionHandler;
import de.qtc.rmg.internal.RMGOption;
import de.qtc.rmg.io.Logger;
import de.qtc.rmg.operations.Operation;
import de.qtc.rmg.utils.RMGUtils;
Expand Down Expand Up @@ -49,8 +50,15 @@ public static void init(String pluginPath)
argumentProvider = provider;
socketFactoryProvider = provider;

if(pluginPath != null)
if (RMGOption.GENERIC_PRINT.getBool())
{
responseHandler = new GenericPrint();
}

if (pluginPath != null)
{
loadPlugin(pluginPath);
}
}

/**
Expand All @@ -73,55 +81,73 @@ private static void loadPlugin(String pluginPath)
JarInputStream jarStream = null;
File pluginFile = new File(pluginPath);

if(!pluginFile.exists()) {
if (!pluginFile.exists())
{
Logger.eprintlnMixedYellow("Specified plugin path", pluginPath, "does not exist.");
RMGUtils.exit();
}

try {
try
{
jarStream = new JarInputStream(new FileInputStream(pluginFile));
Manifest mf = jarStream.getManifest();
pluginClassName = mf.getMainAttributes().getValue(manifestAttribute);
jarStream.close();

if(pluginClassName == null)
if (pluginClassName == null)
{
throw new MalformedPluginException();

} catch(Exception e) {
}
}

catch (Exception e)
{
Logger.eprintlnMixedYellow("Caught", e.getClass().getName(), "while reading the Manifest of the specified plugin.");
Logger.eprintlnMixedBlue("Plugins need to be valid JAR files that contain the", manifestAttribute, "attribute.");
RMGUtils.exit();
}

try {
try
{
URLClassLoader ucl = new URLClassLoader(new URL[] {pluginFile.toURI().toURL()});
Class<?> pluginClass = Class.forName(pluginClassName, true, ucl);
pluginInstance = pluginClass.newInstance();
} catch(Exception e) {
}

catch (Exception e)
{
Logger.eprintMixedYellow("Caught", e.getClass().getName(), "while reading plugin file ");
Logger.printlnPlainBlue(pluginPath);
ExceptionHandler.showStackTrace(e);
RMGUtils.exit();
}

if(pluginInstance instanceof IPayloadProvider) {
if (pluginInstance instanceof IPayloadProvider)
{
payloadProvider = (IPayloadProvider)pluginInstance;
inUse = true;

} if(pluginInstance instanceof IResponseHandler) {
}

if (pluginInstance instanceof IResponseHandler)
{
responseHandler = (IResponseHandler)pluginInstance;
inUse = true;

} if(pluginInstance instanceof IArgumentProvider) {
}

if(pluginInstance instanceof IArgumentProvider)
{
argumentProvider = (IArgumentProvider)pluginInstance;
inUse = true;

} if(pluginInstance instanceof ISocketFactoryProvider) {
}

if(pluginInstance instanceof ISocketFactoryProvider)
{
socketFactoryProvider = (ISocketFactoryProvider)pluginInstance;
inUse = true;
}

if(!inUse) {
if (!inUse)
{
Logger.eprintMixedBlue("Plugin", pluginPath, "was successfully loaded, but is ");
Logger.eprintlnPlainYellow("not in use.");
Logger.eprintlnMixedYellow("Plugins should implement at least one of the", "IPayloadProvider, IResponseHandler, IArgumentProvider or ISocketFactoryProvider", "interfaces.");
Expand Down

0 comments on commit b09e9a5

Please sign in to comment.