rm -rf remains

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

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

root@rmrf:/# 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?

root@rmrf:/# 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.

root@rmrf:/# for file in /dev/pts/*; do echo $file; done
/dev/pts/0
/dev/pts/3
/dev/pts/ptmx

Several Redditors pointed out that printf is available.

root@rmrf:/# 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.

root@rmrf:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }
-bash: syntax error near unexpected token `('

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

root@rmrf:/# 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.

root@rmrf:/# unalias ls
root@rmrf:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }
root@rmrf:/# ls
/dev
/proc
/run
/sys
root@rmrf:/# ls /dev
/dev/pts

And save our work.

root@rmrf:/# echo 'ls() { printf '%s\n' ${1:+${1%/}/}*; }' >> utils.sh
root@rmrf:/# source utils.sh

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

root@rmrf:/# (while read line; do echo "$line"; done) < utils.sh
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!

root@rmrf:/# for file in /*; do executable $file; done
root@rmrf:/# for file in /*/*; do executable $file; done
root@rmrf:/# for file in /*/*/*; do executable $file; done
/proc/1107/exe
/proc/1136/exe
/proc/1149/exe
/proc/1179/exe
/proc/1215/exe
/proc/1217/exe
/proc/1220/exe
/proc/1221/exe
/proc/1223/exe
/proc/1248/exe
/proc/1277/exe
/proc/1468/exe
/proc/1478/exe
/proc/1625/exe
/proc/1644/exe
/proc/1/exe
/proc/374/exe
/proc/378/exe
/proc/471/exe
/proc/616/exe
/proc/657/exe
/proc/self/exe

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.

root@rmrf:/# executable () { if [[ ( ! -d $1 ) && ( ! -h $1 ) && -x $1 ]] ; then echo "$1"; fi }
root@rmrf:/# for file in /*/*/*; do executable $file; done
root@rmrf:/# for file in /*/*/*/*; do executable $file; done
root@rmrf:/# for file in /*/*/*/*/*; do executable $file; done
root@rmrf:/# 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:

root@rmrf:/# echo 1 > /proc/sys/kernel/sysrq
root@rmrf:/# 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 };
EOF
$ 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:

final.gif


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.

 
3,223
Kudos
 
3,223
Kudos

Read this next

No NoSQL

Can we stop saying NoSQL? I’m really tired of describing things by what they’re not. I’d much rather describe them by what they are. “This is a JSON document store” “This is an object store”... Continue →