|
From: | Vladimir Santos |
Subject: | [Mobiusft-list] User password report for Hive extension |
Date: | Fri, 14 Jan 2011 06:49:47 +0300 |
Hello Eduardo, I've been working on a user password hash report for use with the Hive extension and I'd like to send this new contribution to Mobius Forensic Toolkit project. It can be distributed under GPLv2 license or later. During development I've found some bugs in the Hive extension. Firstly, there's no service for unregistering a report. A second bug is in the HiveFile.retrieve_nk method, where the classname property of a key is being miscalculated. The following code produces the right value: key.classname = unicode(self.retrieve_value_data(value[9], value[11]), 'utf-16-le') Another bug is the AttributeError produced when a report is selected before adding a hive file to the file list view in the Hive extension. There's yet one more bug in Hive Report extension, where the OSInformationReport class produces an struct.error if it is selected after removing the hive files from the file list view in the Hive extension. I hope the user password report will be useful. In the following code the implementations of the RC4 and DES cryptographic algorithms in pure Python aren't very fast, but they prevent external dependencies. Best regards, Vladimir Santos --- begin here ---------------------------------------------------------------- import struct import hashlib import binascii # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Cryptographic helper classes # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # RC4 algorithm class RC4(object): '''Alleged RC4 cipher Based on the posting 'RC4 Algorithm revealed' to Usenet newsgroups sci.crypt, alt.security, comp.security.misc and alt.privacy on September 14, 1994. http://en.wikipedia.org/wiki/RC4 http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt ''' def __init__(self, key): '''Creates a new RC4 object. ''' if len(key) < 1 or len(key) > 256: raise ValueError('Key size must be between 1 and 256 bytes') self.__key = [ord(c) for c in key] self._i, self._j, self._S = self.__keyschedule(self.__key) def __keyschedule(self, key): '''Key-scheduling algorithm (KSA). ''' j, S = 0, range(256) for i in range(256): j = (j + S[i] + key[i % len(key)]) % 256 S[i], S[j] = S[j], S[i] i, j = 0, 0 return i, j, S def __nextbyte(self, nbytes = 1): '''Pseudo-random generation algorithm (PRGA). ''' i, j, S = self._i, self._j, self._S result = [] for n in range(nbytes): i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] result.append(S[(S[i] + S[j]) % 256]) self._i, self._j, self._S = i, j, S return result def __crypt(self, message): '''Encrypts or decrypts a message. ''' return ''.join([chr(b ^ ord(c)) for b, c in zip(self.__nextbyte(len(message)), message)]) def copy(self): '''Makes a separate copy of this cipher object. ''' other = self.__class__(self.key) other._i, other._j, other._S = self._i, self._j, self._S[:] return other def encrypt(self, message): '''Encrypts a message. ''' return self.__crypt(message) def decrypt(self, message): '''Decrypts a message. ''' return self.__crypt(message) @property def name(self): '''Name of the cipher algorithm. ''' return 'rc4' @property def key(self): '''Current key of the cipher. ''' return ''.join([chr(b) for b in self.__key]) @property def block_size(self): '''Current block size of the cipher in bytes. ''' return 1 @property def key_size(self): '''Current key size of the cipher in bytes. ''' return len(self.__key) # DES algorithm class DES(object): '''DES cipher Based on the Data Encryption Standard defined by the National Institute of Standards and Technology (NIST) of the U.S. Department of Commerce in the Federal Information Processing Standards Publication 46-3 (FIPS PUB 46-3). ''' # Initial permutation (IP) __IP = ( 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7, 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6 ) # Inverse of the initial permutation (IP^-1) __IP_1 = ( 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, 32, 0, 40, 8, 48, 16, 56, 24 ) # E bit-selection table __E = ( 31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 19, 20, 21, 22, 23, 24, 23, 24, 25, 26, 27, 28, 27, 28, 29, 30, 31, 0 ) # Selection functions (S1 to S8) __S = ( ( 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 ), ( 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 ), ( 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 ), ( 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 ), ( 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 ), ( 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 ), ( 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 ), ( 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 ) ) # Permutation function __P = ( 15, 6, 19, 20, 28, 11, 27, 16, 0, 14, 22, 25, 4, 17, 30, 9, 1, 7, 23, 13, 31, 26, 2, 8, 18, 12, 29, 5, 21, 10, 3, 24 ) # Permuted choice 1 __PC1 = ( 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 ) # Schedule of left shifts (rotations) __LSHIFT = ( 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 ) # Permuted choice 2 __PC2 = ( 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 ) def __init__(self, key): '''Creates a new DES object. ''' self.__key = self.__fixparity(key) self.__kn = self.__keyschedule(self.__key) def __fixparity(self, key): '''Sets the last bit in every byte in key to reflect the odd parity. If a seven byte key is used, it will be expanded into eight bytes. ''' if len(key) not in (7, 8): raise ValueError('Key size must be 7 or 8 bytes') key = [ord(c) for c in key] # expand seven byte key into eight bytes if len(key) == 7: key = [key[0] & 0xfe, ((key[0] & 0x01) << 7) | ((key[1] & 0xfc) >> 1), ((key[1] & 0x03) << 6) | ((key[2] & 0xf8) >> 2), ((key[2] & 0x07) << 5) | ((key[3] & 0xf0) >> 3), ((key[3] & 0x0f) << 4) | ((key[4] & 0xe0) >> 4), ((key[4] & 0x1f) << 3) | ((key[5] & 0xc0) >> 5), ((key[5] & 0x3f) << 2) | ((key[6] & 0x80) >> 6), (key[6] & 0x7f) << 1] # set odd parity for each byte of the key for i in range(self.key_size): parity = [1 for m in [1, 2, 4, 8, 16, 32, 64, 128] if (key[i] & m) > 0] if (len(parity) & 0x01) == 0: key[i] = (key[i] & 0xfe) | ((key[i] & 0x01) ^ 0x01) return key def __keyschedule(self, key): '''Key scheduling. ''' kn = [0] * 16 pc = self.__permutate(DES.__PC1, key) for n in range(16): # rotate left each half for i in range(DES.__LSHIFT[n]): msb = [(b & 0x80) >> 7 for b in pc[1:]] msb.append((pc[3] & 0x08) >> 3) msb.append((pc[0] & 0x80) >> 3) pc = [((pc[i] << 1) & 0xff) | msb[i] for i in range(7)] pc[3] = (pc[3] & 0xef) | msb[7] kn[n] = self.__permutate(DES.__PC2, pc) return kn def __permutate(self, table, data): '''Permutates data bits accordingly to the specified table. ''' bytenum = len(table) >> 3 x = [0] * bytenum for i in range(bytenum): tt = table[i << 3:(i << 3) + 8] x[i] = (((data[tt[0] >> 3] << (tt[0] & 7)) & 0x80) | (((data[tt[1] >> 3] << (tt[1] & 7)) & 0x80) >> 1) | (((data[tt[2] >> 3] << (tt[2] & 7)) & 0x80) >> 2) | (((data[tt[3] >> 3] << (tt[3] & 7)) & 0x80) >> 3) | (((data[tt[4] >> 3] << (tt[4] & 7)) & 0x80) >> 4) | (((data[tt[5] >> 3] << (tt[5] & 7)) & 0x80) >> 5) | (((data[tt[6] >> 3] << (tt[6] & 7)) & 0x80) >> 6) | (((data[tt[7] >> 3] << (tt[7] & 7)) & 0x80) >> 7)) return x def __crypt(self, message, rounds): '''Encrypts or decrypts a message. ''' if len(message) % self.block_size != 0: raise ValueError('Cipher block length must be multiple of 8 bytes') result = '' # process data in 8-byte chunks for blk in range(len(message) // self.block_size): i = blk * self.block_size block = [ord(c) for c in message[i:i + self.block_size]] # initial permutation block = self.__permutate(DES.__IP, block) l, r = block[:4], block[4:] for n in rounds: # expand input from 32 bits to 48 bits b = [x ^ y for x, y in zip(self.__kn[n], self.__permutate(DES.__E, r))] # define 6-bit blocks b = [(((b[0] & 0x80) >> 2) | ((b[0] & 0x04) << 2) | ((b[0] & 0x78) >> 3)), (((b[0] & 0x02) << 4) | (b[1] & 0x10) | ((b[0] & 0x01) << 3) | ((b[1] & 0xe0) >> 5)), (((b[1] & 0x08) << 2) | ((b[2] & 0x40) >> 2) | ((b[1] & 0x07) << 1) | ((b[2] & 0x80) >> 7)), ((b[2] & 0x20) | ((b[2] & 0x01) << 4) | ((b[2] & 0x1e) >> 1)), (((b[3] & 0x80) >> 2) | ((b[3] & 0x04) << 2) | ((b[3] & 0x78) >> 3)), (((b[3] & 0x02) << 4) | (b[4] & 0x10) | ((b[3] & 0x01) << 3) | ((b[4] & 0xe0) >> 5)), (((b[4] & 0x08) << 2) | ((b[5] & 0x40) >> 2) | ((b[4] & 0x07) << 1) | ((b[5] & 0x80) >> 7)), ((b[5] & 0x20) | ((b[5] & 0x01) << 4) | ((b[5] & 0x1e) >> 1))] # consolidate a single 32-bit block from 4-bit blocks s = [(DES.__S[0][b[0]] << 4) | DES.__S[1][b[1]], (DES.__S[2][b[2]] << 4) | DES.__S[3][b[3]], (DES.__S[4][b[4]] << 4) | DES.__S[5][b[5]], (DES.__S[6][b[6]] << 4) | DES.__S[7][b[7]]] # permutate the result of the iteration l, r = r, [x ^ y for x, y in zip(l, self.__permutate(DES.__P, s))] # final permutation (the inverse of the initial one) block = self.__permutate(DES.__IP_1, r + l) result = ''.join((result, ''.join(chr(c) for c in block))) return result def copy(self): '''Makes a separate copy of this cipher object. ''' other = self.__class__(self.key) return other def encrypt(self, message): '''Encrypts a message. ''' return self.__crypt(message, range(0, 16, 1)) def decrypt(self, message): '''Decrypts a message. ''' return self.__crypt(message, range(15, -1, -1)) @property def name(self): '''Name of the cipher algorithm. ''' return 'des' @property def key(self): '''Current key of the cipher. ''' return ''.join([chr(b) for b in self.__key]) @property def block_size(self): '''Current block size of the cipher in bytes. ''' return 8 @property def key_size(self): '''Current key size of the cipher in bytes. ''' return 8 # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # @brief Report: User Passowrd # # User password hash cryptography implementation is based upon Brendan Dolan- # Gavitt's code at framework/win32/hashdump.py in the creddump distribution # (http://code.google.com/p/creddump/). # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= class UserPasswordReport(object): # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # @brief initialize object # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= def __init__(self): self.name = 'user passwords' self.viewer = None # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # @brief System hashed bootkey # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= def _get_bootkey(self, registry): p = 'HKLM/SYSTEM/CurrentControlSet/Control/Lsa/' bootkey = binascii.a2b_hex(''.join(registry.get_key(p + i).classname for i in ('JD', 'Skew1', 'GBG', 'Data'))) # permutation matrix for boot key bootkey = ''.join(bootkey[i] for i in ( 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7)) f = registry.get_data('HKLM/SAM/SAM/Domains/Account/F') md5 = hashlib.md5(f[112:128] + 'address@hidden&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0' + bootkey + '0123456789012345678901234567890123456789\0') rc4 = RC4(md5.digest()) return rc4.encrypt(f[128:160]) # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # @brief Decrypt the user hash # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= def _decrypt_hash(self, bootkey, rid, enc_lmnthash, lmntstr): md5 = hashlib.md5(bootkey[:16] + struct.pack('<I', rid) + lmntstr) rc4 = RC4(md5.digest()) obfkey = rc4.encrypt(enc_lmnthash) # convert rid to key des_key = ''.join([chr(rid & 0xff), chr((rid >> 8) & 0xff), chr((rid >> 16) & 0xff), chr((rid >> 24) & 0xff)]) des1 = DES(des_key + des_key[:3]) des2 = DES(des_key[3] + des_key + des_key[:2]) return des1.decrypt(obfkey[:8]) + des2.decrypt(obfkey[8:]) # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # @brief generate report # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= def generate(self, registry): if ((registry.get_key('HKLM/SYSTEM') is None) or (registry.get_key('HKLM/SAM') is None)): return # set report name name = registry.get_data('HKLM/SYSTEM/CurrentControlSet/Control/' 'ComputerName/ComputerName/ComputerName') or '' self.viewer.set_report_name('User passwords of computer <{0}>'.format(name)) # fill data empty_nthash = binascii.a2b_hex('31d6cfe0d16ae931b73c59d7e0c089c0') empty_lmhash = binascii.a2b_hex('aad3b435b51404ee') bootkey = self._get_bootkey(registry) users = registry.get_key('HKLM/SAM/SAM/Domains/Account/Users') or [] for key in users: if key.name.startswith('0'): rid = int(key.name, 16) v = key.get_data('V') offset, size = struct.unpack('II', v[12:20]) username = v[0xcc + offset:0xcc + offset + size].decode('utf-16') offset, size = struct.unpack('II', v[156:164]) lmhash = self._decrypt_hash(bootkey, rid, v[0xcc + offset:0xcc + offset + size][4:], 'LMPASSWORD\0') offset, size = struct.unpack('II', v[168:176]) nthash = self._decrypt_hash(bootkey, rid, v[0xcc + offset:0xcc + offset + size][4:], 'NTPASSWORD\0') password = '' status = '' if nthash == empty_nthash or lmhash.startswith(empty_lmhash): status = 'empty' elif lmhash.endswith(empty_lmhash): status = 'less than 8 bytes' elif lmhash != '' and not lmhash.endswith(empty_lmhash): status = 'greater than 7 bytes' elif nthash == '' and lmhash == '': status = 'no hashes' self.viewer.add_row((rid, username, binascii.b2a_hex(lmhash), binascii.b2a_hex(nthash), password, status)) # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # @brief build viewer # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= def build_viewer(self): self.viewer = gdata.mediator.call('ui.new-listview', None) self.viewer.add_column('rid', 'RID', column_type='int', is_sortable=True) self.viewer.add_column('username', 'username', is_sortable=True) self.viewer.add_column('lmhash', 'LM hash', is_sortable=True) self.viewer.add_column('nthash', 'NT hash', is_sortable=True) self.viewer.add_column('password', 'password', is_sortable=True) self.viewer.add_column('status', 'password status', is_sortable=True) self.viewer.set_report_id('os.user-passwords') self.viewer.set_report_app('%s v%s' % (EXTENSION_NAME, EXTENSION_VERSION)) self.viewer.show() usrpwdrpt = UserPasswordReport() # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # @brief start function # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= def pvt_start(): gdata.mediator.call('registry.add-report', usrpwdrpt) # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # @brief stop function # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= def pvt_stop(): gdata.mediator.call('registry.remove-report', usrpwdrpt) --- end here ------------------------------------------------------------------ |
[Prev in Thread] | Current Thread | [Next in Thread] |