A show and tell of what I’ve done in about a month of working on my custom C2 framework, the Diet-C2.

Intro#

This post will not go into the code and explain it line by line, because if you wanted to do that you can read it yourself - instead I thought it would be more interesting to go over my progress, issues I ran into as well as the general structure of the key parts of the client, server and implant.

What I wanted to do to call the “foundation” complete was be able to implement 4 common commands and be able to send them from the client to the implant.

The 4 implant commands that are fully implemented are:

  • kill - cleanly exits the implant
  • shellcode-inject SHELLCODE_FILE.bin PID_# - injects shellcode specified by the file into the selected PID
  • shellcode-spawn SHELLCODE_FILE.bin - runs shellcode in a separate thread under the same process as the implant
  • shell CMD.EXE_COMMANDS - executes commands with cmd.exe and returns the output of them

The Diet-Client#

The client is where I spent the most amount of time over the last month. Mostly working on the user interface, handling errors, and doing a full rewrite when I was happy with the functionality but not the code state.

Using Textual for a Terminal User Interface#

I had decided to go with a terminal user interface from the very beginning to be able to use other terminal tools at the same time as using the Diet-C2, like tmux for terminal multiplexing.

I think it turned out really well, and I’m really happy with my ability to add new functionality to the UI. I wanted to keep it fairly clear without any unnecessary clutter. I settled on 3 log windows - one for an implant list with some basic implant info in the top left, server logs and messages in the top right, and an output log of commands in the center. The input is placed at the bottom of the screen and is the only section of the TUI that can be focused on.

That being said, I went through about 4 or 5 different terminal user interface libraries in python before settling on Textual. The reason I stuck with Textual was because the docs were phenomenal, with an API reference, a quick start guide and a more advanced guide for each element of the library with example code. The docs made it super easy to search through for what I wanted and incrementally piece together my client.

However there were still some important decisions that needed to be made on the client side.

Input and Errors#

The most important part of the client to get right for me was to handle input and errors in an extensible way so I could easily extend both of these to add new commands.

I parse the commands first, stripping the whitespace and put each word into a list. Next I match the first word of the command, and then calling the appropriate handling function.

This try-except block can be seen below

try:
# Calls associated function for 1st argument from CMD_TABLE
    commands.CMD_TABLE[args[0]](args, self.app)
except Exception as err:
# The exceptions raised get handled by an ERROR_TABLE of handlers
    if type(err) in client_errors.ERROR_TABLE:
        client_errors.ERROR_TABLE[type(err)](args, self.app)
    else:
        self.log_error("Exception: {}".format(type(err).__name__))
        self.log_error("Exception message: {}".format(err))

For example, for shell whoami, the root word is “shell”, which will get looked up in the dictionary CMD_TABLE, and calls cmd_shell() with the arguments.

CMD_TABLE = {
                "server": cmd_server,
                "exit": cmd_exit,
                "select": cmd_select,
                "shell": cmd_shell,
                "shellcode-inject": cmd_shellcode_inject,
                "shellcode-spawn": cmd_shellcode_spawn,
                "shellcode-earlybird": cmd_shellcode_earlybird,
                "kill": cmd_kill_implant,
                "!": cmd_terminal_passthrough,
                "nickname": cmd_nickname,
}

I used this method for errors as well by creating custom errors (like “CommandDoesntExist”) and raising it from within my command handling functions. So when the exception bubbles up to the except block, the type of the exception is looked up in a table and calls the associated handling function. All errors not in the table are just pasted to the screen for debugging, whenever I see a new error, I track it down and add a handler function for it.

Rewriting the Client#

I’m a big fan of solving the problem first, and then doing a full rewrite of the code to be more extensible, easier to read and just better in general.

The initial version of the client was a great example of a minimum viable product, using if statements to match each command, functionality not split up into different files and so on - it was a nightmare to debug and add new commands.

After implementing two commands, shell and shellcode-inject, I decided to rewrite the client - as I had settled on a way to transmit my command to the implant and solved all of the issues I came across during that process.

Following this post, I will also look into rewriting the server code to be more extensible, as well as doing a full rewrite of the implant once I get all of the technicalities sorted (and who knows, maybe even using zig or rust instead of C++)

The Diet-Server#

I thought the server was the easiest part of the Diet-C2’s foundation, in large part because the Flask library is so easy to work with. That being said, there was one design decision that I wanted to talk about, having two structures for commands - one for string commands (think shell whoami) and one for commands involving files (think shellcode-inject SHELLCODE.bin).

Strings and Files#

As I was developing and testing the Diet-C2, I was using some input binary files to test the shellcode execution functionality. I used small files that could easily be encrypted and then base64 encoded while staying well under a kilobyte - but I also tested on large shellcode files like my entire diet-implant executable (converted to shellcode using PE to Shellcode), encrypted and base64’d which measured just under 100 kilobytes.

To manage larger input data, like commands that involve files, I split commands in 2. One that sends commands as a straight string, as well as one that allows for large files to be transfered.

For reference, the structure of a command sent from the client to the implant is ::: delimited, the first two portions always being the unique command ID and command type, respectively.

UNIQUE_COMMAND_ID:::CMD_TYPE:::PARAM1:::PARAM2::: ...

This works great for simple commands like shell, but for those that use a larger amount of memory / data like executing shellcode with shellcode-inject, I thought of a better idea.

It would be fairly easy to stream a 100 kilobyte file as part of this command string in one of the parameter fields, but I thought that wasn’t ideal as the file would be stored in memory on the server forever as well as looking suspicious when being downloaded to the implant.

I thought a better solution would be to send a command string like the one below, and send the file as an attachment to the server.

UNIQUE_CMD_ID:::CMD_TYPE_FILE

On the server side, the file is read, encrypted, and saved in a directory with a random name. Then a command is placed into the implants command queue that appends on the original command string above with where to find the file associated with the command.

UNIQUE_CMD_ID:::CMD_TYPE_FILE:::FILE_NAME

So when the implant reads this command when it checks in, it knows that it needs to download a subsequent file named FILE_NAME, downloads it and decrypts it then does what it needs to do with it (like executing it in the case of shellcode).

The Diet-Implant#

The implant functionality rather than design is where I had most of the trouble - some gotchas with wide unicode characters in windows, getting WinHTTP to work with self-signed HTTPS certificates and a strange bug which adds 16 garbage bytes to the front of anything I encrypt or decrypt with my AES function (fixed by removing the first 16 bytes so I’ll call it a feature, “accidental obfuscation”).

That being said, I think a couple of the commands are pretty cool and give the client more control with what they want to do, namely being able to choose what memory permissions to use when running shellcode.

RW vs RWX Shellcode Execution#

Early on I ran into shellcode that needs local variables crashing due to a memory permissions error if the underlying functionality relied on storing data. For example if the shellcode was stored in a RX region, and tries to write to some location inside it, it will crash with a memory permissions error.

As a result, I built in functionality to choose what memory you use to execute the shellcode - either copying into RWX and executing right away, or copying into RW then changing the memory permissions to RX and then executing. By default, all shellcode injection uses RWX.

For something complex like implant shellcode you’d need the ability to write and execute - but for specially crafted shellcode (or just generally small shellcode), executing from RX works just as well with a smaller footprint as RWX memory regions are regularly looked for by AV.

On the client side, there is an optional RX or RWX command specifier for shellcode commands like below.

shellcode-inject SHELLCODE.bin RX

It’s Usable!#

After all that I think it’s in a good state to begin adding functionality beyond what I learned in my post about bypassing Windows Defender, and start the intermediate malware development course from Sektor7 - implementing the techniques I learned along the way.

The implant also already bypasses Windows Defender and scores 0/26 on antiscan.me - which was very surprising to me.

Thanks for reading