This repository contains example code for the live training provided for BSides San Diego.
This project is for educational purposes only! The author is not responsible for any illegal activities undertaken with this software, or software based on it. Don't do crimes.
We are building a lil baby Rust C2! We won't do anything crazy with encrypted comms, but we will be writing a command handler.
- Cross-compilation
- Windows crate usage
- Network programming
- Advantages (and drawbacks) of using Rust for offensive code
- Windows API interoperability
- Rust types
- The Rust build pipeline
Ideally, you've done some programming before. Rust experience is not required, but fluency in one language will be very helpful.
Familiarity with some Windows API concepts will also be helpful, but isn't a firm requirement.
- Computer capable of compiling Rust programs. Linux is easier, but Windows will do. I recommend using WSL
This repository is built in stages, with each stage given a different branch. You can follow along by switching between stages, or simply seeing the final result on main
.
But the point is for you to create this code, or something like it, during the training. This is merely a reference point.
Head to rustup.rs and follow the instructions.
I recommend VS Code.
If you're using Windows as your dev environment, this can be the same machine. Otherwise, you should have a Windows computer that we'll use as our victim. It should be on the same network as the dev machine.
If you're on Linux, you're sorted. On Windows, I recommend downloading Nmap and making sure you install NCat
along with everything else.
Get the Rust Standard Library open right now. I promise you'll need a few dozen tabs of this thing open.
Our initial prototype to prove communication over TCP. Nothing fancy, but a few things to note:
- The syntax for importing modules
- The
Result
type, and how it's handled - The clarity of the TCP stack
Before we do more with that network connection, let's get comfy with the Windows API. We'll simply pop a MessageBox when the agent launches. But "simple" isn't really how Rust rolls.
Observe the imports from the windows
crate. That's just what we needed to make a simple popup work! Also note how we're wrapping values in the Windows structs (e.g. PCSTR
) to make the typing work.
Also, you can see that this interop is, by definition, unmanaged. That's why we need to wrap our Windows function calls in unsafe
. It doesn't mean what you think it means.
Now we'll establish two-way communication over the TCP socket. This introduces the BufWriter
and BufReader
structs for handling the transmission and receipt of data.
This stage also introduces the match
statement, a core flow control structure in Rust. We use it to handle what we're reading from the socket, thereby checking whether the connection has been closed.
Now that we have both send and receive working, it's time to kick off some commands! We'll start with basic Powershell, since it's easy enough. We introduce std::process
and all its fun tricks here.
In this stage, we begin to build out our C2Command
enum and handler. Shell commands are cool, but we'd like to not rely on them. By prepending our input with !
, we indicate it is a proper C2 command.
But most importantly, we do all this in cmd
module, so now we know how to modularize our code.
We haven't implemented either of our starter C2 commands, but we're about to.
A little more restructuring and cleanup here. Many of the changes you see at this stage are the result of cargo clippy
making sure we're doing things with best practices. We also add submodules for our two commands, and a handler function.
We've also structured the output to the stream to differentiate between an error and a success.
Implementation! We use the winreg
crate to easily access the Windows registry to establish persistence via a common technique. Is it the stealthiest? No, but it beats the command line!
Persistence is one thing, but wouldn't it be cool if we could get SYSTEM
privileges. We can, using the Token Duplication technique! This involves a good deal of Windows API programming, but that's what we're here for. We do fudge a little bit and use a third party crate for enumerated processes, because that is an unreasonable faff.
In this version, we're going to take our whole project, and make it usable as a...DLL! That's right, Rust can make DLLs!
To make the project buildable as both a library and a binary, we've heavily restructured the modules for both. Also, look at what we've added to Cargo.toml
.
Our final form! We handle an annoying gotcha with getsystem
and SeDebugPrivilege
, and we also clean up some of our error handling to ensure the agent doesn't die on us. We also add some optimizations