Created
February 26, 2017 11:52
-
-
Save Warchant/029187b8ba4be6ed8f08be35276629cc to your computer and use it in GitHub Desktop.
Results of our research about forensics properties of keepass
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python2 | |
import struct | |
import math | |
import string | |
import salsa20 | |
PTR_SIZE = 8 # bytes. | |
assert PTR_SIZE == 8 or PTR_SIZE == 4, "x64 or x32" | |
PACK_FMT = "<Q" if PTR_SIZE == 8 else "<L" | |
MAX_IV = 512 | |
def get_body(blob, prefix, size): | |
index = blob.find(prefix) | |
psize = len(prefix) | |
while index != -1: | |
key = blob[index+psize : index + psize+size] | |
yield key | |
index = blob.find(prefix, index+1) | |
def get_prefix(blob, body, psize): | |
index = blob.find(body) | |
while index != -1: | |
key = blob[index-psize : index] | |
yield key | |
index = blob.find(body, index+len(body)) | |
def get_between(blob, start, end): | |
a = blob.find(start) | |
while a != -1: | |
b = blob.find(end, a+len(start)) | |
if b != -1: | |
key = blob[a +len(end): b] | |
a = blob.find(start, b+len(end)) | |
yield key | |
def is_zero(_input): | |
if isinstance(_input, str): | |
for i in _input: | |
if i != '\x00': | |
return False | |
else: | |
return True | |
raise NotImplementedError("shrug") | |
# we are looking for all prefixes of 0x1000..00, 0x2000..00, then read all their prefixes and calculate | |
# mode(prefix_list) | |
def get_method_table(blob, iterations=3): | |
z = [] | |
zz = [] | |
for i in range(iterations): | |
z.append({}) | |
# 0x1000..00, 0x2000..00, 0x3000..00, PTR_SIZE bytes each | |
b = struct.pack(PACK_FMT, 16 << i) | |
d = {} | |
for j in get_prefix(blob, b, PTR_SIZE): | |
if not is_zero(j): # method table can not be 0 | |
if j not in d: | |
d[j] = 1 | |
else: | |
d[j] += 1 | |
zz += [max(d.items(), key=lambda x:x[1])] | |
d = {} | |
for k,v in zz: | |
if k not in d: | |
d[k] = v | |
else: | |
d[k] += v | |
return max(d) | |
def get_array_data(blob, method_table, array_length): | |
assert isinstance(method_table, str), "method_table must be str" | |
assert len(method_table) == 0 or len(method_table) == PTR_SIZE, "method_table ptr should have len = PTR_SIZE" | |
assert isinstance(array_length, int) and array_length > 0, "array_length must be positive number" | |
size = struct.pack(PACK_FMT, array_length) | |
prefix = method_table + size | |
for i in get_body(blob, prefix, array_length): | |
yield i | |
# number to vector. len(v) = 8 | |
def expand(number): | |
# <Q is both for x32 and x64 | |
return struct.pack("<Q", number) | |
# returns True if first N letters are printable, and rest are '\x00's | |
def is_printable(text): | |
zeros = False | |
for b in text: | |
b = ord(b) | |
if b == 0: | |
zeros = True | |
if not zeros and 32 < b < 127: | |
pass | |
elif zeros and b == 0: | |
pass | |
else: | |
return False | |
return True | |
def d(h): | |
h = h.split("-") | |
h = "".join([chr(int(x, 16)) for x in h]) | |
return h | |
def pprint(key, iv, ct, pt): | |
print "Using key: ", key.encode('hex') | |
print "Using IV: ", iv.encode('hex') | |
print "Ciphertext: ", ct.encode('hex') | |
print "Plaintext: ", pt.encode('hex') | |
print "Plaintext: ", pt | |
print "" | |
def extract(blob): | |
def get(size, method=None, level=0): | |
if method == None: | |
raise Exception("specify method_table") | |
if level > 1: | |
raise Exception("Can not find ProtectedBinary with length {0}".format(size)) | |
print "Getting ProtectedBinary[{0}] with method \"{1}\"...".format(size, method.encode('hex')) | |
a = set() | |
for i in get_array_data(blob, method, size): | |
a.add(i) | |
ret = [] | |
for i in a: | |
# if more than 50% of '\x00', then skip | |
if i.count('\x00') < 0.5 * len(i): | |
ret += [i] | |
if len(ret) == 0: | |
return get(size, "", level + 1) | |
else: | |
return ret | |
method = get_method_table(blob) | |
space = { | |
16: get(16, method), | |
32: get(32, method), | |
48: get(48, method), | |
64: get(64, method) | |
} | |
if len(space[32]) == 0: | |
raise Exception("can not find any ProtectedBinary[32]") | |
for k in space: | |
print "byte[{0}]: {1} items".format(k, len(space[k])) | |
def seek_for_keys(cts): | |
for ct in cts: | |
for key in space[32]: | |
if ct == key: | |
continue | |
for iv in range(MAX_IV): | |
iv = expand(iv) | |
pt = salsa20.Salsa20_xor(ct, iv, key) | |
if is_printable(pt): | |
yield key | |
print "Searching for salsa keys..." | |
ct_ind = 16 if len(space[16]) < len(space[32]) else 32 # optimization | |
for key in seek_for_keys(space[ct_ind]): | |
print "KEY FOUND: {0}".format(key.encode('hex')) | |
for k in space: | |
for ct in space[k]: | |
for iv in range(MAX_IV): | |
iv = expand(iv) | |
pt = salsa20.Salsa20_xor(ct,iv,key) | |
if is_printable(pt): | |
yield pt | |
f = open("KeePassMemory.dmp", "rb") | |
data = f.read() | |
f.close() | |
for pt in extract(data): | |
print pt | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment