Skip to content

Instantly share code, notes, and snippets.

@Warchant
Created February 26, 2017 11:52
Show Gist options
  • Save Warchant/029187b8ba4be6ed8f08be35276629cc to your computer and use it in GitHub Desktop.
Save Warchant/029187b8ba4be6ed8f08be35276629cc to your computer and use it in GitHub Desktop.
Results of our research about forensics properties of keepass
#!/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