From 295fd56a11278c28204c36e445d538d21737821e Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Fri, 20 Oct 2023 22:26:59 +0200 Subject: [PATCH] Start to implement inline-gadgets --- .../exceptions/InvalidGadgetException.java | 16 +++ src/de/qtc/rmg/internal/ArgumentHandler.java | 2 +- src/de/qtc/rmg/internal/RMGOption.java | 8 +- src/de/qtc/rmg/io/GadgetOutputStream.java | 109 ++++++++++++++++++ src/de/qtc/rmg/io/GadgetWrapper.java | 40 +++++++ src/de/qtc/rmg/io/RawObjectOutputStream.java | 29 ++++- src/de/qtc/rmg/networking/RMIEndpoint.java | 16 ++- src/de/qtc/rmg/operations/Operation.java | 8 ++ src/de/qtc/rmg/plugin/DefaultProvider.java | 53 +++++++-- 9 files changed, 267 insertions(+), 14 deletions(-) create mode 100644 src/de/qtc/rmg/exceptions/InvalidGadgetException.java create mode 100644 src/de/qtc/rmg/io/GadgetOutputStream.java create mode 100644 src/de/qtc/rmg/io/GadgetWrapper.java diff --git a/src/de/qtc/rmg/exceptions/InvalidGadgetException.java b/src/de/qtc/rmg/exceptions/InvalidGadgetException.java new file mode 100644 index 00000000..c6948202 --- /dev/null +++ b/src/de/qtc/rmg/exceptions/InvalidGadgetException.java @@ -0,0 +1,16 @@ +package de.qtc.rmg.exceptions; + +/** + * @author Tobias Neitzel (@qtc_de) + */ +public class InvalidGadgetException extends Exception +{ + private static final long serialVersionUID = 1L; + + public InvalidGadgetException() {} + + public InvalidGadgetException(String message) + { + super(message); + } +} diff --git a/src/de/qtc/rmg/internal/ArgumentHandler.java b/src/de/qtc/rmg/internal/ArgumentHandler.java index 15cf98d7..da248e26 100644 --- a/src/de/qtc/rmg/internal/ArgumentHandler.java +++ b/src/de/qtc/rmg/internal/ArgumentHandler.java @@ -307,7 +307,7 @@ public Object getGadget() else { gadget = (String) RMGOption.require(RMGOption.GADGET_NAME); - command = RMGOption.require(RMGOption.GADGET_CMD); + command = RMGOption.GADGET_CMD.getValue(); } return PluginSystem.getPayloadObject(this.getAction(), gadget, command); diff --git a/src/de/qtc/rmg/internal/RMGOption.java b/src/de/qtc/rmg/internal/RMGOption.java index 269ae621..caf32eb2 100644 --- a/src/de/qtc/rmg/internal/RMGOption.java +++ b/src/de/qtc/rmg/internal/RMGOption.java @@ -110,7 +110,10 @@ 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), + + FILE_GADGET("--gadget-file", "the specified gadget is actually a file containing the gadget", Arguments.storeTrue(), RMGOptionGroup.ACTION), + B64_GADGET("--gadget-b64", "the specified gadget is actually a b64 string containing the gadget", Arguments.storeTrue(), RMGOptionGroup.ACTION); public final String name; public final String description; @@ -366,6 +369,9 @@ public static void addModifiers(RMGOption option, Argument arg) } else if( option == RMGOption.DGC_METHOD ) { arg.choices("clean", "dirty"); + } else if( option == RMGOption.GADGET_CMD || option == RMGOption.BIND_GADGET_CMD ) { + arg.nargs("?"); + } else if( intOptions.contains(option) ) { arg.type(Integer.class); diff --git a/src/de/qtc/rmg/io/GadgetOutputStream.java b/src/de/qtc/rmg/io/GadgetOutputStream.java new file mode 100644 index 00000000..10e96b8c --- /dev/null +++ b/src/de/qtc/rmg/io/GadgetOutputStream.java @@ -0,0 +1,109 @@ +package de.qtc.rmg.io; + +import java.io.IOException; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; + +public class GadgetOutputStream implements ObjectOutput +{ + private final ObjectOutput out; + + public GadgetOutputStream(ObjectOutput out) + { + this.out = out; + } + + @Override + public void writeBoolean(boolean v) throws IOException { + out.writeBoolean(v); + } + + @Override + public void writeByte(int v) throws IOException { + out.writeByte(v); + } + + @Override + public void writeShort(int v) throws IOException { + out.writeShort(v); + } + + @Override + public void writeChar(int v) throws IOException { + out.writeChar(v); + } + + @Override + public void writeInt(int v) throws IOException { + out.writeInt(v); + } + + @Override + public void writeLong(long v) throws IOException { + out.writeLong(v); + } + + @Override + public void writeFloat(float v) throws IOException { + out.writeFloat(v); + } + + @Override + public void writeDouble(double v) throws IOException { + out.writeDouble(v); + } + + @Override + public void writeBytes(String s) throws IOException { + out.writeBytes(s); + } + + @Override + public void writeChars(String s) throws IOException { + out.writeChars(s); + } + + @Override + public void writeUTF(String s) throws IOException { + out.writeUTF(s); + } + + @Override + public void writeObject(Object obj) throws IOException { + if (obj instanceof GadgetWrapper) + { + RawObjectOutputStream rout = new RawObjectOutputStream(out); + rout.writeRawObject(((GadgetWrapper)obj).getGadget()); + } + + else + { + out.writeObject(obj); + } + } + + @Override + public void write(int b) throws IOException { + out.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + out.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + out.close(); + } +} diff --git a/src/de/qtc/rmg/io/GadgetWrapper.java b/src/de/qtc/rmg/io/GadgetWrapper.java new file mode 100644 index 00000000..74adff53 --- /dev/null +++ b/src/de/qtc/rmg/io/GadgetWrapper.java @@ -0,0 +1,40 @@ +package de.qtc.rmg.io; + +import java.nio.ByteBuffer; +import java.util.Base64; + +import de.qtc.rmg.exceptions.InvalidGadgetException; +import de.qtc.rmg.utils.RMGUtils; + +public class GadgetWrapper +{ + private final byte[] gadget; + + public GadgetWrapper(String gadget) throws InvalidGadgetException + { + this.gadget = truncate(Base64.getDecoder().decode(gadget.getBytes())); + } + + public GadgetWrapper(byte[] gadget) throws InvalidGadgetException + { + this.gadget = truncate(gadget); + } + + public byte[] getGadget() + { + return gadget; + } + + private byte[] truncate(byte[] fullGadget) throws InvalidGadgetException + { + if (!ByteBuffer.wrap(fullGadget, 0, 4).equals(ByteBuffer.wrap(RMGUtils.hexToBytes("aced0005")))) + { + throw new InvalidGadgetException(); + } + + byte[] truncated = new byte[fullGadget.length - 4]; + System.arraycopy(fullGadget, 4, truncated, 0, truncated.length); + + return truncated; + } +} diff --git a/src/de/qtc/rmg/io/RawObjectOutputStream.java b/src/de/qtc/rmg/io/RawObjectOutputStream.java index 4ad82d6c..0f417347 100644 --- a/src/de/qtc/rmg/io/RawObjectOutputStream.java +++ b/src/de/qtc/rmg/io/RawObjectOutputStream.java @@ -2,9 +2,11 @@ import java.io.DataOutput; import java.io.IOException; +import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.lang.reflect.Field; +import java.lang.reflect.Method; import de.qtc.rmg.internal.ExceptionHandler; @@ -18,6 +20,7 @@ public class RawObjectOutputStream { private DataOutput bout; private OutputStream outStream; + private Method setBlockDataMode; /** * Wraps an ObjectOutputStream into an RawObjectOutputStream. The underlying OutputStream object is made @@ -25,7 +28,7 @@ public class RawObjectOutputStream { * * @param out OutputStream to wrap around */ - public RawObjectOutputStream(ObjectOutputStream out) + public RawObjectOutputStream(ObjectOutput out) { try { Field boutField = ObjectOutputStream.class.getDeclaredField("bout"); @@ -38,6 +41,9 @@ public RawObjectOutputStream(ObjectOutputStream out) if(c.getCanonicalName().endsWith("BlockDataOutputStream")) { outputStreamField = c.getDeclaredField("out"); outputStreamField.setAccessible(true); + + setBlockDataMode = c.getDeclaredMethod("setBlockDataMode", new Class[] {boolean.class}); + setBlockDataMode.setAccessible(true); } } @@ -58,4 +64,25 @@ public void writeRaw(byte content) throws IOException { outStream.write(content); } + + /** + * Write raw bytes to the underlying output stream. + * + * @param content bytes to write + * @throws IOException + */ + public void writeRawObject(byte[] content) throws IOException + { + try + { + setBlockDataMode.invoke(bout, false); + outStream.write(content); + setBlockDataMode.invoke(bout, true); + } + + catch (Exception e) + { + e.printStackTrace(); + } + } } diff --git a/src/de/qtc/rmg/networking/RMIEndpoint.java b/src/de/qtc/rmg/networking/RMIEndpoint.java index 3ce8a19e..0beb934a 100644 --- a/src/de/qtc/rmg/networking/RMIEndpoint.java +++ b/src/de/qtc/rmg/networking/RMIEndpoint.java @@ -14,6 +14,7 @@ import de.qtc.rmg.internal.MethodArguments; import de.qtc.rmg.internal.MethodCandidate; import de.qtc.rmg.internal.Pair; +import de.qtc.rmg.io.GadgetOutputStream; import de.qtc.rmg.io.MaliciousOutputStream; import de.qtc.rmg.io.RawObjectInputStream; import de.qtc.rmg.plugin.PluginSystem; @@ -227,11 +228,14 @@ public void unmanagedCall(ObjID objID, int callID, long methodHash, MethodArgume try { ObjectOutputStream out = (ObjectOutputStream)call.getOutputStream(); + if(locationStream) out = new MaliciousOutputStream(out); - for(Pair p : callArguments) { - marshalValue(p.right(), p.left(), out); + for(Pair p : callArguments) + { + ObjectOutput gadgetStream = new GadgetOutputStream(out); + marshalValue(p.right(), p.left(), gadgetStream); } } catch(java.io.IOException e) { @@ -276,7 +280,8 @@ public void unmanagedCall(ObjID objID, int callID, long methodHash, MethodArgume */ private static void marshalValue(Class type, Object value, ObjectOutput out) throws IOException { - if (type.isPrimitive()) { + if (type.isPrimitive()) + { if (type == int.class) { out.writeInt(((Integer) value).intValue()); } else if (type == boolean.class) { @@ -296,7 +301,10 @@ private static void marshalValue(Class type, Object value, ObjectOutput out) } else { throw new Error("Unrecognized primitive type: " + type); } - } else { + } + + else + { out.writeObject(value); } } diff --git a/src/de/qtc/rmg/operations/Operation.java b/src/de/qtc/rmg/operations/Operation.java index 6fe043de..010f2863 100644 --- a/src/de/qtc/rmg/operations/Operation.java +++ b/src/de/qtc/rmg/operations/Operation.java @@ -44,6 +44,8 @@ public enum Operation { RMGOption.BIND_GADGET_NAME, RMGOption.BIND_GADGET_CMD, RMGOption.YSO, + RMGOption.B64_GADGET, + RMGOption.FILE_GADGET, RMGOption.SOCKET_FACTORY, RMGOption.SOCKET_FACTORY_SSL, RMGOption.SOCKET_FACTORY_PLAIN, @@ -184,6 +186,8 @@ public enum Operation { RMGOption.GADGET_NAME, RMGOption.GADGET_CMD, RMGOption.YSO, + RMGOption.B64_GADGET, + RMGOption.FILE_GADGET, }), OBJID("dispatchObjID", "", "Print information contained within an ObjID", new RMGOption[] { @@ -216,6 +220,8 @@ public enum Operation { RMGOption.BIND_GADGET_NAME, RMGOption.BIND_GADGET_CMD, RMGOption.YSO, + RMGOption.B64_GADGET, + RMGOption.FILE_GADGET, RMGOption.SOCKET_FACTORY, RMGOption.SOCKET_FACTORY_SSL, RMGOption.SOCKET_FACTORY_PLAIN, @@ -274,6 +280,8 @@ public enum Operation { RMGOption.GADGET_NAME, RMGOption.GADGET_CMD, RMGOption.YSO, + RMGOption.B64_GADGET, + RMGOption.FILE_GADGET, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, RMGOption.SOCKET_FACTORY, diff --git a/src/de/qtc/rmg/plugin/DefaultProvider.java b/src/de/qtc/rmg/plugin/DefaultProvider.java index 15494528..4163a652 100644 --- a/src/de/qtc/rmg/plugin/DefaultProvider.java +++ b/src/de/qtc/rmg/plugin/DefaultProvider.java @@ -1,11 +1,16 @@ package de.qtc.rmg.plugin; +import java.io.IOException; import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Paths; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMISocketFactory; +import de.qtc.rmg.exceptions.InvalidGadgetException; import de.qtc.rmg.internal.ExceptionHandler; import de.qtc.rmg.internal.RMGOption; +import de.qtc.rmg.io.GadgetWrapper; import de.qtc.rmg.io.Logger; import de.qtc.rmg.networking.DGCClientSocketFactory; import de.qtc.rmg.networking.LoopbackSocketFactory; @@ -51,21 +56,26 @@ public class DefaultProvider implements IArgumentProvider, IPayloadProvider, ISo @Override public Object getPayloadObject(Operation action, String name, String args) { - switch(action) { - + switch(action) + { case BIND: case REBIND: case UNBIND: - if(name.equalsIgnoreCase("jmx")) { + if(name.equalsIgnoreCase("jmx")) + { String[] split = RMGUtils.splitListener(args); return RegistryClient.prepareRMIServerImpl(split[0], Integer.valueOf(split[1])); + } - } else if(args == null) { + else if(args == null) + { String[] split = RMGUtils.splitListener(name); return RegistryClient.prepareRMIServerImpl(split[0], Integer.valueOf(split[1])); + } - } else { + else + { Logger.eprintlnMixedYellow("The specified gadget", name, "is not supported for this action."); RMGUtils.exit(); } @@ -74,8 +84,37 @@ public Object getPayloadObject(Operation action, String name, String args) default: - if(args == null) { - Logger.eprintlnMixedBlue("Specifying a", "gadget argument", "is required for this action."); + try + { + if (RMGOption.B64_GADGET.getBool()) + { + return new GadgetWrapper(name); + } + + else if(RMGOption.FILE_GADGET.getBool()) + { + byte[] gadget = Files.readAllBytes(Paths.get(name)); + return new GadgetWrapper(gadget); + } + } + + catch (InvalidGadgetException e) + { + Logger.eprintlnMixedBlue("The specified gadget", "is invalid."); + Logger.eprintlnMixedYellow("A valid gadget starts with", "0xaced0005", "as signature."); + RMGUtils.exit(); + } + + catch (IOException e) + { + Logger.eprintlnMixedBlue("Unable to read gadget file", RMGOption.FILE_GADGET.getValue()); + ExceptionHandler.showStackTrace(e); + RMGUtils.exit(); + } + + if(args == null) + { + Logger.eprintlnMixedBlue("Specifying a", "gadget argument", "is required for this gadget."); RMGUtils.exit(); }