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: https://attack.mitre.org/techniques/T1156/
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:
- Install musl
- Obtain bash’s source code
- Edit bash’s source to enable/disable features
- Configure the build to enable/disable features
- Build a static-linked bin with musl
musl is an alternative libc implementation that works very well for static linking: https://www.musl-libc.org/intro.html. 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 Aborted
Assuming you have a working build environment on your system, musl is straightforward to install:
git clone git://git.musl-libc.org/musl cd musl ./configure make 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 http://ftp.gnu.org/gnu/bash/
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 https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Controlling-the-Prompt:
#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 */
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:
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: HISTORY: PID=3471 UID=501 evil hacker command Oct 30 13:24:13 localhost ./bash: 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/127.0.0.1/22 ### no error; it worked! -bash-4.1$ ./bash [vagrant@localhost:/vagrant/bash-5.0]$ echo hi >/dev/tcp/127.0.0.1/22 bash: /dev/tcp/127.0.0.1/22: No such file or directory
Where to go from here
- 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.