Writing a remote shell in Java
This note describes how to write a simple remote shell in Java.
Remote shell
A remote shell application obviously need to be able to run a command on the host computer, it also need some kind of socket server in order for clients to run commands remotely.
Executing command
The application need to execute a command and grab its output. The output should then be sent to the remote client.
Process command = Runtime.getRuntime().exec(commandStr);
BufferedReader reader = new BufferedReader(
new InputStreamReader(command.getInputStream()));
So this code will run the command in commandStr
and then get the InputStream
from the Process
object (where input means input to the application, i.e. the
output from the command). A reader is created for the stream and will later be
used to grab the output from the command.
Reading command output
To read the command output just use the reader created earlier:
String line = "";
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
command.waitFor();
The loop will read all outputs from the command and terminates on end of line.
The command.waitFor()
waits for the command to exit.
Starting the server
Start a server listening on port
.
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
When a client connects the blocking accept()
call will return a Socket
which
is used for communicating with the client.
Writing command output to client
Writing the output from the command to the client means writing the command
input stream to the socket output stream. We create an OutputStream
from the
socket:
// Write output to client
OutputStream output = socket.getOutputStream();
Then write the command output to the socket output stream. Modifying the loop above when reading command output:
String line = "";
while ((line = reader.readLine()) != null) {
System.out.println(line);
line += "\n";
output.write(line.getBytes());
}
Now the simple case with a one-off command is complete. The application accepts
a connection, sends the command output to the client and exits. However, if we
run an interactive command, like for example bash
there is yet no way to write
input to the running command. That’s the next task to add.
Writing client input to an executing command
With the simple case done, its time to also read input from the connected client and write to the command output stream. Get the input stream from the socket (i.e. this is where the client command will be received)
// Read from client
InputStream input = socket.getInputStream();
Create a writer for the command stream
OutputStream commandStream = command.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(commandStream));
Then if the command is still running read from the client input stream and write to the command ouput stream.
byte[] buffer = new byte[1024];
while (command.isAlive() &&
((bytesRead = input.read(buffer)) != -1))
{
buffer[bytesRead] = '\n';
if (command.isAlive()) {
writer.write(new String(buffer));
writer.flush();
} else {
break;
}
}
The way this loop is constructed the loop will not exit until the command exits.
Reading the input stream is a blocking call, hence the other loop writing the
command output to the client will never be reached. So this loop is meant to be
run in a sepearate thread, in parallel with the loop writing to client output
stream. So put the above in a Runnable
new Thread(new Runnable() {
@Override
public void run() {
// read socket and write to command stream
}
}).start()
Running the Remote Shell
The remote shell can now be run as a one-off command like this:
$ java RemoteShell 3333 uptime
When connecting with a client the output of the command (here uptime
) will be
sent to the client and the application will exit:
$ netcat localhost 3333
17:00:29 up 7 days, 9:05, 1 user, load average: 1.87, 1.51, 1.34
If the remote shell is given the parameter to start bash it is possible for the client to write commands to the bash command i.e. basically run any command. This is the real remote shell.
$ java ExecCommand 3333 bash
On the client, note that there will be no prompt. Just type the command and see its output.
$ netcat localhost 3333
date
Thu Jan 16 17:14:09 CET 2020
uptime
17:14:12 up 7 days, 9:19, 1 user, load average: 1.76, 1.50, 1.45
ls -l /media
total 4
drwxr-x---+ 2 root root 4096 May 2 2018 bjorn
GitHub source code
The code for this small application can be found here. Enjoy!