05 May 2013

31337 Password Guessing

In the digital forensics and incident response we tend to deal with encrypted containers on a regular basis. With encrypted containers means dealing with various styles and iterations of passwords used to access these containers. In a large-scale incident response, it's not uncommon to have a dedicated spreadsheet that just maintains what passwords open what volumes, with the spreadsheet itself password protected.

But, what happens when you forget that password?



The common problems I've run into have been trying to access a volume months, sometimes years, after the fact. In civil cases, we've been surprised to see a case resurface years after we thought it had been settled, and rushed back to open old archives. This inevitably leaves us asking "Did I use a zero or an `O` for that byte?"

By making a spreadsheet of every possible password permutation, we've always been able to get to the data, but the issue does occasionally pop up.

For instance, in an intrusion-related case, a case agent used their agency's forensics group to seize a laptop drive. The drive contained a user file encrypted with TrueCrypt. Through the telephone-game, the owner says the password is XooXooX, which the responder writes as xooxoox, which is transcribed by the case agent as X00x00x. Attempts to decrypt the volume fail, and being that the original responder has now moved on and no notes were kept, all we're told is that the password is "something like ....".

Reasonable and resourceful shops would then write custom password filters to throw into software like PRTK, using DNA clustering to quickly determine the password. However, you don't work in a reasonable and resourceful shop. You can't afford PRTK. What do you do? Write a password guesser in Python that just uses TrueCrypt.




TrueCrypt has a variety of command-line arguments to automatically mount images given a specified password:


With these in mind, we can craft a command-line argument to mount the volume, and rely upon TrueCrypt's return code (0 or 1) to tell us if the mount was successful or not.

Take the case of an agent who says: "The password is Volleyball. We know the first letter is capitalized, and the rest is in leet speak."  Based on this, I write a leet-speak character substitution routine:


def leet_lookup(char):
    list = {"a": ["a","A","@"],
            "b": ["b", "B", "8"],
            "c": ["c", "C", "<"],
            "e": ["e", "E", "3"],
            "i": ["i", "I", "1"],
            "l": ["l", "L", "1"],
            "o": ["o", "O", "0"],
            "t": ["t", "T", "7"] }
    try:
        result = list[char.lower()]
    except KeyError:
        result = [char.lower(), char.upper()]
    return result


This little routine lets us create a list of possible substitutions for each byte value. If the specified byte isn't declared, it just returns the upper and lower case version of it.

Now, we put this into action here:

import os
import subprocess

tc_exe = "C:\\Program Files\\TrueCrypt\\truecrypt.exe"
tc_file = "E:\\test.tlc"
drive_letter = "P"

def leet_lookup(char):
    list = {"a": ["a","A","@"],
            "b": ["b", "B", "8"],
            "c": ["c", "C", "<"],
            "e": ["e", "E", "3"],
            "i": ["i", "I", "1"],
            "l": ["l", "L", "1"],
            "o": ["o", "O", "0"],
            "t": ["t", "T", "7"] }
    try:
        result = list[char.lower()]
    except KeyError:
        result = [char.lower(), char.upper()]
    return result
list = []
# V o l l e y b a l l = 10 chars
for c1 in leet_lookup('v'):
  for c2 in leet_lookup('o'):
    for c3 in leet_lookup('l'):
      for c4 in leet_lookup('l'):
        for c5 in leet_lookup('e'):
          for c6 in leet_lookup('y'):
            for c7 in leet_lookup('b'):
              for c8 in leet_lookup('a'):
                for c9 in leet_lookup('l'):
                  for c10 in leet_lookup('l'):
                    list.append("%s%s%s%s%s%s%s%s%s%s" % (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10))
print "%d passwords calculated. Now testing:" % len(list)

count = 0
for password in list:
  count += 1
  if not count % 10: print ".",
  tc_cmdline = "%s %s /l %s /b /a /m ro /q /s /p %s" % (tc_exe, tc_file, drive_letter, password)
  process = subprocess.Popen(tc_cmdline)
  returncode = process.wait()
  if not returncode:
    close_cmdline = "%s /d /l %s /q /s" % (tc_exe, drive_letter)
    process = subprocess.Popen(close_cmdline).wait()
    print "\r\nPassword found: %s" % password
    quit()

The main portion is where we specify the 10 character of Volleyball (which I'm sure could be written cleaner with a Python lambda, but ain't nobody got time for that). This script will try every permutation of Volleyball against a file named "E:\test.tlc", mounting it to drive letter "P:" if successful.


In action, there were 26,244 possible permutations of the file, with a dot written for every 10 tests. After 30 minutes of testing, the correct one was found to be:  V0ll3ybal1



A copy of this code can be found as a GitHub Gist at: https://gist.github.com/Rurik/5521081

Additionally, the same concept can be applied to other products, such as RAR or ZIP password archives.

No comments:

Post a Comment