Skip to content

Commit

Permalink
99% done with RCR, not yet been tested.
Browse files Browse the repository at this point in the history
  • Loading branch information
MaorCore committed Oct 25, 2018
1 parent d02b9c2 commit 17b344f
Show file tree
Hide file tree
Showing 18 changed files with 371 additions and 426 deletions.
Empty file added monkey/common/utils/__init__.py
Empty file.
83 changes: 83 additions & 0 deletions monkey/common/utils/mongo_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import wmi
import win32com

__author__ = 'maor.rayzin'


class MongoUtils:

def __init__(self):
# Static class
pass

@staticmethod
def fix_obj_for_mongo(o):
if type(o) == dict:
return dict([(k, MongoUtils.fix_obj_for_mongo(v)) for k, v in o.iteritems()])

elif type(o) in (list, tuple):
return [MongoUtils.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 MongoUtils.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)

@staticmethod
def fix_wmi_obj_for_mongo(o):
row = {}

for prop in o.properties:
try:
value = getattr(o, prop)
except wmi.x_wmi:
# This happens in Win32_GroupUser when the user is a domain user.
# For some reason, the wmi query for PartComponent fails. This table
# is actually contains references to Win32_UserAccount and Win32_Group.
# so instead of reading the content to the Win32_UserAccount, we store
# only the id of the row in that table, and get all the other information
# from that table while analyzing the data.
value = o.properties[prop].value

row[prop] = MongoUtils.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 = MongoUtils.fix_obj_for_mongo(value)
row[method_name[3:]] = value

except wmi.x_wmi:
continue

return row

25 changes: 25 additions & 0 deletions monkey/common/utils/reg_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import _winreg

from common.utils.mongo_utils import MongoUtils

__author__ = 'maor.rayzin'


class RegUtils:

def __init__(self):
# Static class
pass

@staticmethod
def get_reg_key(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 = MongoUtils.fix_obj_for_mongo(d)

subkey.Close()
key.Close()

return d
27 changes: 27 additions & 0 deletions monkey/common/utils/wmi_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import wmi

from mongo_utils import MongoUtils

__author__ = 'maor.rayzin'


class WMIUtils:

def __init__(self):
# Static class
pass

@staticmethod
def get_wmi_class(class_name, moniker="//./root/cimv2", 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:
return

return MongoUtils.fix_obj_for_mongo(wmi_class)
1 change: 1 addition & 0 deletions monkey/infection_monkey/system_info/mimikatz_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def get_logon_info(self):
Gets the logon info from mimikatz.
Returns a dictionary of users with their known credentials.
"""
LOG.info('Getting mimikatz logon information')
if not self._isInit:
return {}
LOG.debug("Running mimikatz collector")
Expand Down
145 changes: 9 additions & 136 deletions monkey/infection_monkey/system_info/windows_info_collector.py
Original file line number Diff line number Diff line change
@@ -1,125 +1,20 @@
import os
import logging

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
import infection_monkey.config
from infection_monkey.system_info.mimikatz_collector import MimikatzCollector
from infection_monkey.system_info import InfoCollector
from infection_monkey.system_info.wmi_consts import WMI_CLASSES
from common.utils.wmi_utils import WMIUtils

LOG = logging.getLogger(__name__)
LOG.info('started windows info collector')

__author__ = 'uri'

WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount",
"Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service",
"Win32_OptionalFeature"}

# 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_computer": ("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:
# This happens in Win32_GroupUser when the user is a domain user.
# For some reason, the wmi query for PartComponent fails. This table
# is actually contains references to Win32_UserAccount and Win32_Group.
# so instead of reading the content to the Win32_UserAccount, we store
# only the id of the row in that table, and get all the other information
# from that table while analyzing the data.
value = o.properties[prop].value

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:
continue

return row


class WindowsInfoCollector(InfoCollector):
"""
Expand Down Expand Up @@ -147,48 +42,26 @@ def get_info(self):

self.get_wmi_info()
LOG.debug('finished get_wmi_info')
#self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa")
self.get_installed_packages()
LOG.debug('Got installed packages')

mimikatz_collector = MimikatzCollector()
mimikatz_info = mimikatz_collector.get_logon_info()
if mimikatz_info:
if "credentials" in self.info:
self.info["credentials"].update(mimikatz_info)
self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
else:
LOG.info('No mimikatz info was gathered')

return self.info

def get_installed_packages(self):
LOG.info('getting installed packages')
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):
LOG.info('getting wmi info')
for wmi_class_name in WMI_CLASSES:
self.info['wmi'][wmi_class_name] = self.get_wmi_class(wmi_class_name)

def get_wmi_class(self, class_name, moniker="//./root/cimv2", 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:
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['reg'][subkey_path] = d

subkey.Close()
key.Close()
self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name)
32 changes: 32 additions & 0 deletions monkey/infection_monkey/system_info/wmi_consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount",
"Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service",
"Win32_OptionalFeature"}

# These wmi queries are able to return data about all the users & machines in the domain.
# For these queries to work, the monkey should 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_computer": ("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"),
}

2 changes: 1 addition & 1 deletion monkey/monkey_island/cc/island_logger_default_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

"info_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"level": "INFO",
"formatter": "simple",
"filename": "info.log",
"maxBytes": 10485760,
Expand Down
14 changes: 3 additions & 11 deletions monkey/monkey_island/cc/resources/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,19 +186,11 @@ def process_system_info_telemetry(telemetry_json):
Telemetry.add_system_info_creds_to_config(creds)
Telemetry.replace_user_dot_with_comma(creds)
if 'mimikatz' in telemetry_json['data']:
users_secrets = user_info.extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', ''))
users_secrets = user_info.MimikatzSecrets.\
extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', ''))
if 'wmi' in telemetry_json['data']:
wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
wmi_handler.add_groups_to_collection()
wmi_handler.add_users_to_collection()
wmi_handler.create_group_user_connection()
wmi_handler.insert_info_to_mongo()

wmi_handler.add_admin(wmi_handler.info_for_mongo[wmi_handler.ADMINISTRATORS_GROUP_KNOWN_SID], monkey_id)
wmi_handler.update_admins_retrospective()
wmi_handler.update_critical_services(telemetry_json['data']['wmi']['Win32_Service'],
telemetry_json['data']['wmi']['Win32_Product'],
monkey_id)
wmi_handler.process_and_handle_wmi_info()

@staticmethod
def add_ip_to_ssh_keys(ip, ssh_info):
Expand Down
Loading

0 comments on commit 17b344f

Please sign in to comment.