Password hashing, Cracking /etc/shadow passwords using John the Ripper, and Breaking into Linux systems with Physical Access

Theory

When we store user passwords we to check that the user entered the correct password so that they can be authenticated. How can we do this? The caveman way is to just store the exact password for the user in plain text then check if the strings are the same. The smarter way is to use a hashing function.

A Hashing function is like a one way door. In theory, ignoring collisions, one input will always result in exactly one unique output. When we authenticate users, we hash their input and compare it to the stored password hash. A hash cannot be reversed because it is a one way function.

When we are “cracking hashes”, we are not trying to reverse the one way hash function. Instead, we are trying to guess which combination of characters will result in the target hash. It’s a lot like algebra. hashfunct(string)=hash. We know the hashfunct, we know the hash, we just need to solve for string.

Exercise

For the reader at home, try to figure out which 4 digit pin will result in the following md5 hash. Not that md5sums are used in the real world for password storage, but because they are fast to compute and make the exercise less time consuming.

e8eec49af0be2d32a90375926aeedb62  -

If you get stuck, I have included everything required to get to the solution below.

This is a simple C program I wrote that will generate all possible 4 digit pins.

#include <stdio.h>
#include <stdlib.h>

int main(){
    int a,b,c,d;
    a=b=c=d=0;

    // loop from 0000-9999
    for(d=0; d<=9; d++){
        for(c=0; c<=9; c++){
            for(b=0; b<=9; b++){
                for(a=0; a<=9; a++){
                    printf("%d%d%d%d\n", d,c,b,a);
                }
                a=0;
            }
            b=0;
        }
        c=0;
    }

    return 0;
}

Using a bit of shellghetti, we can easily calculate the md5sum for all 4 digit pins.

main@0x19:~$ ./a.out | xargs -L1 -I % sh -c 'printf "$(echo %) \t $(echo % | md5sum) \n"';

Use grep and you have the answer.

Cracking passwords using John

Taking the previous section into something based in the real world, we can use a similar strategy of “slam everything through the one way door until we get the result we want. The use real world use case here is to bully your users for having shit passwords. You generally need root for this to work which will be covered in the next section. Either way, this is an exercise. Act accordingly.

First, we will make a dummy user.

main@0x19:~$ sudo useradd dummy1
main@0x19:~$ sudo passwd dummy1
Changing password for user dummy1.
New password: 
BAD PASSWORD: The password is shorter than 8 characters
Retype new password: 
passwd: all authentication tokens updated successfully.
main@0x19:~$

The first thing we need to do is combine /etc/passwd with /etc/shadow. We do this using the unshadow command. Let’s unshadow then grep for the dummy users because we are doing this purely for testing purposes. Normally /etc/shadow/ is only readable by root so some privilege escalation might be required to obtain this file.

main@0x19:~$ sudo unshadow /etc/passwd /etc/shadow | grep dummy > dummy-unshadowed
main@0x19:~$ cat dummy-unshadowed 
dummy1:$y$j9T$0djW7V6BBCVQguuU5Ylhb1$aRiarlND2jyWZxeP9SMp6d1/lFTGHJIH6pvA...CRb/:1002:1002::/home/dummy1:/bin/bash
main@0x19:~$ 

The next step is to determine the hashing algorithm used by the system. Fedora uses yescrypt by default. John only supports yescrypt on platforms that support it natively. Other possible algos used could include sha256 or sha512 which are easier to crack and distro agnostic.

main@0x19:~$ cat /etc/login.defs  | grep -i encrypt_method
ENCRYPT_METHOD YESCRYPT
# Only works if ENCRYPT_METHOD is set to SHA256 or SHA512.
# Only works if ENCRYPT_METHOD is set to BCRYPT.
# Only works if ENCRYPT_METHOD is set to YESCRYPT.
main@0x19:~$ 

The final step is to actually crack the hashes. If your /etc/login.defs specifies sha256 or sha512 you don’t need to pass the --format flag. --wordlist= is an optional flag that might increase the speed at which the passwords are cracked.

Dictionary attack

John comes with it’s own word list at /usr/share/john/password.lst. We can, however, use our own word lists. In this example I am using the system’s spelling dictionary.

main@0x19:~$ john --format=crypt --wordlist=/usr/share/dict/words ./dummy-unshadowed 
Loaded 1 password hashe (crypt, generic crypt(3) [?/64])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:01:18:57 100% 0g/s 101.2p/s 202.5c/s 202.5C/s zymotoxic..ZZZ
Session completed
main@0x19:~$ john --show ./dummy-unshadowed 
0 password hashes cracked, 0 left
main@0x19:~$

At this point you can wait while your shitty laptop cooks itself. Eventually it will finish. It took my 8 core system about an hour and twenty minutes to work it’s way through the /usr/share/dict/words wordlist. If you are using this in the real world it’s probably a better idea to use a more appropriate password wordlist than the massive system dictionary typically used by spell checking programs.

In my case, checking the results, the dictionary attack did not work. This is because I intentionally created the password so that it would be resistant to dictionary attacks.

Brute Force

If a dictionary attack fails, we can start a brute force attack using the --incremental flag. This flag will cause john to cycle through characters until it finds a string that matches the hash. This will typically be a lot slower than using a dictionary attack because every possible combination of characters is tried, not just the ones that are likely to be a password.

I aborted the brute force after 20 hours because nothing was being produced. Even testing with a single character password, it took too long.

main@0x19:~$ john --format=crypt --incremental ./dummy-unshadowed 
Loaded 1 password hash (crypt, generic crypt(3) [?/64])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:19:51:58 0g/s 191.1p/s 191.1c/s 191.1C/s clpe10..clpel1
Session aborted

Getting results

Because I did not want to wait until the heat death of the universe to finish this article, I changed the test user’s password to something simpler and easier to crack: the single word “computer”. This password is susceptible to dictionary attacks and it took my system 4 seconds to crack.

main@0x19:~$ passwd dummy1
passwd: Only root can specify a user name.
main@0x19:~$ sudo passwd dummy1
[sudo] password for main: 
Changing password for user dummy1.
New password: 
BAD PASSWORD: The password fails the dictionary check - it is based on a dictionary word
Retype new password: 
passwd: all authentication tokens updated successfully.
main@0x19:~$ sudo unshadow /etc/passwd /etc/shadow | grep dummy > dummy-unshadowed
main@0x19:~$ john --format=crypt ./dummy-unshadowed 
Loaded 1 password hash (crypt, generic crypt(3) [?/64])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
computer         (dummy1)
1g 0:00:00:04 100% 2/3 0.2293g/s 226.6p/s 226.6c/s 226.6C/s 123456..pepper
Use the "--show" option to display all of the cracked passwords reliably
Session completed
main@0x19:~$ john --show ./dummy-unshadowed 
dummy1:</b>computer<b>:1002:1002::/home/dummy1:/bin/bash

1 password hash cracked, 0 left
main@0x19:~$ 

Breaking into the root account

  • you need physical access or access to the bootloader in some way
  • you need a system that does not require a power on password (or a way to reset it like removing the CMOS battery and draining the capacitors)
  • 2 seconds of googling because every distro that isn’t RH approved is less predictable than tainted dog water when it comes to the system behaving how anyone actually expects it to.

On a RH system (RHEL, CentOS, Fedora), we can reset the root password easily and reliably.

First, enter the grub splash screen. Every distro does this differently but most of the time you can either spam the arrow keys or hold shift/escape while the system is booting. It will look like this:

The grub splash screen

Go to the top entry (this is usually the most recent kernel) and press the e key to edit the grub entry. You will be presented with an editable version of the boot options. It will look like this:

Grub boot options for a fedora linux system

Move the cursor to the end of the line that starts with linux and append rd.break. Press control+x to boot the system using your new, temporary boot options. Once edited, it should look something like this:

Modified grub boot options to include rd.break

The system will now boot into emergency mode. We have two options: press the enter key to open a shell or press Control+d to break and reboot into a normal, multiuser runlevel. For our purposes, we should enter a shell.

Linux booted into emergency mode

Once we get a shell we can run a couple of commands to do a bit of clandestine “maintenance” on the system. When we are finished, we can reboot the system and gain root access using our new password.

Shell commands for resetting the root password (text block below)

First we remount the root partition read write, then we chroot into the root partition (the actual path for chroot will vary depending on distro), then we run the passwd command, create the /.autorelabel file so that SELinux can relabel the system, exit, and reboot.

Press Enter for maintenance
(or press Control-D to continue): 
sh-5.2# mount -o remount rw /sysroot
sh-5.2# /sysroot/usr/sbin/chroot /sysroot
sh-5.2# pwd
/
sh-5.2# whoami
root
sh-5.2# passwd
Changing password for user root. 
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
sh-5.2# touch /.autorelabel
sh-5.2# exit
exit
sh-5.2# reboot

On some distributions, rd.break in the grub config will not work so instead you can change the line to linux ($root)/vmlinuz-x.y.z root=UUID=x-x-x-x-x rw init=/bin/bash. This method is a bit messier but you won’t have to do any chrooting.

Conclusion

To protect yourself from clandestine system maintenance, you should always use full disk encryption. A bad actor cannot boot the system into recovery mode if he cannot even unlock the encrypted volumes. Transparent “full disk encryption” like shiltocker is not good enough because it is oftentimes trivial to bypass: Breaking Bitlocker - Bypassing the Windows Disk Encryption.

In addition to full disk encryption, user passwords should be strong and hard to crack. Use a pass sentence instead of a password. Make it long, include words that don’t exist in a dictionary, use extra characters and numbers to increase entropy.

Secure your hardware. Secure your software. They can’t get root if they have to beat it out of you with a rubber hose.