From 5c0c38ab9928f996cf93a26bbb0e4a4ed0c493b7 Mon Sep 17 00:00:00 2001 From: Ulf Frisk Date: Sat, 12 Oct 2024 21:18:19 +0200 Subject: [PATCH] Plugin Update --- README.md | 17 - files/plugins/pym_pypykatz/__init__.py | 9 - files/plugins/pym_pypykatz/pym_pypykatz.py | 296 ------------------ files/plugins/pym_pypykatz/pypyreader.py | 283 ----------------- files/plugins/pym_pypykatz/sysinfo_helpers.py | 69 ---- files/plugins/pym_pypykatz/version.txt | 1 - 6 files changed, 675 deletions(-) delete mode 100644 files/plugins/pym_pypykatz/__init__.py delete mode 100644 files/plugins/pym_pypykatz/pym_pypykatz.py delete mode 100644 files/plugins/pym_pypykatz/pypyreader.py delete mode 100644 files/plugins/pym_pypykatz/sysinfo_helpers.py delete mode 100644 files/plugins/pym_pypykatz/version.txt diff --git a/README.md b/README.md index 234fe24..a3159dd 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,6 @@ This repository contains various non-core plugins for [MemProcFS - The Memory Pr Plugins range from non-core plugins to plugins that have offensive capabilities - such as _pypykatz_. Please find a short description for each plugin below: -## pypykatz - -#### Author: -Tamas Jos ([@skelsec](https://twitter.com/SkelSec)) , info@skelsec.com , https://github.com/skelsec/ - -#### Overview: -_pypykatz_ for MemProcFS exposes mimikatz functionality in the folder `/py/secrets/` in the file system root provided that the target is a supported Windows system. Functionality includes retrieval of hashes, passwords, kerberos tickets and various other credentials. -

- -#### Installation instructions: -1) Ensure MemProcFS supported version of 64-bit Python for Windows is on the system path (or specify in `-pythonpath` option when starting MemProcFS). NB! embedded Python will not work with _pypykatz_ since it requires access to Python pip installed packages. -2) Install _pypykatz_ pip package, in correct python environment, by running `pip install dissect.cstruct pypykatz`. -3) Copy the _pypykatz_ for _MemProcFS_ plugin by copying all files from [`/files/plugins/pym_pypykatz`](https://github.com/ufrisk/MemProcFS-plugins/tree/master/files/plugins/pym_pypykatz) to corresponding folder in MemProcFS - overwriting any existing files there. -4) Start MemProcFS. - -#### Last updated: 2021-03-21 - ## pypykatz regsecrets #### Author: diff --git a/files/plugins/pym_pypykatz/__init__.py b/files/plugins/pym_pypykatz/__init__.py deleted file mode 100644 index 47e4c46..0000000 --- a/files/plugins/pym_pypykatz/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from plugins.pym_pypykatz.pym_pypykatz import ( - Initialize, - Notify, -) - -__all__ = [ - "Initialize", - "Notify", -] \ No newline at end of file diff --git a/files/plugins/pym_pypykatz/pym_pypykatz.py b/files/plugins/pym_pypykatz/pym_pypykatz.py deleted file mode 100644 index df82296..0000000 --- a/files/plugins/pym_pypykatz/pym_pypykatz.py +++ /dev/null @@ -1,296 +0,0 @@ -# pym_pypykatz.py -# -# Pypykatz plugin for MemProcFss -# -# https://github.com/skelsec/ -# -# (c) Tamas Jos, 2019 -# Author: Tamas Jos (@skelsec), info@skelsec.com -# - -import memprocfs -from vmmpyplugin import * -import json -import traceback -import datetime - -# globals needed for FS -all_secrets = '' #all secrets extracted from lsass in json format -luids = {} #secrets per-luid (logon session) in txt format -domains = {} -kerberos = {} - -first_run = True - -import_failed = None -parsing_failed = None - -import_error_text_template = """ -The imports for pypykatz plugin have failed at some point. -Common causes: - 1. You dont have pypykatz installed - 2. Python runtime environment used by MemProcFs is not the same as you have installed pypykatz in. - 3. You are not using the correct python version - -Error traceback: -%s -""" - -parsing_error_template = """ -pypykatz plugin tried to parse the lsass.exe process in your memory dump but failed. -This could be caused by multiple things: - 1. The pypykatz's parser code is potato - 2. MemProcFs could not fully parse the memory, usually this happens with incorrect memory dump files. - Check for error strings like "Could not load segment data" - -In case you are cretain the problem is caused by the parser, -please submit an issue with the info below this line: -%s - -%s -""" - -import_error_text = None -parsing_error_text = None - -try: - from pypykatz.pypykatz import pypykatz - from pypykatz.commons.common import UniversalEncoder - from plugins.pym_pypykatz.pypyreader import MemProcFsReader - - #this needs to be the last line! - import_failed = False - - -except Exception as e: - import_failed = True - if VmmPyPlugin_fPrintV: - traceback.print_exc() - import_error_text = import_error_text_template % traceback.format_exc() - pass - -class KerberosInfo: - def __init__(self, type, domain, user, data): - self.type = type.name - self.domain = domain - self.user = user - self.data = data - - def get_filename_base(self): - return '%s_%s_%s' % (self.type, self.domain, self.user) - -def process_lsass(): - """ - Processing lsass.exe with pypykatz, storing the results in the globals - - """ - global all_secrets - global luids - global domains - global kerberos - global parsing_error_text - global parsing_failed - - basic_info = '' - try: - memreader = MemProcFsReader() - - basic_info = '===== BASIC INFO. SUBMIT THIS IF THERE IS AN ISSUE =====\r\n' - basic_info += 'CPU arch: %s\r\n' % memreader.sysinfo.architecture.name - basic_info += 'OS: %s\r\n' % memreader.sysinfo.operating_system - basic_info += 'BuildNumber: %s\r\n' % memreader.sysinfo.buildnumber - basic_info += 'MajorVersion: %s\r\n' % memreader.sysinfo.major_version - basic_info += 'MSV timestamp: %s\r\n' % memreader.sysinfo.msv_dll_timestamp - - - mimi = pypykatz(memreader, memreader.sysinfo) - mimi.start() - - all_secrets = json.dumps(mimi, cls = UniversalEncoder, indent=4, sort_keys=True) - for luid in mimi.logon_sessions: - luids[str(luid)] = str(mimi.logon_sessions[luid]) - for kc in mimi.logon_sessions[luid].kerberos_creds: - for ticket in kc.tickets: - if str(luid) not in kerberos: - kerberos[str(luid)] = [] - - ki = KerberosInfo(ticket.type, ticket.DomainName, '.'.join(ticket.EClientName), ticket.to_asn1().dump()) - kerberos[str(luid)].append(ki) - domain = mimi.logon_sessions[luid].domainname - user = mimi.logon_sessions[luid].username - - if domain == '': - domain = 'local' - - if user == '': - user = 'empty' - - if domain not in domains: - domains[domain] = {} - if user not in domains[domain]: - domains[domain][user] = {} - - domains[domain][user][str(luid)] = str(mimi.logon_sessions[luid]) - - parsing_failed = False - - except Exception as e: - parsing_failed = True - if VmmPyPlugin_fPrintV: - traceback.print_exc() - parsing_error_text = parsing_error_template % (basic_info, traceback.format_exc()) - pass - - -def ReadAllResults(pid, file_path, file_name, file_attr, bytes_length, bytes_offset): - """ - reads the all_results data as file on the virtual FS - """ - - return all_secrets[bytes_offset:bytes_offset+bytes_length].encode() - -def ReadLuid(pid, file_path, file_name, file_attr, bytes_length, bytes_offset): - """ - reads the secrets for a specific luid data as file on the virtual FS - """ - try: - - luid = file_name.rsplit('.', 1)[0] - if luid.find('_') != -1: - luid = luid.split('_')[1] - return luids[luid].encode()[bytes_offset:bytes_offset+bytes_length] - - except Exception as e: - if VmmPyPlugin_fPrintV: - traceback.print_exc() - return None - -def ReadKerberos(pid, file_path, file_name, file_attr, bytes_length, bytes_offset): - try: - - t = file_name.rsplit('.', 1)[0] - t, luid, pos = t.rsplit('_', 2) - data = kerberos[luid][int(pos)].data - - return data[bytes_offset:bytes_offset+bytes_length] - - except Exception as e: - if VmmPyPlugin_fPrintV: - traceback.print_exc() - return None - -def ReadErrors(pid, file_path, file_name, file_attr, bytes_length, bytes_offset): - try: - - if file_name == 'import_error.txt': - return import_error_text.encode()[bytes_offset:bytes_offset+bytes_length] - if file_name == 'parsing_error.txt': - return parsing_error_text.encode()[bytes_offset:bytes_offset+bytes_length] - - except Exception as e: - if VmmPyPlugin_fPrintV: - traceback.print_exc() - return None - -def List(pid, path): - # - # List function - this module employs a dynamic list function - which makes - # it responsible for providing directory listings of its contents in a - # highly optimized way. It is very important that the List function is as - # speedy as possible - to avoid locking up the file system. - # - # First check the directory to be listed. Only the module root directory is - # allowed. If it's not the module root directory return None. - global first_run - try: - - if path[:7] != 'secrets': - return None - - if first_run == True: - process_lsass() - first_run = False - - if import_failed == True: - print(import_failed) - result = { - 'import_error.txt': {'size': len(import_error_text), 'read': ReadErrors, 'write': None}, - } - return result - - if parsing_failed == True: - result = { - 'parsing_error.txt': {'size': len(parsing_error_text), 'read': ReadErrors, 'write': None}, - } - return result - - - if path == 'secrets': - result = { - 'all_results.json': {'size': len(all_secrets), 'read': ReadAllResults, 'write': None}, - } - result['by_luid'] = {'dirs' : True} - result['by_domain'] = {'dirs' : True} - result['kerberos'] = {'dirs' : True} - return result - - if path == 'secrets/by_luid': - result = {} - for luid in luids: - result['%s.txt' % luid] = {'size': len(luids[luid]), 'read': ReadLuid, 'write': None} - return result - - if path == 'secrets/by_domain': - result = {} - for domain in domains: - result[domain] = {'dirs' : True} - return result - - if path.find('secrets/by_domain/') == 0: - result = {} - domain = path.rsplit('/',1)[1] - - if domain not in domains and domain.lower() == 'system32': - #this is a special case for an unknown behaviour coming from windows itself. - #for some reason it requests /System32 and /System32/System32 for reasons beyond science... - return None - - for user in domains[domain]: - for luid in domains[domain][user]: - result['%s_%s.txt' % (user, luid)] = {'size': len(luids[luid]), 'read': ReadLuid, 'write': None} - return result - - if path == 'secrets/kerberos': - result = {} - for luid in kerberos: - result[luid] = {'dirs' : True} - return result - - if path.find('secrets/kerberos/') == 0: - - luid = path.rsplit('/',1)[1] - result = {} - for i, ki in enumerate(kerberos[luid]): - result['%s_%s_%s.kirbi' % (ki.get_filename_base(), luid, i)] = {'size': len(ki.data), 'read': ReadKerberos, 'write': None} - - return result - - except Exception as e: - if VmmPyPlugin_fPrintV: - traceback.print_exc() - return None - - -def Notify(fEvent, bytesData): - if fEvent == memprocfs.PLUGIN_EVENT_TOTALREFRESH and not import_failed and not parsing_failed: - global first_run - first_run = True - - -def Initialize(target_system, target_memorymodel): - # Check that the operating system is 32-bit or 64-bit Windows. If it's not - # then raise an exception to terminate loading of this module. - if target_system != memprocfs.SYSTEM_WINDOWS_X64 and target_system != memprocfs.SYSTEM_WINDOWS_X86: - raise RuntimeError("Only Windows is supported by the pym_pypykatz module.") - VmmPyPlugin_FileRegisterDirectory(None, 'secrets', List) - \ No newline at end of file diff --git a/files/plugins/pym_pypykatz/pypyreader.py b/files/plugins/pym_pypykatz/pypyreader.py deleted file mode 100644 index a441397..0000000 --- a/files/plugins/pym_pypykatz/pypyreader.py +++ /dev/null @@ -1,283 +0,0 @@ -# pypyreader.py -# -# Pypykatz reader plugin for MemProcFss -# -# https://github.com/skelsec/ -# -# (c) Tamas Jos, 2019 -# Author: Tamas Jos (@skelsec), info@skelsec.com -# - -from pypykatz.commons.common import KatzSystemArchitecture, KatzSystemInfo -from .sysinfo_helpers import * - -import memprocfs -from vmmpyplugin import * -import copy - -class Module: - def __init__(self): - self.name = None - self.baseaddress = None - self.size = None - self.endaddress = None - self.pages = [] - - self.versioninfo = None - self.checksum = None - self.timestamp = None - - def inrange(self, addr): - return self.baseaddress <= addr < self.endaddress - - def parse(data, timestamp = None): - m = Module() - m.name = data.name - m.baseaddress = data.base - m.size = data.image_size - m.endaddress = m.baseaddress + m.size - - m.timestamp = timestamp - - return m - - def __str__(self): - return '%s %s %s %s %s' % (self.name, hex(self.baseaddress), hex(self.size), hex(self.endaddress), self.timestamp ) - -class Page: - def __init__(self): - self.BaseAddress = None - self.AllocationBase = None - self.AllocationProtect = None - self.RegionSize = None - self.EndAddress = None - self.data = None - - @staticmethod - def parse(page_info, module): - p = Page() - p.BaseAddress = page_info['VirtualAddress'] + module.baseaddress - p.AllocationBase = None #page_info['VirtualAddress'] ???? TODO - p.AllocationProtect = None #page_info['VirtualAddress'] ???? TODO - p.RegionSize = min(page_info['SizeOfRawData'], 100*1024*1024) # TODO: need this currently to stop infinite search - p.EndAddress = p.BaseAddress + p.RegionSize - return p - - @staticmethod - def parse_raw(page_info): - p = Page() - p.BaseAddress = page_info['va'] - p.AllocationBase = None #page_info['VirtualAddress'] ???? TODO - p.AllocationProtect = None #page_info['VirtualAddress'] ???? TODO - p.RegionSize = min(page_info['size'], 100*1024*1024) # TODO: need this currently to stop infinite search - p.EndAddress = p.BaseAddress + p.RegionSize - return p - - def read_data(self, pid): - self.data = vmm.process(pid).memory.read(self.BaseAddress, self.RegionSize) - - def inrange(self, addr): - return self.BaseAddress <= addr < self.EndAddress - - def search(self, pattern, pid): - if len(pattern) > self.RegionSize: - return [] - data = vmm.process(pid).memory.read(self.BaseAddress, self.RegionSize) - fl = [] - offset = 0 - while len(data) > len(pattern): - marker = data.find(pattern) - if marker == -1: - return fl - fl.append(marker + offset + self.BaseAddress) - data = data[marker+1:] - offset = marker + 1 - - return fl - - def __str__(self): - return '0x%08x 0x%08x %s 0x%08x' % (self.BaseAddress, self.AllocationBase, self.AllocationProtect, self.RegionSize) - - -class MemProcFsReader: - def __init__(self, process_name = 'lsass.exe', filename = None): - self.filename = filename - self.process_name = process_name - self.sysinfo = None - self.process = None - self.current_position = None - self.modules = [] - - - self.setup() - - def get_sysinfo(self): - self.sysinfo = KatzSystemInfo() - - #print('[+] Getting BuildNumer') - self.sysinfo.buildnumber = vmm.get_config(memprocfs.OPT_WIN_VERSION_BUILD) - #print('[+] Found BuildNumber %s' % self.sysinfo.buildnumber) - - #print('[+] Getting msv_dll_timestamp') - self.sysinfo.msv_dll_timestamp = int(PEGetFileTime(self.process, self.process_name)) - #print('[+] Found msv_dll_timestamp %s' % self.sysinfo.msv_dll_timestamp) - - #print('[+] Getting arch') - val = vmm.get_config(memprocfs.OPT_CORE_SYSTEM) - if val == memprocfs.SYSTEM_WINDOWS_X64: - self.sysinfo.architecture = KatzSystemArchitecture.X64 - else: - self.sysinfo.architecture = KatzSystemArchitecture.X86 - - #print('[+] Got arch %s' % self.sysinfo.architecture) - - - def setup(self): - - if self.filename: - # if filename is specified we dont want to use the virtual FS, but then we need to init the vmmpy module - VmmPy_Initialize(["-device", self.filename,'-vv']) - - #print('[+] Searching LSASS') - self.process = vmm.process(self.process_name) - #print('[+] Found LSASS on PID %s' % self.process.pid) - - self.get_sysinfo() - - #print('[+] Getting modules info') - for module in self.process.module_list(): - #print('moduleinfo: %s' % str(moduleinfo)) - m = Module.parse(module) - try: - for pageinfo in module.maps.sections(): - #print('pageinfo: %s' % str(pageinfo)) - m.pages.append(Page.parse(pageinfo, m)) - - self.modules.append(m) - except: - #module is paged out, hoping that it's not a module that is needed - pass - - #print('[+] Got modules info') - - - def get_module_by_name(self, module_name): - for mod in self.modules: - if mod.name.lower().find(module_name.lower()) != -1: - return mod - return None - - def find_in_module(self, module_name, pattern): - mod = self.get_module_by_name(module_name) - if mod is None: - raise Exception('Could not find module! %s' % module_name) - t = [] - for page in mod.pages: - t += self.find(page.BaseAddress, page.EndAddress, pattern) - return t - - @staticmethod - def find_all_pattern(data, pattern): - substring_length = len(pattern) - def recurse(locations_found, start): - location = data.find(pattern, start) - if location != -1: - return recurse(locations_found + [location], location+substring_length) - else: - return locations_found - - return recurse([], 0) - - def find(self, start, end, pattern): - """ - Searches for all occurrences of a pattern in the current memory segment, returns all occurrences as a list - """ - data = self.process.memory.read(start, end - start) - pos = [] - for p in MemProcFsReader.find_all_pattern(data, pattern): - pos.append( p + start) - return pos - - def seek(self, offset, whence = 0): - """ - Changes the current address to an offset of offset. - """ - self.current_position += offset - return - - def move(self, address): - """ - Moves the buffer to a virtual address specified by address - """ - self.current_position = address - return - - def align(self, alignment = None): - """ - Repositions the current reader to match architecture alignment - """ - if alignment is None: - if self.sysinfo.architecture == KatzSystemArchitecture.X64: - alignment = 8 - else: - alignment = 4 - offset = self.current_position % alignment - if offset == 0: - return - offset_to_aligned = (alignment - offset) % alignment - self.seek(offset_to_aligned, 1) - return - - def tell(self): - """ - Returns the current virtual address - """ - return self.current_position - - def peek(self, length): - t = self.current_position - data = self.read(length) - self.current_position = t - - return data - - def read(self, size = -1): - data = self.process.memory.read(self.current_position, size) - self.current_position += size - return data - - def read_int(self): - """ - Reads an integer. The size depends on the architecture. - Reads a 4 byte small-endian singed int on 32 bit arch - Reads an 8 byte small-endian singed int on 64 bit arch - """ - if self.sysinfo.architecture == KatzSystemArchitecture.X64: - return int.from_bytes(self.read(8), byteorder = 'little', signed = True) - else: - return int.from_bytes(self.read(4), byteorder = 'little', signed = True) - - def read_uint(self): - """ - Reads an integer. The size depends on the architecture. - Reads a 4 byte small-endian unsinged int on 32 bit arch - Reads an 8 byte small-endian unsinged int on 64 bit arch - """ - if self.sysinfo.architecture == KatzSystemArchitecture.X64: - return int.from_bytes(self.read(8), byteorder = 'little', signed = False) - else: - return int.from_bytes(self.read(4), byteorder = 'little', signed = False) - - def get_ptr(self, pos): - self.move(pos) - return self.read_uint() - - def get_ptr_with_offset(self, pos): - if self.sysinfo.architecture == KatzSystemArchitecture.X64: - self.move(pos) - ptr = int.from_bytes(self.read(4), byteorder = 'little', signed = True) - return pos + 4 + ptr - else: - self.move(pos) - return self.read_uint() - diff --git a/files/plugins/pym_pypykatz/sysinfo_helpers.py b/files/plugins/pym_pypykatz/sysinfo_helpers.py deleted file mode 100644 index 425f76a..0000000 --- a/files/plugins/pym_pypykatz/sysinfo_helpers.py +++ /dev/null @@ -1,69 +0,0 @@ -# sysinfo_helpers.py -# -# Helper functions to retrieve the file time and version information from a file. -# NB! there are cleaner and better ways to do this, but this works ... -# -# https://github.com/ufrisk/ -# -# (c) Ulf Frisk, 2019-2021 -# Author: Ulf Frisk, pcileech@frizk.net -# - -from io import BytesIO -from dissect import cstruct -import memprocfs - -PE_STRUCT_DEFINITIONS = """ - #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 - #define IMAGE_SIZEOF_SHORT_NAME 8 - typedef struct _IMAGE_DOS_HEADER - { - WORD e_magic; - WORD e_cblp; - WORD e_cp; - WORD e_crlc; - WORD e_cparhdr; - WORD e_minalloc; - WORD e_maxalloc; - WORD e_ss; - WORD e_sp; - WORD e_csum; - WORD e_ip; - WORD e_cs; - WORD e_lfarlc; - WORD e_ovno; - WORD e_res[4]; - WORD e_oemid; - WORD e_oeminfo; - WORD e_res2[10]; - LONG e_lfanew; - } IMAGE_DOS_HEADER; - typedef struct _IMAGE_FILE_HEADER { - WORD Machine; - WORD NumberOfSections; - DWORD TimeDateStamp; - DWORD PointerToSymbolTable; - DWORD NumberOfSymbols; - WORD SizeOfOptionalHeader; - WORD Characteristics; - } IMAGE_FILE_HEADER; -""" - -def PEGetFileTime(process, module): - mz_va = process.module(module).base - mz_bytes = process.memory.read(mz_va, 0x1000) - mz_stream = BytesIO(mz_bytes) - # Set up dissect.cstruct - pestruct = cstruct.cstruct() - pestruct.load(PE_STRUCT_DEFINITIONS) - # Parse MZ header - struct_mz = pestruct.IMAGE_DOS_HEADER(mz_stream) - if struct_mz.e_magic != 0x5a4d: - return 0 - mz_stream.seek(struct_mz.e_lfanew) - signature = pestruct.uint32(mz_stream) - if signature != 0x4550: - return 0 - # Parse the PE file_header struct. - struct_file_header = pestruct.IMAGE_FILE_HEADER(mz_stream) - return struct_file_header.TimeDateStamp diff --git a/files/plugins/pym_pypykatz/version.txt b/files/plugins/pym_pypykatz/version.txt deleted file mode 100644 index f0bb29e..0000000 --- a/files/plugins/pym_pypykatz/version.txt +++ /dev/null @@ -1 +0,0 @@ -1.3.0