Skip to content

Commit

Permalink
new module - retrieve Keepass password from memory
Browse files Browse the repository at this point in the history
  • Loading branch information
AlessandroZ committed Nov 28, 2016
1 parent 72b9efc commit daf0f25
Show file tree
Hide file tree
Showing 8 changed files with 1,425 additions and 0 deletions.
Empty file.
163 changes: 163 additions & 0 deletions Windows/lazagne/softwares/memory/keepass.py

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions Windows/lazagne/softwares/memory/libkeepass/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
import io
from contextlib import contextmanager

from common import read_signature
# from kdb3 import KDB3Reader, KDB3_SIGNATURE
from kdb4 import KDB4Reader, KDB4_SIGNATURE

BASE_SIGNATURE = 0x9AA2D903

_kdb_readers = {
# KDB3_SIGNATURE[1]: KDB3Reader,
#0xB54BFB66: KDB4Reader, # pre2.x may work, untested
KDB4_SIGNATURE[1]: KDB4Reader,
}

@contextmanager
def open(filename, **credentials):
"""
A contextmanager to open the KeePass file with `filename`. Use a `password`
and/or `keyfile` named argument for decryption.
Files are identified using their signature and a reader suitable for
the file format is intialized and returned.
Note: `keyfile` is currently not supported for v3 KeePass files.
"""
kdb = None
try:
with io.open(filename, 'rb') as stream:
signature = read_signature(stream)
cls = get_kdb_reader(signature)
kdb = cls(stream, **credentials)
yield kdb
kdb.close()
except:
if kdb: kdb.close()
raise

def add_kdb_reader(sub_signature, cls):
"""
Add or overwrite the class used to process a KeePass file.
KeePass uses two signatures to identify files. The base signature is
always `0x9AA2D903`. The second/sub signature varies. For example
KeePassX uses the v3 sub signature `0xB54BFB65` and KeePass2 the v4 sub
signature `0xB54BFB67`.
Use this method to add or replace a class by givin a `sub_signature` as
integer and a class, which should be a subclass of
`keepass.common.KDBFile`.
"""
_kdb_readers[sub_signature] = cls

def get_kdb_reader(signature):
"""
Retrieve the class used to process a KeePass file by `signature`, which
is a a tuple or list with two elements. The first being the base signature
and the second the sub signature as integers.
"""
if signature[0] != BASE_SIGNATURE:
raise IOError('Unknown base signature.')

if signature[1] not in _kdb_readers:
raise IOError('Unknown sub signature.')

return _kdb_readers[signature[1]]

278 changes: 278 additions & 0 deletions Windows/lazagne/softwares/memory/libkeepass/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
# -*- coding: utf-8 -*-

# file header

class HeaderDictionary(dict):
"""
A dictionary on steroids for comfortable header field storage and
manipulation.
Header fields must be defined in the `fields` property before filling the
dictionary with data. The `fields` property is a simple dictionary, where
keys are field names (string) and values are field ids (int)::
>>> h.fields['rounds'] = 4
Now you can set and get values using the field id or the field name
interchangeably::
>>> h[4] = 3000
>>> print h['rounds']
3000
>>> h['rounds'] = 6000
>>> print h[4]
6000
It is also possible to get and set data using the field name as an
attribute::
>>> h.rounds = 9000
>>> print h[4]
9000
>>> print h.rounds
9000
For some fields it is more comfortable to unpack their byte value into
a numeric or character value (eg. the transformation rounds). For those
fields add a format string to the `fmt` dictionary. Use the field id as
key::
>>> h.fmt[4] = '<q'
Continue setting the value as before if you have it as a number and if you
need it as a number, get it like before. Only when you have the packed value
use a different interface::
>>> h.b.rounds = '\x70\x17\x00\x00\x00\x00\x00\x00'
>>> print h.b.rounds
'\x70\x17\x00\x00\x00\x00\x00\x00'
>>> print h.rounds
6000
The `b` (binary?) attribute is a special way to set and get data in its
packed format, while the usual attribute or dictionary access allows
setting and getting a numeric value::
>>> h.rounds = 3000
>>> print h.b.rounds
'\xb8\x0b\x00\x00\x00\x00\x00\x00'
>>> print h.rounds
3000
"""
fields = {}
fmt = {}

def __init__(self, *args):
dict.__init__(self, *args)

def __getitem__(self, key):
if isinstance(key, int):
return dict.__getitem__(self, key)
else:
return dict.__getitem__(self, self.fields[key])

def __setitem__(self, key, val):
if isinstance(key, int):
dict.__setitem__(self, key, val)
else:
dict.__setitem__(self, self.fields[key], val)

def __getattr__(self, key):
class wrap(object):
def __init__(self, d):
object.__setattr__(self, 'd', d)
def __getitem__(self, key):
fmt = self.d.fmt.get(self.d.fields.get(key, key))
if fmt: return struct.pack(fmt, self.d[key])
else: return self.d[key]
__getattr__ = __getitem__
def __setitem__(self, key, val):
fmt = self.d.fmt.get(self.d.fields.get(key, key))
if fmt: self.d[key] = struct.unpack(fmt, val)[0]
else: self.d[key] = val
__setattr__ = __setitem__

if key == 'b':
return wrap(self)
try:
return self.__getitem__(key)
except KeyError:
raise AttributeError(key)

def __setattr__(self, key, val):
try:
return self.__setitem__(key, val)
except KeyError:
return dict.__setattr__(self, key, val)


# file baseclass

import io
from crypto import sha256

class KDBFile(object):
def __init__(self, stream=None, **credentials):
# list of hashed credentials (pre-transformation)
self.keys = []
self.add_credentials(**credentials)

# the buffer containing the decrypted/decompressed payload from a file
self.in_buffer = None
# the buffer filled with data for writing back to a file before
# encryption/compression
self.out_buffer = None
# position in the `in_buffer` where the payload begins
self.header_length = None
# decryption success flag, set this to true upon verification of the
# encryption masterkey. if this is True `in_buffer` must contain
# clear data.
self.opened = False

# the raw/basic file handle, expect it to be closed after __init__!
if stream is not None:
if not isinstance(stream, io.IOBase):
raise TypeError('Stream does not have the buffer interface.')
self.read_from(stream)

def read_from(self, stream):
if not (isinstance(stream, io.IOBase) or isinstance(stream, file)):
raise TypeError('Stream does not have the buffer interface.')
self._read_header(stream)
self._decrypt(stream)

def _read_header(self, stream):
raise NotImplementedError('The _read_header method was not '
'implemented propertly.')

def _decrypt(self, stream):
self._make_master_key()
# move read pointer beyond the file header
if self.header_length is None:
raise IOError('Header length unknown. Parse the header first!')
stream.seek(self.header_length)

def write_to(self, stream):
raise NotImplementedError('The write_to() method was not implemented.')

def add_credentials(self, **credentials):
if credentials.has_key('password'):
self.add_key_hash(sha256(credentials['password']))
if credentials.has_key('keyfile'):
self.add_key_hash(load_keyfile(credentials['keyfile']))

def clear_credentials(self):
"""Remove all previously set encryption key hashes."""
self.keys = []

def add_key_hash(self, key_hash):
"""
Add an encryption key hash, can be a hashed password or a hashed
keyfile. Two things are important: must be SHA256 hashes and sequence is
important: first password if any, second key file if any.
"""
if key_hash is not None:
self.keys.append(key_hash)

def _make_master_key(self):
if len(self.keys) == 0:
raise IndexError('No credentials found.')

def close(self):
if self.in_buffer:
self.in_buffer.close()

def read(self, n=-1):
"""
Read the decrypted and uncompressed data after the file header.
For example, in KDB4 this would be plain, utf-8 xml.
Note that this is the source data for the lxml.objectify element tree
at `self.obj_root`. Any changes made to the parsed element tree will
NOT be reflected in that data stream! Use `self.pretty_print` to get
XML output from the element tree.
"""
if self.in_buffer:
return self.in_buffer.read(n)

def seek(self, offset, whence=io.SEEK_SET):
if self.in_buffer:
return self.in_buffer.seek(offset, whence)

def tell(self):
if self.in_buffer:
return self.in_buffer.tell()


# loading keyfiles

import base64
import hashlib
from lxml import etree

def load_keyfile(filename):
try:
return load_xml_keyfile(filename)
except:
pass
try:
return load_plain_keyfile(filename)
except:
pass

def load_xml_keyfile(filename):
"""
// Sample XML file:
// <?xml version="1.0" encoding="utf-8"?>
// <KeyFile>
// <Meta>
// <Version>1.00</Version>
// </Meta>
// <Key>
// <Data>ySFoKuCcJblw8ie6RkMBdVCnAf4EedSch7ItujK6bmI=</Data>
// </Key>
// </KeyFile>
"""
with open(filename, 'r') as f:
# ignore meta, currently there is only version "1.00"
tree = etree.parse(f).getroot()
# read text from key, data and convert from base64
return base64.b64decode(tree.find('Key/Data').text)
raise IOError('Could not parse XML keyfile.')

def load_plain_keyfile(filename):
"""
A "plain" keyfile is a file containing only the key.
Any other file (JPEG, MP3, ...) can also be used as keyfile.
"""
with open(filename, 'rb') as f:
key = f.read()
# if the length is 32 bytes we assume it is the key
if len(key) == 32:
return key
# if the length is 64 bytes we assume the key is hex encoded
if len(key) == 64:
return key.decode('hex')
# anything else may be a file to hash for the key
return sha256(key)
raise IOError('Could not read keyfile.')

import struct

def stream_unpack(stream, offset, length, typecode='I'):
if offset is not None:
stream.seek(offset)
data = stream.read(length)
return struct.unpack('<'+typecode, data)[0]

def read_signature(stream):
sig1 = stream_unpack(stream, 0, 4)
sig2 = stream_unpack(stream, None, 4)
#ver_minor = stream_unpack(stream, None, 2, 'h')
#ver_major = stream_unpack(stream, None, 2, 'h')
#return (sig1, sig2, ver_major, ver_minor)
return (sig1, sig2)



45 changes: 45 additions & 0 deletions Windows/lazagne/softwares/memory/libkeepass/crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
import hashlib
import struct
from Crypto.Cipher import AES
from pureSalsa20 import Salsa20

AES_BLOCK_SIZE = 16

def sha256(s):
"""Return SHA256 digest of the string `s`."""
return hashlib.sha256(s).digest()

def transform_key(key, seed, rounds):
"""Transform `key` with `seed` `rounds` times using AES ECB."""
# create transform cipher with transform seed
cipher = AES.new(seed, AES.MODE_ECB)
# transform composite key rounds times
for n in range(0, rounds):
key = cipher.encrypt(key)
# return hash of transformed key
return sha256(key)

def aes_cbc_decrypt(data, key, enc_iv):
"""Decrypt and return `data` with AES CBC."""
cipher = AES.new(key, AES.MODE_CBC, enc_iv)
return cipher.decrypt(data)

def aes_cbc_encrypt(data, key, enc_iv):
cipher = AES.new(key, AES.MODE_CBC, enc_iv)
return cipher.encrypt(data)

def unpad(data):
extra = ord(data[-1])
return data[:len(data)-extra]

def pad(s):
n = AES_BLOCK_SIZE - len(s) % AES_BLOCK_SIZE
return s + n * struct.pack('b', n)

def xor(aa, bb):
"""Return a bytearray of a bytewise XOR of `aa` and `bb`."""
result = bytearray()
for a, b in zip(bytearray(aa), bytearray(bb)):
result.append(a ^ b)
return result
Loading

0 comments on commit daf0f25

Please sign in to comment.