// Simple JMX scanner extracting data (IP:Port) from RMI Registry and verifying RCE (using MLet) exploitability without credentials.
// Version: 0.1
// Sample use of: https://rabobank.jobs/e // Simple JMX scanner extracting data (IP:Port) from RMI Registry and verifying RCE (using MLet) exploitability without credentials.
// Version: 0.1
// Sample use of: https://rabobank.jobs/en/techblog/security-risk-infrastructure/jmx-the-best-buddy-of-every-red-teamer-marcin-wolak/
// Author: Marcin Wolak
// LinkedIn: https://www.linkedin.com/in/marcinwolak/
// Medium: https://marcin-wolak.medium.com/
// Github: https://github.com/marcin-wolak/
// Tested successfuly against JMX enabled on Java x64:
// Java(TM) SE Runtime Environment (build 1.7.0_80-b15) on Windows 10
// Java(TM) SE Runtime Environment (build 1.8.0_202-b08) on Windows 10
// Java(TM) SE Runtime Environment (build 9.0.4+11) on Windows 10
// OpenJDK Runtime Environment (build 11.0.17+8-post-Ubuntu-1ubuntu222.04)
// OpenJDK Runtime Environment (build 17.0.5+8-Debian-2)

import java.net.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.System;
import java.util.*;
import java.io.DataInputStream;
import java.io.ByteArrayInputStream;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.HelpFormatter;


public class SimpleRmiDiscoverer {

// IP Address of the RMI Registry to query (CMD Parameter)
private String regIP;
// TCP Port of the RMI Registry to query (CMD Parameter)
private int regPort;
// Ignore endpoint from RMI Registry to verify exploitability without credentials. Use instead IP of the registry itself and dynamic TCP port from the registry dump (CMD Parameter)
private boolean ignoreEndpoint;
// Extract Host:Port from the RMI Registry without checking RCE exploitability without credentials (CMD Parameter)
private boolean dumpOnly;

// IP Address and TCP port for the endpoint exposed via RMI Registry
private String endPIP;
private int endPPort;

// Sockets for communication with RMI Registry and exposed endpoint
private Socket regS;
private Socket endPS;

//Streams for communication via above sockets
private InputStream in, inEndP;
private OutputStream out, outEndP;

// RMI Handshake data
private byte[] hShake1 ={0x4A,0x52,0x4D,0x49,0x00,0x02,0x4B};
private byte[] hShake2 ={0x00,0x09,0x31,0x32,0x37,0x2E,0x30,0x2E,0x30,0x2E,0x31,0x00,0x00,0x00,0x00};

// Calling Obj
private byte[] hObj ={0x50,(byte)0xac,(byte)0xed,0x00,0x05,0x77,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x44,0x15,0x4d,(byte)0xc9,(byte)0xd4,(byte)0xe6,0x3b,(byte)0xdf,0x74,0x00,0x06,0x6a,0x6d,0x78,0x72,0x6d,0x69};


//Pattern to find in dumped registry (UnicastRef)
private byte[] ucRef = {0x55,0x6e,0x69,0x63,0x61,0x73,0x74,0x52,0x65,0x66};

//Pattern to find in exception dump (Credentials required)
private byte[] credReq = {(byte)0x43,(byte)0x72,(byte)0x65,(byte)0x64,(byte)0x65,(byte)0x6e,(byte)0x74,(byte)0x69,(byte)0x61,(byte)0x6c,(byte)0x73,(byte)0x20,(byte)0x72,(byte)0x65,(byte)0x71,(byte)0x75,(byte)0x69,(byte)0x72,(byte)0x65,(byte)0x64};

// Calling Obj (after modification) on the dynamic port
private byte[] hDObj ={(byte)0x50,(byte)0xac,(byte)0xed,(byte)0x00,(byte)0x05,(byte)0x77,(byte)0x22,(byte)0x6d,(byte)0xf9,(byte)0xa5,(byte)0x39,(byte)0xb4,(byte)0xfa,(byte)0x06,(byte)0xc3,(byte)0xea,(byte)0x8e,(byte)0xa7,(byte)0x0e,(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x85,(byte)0x06,(byte)0x13,(byte)0xa1,(byte)0x4d,(byte)0x80,(byte)0x01,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xf0,(byte)0xe0,(byte)0x74,(byte)0xea,(byte)0xad,(byte)0x0c,(byte)0xae,(byte)0xa8,(byte)0x70};

private String endpIP;
private short enpPort;

//javax.management.remote.rmi.RMIServerImpl_Stub object details collected from RMI registry:
private byte[] rmiStub;
int rmiStubLength;
int eIPlength;
byte [] eIP;
byte [] ePort;

// Constructor. Parameter dumps determines if only dynamic endpoint should be extracted from RMI Registry without checking if JMX is username/password protected
// Ignore detrmines if the IP of endpoint extracted from registry should be ignored and the IP of RMI Registry used instead
public SimpleRmiDiscoverer(String rIP, int rPort, boolean ignore, boolean dump) {
regPort = rPort;
regIP = rIP;
rmiStub = new byte[4096];
ignoreEndpoint = ignore;
dumpOnly = dump;
}

// findArray taken from: https://stackoverflow.com/questions/3940194/find-an-array-inside-another-larger-array
public int findArray(byte[] largeArray, byte[] subArray) {

/* If any of the arrays is empty then not found */
if (largeArray.length == 0 || subArray.length == 0) {
return -1;
}

/* If subarray is larger than large array then not found */
if (subArray.length > largeArray.length) {
return -1;
}

for (int i = 0; i < largeArray.length; i++) {
/* Check if the next element of large array is the same as the first element of subarray */
if (largeArray[i] == subArray[0]) {

boolean subArrayFound = true;
for (int j = 0; j < subArray.length; j++) {
/* If outside of large array or elements not equal then leave the loop */
if (largeArray.length <= i+j || subArray[j] != largeArray[i+j]) {
subArrayFound = false;
break;
}
}

/* Sub array found - return its index */
if (subArrayFound) {
return i;
}

}
}

/* Return default value */
return -1;
}

// Connecting to RMI Registry using IP/TCP pair from CLI
private int connectRegistry()
{
try {
//System.out.println("IP: " + regIP);
regS = new Socket(regIP, regPort);
in = this.regS.getInputStream();
out = this.regS.getOutputStream();
}
catch (Exception e) {
System.out.println("[!] Cannot connect to RMI Registry.");
//e.printStackTrace();
return 0;
}
return 1;
}

// RMI handshake. Common function for handshaking with RMI Registry as well as the endpoint extracted from it
private int rmiHandShake(InputStream inP, OutputStream outP)
{
byte [] buffer = new byte[4096];
try {
outP.write(hShake1, 0, hShake1.length);
int count = inP.read(buffer);
if(count==0)
{
System.out.println("[!] Handshake failed, no date received. Likely not RMI.");
return -1;
}
else
{
if(buffer[0]!=0x4E)
{
System.out.println("[!] Handshake failed, bad response. Likely not RMI.");
return -2;
}
outP.write(hShake2, 0, hShake2.length);
}
}
catch (Exception e) {
System.out.println("[!] Handshake failed, Error in RMI Communication.");
//e.printStackTrace();
return 0;
}
return 1;
}

// Invoking RMIServerImpl_Stub over RMI Registry
private int invokeObject()
{
try {
out.write(hObj, 0, hObj.length);
int rmiStubLength = in.read(rmiStub);
if(rmiStubLength==0)
{
System.out.println("[-] RMIServerImpl_Stub invocation against RMI Registry failed, no response!");
return -1;
}
//String s = new String(rmiStub);
//System.out.println(s);
//Findinf "UnicastRef" in the response from RMI Registry:
int unipos = findArray(rmiStub, ucRef);
if(unipos<1)
{
System.out.println("[-] RMIServerImpl_Stub invocation against RMI Registry failed, Malformed registry dump.");
return -2;
}
//else
// System.out.println(" The position is: " + unipos);
eIPlength = (int) rmiStub[unipos+11];
//System.out.println(" Length of eIP is: " + eIPlength);
eIP = Arrays.copyOfRange(rmiStub,unipos+12,unipos+12+eIPlength);
endPIP = new String(eIP);
//System.out.println(" The eIP is: " + endPIP);
ePort = Arrays.copyOfRange(rmiStub,unipos+12+eIPlength,unipos+16+eIPlength);
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(ePort));
endPPort = inputStream.readInt();
//System.out.println(endPPort);
//System.out.println(" The ePort is: " + Integer.valueOf(new String(ePort)));
}
catch (Exception e) {
System.out.println("[-] RMIServerImpl_Stub invocation against RMI Registry failed, Other Error.");
// e.printStackTrace();
return 0;
}
return 1;
}

// Connecting to the endpoint extracted from RMI registry or (when ignore option active) to RMI Registry IP and TCP port extracted from this Registry.
private int connectEndPoint(boolean ignoreEndPoint)
{
try {
//System.out.println("End Point IP: " + endPIP);
//System.out.println("End Point Port: " + endPPort);
System.out.println("[+] Connecting to the dynamic Endpoint ... ");
if(!ignoreEndPoint){
endPS = new Socket(endPIP, endPPort);
}
else
{
System.out.println("[+] Ignoring the endpoint exposed on: " + endPIP);
System.out.println("[+] Connecting instead to: " + regIP + " on TCP Port:" + Integer.toString(endPPort));
endPS = new Socket(regIP, endPPort);
}
inEndP = this.endPS.getInputStream();
outEndP = this.endPS.getOutputStream();
}
catch (Exception e) {
// e.printStackTrace();
System.out.println("[-] Connection to the dynamic Endpoint failed!");
return 0;
}
System.out.println("[+] Dynamic Endpoint connected successfuly!");
return 1;
}

// Invoking RMIServerImpl_Stub over the endpoint extracted from RMI registry or (when ignore option active) over RMI Registry IP and TCP port extracted from this Registry.
private int invokeDObject()
{
int unipos = findArray(rmiStub, ucRef);
System.arraycopy(rmiStub, unipos + 16 + endPIP.length(), hDObj, 7, 8);
System.arraycopy(rmiStub, 8, hDObj, 15, 4);
System.arraycopy(rmiStub, 16, hDObj, 23, 4);
byte [] buffer = new byte[4096];
try {
outEndP.write(hDObj, 0, hDObj.length);
int dStubLength = inEndP.read(buffer);
if(dStubLength==0)
{
System.out.println("[-] Invoking RMIServerImpl_Stub on the dynamic Endpoint failed, no response.");
return -1;
}
String s = new String(buffer);
if(s.contains("Credentials required")){
System.out.println("[-] Dynamic Enpoint requires credentials (username and password).");
}
else if(s.contains("UnicastRef")){
System.out.println("[+] Dynamic Enpoint is VULNERABLE to RCE, NO username and password required.");
}
else {
System.out.println("[-] Unknown outcome from invoking RMIServerImpl_Stub on the dynamic Endpoint. See below:");
System.out.println(s);
}
}
catch (Exception e) {
e.printStackTrace();
return 0;
}
return 1;
}

private void closeSocket(Socket inSock){

try{
inSock.close();}
catch (Exception e) {
//e.printStackTrace();
}

}

public static void main(String[] args) throws Exception{

boolean ignore=false;
boolean donly=false;
String IP = "";
long Port = 1099;
Options options = new Options();
options.addOption("h", "help", false, "Prints help for the tool.");
options.addOption("i", "ignore", false, "Uses RMI registry IP for methods invocations. Ignores endpoint from the Registry dump.");
options.addOption("d", "dumponly", false, "Extracting endpoint <host:port> from RMI Registry without checking exploitabilty, which is by default checked.");
options.addOption(OptionBuilder.withLongOpt("host").withDescription("IP of RMI Registry to query.").hasArg().withArgName("RMI HOST IP ").isRequired().create("H"));
options.addOption(OptionBuilder.withLongOpt("port").withDescription("TCP port of RMI Registry to query.").hasArg().withArgName("RMI HOST TCP PORT ").isRequired().create("P"));
//options.addOption("H", "--host", false, "IP of RMI Registry to query.");
//options.addOption("P", "--port", false, "TCP port of RMI Registry.");
CommandLineParser parser = new DefaultParser();

String header = "SimpleRmiDiscoverer extracts JMX host:port endpoint from RMI registry and checks if is exploitable without credentials using MLet. ";
String footer = " Please check my Blog for more details: https://marcin-wolak.medium.com/";

HelpFormatter formatter = new HelpFormatter();


try {
CommandLine cmd = parser.parse(options, args);
if (cmd.hasOption("h")) {
formatter.printHelp("SimpleRmiDiscoverer", header, options, footer, true);
System.exit(0);
}
if (cmd.hasOption("i")) {
ignore = true;
}
if (cmd.hasOption("d")) {
donly = true;
}
if (cmd.hasOption("H")) {
IP = cmd.getOptionValue("H");
}
if (cmd.hasOption("P")) {
try {
Port = Long.valueOf(cmd.getOptionValue("P")); }
catch (Exception e){
System.out.println(e.getMessage());
System.out.println("[!] Parameter -P --port must be a number between 1 and 65535! ");
formatter.printHelp("SimpleRmiDiscoverer", header, options, footer, true);
System.exit(1);
}
if(Port<0 || Port>65535){
System.out.println("[!] Parameter -P --port out of range! ");
formatter.printHelp("SimpleRmiDiscoverer", header, options, footer, true);
System.exit(0);
}

}

} catch (ParseException e){
System.out.println(e.getMessage());
formatter.printHelp("SimpleRmiDiscoverer", header, options, footer, true);
System.exit(1);

}

System.out.println("[+] Approached RMI Registry IP: " + IP);
System.out.println("[+] Approached RMI Registry TCP Port: " + Port);

SimpleRmiDiscoverer sRD = new SimpleRmiDiscoverer(IP, (int) Port, ignore, donly);
sRD.start();
}

public int start() {
// can now access non-static fields
int isConnected = connectRegistry();
if(isConnected == 0){
System.out.println("[-] Aborting Connection to RMI Registry! Quiting!");
closeSocket(regS);
return -1;
}
int isHShaked = rmiHandShake(in,out);
if(isHShaked != 1){
System.out.println("[-] Aborting handShaking with RMI Registry! Quiting!");
closeSocket(regS);
return -2;
}
int isInvoked = invokeObject();
if(isInvoked != 1){
System.out.println("[-] Object Calling Error, RMI Registry! Quiting");
closeSocket(regS);
return -2;
}

System.out.println("[+] RMI Registry contacted successfuly!");
System.out.println("[+] Extracted Endpoint Name / IP Address: " + endPIP);
System.out.println("[+] Extracted Endpoint Dynamic TCP Port: " + endPPort);

closeSocket(regS);

if(dumpOnly)
{
System.out.println("[+] RMI Registry Dump Completed Successfuly!");
System.exit(0);
}

int isEConnected = connectEndPoint(ignoreEndpoint);
if(isEConnected != 1){
System.out.println("[-] Aborting!");
closeSocket(endPS);
return -3;
}
isHShaked = rmiHandShake(inEndP, outEndP);
if(isHShaked != 1){
System.out.println("[-] Cannot handShake with the dynamic Endpoint!");
closeSocket(endPS);
return -2;
}
int isDInvoked = invokeDObject();
if(isDInvoked != 1){
System.out.println("[-] Cannot Invoke RMIServerImpl_Stub on the dynamic Endpoint!");
closeSocket(endPS);
return -4;
}
closeSocket(endPS);
return 1;
}

}