Remote Code Execution via Reflection

Background

In .Net, distributed computing can be done in a number of ways.

The most widely used way is to use web services, where a remote client would call a publicly exposed web method on a server where the web service is hosted. When using web services, the code of the service method must be physically on the server where the service is hosted. In this scenario the client talks to the server via a proxy class that represents the web service on the server.

Alternatively, we can use .Net remoting to execute code in a distributed environment. Typically, either TCP or HTTP channel is used to communicate object states between client and server. The main advantage of using remoting is that custom sinks can be utilized to incorporate sophisticated data manipulations before the data is sent and received. The amount of data transferred can also be shrunk to minimal if binary formatters are used and thus resulting in very little channel overhead as compared to SOAP messages that typically used in web services.

Object serialization transfers the state of an object, not the physical object itself however. For serialization to work, the object to be serialized needs to exist on both the client and the server. When the object gets serialized, the state of the object is transferred across the wire. And when the object is deserialized, the object on the server side gets created and then populated with the states from data (the serialized object) transferred from the client.

So, in both scenarios, whether using web services or remoting, the object that needs to be used remotely must reside on both ends (a proxy or the actual object).

Autonomous Remote Code Execution

Can we use just one copy (e.g. the client version of the object) of the object to achieve remote code execution in a distributed environment? As it turns out, we can do this using reflection. For a lack of a better name, I call this approach autonomous remote code execution.

The idea here is to create an object on the client side, and physically ship the object over to the server side and the server will execute whatever code that is send over.

I will demonstrate how this can be done using TCP socket. To make the discussion easier, we will use synchronized socket communication model here, the principals illustrated here apply also to asynchronized sockets.

The Client

The client code first creates a TCP socket and reads in whatever .Net application needs to be executed as binary bytes. Then the content is sent through the TCP socket.

 

using System;

using System.Collections.Generic;

using System.Text;

using System.Net;

using System.Net.Sockets;

using System.IO;

namespace SocketClient

{

class Program

{

public void Send()

{

TcpClient socketForClient;

try

{

socketForClient = new TcpClient("alpha", 10); //server name

}

catch {

Console.WriteLine("Failed to connect to server");

return;

}

NetworkStream networkStream = socketForClient.GetStream(); //Read in the program to be sent

FileStream fs = new FileStream("PrimeGen.exe", FileMode.Open);

BinaryReader br = new BinaryReader(fs);

byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));

fs.Close();

br.Close();

StreamReader streamReader = new StreamReader(networkStream);

BinaryWriter streamWriter = new BinaryWriter(networkStream);

try {

string outputString;

outputString = streamReader.ReadLine();

Console.WriteLine(outputString);

streamWriter.Write(bin); streamWriter.Flush();

}

catch {

Console.WriteLine("Exception reading from Server");

}

networkStream.Close();

}

static void Main(string[] args) {

Program p = new Program();

p.Send();

}

}

}

The Server

On the server side, a socket is created and then is blocked listening for the incoming connections. When the client and the server establish the connection, we read in the data as an array of bytes which represent the actual application transferred from the client side.

We then can get the actual object back using reflection. Finally, we can execute the code by jumping to its entry point. In this particular example, it is a .Net program called PrimeGen.

using System;

using System.Collections.Generic;

using System.Text;

using System.Net;

using System.Net.Sockets;

using System.Reflection;

using System.IO;

 

namespace SocketServer {

class Program {

public void Receive() {

TcpListener tcpListener = new TcpListener(Dns.GetHostEntry("alpha").AddressList[0], 10);

tcpListener.Start(); Socket socketForClient = tcpListener.AcceptSocket();

if (socketForClient.Connected) {

Console.WriteLine("Client connected");

NetworkStream networkStream = new NetworkStream(socketForClient);

StreamWriter streamWriter = new StreamWriter(networkStream);

BinaryReader streamReader = new BinaryReader(networkStream);

string theString = "Sending"; streamWriter.WriteLine(theString);

Console.WriteLine(theString); streamWriter.Flush();

//Note, here I simplified it a bit. Since I know that the

//code is less than 16K, so I can just read everything in

//at once. If the code size can not be predetermined, or it

//is too large for a single transmittion, you will have to

//slice it up and reassemble the data at the server side.

byte[] bin = streamReader.ReadBytes(16384);

//load the assembly sent from the wire

Assembly a = Assembly.Load(bin); MethodInfo method = a.EntryPoint;

if (method != null) {

object[] obj = new object[1] {new string[] {}};

try {

method.Invoke(null, obj);

} catch (Exception e) {

Console.WriteLine(e.Message);

}

}

streamReader.Close();

networkStream.Close();

streamWriter.Close();

}

}

 

static void Main(string[] args) {

Program p = new Program(); p.Receive();

}

}

}

Client Server in Action

Here is a screen shot of the client and server in action:

Remote Execution Using Windows

Remote Execution Using Windows

Using this autonomous remote execution method, we can easily run the server anywhere by simply copy the server side executable. Just for fun, here is a screen shot of the server running on a FreeBSD server and the client is running Windows XP.

Remote Execution using FreeBSD

Remote Execution using FreeBSD

Final Thoughts

The autonomous remote code execution method mentioned here can be used in various situations. For instance, when conducting numerical analysis in a distributed fashion, it would typically require setting up the same code on all the computers that participate in the computation. To ensure that the all the computers are setup with the correct version of the code can be quite tedious. Further, whenever the computation task is changed, all the code needs to be re-deployed again across all the machines.

Using the approach mentioned here, we can in theory just install the server version of the code on all the computational nodes. This needs not to be changed over time regardless of whatever the code needs to be run in a distributive fashion. A single machine can be setup to inject the code that needs to be executed collaboratively into all the computational nodes.

Like everything else, this method has its limitations as well. For instance, the entire server instances need to run at a privileged level, otherwise the reflection portion of the server code might fail. Because the server code runs whatever the client tells it to run, running under privileged accounts can be potentially dangerous. However, for scientific computations, this should not be an issue since all the computational nodes are typically contained behind a firewall.

The size of the code needs to be run remotely should be small enough for this method to be efficient, as the bandwidth could become a bottle neck if the code size is big or the number of computational nodes is large or both.

While I only demonstrated running a very simple test program remotely, it is possible to run programs that contain many DLLs and span multiple assemblies. Due to the limitation of our in-memory representation of the code on the server side however, it is mandatory that all assemblies be combined into a single executable before it can be used with the method mentioned here. This can be achieved by using ILMerge when working with Microsoft .Net implementation or mkbundle when using Mono.

 

Be Sociable, Share!

Leave a Reply