public abstract class BaRMIeException extends Exception {
	public BaRMIeException(String message) {
		super(message);
	}
	
	public BaRMIeException(String message, Throwable cause) {
		super(message, cause);
	}
} public class BaRMIeGetObjectException extends BaRMIeException {
	public BaRMIeGetObjectException(String message, Throwable cause) {
		super(message, cause);
	}
} public class BaRMIeInputException extends BaRMIeException {
	public BaRMIeInputException(Throwable cause) {
		super("An IOException occurred whilst reading from STDIN.", cause);
	}
} public class BaRMIeWriteFileException extends BaRMIeException {
	public BaRMIeWriteFileException(String message, Throwable cause) {
		super(message, cause);
	}
} public class RMIEndpoint {
	/*******************
	 * Properties
	 ******************/
	private final TCPEndpoint _endpoint;				//The host and port of the RMI endpoint
	private boolean _isRMIEndpoint;						//True if this endpoint appears to be an RMI endpoint (RMI registry or RMI object endpoint)
	private boolean _isRegistry;						//True if this endpoint is an RMI registry, false if it's an RMI object endpoint
	private boolean _isRemotelyModifiable;				//True if the RMI registry appears to be remotely modifiable
	private final ArrayList<RMIObject> _exposedObjects;	//A list of all objects exposed by an RMI registry
	private Exception _enumerationException;			//Used to store any noteworthy exceptions triggered whilst enumerating the endpoint
	
	/*******************
	 * Initialise the RMI endpoint object with a given TCP endpoint.
	 * 
	 * @param endpoint The TCP endpoint this object represents.
	 ******************/
	public RMIEndpoint(TCPEndpoint endpoint) {
		this._endpoint = endpoint;
		this._isRMIEndpoint = false;
		this._isRegistry = false;
		this._isRemotelyModifiable = false;
		this._exposedObjects = new ArrayList<RMIObject>();
	}
	
	/*******************
	 * Add an RMIObject to the list of objects exposed through an RMI registry
	 * endpoint.
	 * 
	 * @param object An RMIObject describing the remote object.
	 ******************/
	public void addRMIObject(RMIObject object) {
		this._exposedObjects.add(object);
	}
	
	/*******************
	 * Check if any of the objects on this endpoint use a given class name.
	 * 
	 * @param className The class name to search for.
	 * @return True if any objects on this endpoint use the given class.
	 ******************/
	public boolean hasClass(String className) {
		for(RMIObject obj: this._exposedObjects) {
			for(String c: obj.getObjectClasses().keySet()) {
				if(c.equals(className)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/*******************
	 * Find the name of an object that uses the given class.
	 * 
	 * Helper method to find objects to target.
	 * 
	 * @param className The class name to search for.
	 * @return An object name or null if no matching objects are found.
	 ******************/
	public String findObjectWithClass(String className) {
		for(RMIObject obj: this._exposedObjects) {
			for(String c: obj.getObjectClasses().keySet()) {
				if(c.equals(className)) {
					return obj.getObjectName();
				}
			}
		}
		return null;
	}
	
	/*******************
	 * Check if any of the objects on this endpoint are annotated with the
	 * given JAR file name.
	 * 
	 * @param jarName The jar file name to search for.
	 * @return True if any objects on this endpoint are annotated with the given jar file name.
	 ******************/
	public boolean hasJar(String jarName) {
		String[] pathParts;
		
		//Iterate over all annotations of all exposed objects
		for(RMIObject obj: this._exposedObjects) {
			for(String a: obj.getStringAnnotations()) {
				//Split the annotation on the space, colon, and semi-colon characters to get separate paths/URLs
				for(String path: a.split("[ ;:]")) {
					//Split the path on forward and backward slashes to get the JAR filename
					pathParts = path.split("[\\\\/]");
					
					//Check if the jar filename matches the one we were given
					if(pathParts.length > 0) {
						if(jarName.toLowerCase().equals(pathParts[pathParts.length - 1].toLowerCase())) {
							return true;
						}
					}
				}
			}
		}
		
		//No match
		return false;
	}
	
	/*******************
	 * Setters
	 ******************/
	public void setIsRMIEndpoint(boolean isRMIEndpoint) { this._isRMIEndpoint = isRMIEndpoint; }
	public void setIsRegistry(boolean isRegistry) { this._isRegistry = isRegistry; }
	public void setIsRemotelyModifiable(boolean isModifiable) { this._isRemotelyModifiable = isModifiable; }
	public void setEnumException(Exception ex) { this._enumerationException = ex; }
	
	/*******************
	 * Getters
	 ******************/
	public TCPEndpoint getEndpoint() { return this._endpoint; }
	public boolean isRMIEndpoint() { return this._isRMIEndpoint; }
	public boolean isRegistry() { return this._isRegistry && this._isRMIEndpoint; }
	public boolean isObjectEndpoint() { return (!this._isRegistry) && this._isRMIEndpoint; }
	public boolean isRemotelyModifiable() { return this._isRemotelyModifiable; }
	public Exception getEnumException() { return this._enumerationException; }
	public ArrayList<RMIObject> getExposedObjects() { return this._exposedObjects; }
} public class RMIObject {
	/*******************
	 * Properties
	 ******************/
	private final String _objectName;							//The name that the object is bound to in the RMI registry service
	private final HashMap<String, ArrayList<String>> _classes;	//The classes behind the object, mapped to any string annotations attached to the class definitions
	private TCPEndpoint _objectEndpoint;						//The host/port where the object is hosted
	private Exception _objectParsingException;					//Set if an exception occurred whilst parsing the object details from network packets
	
	/*******************
	 * Construct the object with the name that the remote object is bound to.
	 * 
	 * @param objectName The name that the remote object is bound to in an RMI registry.
	 ******************/
	public RMIObject(String objectName) {
		//Initialise the object
		this._objectName = objectName;
		this._classes = new HashMap<String, ArrayList<String>>();
		this._objectEndpoint = null;
		this._objectParsingException = null;
	}
	
	/*******************
	 * Add a class to the object description - this will be one of the
	 * hierarchy of classes and interfaces behind the object.
	 * 
	 * @param classname A fully-qualified class name extracted from the RMI ReplyData packet.
	 ******************/
	public void addClass(String classname) {
		this._classes.put(classname, new ArrayList<String>());
	}
	
	/*******************
	 * Add a string annotation to a class.
	 * 
	 * @param classname The fully-qualified class name to annotate.
	 * @param annotation The string annotation.
	 ******************/
	public void addStringAnnotation(String classname, String annotation) {
		this._classes.get(classname).add(annotation);
	}
	
	/*******************
	 * Get all string annotations from this object's classes.
	 * 
	 * @return A list of string annotations from this object's classes.
	 ******************/
	public ArrayList<String> getStringAnnotations() {
		ArrayList<String> annotations = new ArrayList<String>();
		
		//Build a list of string annotations for each class making up this object
		for(String className: this._classes.keySet()) {
			for(String annotation: this._classes.get(className)) {
				annotations.add(annotation);
			}
		}
		
		//Return the annotations
		return annotations;
	}
		
	/*******************
	 * Setters
	 ******************/
	public void setObjectEndpoint(TCPEndpoint endpoint) { this._objectEndpoint = endpoint; }
	public void setParsingException(Exception exception) { this._objectParsingException = exception; }
	
	/*******************
	 * Getters
	 ******************/
	public String getObjectName() { return this._objectName; }
	public HashMap<String, ArrayList<String>> getObjectClasses() { return this._classes; }
	public TCPEndpoint getObjectEndpoint() { return this._objectEndpoint; }
	public Exception getParsingException() { return this._objectParsingException; }
} public class PortForwarder extends ProxyServer {
	/*******************
	 * Construct the port forwarder.
	 * 
	 * @param targetHost The host to forward connections to.
	 * @param targetPort The port to forward connections to.
	 * @param options The program options.
	 ******************/
	public PortForwarder(InetAddress targetHost, int targetPort, ProgramOptions options) {
		//Initialise super class
		super(targetHost, targetPort, options);
		
		//Set the listen port to the target port
		this.setServerListenPort(targetPort);
	}
	
	/*******************
	 * Create a proxy session object which allows data to pass through
	 * untouched in both directions.
	 * 
	 * @param clientSock A Socket for the incoming client connection.
	 * @param targetSock A Socket connected to the proxy target.
	 * @return A ProxySession object to handle the connection and data transfer.
	 ******************/
	protected ProxySession createProxySession(Socket clientSock, Socket targetSock) {
		return new ProxySession(
				new PassThroughProxyThread(clientSock, targetSock),
				new PassThroughProxyThread(targetSock, clientSock)
		);
	}
} public class ProxyServer extends Thread {
	/*******************
	 * Constants
	 ******************/
	private static final int SERVER_SOCK_TIMEOUT = 100;				//Time in milliseconds that the server socket should block waiting for incoming connections on each call to accept()
	private static final int SERVER_THREAD_JOIN_TIMEOUT = 300;		//Time in milliseconds to wait on shutdown for the server thread to terminate
	
	/*******************
	 * Properties
	 ******************/
	private volatile ServerSocket _serverSocket;				//The server socket used to accept connections to the proxy
	private final InetAddress _targetHost;						//The target host to connect proxy clients to
	private final int _targetPort;								//The target port to connect proxy clients to
	private int _listenPort;									//The port that the server socket should attempt to listen on (defaults to 0 for auto select)
	protected final ProgramOptions _options;					//The program options (including socket timeout value)
	protected volatile ArrayList<ProxySession> _proxySessions;	//A list of active proxy sessions, tracked purely so they can be shutdown
	private volatile BaRMIeException _startupException;			//Exception set if there is a problem starting the proxy server
	private volatile boolean _readyForConnections;				//Flag indicating that the proxy is ready to accept incoming connections
	private volatile boolean _shutdown;							//Flag indicating that the proxy server thread should shutdown
	private volatile boolean _forceShutdown;					//Flag indicating that a forced shutdown has been requested
	
	/*******************
	 * Construct the proxy.
	 * 
	 * @param targetHost The host to forward connections to.
	 * @param targetPort The port to forward connections to.
	 * @param options The program options.
	 ******************/
	public ProxyServer(InetAddress targetHost, int targetPort, ProgramOptions options) {
		this._serverSocket = null;
		this._targetHost = targetHost;
		this._targetPort = targetPort;
		this._listenPort = 0;
		this._options = options;
		this._proxySessions = new ArrayList<ProxySession>();
		this._startupException = null;
		this._readyForConnections = false;
		this._shutdown = false;
		this._forceShutdown = false;
	}
	
	/*******************
	 * Start the proxy server thread ready to start accepting incoming
	 * connections.
	 * 
	 * @throws BaRMIeException If the proxy is already running or an exception occurs during proxy startup.
	 ******************/
	final public void startProxy() throws BaRMIeException {
		//Bail if already started
		if(this._serverSocket != null) { throw new BaRMIeProxyAlreadyStartedException("The proxy server has already been started and must be shutdown before it can be started again."); }
		if(this._proxySessions.size() > 0) { throw new BaRMIeProxyAlreadyStartedException("There are existing proxy server sessions that must be terminated before starting the proxy again."); }
		
		//Reset flags and startup exception field
		this._startupException = null;
		this._readyForConnections = false;
		this._shutdown = false;
		
		//Start the proxy server thread
		this.start();
		
		//Block until the proxy is ready or an exception is thrown
		while(this._readyForConnections == false && this._startupException == null) {}
		if(this._startupException != null) {
			//Exception thrown during startup, throw it back to the caller
			throw this._startupException;
		}
	}
	
	/*******************
	 * Main thread function - creates the server socket and handles connections
	 * until the proxy is shutdown.
	 ******************/
	final public void run() {
		ProxySession ps;
		Socket clientSock;
		Socket targetSock;
		
		//Bail if already started
		if(this._serverSocket != null) { this._startupException = new BaRMIeProxyAlreadyStartedException("The proxy server has already been started and must be shutdown before it can be started again."); return; }
		if(this._proxySessions.size() > 0) { this._startupException = new BaRMIeProxyAlreadyStartedException("There are existing proxy server sessions that must be terminated before starting the proxy again."); return; }
		
		//Create the server socket
		try {
			this._serverSocket = new ServerSocket(this._listenPort, 2, InetAddress.getLoopbackAddress());
			this._serverSocket.setSoTimeout(ProxyServer.SERVER_SOCK_TIMEOUT); //Low timeout allows the server to be shutdown quicker
		} catch(Exception ex) {
			//Close the server socket if it was created
			if(this._serverSocket != null) {
				try { this._serverSocket.close(); } catch(Exception ex1) {}
				this._serverSocket = null;
			}
			
			//Store the exception and exit the thread
			this._startupException = new BaRMIeProxyStartupException("An exception occurred whilst attempting to start an RMI object proxy.", ex);
			return;
		}
		
		//Set the flag indicating that the proxy is ready to accept connections
		this._readyForConnections = true;
		
		//Enter the main proxy server loop
		while(this._shutdown == false) {
			try {
				//Attempt to accept a connection to the server socket
				clientSock = this._serverSocket.accept();
				clientSock.setSoTimeout(this._options.getSocketTimeout());
				
				//Attept to establish an outbound connection to the target
				targetSock = new Socket(this._targetHost, this._targetPort);
				targetSock.setSoTimeout(this._options.getSocketTimeout());
				
				//Create a new proxy session
				ps = this.createProxySession(clientSock, targetSock);
				if(ps != null) {
					//Add to the list of proxy sessions
					this._proxySessions.add(ps);
					
					//Start the new proxy session
					ps.start();
				}
			} catch(SocketTimeoutException ste) {
				//Server socket accept timeout passed, continue
				continue;
			} catch(SocketException se) {
				//Most likely an exception caused by a connection closing down somewhere or a proxy being terminated, swallow it up
				continue;
			} catch(Exception ex) {
				//Print the exception details
				System.out.println("[-] An exception occurred in the proxy main loop (" + this.getClass().getSimpleName() + ").\n\t" + ex.toString());
				ex.printStackTrace();
			}
		}
		
		//No longer ready to accept connections
		this._readyForConnections = false;
		
		//Close and release the server socket
		if(this._serverSocket != null) {
			try { this._serverSocket.close(); } catch(Exception ex) {}
			this._serverSocket = null;
		}
	}
	
	/*******************
	 * Stop the proxy server gracefully.
	 ******************/
	final public void stopProxy() {
		this.stopProxy(false);
	}
	
	/*******************
	 * Stop the proxy server and shutdown all proxy sessions.
	 * 
	 * @param force Set to true to force the proxy to stop immediately.
	 ******************/
	final public void stopProxy(boolean force) {
		//Don't shutdown if we're already shutting down
		if(this._shutdown == false) {
			//Set the shutdown and force shutdown flags
			this._shutdown = true;
			this._forceShutdown = force;
			
			//If the shutdown is forced, close the server socket and interrupt the thread
			if(force == true) {
				//Close the server socket
				if(this._serverSocket != null) {
					try { this._serverSocket.close(); } catch(Exception ex) {}
					this._serverSocket = null;
				}
				
				//Interrupt the thread
				if(this.isAlive()) { this.interrupt(); }
			} else {
				//Attempt to join the thread
				try { this.join(ProxyServer.SERVER_THREAD_JOIN_TIMEOUT); } catch(InterruptedException ie) {}
				if(this.isAlive()) { this.interrupt(); }
				
				//Shutdown the server socket if necessary
				if(this._serverSocket != null) {
					try { this._serverSocket.close(); } catch(Exception ex) {}
					this._serverSocket = null;
				}
			}
			
			//If there are any active proxy sessions then shut them down
			for(ProxySession ps: this._proxySessions) {
				ps.shutdown(force);
			}
			this._proxySessions.clear();
		}
	}
	
	/*******************
	 * Get the listen address of the proxy server socket.
	 * 
	 * @return The listen address of the proxy server socket.
	 ******************/
	final public InetAddress getServerListenAddress() { return this._serverSocket.getInetAddress(); }
	
	/*******************
	 * Get the local port that the proxy server is listening on.
	 * 
	 * @return The local port that the proxy server is listening on.
	 ******************/
	final public int getServerListenPort() { return this._serverSocket.getLocalPort(); }
	
	/*******************
	 * Set the local port on which the proxy server should listen.
	 * 
	 * Setting this to zero (default) will lead to a free port being selected
	 * automatically.
	 * 
	 * @param port The TCP port to listen on.
	 ******************/
	final public void setServerListenPort(int port) {
		this._listenPort = port;
	}
	
	/*******************
	 * Create a proxy session object - called when a client connects to the
	 * proxy server socket and a connection to the target has been established
	 * in order to create a ProxySession to handle transmission of data between
	 * the client and target.
	 * 
	 * By default this returns a proxy session that passes data straight through
	 * from client to server and back. This method must be overridden by
	 * sub classes in order to capture and/or manipulate data passing over the
	 * network.
	 * 
	 * @param clientSock A Socket for the incoming client connection.
	 * @param targetSock A Socket connected to the proxy target.
	 * @return A ProxySession object to handle the connection and data transfer.
	 ******************/
	protected ProxySession createProxySession(Socket clientSock, Socket targetSock) {
		return new ProxySession(
				new PassThroughProxyThread(clientSock, targetSock),
				new PassThroughProxyThread(targetSock, clientSock)
		);
	}
} public class RMIObjectUIDFixingProxy extends ProxyServer {
	/*******************
	 * Construct the proxy.
	 * 
	 * @param targetHost The host to forward connections to.
	 * @param targetPort The port to forward connections to.
	 * @param options The program options.
	 ******************/
	public RMIObjectUIDFixingProxy(InetAddress targetHost, int targetPort, ProgramOptions options) {
		//Initialise super class
		super(targetHost, targetPort, options);
	}
	
	/*******************
	 * Create a proxy session object which allows outbound requests to pass
	 * through, but modifies the serialVersionUID values found within inbound
	 * object references.
	 * 
	 * @param clientSock A Socket for the incoming client connection.
	 * @param targetSock A Socket connected to the proxy target.
	 * @return A ProxySession object to handle the connection and data transfer.
	 ******************/
	protected ProxySession createProxySession(Socket clientSock, Socket targetSock) {
		return new ProxySession(
				new PassThroughProxyThread(clientSock, targetSock),
				new UIDFixingProxyThread(targetSock, clientSock)
		);
	}
} public class ProgramOptions {
	/*******************
	 * Constants and properties
	 ******************/
	//Default option values
	private final String DEFAULT_MODE = "-enum";	//Default to enumeration mode if no mode is specified
	private final int DEFAULT_THREADS = 10;			//Default to using 10 worker threads
	private final int DEFAULT_SOCKTIMEOUT = 5000;	//Default to a 5 second timeout on blocking socket operations
	private final int DEFAULT_RMIPORT = 1099;		//Default TCP port for RMI registry services
	
	//Program option properties
	private String _mode;							//Execution mode - enum/attack
	private ArrayList<TCPEndpoint> _targets;		//Targets
	private int _threadCount;						//Number of threads to use
	private int _socketTimeout;						//Timeout for blocking socket operations
	
	/*******************
	 * Initialise default options and parse command line arguments.
	 * 
	 * @param args The strings passed to the program on the command line.
	 * @throws nb.barmie.exceptions.BaRMIeIllegalArgumentException If invalid parameters were passed to the program.
	 ******************/
	public ProgramOptions(String[] args) throws BaRMIeIllegalArgumentException, BaRMIeInvalidPortException {
		//Set default options
		this.setDefaultOptions();
		
		//Parse the command line arguments
		this.parseCommandLineArgs(args);
	}
	
	/*******************
	 * Initialise default program options.
	 ******************/
	private void setDefaultOptions() {
		this._mode = this.DEFAULT_MODE;
		this._targets = new ArrayList<TCPEndpoint>();
		this._threadCount = this.DEFAULT_THREADS;
		this._socketTimeout = this.DEFAULT_SOCKTIMEOUT;
	}
	
	/*******************
	 * Parse the command line arguments, validating along the way.
	 * 
	 * @param args The strings passed to the program on the command line.
	 ******************/
	private void parseCommandLineArgs(String[] args) throws BaRMIeIllegalArgumentException, BaRMIeInvalidPortException {
		LinkedList<String> argQueue = new LinkedList<String>(Arrays.asList(args));
		String targetHost;
		String opt;
		String val;
		int targetPort;
		
		//If the first parameter is "-enum" or "-attack" then set the mode
		if(argQueue.peekFirst().toLowerCase().equals("-enum") || argQueue.peekFirst().toLowerCase().equals("-attack")) {
			this._mode = argQueue.pop().toLowerCase();
		}
		
		//Handle options (all begin with '--')
		while(argQueue.size() > 0 && argQueue.peekFirst().startsWith("--")) {
			//Pop the option and value off of the argument queue
			opt = argQueue.pop().toLowerCase();
			val = argQueue.pop();
			
			//Handle the option
			switch(opt) {
				case "--threads":
					//Unsupported in attack mode
					if(this._mode.equals("-attack")) {
						throw new BaRMIeIllegalArgumentException("--threads option is not supported in attack mode.");
					}
					
					//Try to parse the value as an integer and store it
					try {
						this._threadCount = Integer.parseInt(val);
					} catch(NumberFormatException nfe) {
						throw new BaRMIeIllegalArgumentException("Thread count must be an integer.");
					}
					break;
					
				case "--timeout":
					//Try to parse the value as an integer and store it
					try {
						this._socketTimeout = Integer.parseInt(val);
					} catch(NumberFormatException nfe) {
						throw new BaRMIeIllegalArgumentException("Timeout must be an integer.");
					}
					break;
					
				case "--targets":
					//Try to parse the given file and build a list of targets
					this.loadTargets(val);
					break;
					
				default:
					//Unsupported option
					throw new BaRMIeIllegalArgumentException("Unsupported option specified (" + opt + ")");
			}
		}
		
		//Check remaining parameters (0 = targets should have been loaded from a file, 1 = target host specified, 3 = target host and port specified)
		if(argQueue.size() == 0) {
			//Make sure some targets were loaded...
			if(this._targets.size() == 0) {
				throw new BaRMIeIllegalArgumentException("No scan targets were specified.");
			}
		} else {
			//If targets were already loaded from a file then this may be a mistake (--targets and single target specified on command line)
			if(this._targets.size() > 0) {
				throw new BaRMIeIllegalArgumentException("Invalid command line, either specify a single target on the command line or load targets from a file using --targets.");
			}
			
			//We should only have 1 or 2 arguments remaining - target host or target host and port
			switch(argQueue.size()) {
				case 1:
					//Create a target from the given host and default RMI port
					this._targets.add(new TCPEndpoint(argQueue.pop(), DEFAULT_RMIPORT));
					break;
					
				case 2:
					//Grab the host and attempt to parse the port number
					opt = argQueue.pop();
					val = argQueue.pop();
					try {
						targetPort = Integer.parseInt(val);
					} catch(NumberFormatException nfe) {
						throw new BaRMIeIllegalArgumentException("Target port (" + val + ") must be a valid integer.");
					}
					
					//Create a target from the given host and port
					this._targets.add(new TCPEndpoint(opt, targetPort));
					break;
					
				default:
					//Invalid command line...
					throw new BaRMIeIllegalArgumentException("Invalid command line, parameters following the target are not supported.");
			}
		}
	}
	
	/*******************
	 * Load a targets file passed in on the command line.
	 * 
	 * @param targetFilename The filename passed in using the --targets command line parameter.
	 ******************/
	private void loadTargets(String targetFilename) throws BaRMIeIllegalArgumentException, BaRMIeInvalidPortException {
		//Work out the target file format and load targets from it accordingly
		switch(this.getNetworkTargetsFileFormat(targetFilename)) {
			case "basic":
				//Each line should contain a single host or space-separated host and port
				this.loadNetworkTargetsBasic(targetFilename);
				break;
				
			case "nmap":
				//Parse nmap output for open TCP port 1099, rmiregistry services, or Java RMI services
				this.loadNetworkTargetsNmap(targetFilename);
				break;
				
			case "gnmap":
				//Parse greppable nmap output for open TCP port 1099, rmiregistry services, or Java RMI services
				this.loadNetworkTargetsGnmap(targetFilename);
				break;
				
			case "nmapxml":
				//Parse nmap xml output for open TCP port 1099, rmiregistry services, or Java RMI services
				this.loadNetworkTargetsNmapXml(targetFilename);
				break;
		}
		
		//Throw an exception if no targets were loaded
		if(this._targets.size() == 0) {
			throw new BaRMIeIllegalArgumentException("No targets were identified within the given targets file.");
		}
	}
	
	/*******************
	 * Attempt to work out the format of a targets file (supports nmap output
	 * or target-per-line formatting).
	 * 
	 * @param targetFilename The filename passed in using the --targets command line parameter.
	 * @return The format of the targets file: basic, nmap, gnmap, or nmapxml.
	 ******************/
	private String getNetworkTargetsFileFormat(String targetFilename) throws BaRMIeIllegalArgumentException {
		BufferedReader targetFileReader = null;
		String fileLine;
		
		//Look for nmap or xml headers in the first two lines of the file in an attempt to determine the targets file format
		try {
			//Open the file and read the first line
			targetFileReader = new BufferedReader(new FileReader(targetFilename));
			fileLine = targetFileReader.readLine();
			
			//Check for an nmap or xml header
			if(fileLine.startsWith("# Nmap ")) {
				//Looks like nmap or greppable nmap output, read the next line to differentiate
				fileLine = targetFileReader.readLine();
				if(fileLine != null && fileLine.startsWith("Host: ")) {
					//Attempt to load the file as greppable nmap output
					return "gnmap";
				} else {
					//Attempt to load the file as normal nmap output
					return "nmap";
				}
			} else if(fileLine.startsWith("<?xml ")) {
				//Attempt to load the file as nmap xml output
				return "nmapxml";
			} else {
				//No nmap or xml header, attempt to load the file as target-per-line format
				return "basic";
			}
		} catch(FileNotFoundException fnfe) {
			//The targets file was not found
			throw new BaRMIeIllegalArgumentException("Targets file could not be found.", fnfe);
		} catch(IOException ioe) {
			//An exception occurred whilst reading from the file
			throw new BaRMIeIllegalArgumentException("Targets file could not be read.", ioe);
		} finally {
			//Close the file if it was opened
			if(targetFileReader != null) { try {targetFileReader.close(); } catch(IOException ioe) {} }
		}
	}
	
	/*******************
	 * Load network targets from a file containing a single host or
	 * space-separated host and port pair per line.
	 * 
	 * @param targetFilename The filename passed in using the --targets command line parameter.
	 ******************/
	private void loadNetworkTargetsBasic(String targetFilename) throws BaRMIeIllegalArgumentException, BaRMIeInvalidPortException {
		BufferedReader targetFileReader = null;
		String fileLine;
		String[] lineParts;
		
		//Load targets from the given file
		try {
			targetFileReader = new BufferedReader(new FileReader(targetFilename));
			while((fileLine = targetFileReader.readLine()) != null) {
				//Lines can either contain a host (with default RMI registry port), or a space-separated host and port
				lineParts = fileLine.split(" ");
				switch(lineParts.length) {
					case 1:
						//Add the target with the default RMI registry port
						this._targets.add(new TCPEndpoint(lineParts[0], this.DEFAULT_RMIPORT));
						break;
						
					case 2:
						//Add the target with the given host and port
						try {
							this._targets.add(new TCPEndpoint(lineParts[0], Integer.parseInt(lineParts[1])));
						} catch(NumberFormatException nfe) {
							//Invalid port number
							throw new BaRMIeIllegalArgumentException("Error parsing targets file, '" + lineParts[1] + "' is not a valid port number.");
						}
						break;
				}
			}
		} catch(FileNotFoundException fnfe) {
			//Targets file not found
			throw new BaRMIeIllegalArgumentException("Targets file could not be found.", fnfe);
		} catch(IOException ioe) {
			//Exception whilst reading from the file
			throw new BaRMIeIllegalArgumentException("Targets file could not be read.", ioe);
		} finally {
			//Close the file if it was opened
			if(targetFileReader != null) { try { targetFileReader.close(); } catch(IOException ioe) {} }
		}
	}
	
	/*******************
	 * Load network targets from standard nmap output.
	 * 
	 * @param targetFilename The filename passed in using the --targets command line parameter.
	 ******************/
	private void loadNetworkTargetsNmap(String targetFilename) throws BaRMIeIllegalArgumentException, BaRMIeInvalidPortException {
		BufferedReader targetFileReader = null;
		String fileLine;
		String currentHost = "";
		
		//Load targets from the given file
		try {
			targetFileReader = new BufferedReader(new FileReader(targetFilename));
			while((fileLine = targetFileReader.readLine()) != null) {
				//Check for the start of a host and record the host
				if(fileLine.startsWith("Nmap scan report for ")) {
					//Scan report line may contain a domain name followed by the IP address in brackets, retrieve the IP
					if(fileLine.contains("(")) {
						currentHost = fileLine.split(" ")[5];
						currentHost = currentHost.substring(1, currentHost.length() - 1);
					} else {
						currentHost = fileLine.split(" ")[4];
					}
				} else {
					//Check for interesting port scan results to target with BaRMIe
					if(fileLine.startsWith("1099/tcp") && fileLine.split("\\w+")[1].equals("open")) {
						//Found open TCP port 1099, track this target
						this._targets.add(new TCPEndpoint(currentHost, this.DEFAULT_RMIPORT));
					} else if(fileLine.contains("rmiregistry") || fileLine.contains("Java RMI") || fileLine.contains("java-rmi")) {
						//Found RMI registry service running on non-default port?
						try {
							this._targets.add(new TCPEndpoint(currentHost, Integer.parseInt(fileLine.split("/")[0])));
						} catch(NumberFormatException nfe) {
							//Invalid port number specified for 'rmiregistry' service
							throw new BaRMIeIllegalArgumentException("Error extracting targets from nmap output - the host '" + currentHost + "' appears to have an 'rmiregistry' service running on a non-standard port number that cannot e converted into an integer '" + fileLine.split("/")[0] + "'.");
						}
					}
				}
			}
		} catch(FileNotFoundException fnfe) {
			//Targets file not found
			throw new BaRMIeIllegalArgumentException("Targets file could not be found.", fnfe);
		} catch(IOException ioe) {
			//Exception whilst reading from the file
			throw new BaRMIeIllegalArgumentException("Targets file could not be read.", ioe);
		} finally {
			//Close the file if it was opened
			if(targetFileReader != null) { try { targetFileReader.close(); } catch(IOException ioe) {} }
		}
	}
	
	/*******************
	 * Load network targets from greppable nmap output.
	 * 
	 * @param targetFilename The filename passed in using the --targets command line parameter.
	 ******************/
	private void loadNetworkTargetsGnmap(String targetFilename) throws BaRMIeIllegalArgumentException, BaRMIeInvalidPortException {
		BufferedReader targetFileReader = null;
		String fileLine;
		String[] allScannedPorts;
		String[] portDetails;
		
		//Load targets from the given file
		try {
			targetFileReader = new BufferedReader(new FileReader(targetFilename));
			while((fileLine = targetFileReader.readLine()) != null) {
				//Check if the line is a port scan result line
				if(fileLine.startsWith("Host: ") && fileLine.contains("Ports: ")) {
					//Split the ports section of the line up
					allScannedPorts = fileLine.split("Ports: ")[1].split(", ");
					
					//Look for interesting port data strings
					for(String currentPortSection: allScannedPorts) {
						//Split the current port details up
						portDetails = currentPortSection.split("/");
						
						//Check for interesting port scan results to target with BaRMIe
						if(currentPortSection.startsWith("1099/open/tcp")) {
							//Found open TCP port 1099, track this target
							try { this._targets.add(new TCPEndpoint(fileLine.split(" ")[0], 1099)); } catch(BaRMIeInvalidPortException bipe) {}
						} else if(currentPortSection.contains("/open/") && (currentPortSection.contains("/rmiregistry/") || currentPortSection.contains("/java-rmi/") || currentPortSection.contains("/Java RMI/"))) {
							//Found RMI registry service on non-default port?
							try {
								this._targets.add(new TCPEndpoint(fileLine.split(" ")[0], Integer.parseInt(portDetails[0])));
							} catch(NumberFormatException nfe) {
								//Invalid port number specified for 'rmiregistry' service
								throw new BaRMIeIllegalArgumentException("Error extracting targets from nmap output - the host '" + fileLine.split(" ")[0] + "' appears to have an 'rmiregistry' service running on a non-standard port number that cannot e converted into an integer '" + portDetails[0] + "'.");
							}
						}
					}
				}
			}
		} catch(FileNotFoundException fnfe) {
			//Targets file not found
			throw new BaRMIeIllegalArgumentException("Targets file could not be found.", fnfe);
		} catch(IOException ioe) {
			//Exception whilst reading from the file
			throw new BaRMIeIllegalArgumentException("Targets file could not be read.", ioe);
		} finally {
			//Close the file if it was opened
			if(targetFileReader != null) { try { targetFileReader.close(); } catch(IOException ioe) {} }
		}
	}
	
	/*******************
	 * Load network targets from nmap xml output.
	 * 
	 * @param targetFilename The filename passed in using the --targets command line parameter.
	 ******************/
	private void loadNetworkTargetsNmapXml(String targetFilename) throws BaRMIeIllegalArgumentException, BaRMIeInvalidPortException {
		DocumentBuilderFactory dbf;
		DocumentBuilder db;
		Document xmlDoc;
		NodeList hostNodes;
		NodeList hostAddressNodes;
		NodeList portNodes;
		NodeList portServiceNodes;
		Element hostElement;
		Element portElement;
		int hostIndex;
		int portIndex;
		
		//Load targets from the given nmap xml file
		try {
			//Create an XML document builder factory and disable unnecessary features
			dbf = DocumentBuilderFactory.newInstance();
			dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
			dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
			dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
			dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
			dbf.setExpandEntityReferences(false);
			dbf.setXIncludeAware(false);
			
			//Create a document builder and load the XML document
			db = dbf.newDocumentBuilder();
			xmlDoc = db.parse(new File(targetFilename));
			
			//Iterate over host elements
			hostNodes = xmlDoc.getElementsByTagName("host");
			for(hostIndex = 0; hostIndex < hostNodes.getLength(); ++hostIndex) {
				//Get the current host element and address nodes
				hostElement = (Element)hostNodes.item(hostIndex);
				hostAddressNodes = hostElement.getElementsByTagName("address");
				
				//Get the child "port" nodes and Look for interesting open TCP ports
				portNodes = hostElement.getElementsByTagName("port");
				for(portIndex = 0; portIndex < portNodes.getLength(); ++portIndex) {
					//Get the current port element
					portElement = (Element)portNodes.item(portIndex);
					
					//Only handle TCP ports
					if(portElement.getAttribute("protocol").equalsIgnoreCase("tcp")) {
						//Only handle open TCP ports
						if(portElement.getElementsByTagName("state").getLength() == 1 && ((Element)portElement.getElementsByTagName("state").item(0)).getAttribute("state").equalsIgnoreCase("open")) {
							//If the port number is 1099 then add this target to the list
							if(portElement.getAttribute("portid").equals("1099")) {
								this._targets.add(new TCPEndpoint(((Element)hostAddressNodes.item(0)).getAttribute("addr"), 1099));
							} else {
								//If service detection was performed and the service is 'rmiregistry', or the product is listed as 'Java RMI', then add this target to the list
								if(portElement.getElementsByTagName("service").getLength() == 1) {
									if(((Element)portElement.getElementsByTagName("service").item(0)).getAttribute("name").equals("rmiregistry")) {
										try { this._targets.add(new TCPEndpoint(((Element)hostAddressNodes.item(0)).getAttribute("addr"), Integer.parseInt(portElement.getAttribute("portid")))); } catch(NumberFormatException ex) { throw new BaRMIeIllegalArgumentException("Invalid port number specified in nmap xml output ('" + portElement.getAttribute("portid") + ").", ex); }
									} else if(((Element)portElement.getElementsByTagName("service").item(0)).getAttribute("product").equals("Java RMI")) {
										try { this._targets.add(new TCPEndpoint(((Element)hostAddressNodes.item(0)).getAttribute("addr"), Integer.parseInt(portElement.getAttribute("portid")))); } catch(NumberFormatException ex) { throw new BaRMIeIllegalArgumentException("Invalid port number specified in nmap xml output ('" + portElement.getAttribute("portid") + ").", ex); }
									} else if(((Element)portElement.getElementsByTagName("service").item(0)).getAttribute("name").equals("java-rmi")) {
										try { this._targets.add(new TCPEndpoint(((Element)hostAddressNodes.item(0)).getAttribute("addr"), Integer.parseInt(portElement.getAttribute("portid")))); } catch(NumberFormatException ex) { throw new BaRMIeIllegalArgumentException("Invalid port number specified in nmap xml output ('" + portElement.getAttribute("portid") + ").", ex); }
									}
								}
							}
						}
					}
				}
			}
		} catch (ParserConfigurationException pce) {
			//Exception occurred whilst ocnfiguring the XML parser
			throw new RuntimeException("An exception occurred whilst configuring the XML parser.", pce);
		} catch (SAXException saxe) {
			//Exception occurred whilst processing the XML
			throw new BaRMIeIllegalArgumentException("An error occurred whilst attempting to parse the targets file as xml.", saxe);
		} catch (IOException ioe) {
			//Exception occurred whilst reading the file
			throw new BaRMIeIllegalArgumentException("Targets file could not be read.", ioe);
		}
	}
	
	/*******************
	 * Getters for program options.
	 ******************/
	public String getExecutionMode() { return this._mode; }
	public ArrayList<TCPEndpoint> getTargets() { return this._targets; }
	public int getThreadCount() { return this._threadCount; }
	public int getSocketTimeout() { return this._socketTimeout; }
}
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InvalidClassException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.security.SecureRandom;
import java.util.ArrayList;
import nb.barmie.exceptions.BaRMIeException;
import nb.barmie.exceptions.BaRMIeGetObjectException;
import nb.barmie.exceptions.BaRMIeInputException;
import nb.barmie.exceptions.BaRMIeWriteFileException;
import nb.barmie.modes.enumeration.RMIEndpoint;
import nb.barmie.modes.enumeration.RMIObject;
import nb.barmie.net.proxy.PortForwarder;
import nb.barmie.net.proxy.ProxyServer;
import nb.barmie.net.proxy.RMIObjectUIDFixingProxy;
import nb.barmie.util.ProgramOptions;
			//Attempt to read a line of input
			try {
				input = br.readLine();
			} catch(IOException ioe) {
				throw new BaRMIeInputException(ioe);
			}
			
			//Break out of the loop if empty input is allowed
			if(allowEmpty == true) {
				break;
			}
		}
		
		//Return the user input
		return input;
	}
	
	/*******************
	 * Helper method to prompt the user for input (such as commands to execute
	 * as part of an attack), with a default response if the user does not
	 * provide any input.
	 * 
	 * @param prompt A prompt asking the user for appropriate input.
	 * @param defaultResponse The default response if the user's input is blank.
	 * @return The user's input or the default response.
	 * @throws BaRMIeInputException If an IOException occurs whilst reading input from STDIN.
	 ******************/
	protected final String promptUserForInputWithDefault(String prompt, String defaultResponse) throws BaRMIeInputException {
		String input;
		
		//Prompt the user for input and allow empty inputs
		input = this.promptUserForInput(prompt, true);
		
		//Return the user's input, or the default response if the user didn't provide any input
		return (input.equals("") ? defaultResponse : input);
	}
	
	/*******************
	 * Helper method to retrieve a remote object reference from the given RMI
	 * endpoint.
	 * 
	 * If the object being retrieved was bound to localhost or 127.0.0.1 and
	 * the RMI registry is not on localhost then this method will automatically
	 * check whether the object's port can be reached on the registry host. If
	 * it can, then a port forwarder is started to forward traffic on the local
	 * port to the RMI registry host. This enables objects to be attacked
	 * remotely when the were only bound to local host.
	 * 
	 * If a remote serialVersionUID does not match the relevant local
	 * serailVersionUID then this method delegates to the method
	 * getRemoteObjectWithUIDHack(), which proxies the RMI registry connection
	 * in order to modify returned serialVersionUID fields to match those of
	 * local classes. This is a hack which won't work in all cases but this
	 * enables BaRMIe to support multiple versions of target applications. This
	 * is because different versions of the class may have different values for
	 * the serialVersionUID field, however the actual interface and the way we
	 * interact with that class/object does not change. Modifying the returned
	 * serialVersionUID effectively "tricks" the local JVM into believing that
	 * the two classes are the same, allowing the remote method invocation to
	 * be performed.
	 * 
	 * @param ep An enumerated RMI endpoint.
	 * @param objectName The name of the object to look up.
	 * @return The remote object reference.
	 * @throws BaRMIeGetObjectException If there was a problem retrieving the requested object.
	 ******************/
	protected Object getRemoteObject(RMIEndpoint ep, String objectName) throws BaRMIeGetObjectException {
		Registry reg;
		Object obj;
		
		//Start port forwarding if necessary
		this.startPortForwarding(ep, objectName);
		
		//Get a reference to the target RMI registry
		try {
			reg = LocateRegistry.getRegistry(ep.getEndpoint().getHost(), ep.getEndpoint().getPort());
		} catch(RemoteException rex) {
			throw new BaRMIeGetObjectException("Failed to create a reference to the target RMI registry.", rex);
		}
		
		//Get the requested object
		try {
			obj = reg.lookup(objectName);
		} catch(UnmarshalException ue) {
			//An Unmarshal exception might occur if the local class has a different serialVersionUID to the remote one, use a hack to modify the serialVersionUID if this is the case...
			if(ue.getCause() instanceof InvalidClassException && ue.getCause().toString().contains("local class incompatible: stream classdesc serialVersionUID = ")) {
				//Attempt to get the object again, this time using a hack to fix the serialVersionUID field
				obj = this.getRemoteObjectWithUIDHack(ep, objectName);
			} else {
				//Unknown exception
				throw new BaRMIeGetObjectException("Failed to lookup the object '" + objectName + "'.", ue);
			}
		} catch(NotBoundException nbe) {
			throw new BaRMIeGetObjectException("The object '" + objectName + "' is not bound to the target RMI registry.", nbe);
		} catch(RemoteException rex) {
			throw new BaRMIeGetObjectException("Failed to lookup the object '" + objectName + "'.", rex);
		}
		
		//Return the requested object
		return obj;
	}
	
	/*******************
	 * Hack to retrieve an RMI object where the remote serialVersionUID does
	 * not match the serialVersionUID of the local class.
	 * 
	 * Here we request the remote object through a proxy. When the object data
	 * comes back over the proxy, we update serialVersionUID fields at the
	 * network level so that they match those of the local classes.
	 * 
	 * This hack makes BaRMIe attacks compatible with multiple versions of
	 * target applications without having to recompile the code with the
	 * required serialVersionUID.
	 * 
	 * @param ep An enumerated RMI endpoint.
	 * @param objectName The name of the object to look up.
	 * @return The remote object reference.
	 * @throws BaRMIeGetObjectException If there was a problem retrieving the requested object.
	 ******************/
	private Object getRemoteObjectWithUIDHack(RMIEndpoint ep, String objectName) throws BaRMIeGetObjectException {
		RMIObjectUIDFixingProxy proxy = null;