#!/usr/bin/env python # K4PC Windows specific routines from __future__ import with_statement import sys, os, re from struct import pack, unpack, unpack_from from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ string_at, Structure, c_void_p, cast import _winreg as winreg MAX_PATH = 255 kernel32 = windll.kernel32 advapi32 = windll.advapi32 crypt32 = windll.crypt32 import traceback # crypto digestroutines import hashlib def MD5(message): ctx = hashlib.md5() ctx.update(message) return ctx.digest() def SHA1(message): ctx = hashlib.sha1() ctx.update(message) return ctx.digest() def SHA256(message): ctx = hashlib.sha256() ctx.update(message) return ctx.digest() # For K4PC 1.9.X # use routines in alfcrypto: # AES_cbc_encrypt # AES_set_decrypt_key # PKCS5_PBKDF2_HMAC_SHA1 from alfcrypto import AES_CBC, KeyIVGen def UnprotectHeaderData(encryptedData): passwdData = 'header_key_data' salt = 'HEADER.2011' iter = 0x80 keylen = 0x100 key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen) key = key_iv[0:32] iv = key_iv[32:48] aes=AES_CBC() aes.set_decrypt_key(key, iv) cleartext = aes.decrypt(encryptedData) return cleartext # simple primes table (<= n) calculator def primes(n): if n==2: return [2] elif n<2: return [] s=range(3,n+1,2) mroot = n ** 0.5 half=(n+1)/2-1 i=0 m=3 while m <= mroot: if s[i]: j=(m*m-3)/2 s[j]=0 while j 0: # get the first item record item = items.pop(0) # the first 32 chars of the first record of a group # is the MD5 hash of the key name encoded by charMap5 keyhash = item[0:32] # the raw keyhash string is used to create entropy for the actual # CryptProtectData Blob that represents that keys contents entropy = SHA1(keyhash) # the remainder of the first record when decoded with charMap5 # has the ':' split char followed by the string representation # of the number of records that follow # and make up the contents srcnt = decode(item[34:],charMap5) rcnt = int(srcnt) # read and store in rcnt records of data # that make up the contents value edlst = [] for i in xrange(rcnt): item = items.pop(0) edlst.append(item) keyname = "unknown" for name in names: if encodeHash(name,charMap5) == keyhash: keyname = name break if keyname == "unknown": keyname = keyhash # the charMap5 encoded contents data has had a length # of chars (always odd) cut off of the front and moved # to the end to prevent decoding using charMap5 from # working properly, and thereby preventing the ensuing # CryptUnprotectData call from succeeding. # The offset into the charMap5 encoded contents seems to be: # len(contents)-largest prime number <= int(len(content)/3) # (in other words split "about" 2/3rds of the way through) # move first offsets chars to end to align for decode by charMap5 encdata = "".join(edlst) contlen = len(encdata) noffset = contlen - primes(int(contlen/3))[-1] # now properly split and recombine # by moving noffset chars from the start of the # string to the end of the string pfx = encdata[0:noffset] encdata = encdata[noffset:] encdata = encdata + pfx # decode using Map5 to get the CryptProtect Data encryptedValue = decode(encdata,charMap5) DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) cnt = cnt + 1 if cnt == 0: DB = None return DB # else newest .kinf2011 style .kinf file # the .kinf file uses "/" to separate it into records # so remove the trailing "/" to make it easy to use split # need to put back the first char read because it it part # of the added entropy blob data = hdr + data[:-1] items = data.split('/') # starts with and encoded and encrypted header blob headerblob = items.pop(0) encryptedValue = decode(headerblob, testMap1) cleartext = UnprotectHeaderData(encryptedValue) # now extract the pieces that form the added entropy pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) for m in re.finditer(pattern, cleartext): added_entropy = m.group(2) + m.group(4) # loop through the item records until all are processed while len(items) > 0: # get the first item record item = items.pop(0) # the first 32 chars of the first record of a group # is the MD5 hash of the key name encoded by charMap5 keyhash = item[0:32] # the sha1 of raw keyhash string is used to create entropy along # with the added entropy provided above from the headerblob entropy = SHA1(keyhash) + added_entropy # the remainder of the first record when decoded with charMap5 # has the ':' split char followed by the string representation # of the number of records that follow # and make up the contents srcnt = decode(item[34:],charMap5) rcnt = int(srcnt) # read and store in rcnt records of data # that make up the contents value edlst = [] for i in xrange(rcnt): item = items.pop(0) edlst.append(item) # key names now use the new testMap8 encoding keyname = "unknown" for name in names: if encodeHash(name,testMap8) == keyhash: keyname = name break # the testMap8 encoded contents data has had a length # of chars (always odd) cut off of the front and moved # to the end to prevent decoding using testMap8 from # working properly, and thereby preventing the ensuing # CryptUnprotectData call from succeeding. # The offset into the testMap8 encoded contents seems to be: # len(contents)-largest prime number <= int(len(content)/3) # (in other words split "about" 2/3rds of the way through) # move first offsets chars to end to align for decode by testMap8 # by moving noffset chars from the start of the # string to the end of the string encdata = "".join(edlst) contlen = len(encdata) noffset = contlen - primes(int(contlen/3))[-1] pfx = encdata[0:noffset] encdata = encdata[noffset:] encdata = encdata + pfx # decode using new testMap8 to get the original CryptProtect Data encryptedValue = decode(encdata,testMap8) cleartext = CryptUnprotectData(encryptedValue, entropy, 1) DB[keyname] = cleartext cnt = cnt + 1 if cnt == 0: DB = None return DB