Building a Hardened Shell For Attack/Defend CTF Supremacy

Why do this?

After playing the Pros versus Joes CTF on multiple occasions, a common tactic employed by the red team is to add malicious entries to bashrc, profile, and such. This is a common and documented persistence technique:

Bash and other shells can be used to create sockets if they are configured/compiled to do so. If this feature is enabled in bash specifically, one can redirect data to /dev/tcp|udp/host/port to achieve netcat-like functionality. This can be used to exfiltrate data, spawn reverse shells, create a makeshift port scanner, or create malware C2 channels without installing any extra software.

CLI logging is also very useful from a defender’s point of view. Bash has a feature that can enabled by defining SYSLOG_HISTORY which will log all commands to syslog. The built-in bash_history file is only written when the shell exits, which results in a lot of confusion and frustration for folks analyzing these logs. Attackers can also ‘unset HISTFILE’, point HISTFILE to /dev/null, poison the logs, selectively delete entries, and a few other tricks to make these logs basically worthless. SYSLOG_HISTORY eliminates the weirdness of analyzing .bash_history files.

Shells are often dynamically linked. Attackers can manipulate the libraries that your shell is linked to. This can provide malicious functionality that is advantageous to the attackers without modifying the shell itself. Dynamic linked binaries are also vulnerable to LD_PRELOAD attacks.

As any blue teamer would, I want to deny attackers as many of these opportunities as possible and increase visibility in my environment. Luckily, the offensive techniques outlined above are not that hard to thwart.


Summing up the previous section, I’d like to make a static-linked bash that disables profiles and rc files, disables socket redirection, and provides CLI logging. This can be broken down roughly into the following steps:

  1. Install musl
  2. Obtain bash’s source code
  3. Edit bash’s source to enable/disable features
  4. Configure the build to enable/disable features
  5. Build a static-linked bin with musl

Install musl

musl is an alternative libc implementation that works very well for static linking: This can be done with glibc as well, but I ran into problems using these binaries on older kernels:

bash-4.1$ ./bash 
FATAL: kernel too old

Assuming you have a working build environment on your system, musl is straightforward to install:

git clone git://
cd musl
sudo make install

On my system, this placed musl-gcc at /usr/local/musl/bin/musl-gcc

Get bash Source

bash’s source code can be downloaded from

Alternatively, if you have set up your package manager to provide source code, you can obtain it with your package manager. For example, I am using apt and have set up source repositories in /etc/apt/sources.list:

apt source bash

If you are using another package manager such as yum, zypper, or pacman, refer to your system’s documentation.

Edit bash’s source

We will be editing two files: config-top.h and shell.c

In config-top.h, you will want to edit the definition for PPROMPT to something more usable. This example is what I ended up using, but feel free to change this however you see fit. The escaped characters pertaining to the prompt are documented here

#define PPROMPT "\\s-\\v\\$ " // CHANGE THIS TO:
#define PPROMPT "[\\u@\\h:\\w]\\$ "

In shell.c, you will want to set these defaults to disable sourcing rc and profile files:

static int no_rc = 1;                   /* Don't execute ~/.bashrc */
static int no_profile = 1;              /* Don't execute .profile */

./configure time…

Now, we will run ./configure with a few flags and tell it to use musl-gcc as the compiler:

CC=/usr/local/musl/bin/musl-gcc CFLAGS="-DSYSLOG_SHOPT -DSYSLOG_HISTORY" ./configure --enable-static-link --without-bash-malloc --disable-net-redirections


From here, we should be able to just run “make”:


If all goes well, we will end up with a static-linked bash in our current working directory:

-bash-4.1$ file bash
bash: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

To save some space, we can strip our copy of bash:

strip bash

To show the syslog feature and PPROMPT in action:

-bash-4.1$ ./bash
[vagrant@localhost:/vagrant/bash-5.0]$ evil hacker command
bash: evil: command not found
[vagrant@localhost:/vagrant/bash-5.0]$ sudo tail /var/log/messages
... SNIP ...
Oct 30 13:24:05 localhost ./bash[3471]: HISTORY: PID=3471 UID=501 evil hacker command
Oct 30 13:24:13 localhost ./bash[3471]: HISTORY: PID=3471 UID=501 sudo tail /var/log/messages

Demonstrating that the socket redirection feature no longer works:

-bash-4.1$ echo hi >/dev/tcp/ ### no error; it worked!
-bash-4.1$ ./bash
[vagrant@localhost:/vagrant/bash-5.0]$ echo hi >/dev/tcp/
bash: /dev/tcp/ No such file or directory

Where to go from here

Some ideas:

  • Introduce some anti-debugging features to this so that attackers can’t attach strace, ltrace, or gdb to your processes.
  • Perform this process against busybox, but add some forensics/IR tools.
  • Perform this process with common IR tools to have a “hardened” trusted set of tools.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s