When deciding on the architecture for RustRAT’s C2 server, I went for something asynchronous. There are no very good reasons for this, but I have never really written anything major in an asynchronous manner before. Besides, a lot of nice looking Rust crates are also written using asynchronous code, so this lets me play with them.
As I am doing something I have never done before, progress is slow as I play with tokio, figure out what to put in my .env file to get sqlx to compile, and attempt to handle HTTP requests with warp. All in all it is an enjoyable experience, mostly. I am sure I will not end up with the most performant solution initially, but optimizations are very far down on my to-do list. The most naive and resource intensive solution should be able to handle the low load I plan to test with to begin with, with just a few rats checking in every once in a while.
The architecture for RustRAT’s server is, stupid as it may sound, heavily inspired by what I imagine a synchronous, threaded server would look like. A lot of processing is done by a single “main” task that receives messages through a MPSC channel and handles them accordingly. Examples of messages handled by this main task are encrypted messages from rats and instructions for the server to enqueue jobs for the rats. The reason to make this “main” task responsible for so much is to try to avoid sharing memory, while at the same time attempting to limit the number of channels used by the server.
Shared memory is something I want to avoid between tasks. However, some shared state will probably be present, in the form of a read-only connection pool of SQLite “connections”. This will allow different parts of the server to read from the database without having to send a message to the main task. Now, this might introduce problems in the future, but by the time that is a problem, a makeover of the server architecture is probably long overdue already.
As the server starts to take shape, I have found that I need to define how the rats and the server will talk to each other. I will be spending some time digging into the world of serialization in Rust. The plan is currently to pass messages by sending serialized enums, or something like that.
One of the main ideas behind RustRAT is to keep as much functionality as possible in WebAssembly. Because of this, I will attempt to keep the number of possible messages as low as possible. Currently, it looks like the first fully functioning rat will be able to send the following messages:
- Check in (and key exchange)
- Ask whether there are any pending tasks
- Retrieve pending task (WebAssembly blob and function name)
- Exit, letting the server know the rat will not be calling back again
Later this might be expanded, for example by making it possible for the rat to download a blob without executing it right away, or retrieving the decryption key for a WebAssembly blob bundled with the rat binary. Different kinds of messages (such as server-to-rat instead of just rat-to-server) might also be implemented in the future to allow for other possibilities and forms of communication, but that it still far in the future.