Plugin Update

This commit is contained in:
Ulf Frisk
2024-10-12 21:18:19 +02:00
parent 5fcc080317
commit 5c0c38ab99
6 changed files with 0 additions and 675 deletions

View File

@@ -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.
<p align="center"><img src="https://raw.githubusercontent.com/wiki/ufrisk/MemProcFS-plugins/resources/p_pypykatz_1.png" height="375"/></p>
#### 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:

View File

@@ -1,9 +0,0 @@
from plugins.pym_pypykatz.pym_pypykatz import (
Initialize,
Notify,
)
__all__ = [
"Initialize",
"Notify",
]

View File

@@ -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 <path>/System32 and <path>/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)

View File

@@ -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()

View File

@@ -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

View File

@@ -1 +0,0 @@
1.3.0