Just for fun, I decided to launch a new Linux server and run rm -rf / as root to see what remains. As I found out, rm lives in the future with idiots like me, so you have to specify --no-preserve-root to kick this exercise off.

# rm -rf --no-preserve-root /

After committing this act of tomfoolery, great utilities like

  • /bin/ls
  • /bin/cat
  • /bin/chmod
  • /usr/bin/file

will all be gone! You should still have your connection over SSH as well as your existing bash session. This means you have all the bash builtins, like echo.

Becoming Bash McGyver

[email protected]:/# ls
-bash: /bin/ls: No such file or directory

There is no ls, but echo and fileglobs are still around. What can we do with those?

[email protected]:/# echo *
dev proc run sys
# echo /dev/pts/*
/dev/pts/0 /dev/pts/3 /dev/pts/ptmx

Hey, we got to keep /dev, /proc, /run, and /sys. Now that we have ls, we might as well make it a little easier to read.

[email protected]:/# for file in /dev/pts/*; do echo $file; done

Several Redditors pointed out that printf is available.

[email protected]:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }

printf will [apply] the format string until it runs out of args.” - camh-

Since you can define functions in bash, we can create an ls utility, albeit very limited.

[email protected]:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }
-bash: syntax error near unexpected token `('

What? That should be completely valid. Is ls already hashed or aliased?

[email protected]:/# type ls
ls is aliased to `ls --color=auto'

Ah, that gets expanded to ls--color=auto () { printf '%s\n' ${1:+${1%/}/}*; }. Yuck. Well, we can unalias that.

[email protected]:/# unalias ls
[email protected]:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }
[email protected]:/# ls
[email protected]:/# ls /dev

And save our work. console [email protected]:/# echo 'ls() { printf '%s\n' ${1:+${1%/}/}*; }' >> [email protected]:/# source

How about cat? The read builtin comes in handy along with pipes and redirection, so we can piece together a rudimentary cat.

[email protected]:/# (while read line; do echo "$line"; done) <
ls() { printf '%s\n' ${1:+${1%/}/}*; }

With these abilities and the fact that we can write arbitrary bytes with echo, we could rebuild and then curl or wget the binaries we want directly. My first choice, echoed by others, would be to get busybox. Busybox is the Swiss Army Knife of Embedded Linux, with builtin versions of wget, dd, tar, and many others. Eusebeîa goes into great detail about how to get a fully escaped version of busybox on your system, so I won’t do that here.

There is a problem though.

Even if we echo all the bytes we need into creating entire binaries, those files won’t be executable. No way to start busybox. The easiest workaround for this is to find something which is executable and overwrite it with echo. We’ve nuked all of /usr and /bin at this point though, so that’s a bit tricky.

We can use shell globs and bash logic to find files with the executable bit set, making sure to ignore directories.

executable () { if [[ ( ! -d $1 ) && -x $1 ]] ; then echo "$1"; fi }

Find the executables!

[email protected]:/# for file in /*; do executable $file; done
[email protected]:/# for file in /*/*; do executable $file; done
[email protected]:/# for file in /*/*/*; do executable $file; done

Great! Hold on a minute though, those are all symbolic links to executables that no longer exist on disk. We’ll just update executable() to ignore symbolic links.

[email protected]:/# executable () { if [[ ( ! -d $1 ) && ( ! -h $1 ) && -x $1 ]] ; then echo "$1"; fi }
[email protected]:/# for file in /*/*/*; do executable $file; done
[email protected]:/# for file in /*/*/*/*; do executable $file; done
[email protected]:/# for file in /*/*/*/*/*; do executable $file; done
[email protected]:/# for file in /*/*/*/*/*/*; do executable $file; done

Well now, that’s bad news bears. Perhaps there is something at the kernel level we can use. After all, we can restart the box using sysrq magic:

[email protected]:/# echo 1 > /proc/sys/kernel/sysrq
[email protected]:/# echo "b" > /proc/sysrq-trigger

Now we’re locked out and I should do something else on a Friday. Thanks for reading! Let me know if you figure out how to get an executable bit set.

UPDATE: Redditor throw_away5046 posted a full, beautiful, solution to this.

From a non-hosed, same architecture box:

$ mkdir $(xxd -p -l 16 /dev/urandom)
$ cd $_
$ apt-get download busybox-static
$ dpkg -x *.deb .
$ alias encode='{ tr -d \\n | sed "s#\\(..\\)#\\\\x\\1#g"; echo; }'
$ alias upload='{ xxd -p | encode | nc -q0 -lp 5050; }'
$ upload < bin/busybox

Back on the rmrf’ed machine

# cd /
# alias decode='while read -ru9 line; do printf "$line"; done'
# alias download='( exec 9<>/dev/tcp/{IP OF NON HOSED BOX}/5050; decode )'
# download > busybox

Now to create a shared object that will change permissions on busybox

$ cat > setx.c <<EOF
extern int chmod(const char *pathname, unsigned int mode);

int entry(void) {

        return !! chmod("busybox", 0700);
char *desc[] = {0};

struct quick_hack {

        char *name; int (*fn)(void); int on;
        char **long_doc, *short_doc, *other;

} setx_struct = { "setx", entry, 1, desc, "chmod 0700 busybox", 0 };
$ gcc -Wall -Wextra -pedantic -nostdlib -Os -fpic -shared setx.c -o setx
$ upload < setx

Time to enable setx as a built in and get busybox executable

# ( download > setx; enable -f ./setx setx; setx; )
# /busybox mkdir .bin
# /busybox  --install -s .bin
# PATH=/.bin

In action:

For all my blog posts I’ve decided to hold discussion on Reddit, linking to the post. Today’s post is on /r/linux and on /r/programming, but feel free to cross post it. PM me if you want me to link it here. Alternatively, you can reach me on Twitter.

Go forth and build your bash knowledge. Personally, I recommend Unix Power Tools.

Through college and career, flipping through this book has always showed me something new.

Blog Logo

Kyle Kelley



Kyle Kelley

Break things and enjoy life.

Back to Overview