forked from p34709852/monkey
194 lines
6.9 KiB
Python
194 lines
6.9 KiB
Python
import os
|
|
import logging
|
|
import traceback
|
|
|
|
import sys
|
|
sys.coinit_flags = 0 # needed for proper destruction of the wmi python module
|
|
import wmi
|
|
import win32com
|
|
import _winreg
|
|
|
|
from mimikatz_collector import MimikatzCollector
|
|
from . import InfoCollector
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
__author__ = 'uri'
|
|
|
|
WMI_CLASSES = set(["Win32_OperatingSystem",
|
|
"Win32_ComputerSystem",
|
|
"Win32_LoggedOnUser",
|
|
"Win32_UserAccount",
|
|
"Win32_UserProfile",
|
|
"Win32_Group",
|
|
"Win32_GroupUser",
|
|
"Win32_Product",
|
|
"Win32_Service",
|
|
"Win32_OptionalFeature",
|
|
#"Win32_Process",
|
|
])
|
|
|
|
# These wmi queries are able to return data about all the users & machines in the domain.
|
|
# For these queries to work, the monkey shohuld be run on a domain machine and
|
|
#
|
|
# monkey should run as *** SYSTEM *** !!!
|
|
#
|
|
WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName",
|
|
"DS_sAMAccountType", "ADSIPath", "DS_userAccountControl",
|
|
"DS_objectSid", "DS_objectClass", "DS_memberOf",
|
|
"DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime",
|
|
"DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp",
|
|
"DS_lastLogoff", "DS_logonCount", "DS_accountExpires"),
|
|
|
|
"ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName",
|
|
"DS_sAMAccountType", "DS_objectSid", "DS_objectClass",
|
|
"DS_name", "DS_memberOf", "DS_member", "DS_instanceType",
|
|
"DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"),
|
|
|
|
"ds_copmuter": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires",
|
|
"DS_adminDisplayName", "DS_badPasswordTime",
|
|
"DS_badPwdCount", "DS_cn", "DS_distinguishedName",
|
|
"DS_instanceType", "DS_lastLogoff", "DS_lastLogon",
|
|
"DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass",
|
|
"DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion",
|
|
"DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName",
|
|
"DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl",
|
|
"DS_whenChanged", "DS_whenCreated"),
|
|
}
|
|
|
|
|
|
def fix_obj_for_mongo(o):
|
|
if type(o) == dict:
|
|
return dict([(k, fix_obj_for_mongo(v)) for k, v in o.iteritems()])
|
|
|
|
elif type(o) in (list, tuple):
|
|
return [fix_obj_for_mongo(i) for i in o]
|
|
|
|
elif type(o) in (int, float, bool):
|
|
return o
|
|
|
|
elif type(o) in (str, unicode):
|
|
# mongo dosn't like unprintable chars, so we use repr :/
|
|
return repr(o)
|
|
|
|
elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object:
|
|
return fix_wmi_obj_for_mongo(o)
|
|
|
|
elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch:
|
|
try:
|
|
# objectSid property of ds_user is problematic and need thie special treatment.
|
|
# ISWbemObjectEx interface. Class Uint8Array ?
|
|
if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}":
|
|
return o.Value
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
return o.GetObjectText_()
|
|
except:
|
|
pass
|
|
|
|
return repr(o)
|
|
|
|
else:
|
|
return repr(o)
|
|
|
|
def fix_wmi_obj_for_mongo(o):
|
|
row = {}
|
|
|
|
for prop in o.properties:
|
|
try:
|
|
value = getattr(o, prop)
|
|
except wmi.x_wmi:
|
|
continue
|
|
|
|
row[prop] = fix_obj_for_mongo(value)
|
|
|
|
for method_name in o.methods:
|
|
if not method_name.startswith("GetOwner"):
|
|
continue
|
|
|
|
method = getattr(o, method_name)
|
|
|
|
try:
|
|
value = method()
|
|
value = fix_obj_for_mongo(value)
|
|
row[method_name[3:]] = value
|
|
|
|
except wmi.x_wmi:
|
|
#LOG.error("Error running wmi method '%s'" % (method_name, ))
|
|
#LOG.error(traceback.format_exc())
|
|
continue
|
|
|
|
return row
|
|
|
|
class WindowsInfoCollector(InfoCollector):
|
|
"""
|
|
System information collecting module for Windows operating systems
|
|
"""
|
|
|
|
def __init__(self):
|
|
super(WindowsInfoCollector, self).__init__()
|
|
|
|
def get_info(self):
|
|
"""
|
|
Collect Windows system information
|
|
Hostname, process list and network subnets
|
|
Tries to read credential secrets using mimikatz
|
|
:return: Dict of system information
|
|
"""
|
|
LOG.debug("Running Windows collector")
|
|
self.get_hostname()
|
|
self.get_process_list()
|
|
self.get_network_info()
|
|
self.get_azure_info()
|
|
|
|
self.get_wmi_info()
|
|
self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa")
|
|
self.get_installed_packages()
|
|
|
|
mimikatz_collector = MimikatzCollector()
|
|
mimikatz_info = mimikatz_collector.get_logon_info()
|
|
self.info["credentials"].update(mimikatz_info)
|
|
self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
|
|
|
|
return self.info
|
|
|
|
def get_installed_packages(self):
|
|
self.info["installed_packages"] = os.popen("dism /online /get-packages").read()
|
|
self.info["installed_features"] = os.popen("dism /online /get-features").read()
|
|
|
|
def get_wmi_info(self):
|
|
for wmi_class_name in WMI_CLASSES:
|
|
self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name)
|
|
|
|
for wmi_class_name, props in WMI_LDAP_CLASSES.iteritems():
|
|
self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name, "//./root/directory/ldap", props)
|
|
|
|
def get_wmi_class(self, class_name, moniker="//./root/civ2", properties=None):
|
|
_wmi = wmi.WMI(moniker=moniker)
|
|
|
|
try:
|
|
if not properties:
|
|
wmi_class = getattr(_wmi, class_name)()
|
|
else:
|
|
wmi_class = getattr(_wmi, class_name)(properties)
|
|
|
|
except wmi.x_wmi:
|
|
#LOG.error("Error getting wmi class '%s'" % (class_name, ))
|
|
#LOG.error(traceback.format_exc())
|
|
return
|
|
|
|
return fix_obj_for_mongo(wmi_class)
|
|
|
|
def get_reg_key(self, subkey_path, store=_winreg.HKEY_LOCAL_MACHINE):
|
|
key = _winreg.ConnectRegistry(None, store)
|
|
subkey = _winreg.OpenKey(key, subkey_path)
|
|
|
|
d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])])
|
|
d = fix_obj_for_mongo(d)
|
|
|
|
self.info[subkey_path] = d
|
|
|
|
subkey.Close()
|
|
key.Close() |