modules.processing.platform.windows

Package modules :: Package processing :: Package platform :: Module windows |
[hide private]
[frames] | no frames]

# Source Code for Module modules.processing.platform.windows

      1  # Copyright (C) 2010-2013 Claudio Guarnieri.
      2  # Copyright (C) 2014-2016 Cuckoo Foundation.
      3  # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
      4  # See the file 'docs/LICENSE' for copying permission.
      5
      6  import datetime
      7  import logging
      8  import re
      9  import shlex
     10
     11  from lib.cuckoo.common.abstracts import BehaviorHandler
     12  from lib.cuckoo.common.netlog import BsonParser
     13  from lib.cuckoo.common.utils import guid_name, jsbeautify, htmlprettify
     14
     15  log = logging.getLogger(__name__)
     16
     17 -class MonitorProcessLog(list):
     18      """Yields each API call event to the parent handler. Optionally it may
     19      beautify certain API calls."""
     20
     21 -    def __init__(self, eventstream, modules):
     22          self.eventstream = eventstream
     23          self.modules = modules
     24          self.first_seen = None
     25          self.has_apicalls = False
     26
     27 -    def init(self):
     28          self.services = {}
     29          self.vbe6_ptrs = {}
     30          self.vbe6_func = {}
     31
     32 -    def _api_COleScript_Compile(self, event):
     33          event["raw"] = "script",
     34          event["arguments"]["script"] = \
     35              jsbeautify(event["arguments"]["script"])
     36
     37 -    def _api_CWindow_AddTimeoutCode(self, event):
     38          event["raw"] = "code",
     39          event["arguments"]["code"] = jsbeautify(event["arguments"]["code"])
     40
     41 -    def _api_CElement_put_innerHTML(self, event):
     42          event["raw"] = "html",
     43          event["arguments"]["html"] = htmlprettify(event["arguments"]["html"])
     44
     45 -    def _api_CDocument_write(self, event):
     46          event["raw"] = "lines",
     47          for idx, line in enumerate(event["arguments"]["lines"]):
     48              event["arguments"]["lines"][idx] = htmlprettify(line)
     49
     50 -    def _api_CIFrameElement_CreateElement(self, event):
     51          """Lowercases the attribute keys."""
     52          attrs = {}
     53          for key, value in event["arguments"]["attributes"].items():
     54              attrs[key.lower()] = value
     55
     56          event["arguments"]["attributes"] = attrs
     57
     58 -    def _remember_service_name(self, event):
     59          """Keep track of the name of this service."""
     60          service_name = event["arguments"]["service_name"]
     61          # We've added logging of the service_handle to the API signature in
     62          # the Monitor, but for backwards compatibility we'll keep it as
     63          # follows for now.
     64          service_handle = "0x%08x" % event["return_value"]
     65          self.services[service_handle] = service_name
     66
     67      _api_OpenServiceA = _remember_service_name
     68      _api_OpenServiceW = _remember_service_name
     69      _api_CreateServiceA = _remember_service_name
     70      _api_CreateServiceW = _remember_service_name
     71
     72 -    def _add_service_name(self, event):
     73          service_name = self.services.get(event["arguments"]["service_handle"])
     74          event["arguments"]["service_name"] = service_name
     75
     76      _api_StartServiceA = _add_service_name
     77      _api_StartServiceW = _add_service_name
     78      _api_ControlService = _add_service_name
     79      _api_DeleteService = _add_service_name
     80
     81      # VBA Macro analysis stuff.
     82
     83 -    def _vbe6_newobject(self, event):
     84          """Keep track which instance pointers belong to which classes."""
     85          this = event["arguments"]["this"]
     86          object_name = event["arguments"]["object_name"]
     87
     88          self.vbe6_ptrs[this] = object_name
     89
     90      _api_vbe6_CreateObject = _vbe6_newobject
     91      _api_vbe6_GetObject = _vbe6_newobject
     92
     93 -    def _api_vbe6_StringConcat(self, event):
     94          pass
     95
     96 -    def _api_vbe6_Import(self, event):
     97          # TODO Move this logic to the monitor.
     98          args = event["arguments"]
     99          if args["library"] == "VBE6.DLL" and not args["function"]:
    100              return False
    101
    102 -    def _api_vbe6_GetIDFromName(self, event):
    103          """Keep track which function has which function index.
    104          This informational call is omitted from the actual logs."""
    105          this = event["arguments"]["this"]
    106          funcidx = event["arguments"]["funcidx"]
    107          funcname = event["arguments"]["funcname"]
    108
    109          class_ = self.vbe6_ptrs.get(this, this)
    110          self.vbe6_func[class_, funcidx] = funcname
    111          return False
    112
    113 -    def _api_vbe6_CallByName(self, event):
    114          """Only used by the monitor for administrative uses."""
    115          return False
    116
    117 -    def _api_vbe6_Invoke(self, event):
    118          this = event["arguments"]["this"]
    119          funcidx = event["arguments"]["funcidx"]
    120
    121          if this in self.vbe6_ptrs:
    122              event["flags"]["this"] = self.vbe6_ptrs[this]
    123
    124          class_ = self.vbe6_ptrs.get(this, this)
    125          if class_ and (class_, funcidx) in self.vbe6_func:
    126              event["arguments"]["funcname"] = self.vbe6_func[class_, funcidx]
    127
    128          del event["arguments"]["funcidx"]
    129
    130      # PDF document analysis.
    131
    132 -    def _api_pdf_eval(self, event):
    133          event["raw"] = "script",
    134          event["arguments"]["script"] = \
    135              jsbeautify(event["arguments"]["script"])
    136
    137 -    def _api_pdf_unescape(self, event):
    138          event["raw"] = "string", "unescaped"
    139
    140          # "%u1234" => "\x34\x12"
    141          # Strictly speaking this does not reflect what unescape() does, but
    142          # in the end it's usually just about the in-memory representation.
    143          event["arguments"]["unescaped"] = re.sub(
    144              "%u([0-9a-fA-F]{4})",
    145              lambda x: x.group(1).decode("hex").decode("latin-1")[::-1],
    146              event["arguments"]["string"]
    147          )
    148
    149          # "%41" => "A"
    150          event["arguments"]["unescaped"] = re.sub(
    151              "%([0-9a-fA-F]{2})",
    152              lambda x: x.group(1).decode("hex").decode("latin-1"),
    153              event["arguments"]["unescaped"]
    154          )
    155
    156 -    def _api___exception__(self, event):
    157          addr = int(event["arguments"]["exception"]["address"], 16)
    158          for module in self.modules:
    159              if "imgsize" not in module:
    160                  continue
    161
    162              baseaddr = int(module["baseaddr"], 16)
    163              if addr >= baseaddr and addr < baseaddr + module["imgsize"]:
    164                  event["arguments"]["exception"]["module"] = module["basename"]
    165                  event["arguments"]["exception"]["offset"] = addr - baseaddr
    166
    167 -    def _api_modifier(self, event):
    168          """Adds flags field to CLSID and IID instances."""
    169          clsid = guid_name(event["arguments"].get("clsid"))
    170          if clsid:
    171              event["flags"]["clsid"] = clsid
    172
    173          iid = event["arguments"].get("iid")
    174          if isinstance(iid, (tuple, list)):
    175              event["flags"]["iid"] = [guid_name(x) for x in iid]
    176          elif guid_name(iid):
    177              event["flags"]["iid"] = guid_name(iid)
    178
    179 -    def __iter__(self):
    180          self.init()
    181          for event in self.eventstream:
    182              if event["type"] == "process":
    183                  self.first_seen = event["first_seen"]
    184              elif event["type"] == "apicall":
    185                  event["time"] = self.first_seen + datetime.timedelta(0, 0, event["time"] * 1000)
    186
    187                  # Remove the event type for reporting output.
    188                  del event["type"]
    189
    190                  # Get rid of the pid (we're below the pid in the structure).
    191                  del event["pid"]
    192
    193                  # Get rid of the unique hash, this is only relevant
    194                  # for automation.
    195                  del event["uniqhash"]
    196
    197                  # If available, call a modifier function.
    198                  apiname = "_api_%s" % event["api"]
    199                  r = getattr(self, apiname, lambda _: None)(event)
    200
    201                  # Generic modifier for various functions.
    202                  self._api_modifier(event)
    203
    204                  # Prevent this event from being passed along by returning
    205                  # False in a _api_() method.
    206                  if r is not False:
    207                      yield event
    208
    209 -    def __nonzero__(self):
    210          """Required for the JSON reporting module as otherwise the on-demand
    211          generated list of API calls would be seen as empty.
    212
    213          Note that the result structure is kept between processing and
    214          reporting time which means that at reporting time, where this
    215          functionality is actually needed, the has_apicalls will already have
    216          been set while iterating through the BSON logs iterator in the parse()
    217          function of the WindowsMonitor class. We use this knowledge to pass
    218          along whether or not this log actually has API call events and thus
    219          whether it's "nonzero" or not. (The correctness of this field is
    220          required as otherwise the json.dump() function will fail - probably
    221          due to buffering issues).
    222          """
    223          return self.has_apicalls
    224
    225 -class WindowsMonitor(BehaviorHandler):
    226      """Parses monitor generated logs."""
    227      key = "processes"
    228
    229 -    def __init__(self, *args, **kwargs):
    230          super(WindowsMonitor, self).__init__(*args, **kwargs)
    231          self.processes = []
    232          self.behavior = {}
    233          self.reboot = {}
    234          self.matched = False
    235
    236 -    def handles_path(self, path):
    237          if path.endswith(".bson"):
    238              self.matched = True
    239              return True
    240
    241 -    def parse(self, path):
    242          # Invoke parsing of current log file.
    243          parser = BsonParser(open(path, "rb"))
    244          parser.init()
    245
    246          for event in parser:
    247              if event["type"] == "process":
    248                  process = dict(event)
    249                  process["calls"] = MonitorProcessLog(
    250                      parser, process["modules"]
    251                  )
    252                  self.processes.append(process)
    253
    254                  self.behavior[process["pid"]] = BehaviorReconstructor()
    255                  self.reboot[process["pid"]] = RebootReconstructor()
    256
    257              # Create generic events out of the windows calls.
    258              elif event["type"] == "apicall":
    259                  behavior = self.behavior[event["pid"]]
    260                  reboot = self.reboot[event["pid"]]
    261
    262                  for category, arg in behavior.process_apicall(event):
    263                      yield {
    264                          "type": "generic",
    265                          "pid": event["pid"],
    266                          "category": category,
    267                          "value": arg,
    268                      }
    269
    270                  # Process the reboot reconstructor.
    271                  for category, args in reboot.process_apicall(event):
    272                      # TODO Improve this where we have to calculate the "real"
    273                      # time again even though we already do this in
    274                      # MonitorProcessLog.
    275                      ts = process["first_seen"] + \
    276                          datetime.timedelta(0, 0, event["time"] * 1000)
    277
    278                      yield {
    279                          "type": "reboot",
    280                          "category": category,
    281                          "args": args,
    282                          "time": int(ts.strftime("%d")),
    283                      }
    284
    285                  # Indicate that the process has API calls. For more
    286                  # information on this matter, see also the __nonzero__ above.
    287                  process["calls"].has_apicalls = True
    288
    289              yield event
    290
    291 -    def run(self):
    292          if not self.matched:
    293              return
    294
    295          self.processes.sort(key=lambda process: process["first_seen"])
    296          return self.processes
    297
    298 -def NT_SUCCESS(value):
    299      return value % 2**32 < 0x80000000
    300
    301 -def single(key, value):
    302      return [(key, value)]
    303
    304 -def multiple(*l):
    305      return l
    306
    307 -class BehaviorReconstructor(object):
    308      """Reconstructs the behavior of behavioral API logs."""
    309 -    def __init__(self):
    310          self.files = {}
    311
    312 -    def process_apicall(self, event):
    313          fn = getattr(self, "_api_%s" % event["api"], None)
    314          if fn is not None:
    315              ret = fn(
    316                  event["return_value"], event["arguments"], event.get("flags")
    317              )
    318              return ret or []
    319          return []
    320
    321      # Generic file & directory stuff.
    322
    323 -    def _api_CreateDirectoryW(self, return_value, arguments, flags):
    324          return single("directory_created", arguments["dirpath"])
    325
    326      _api_CreateDirectoryExW = _api_CreateDirectoryW
    327
    328 -    def _api_RemoveDirectoryA(self, return_value, arguments, flags):
    329          return single("directory_removed", arguments["dirpath"])
    330
    331      _api_RemoveDirectoryW = _api_RemoveDirectoryA
    332
    333 -    def _api_MoveFileWithProgressW(self, return_value, arguments, flags):
    334          return single("file_moved", (
    335              arguments["oldfilepath"], arguments["newfilepath"]
    336          ))
    337
    338 -    def _api_CopyFileA(self, return_value, arguments, flags):
    339          return single("file_copied", (
    340              arguments["oldfilepath"], arguments["newfilepath"]
    341          ))
    342
    343      _api_CopyFileW = _api_CopyFileA
    344      _api_CopyFileExW = _api_CopyFileA
    345
    346 -    def _api_DeleteFileA(self, return_value, arguments, flags):
    347          return single("file_deleted", arguments["filepath"])
    348
    349      _api_DeleteFileW = _api_DeleteFileA
    350      _api_NtDeleteFile = _api_DeleteFileA
    351
    352 -    def _api_FindFirstFileExA(self, return_value, arguments, flags):
    353          return single("directory_enumerated", arguments["filepath"])
    354
    355      _api_FindFirstFileExW = _api_FindFirstFileExA
    356
    357 -    def _api_LdrLoadDll(self, return_value, arguments, flags):
    358          return single("dll_loaded", arguments["module_name"])
    359
    360 -    def _api_NtCreateFile(self, return_value, arguments, flags):
    361          self.files[arguments["file_handle"]] = arguments["filepath"]
    362          if NT_SUCCESS(return_value):
    363              status_info = flags.get("status_info", "").lower()
    364              if status_info in ("file_overwritten", "file_superseded"):
    365                  return single("file_recreated", arguments["filepath"])
    366              elif status_info == "file_exists":
    367                  return single("file_opened", arguments["filepath"])
    368              elif status_info == "file_does_not_exist":
    369                  return single("file_failed", arguments["filepath"])
    370              elif status_info == "file_created":
    371                  return single("file_created", arguments["filepath"])
    372              else:
    373                  return single("file_opened", arguments["filepath"])
    374          else:
    375              return single("file_failed", arguments["filepath"])
    376
    377      _api_NtOpenFile = _api_NtCreateFile
    378
    379 -    def _api_NtReadFile(self, return_value, arguments, flags):
    380          h = arguments["file_handle"]
    381          if NT_SUCCESS(return_value) and h in self.files:
    382              return single("file_read", self.files[h])
    383
    384 -    def _api_NtWriteFile(self, return_value, arguments, flags):
    385          h = arguments["file_handle"]
    386          if NT_SUCCESS(return_value) and h in self.files:
    387              return single("file_written", self.files[h])
    388
    389 -    def _api_GetFileAttributesW(self, return_value, arguments, flags):
    390          return single("file_exists", arguments["filepath"])
    391
    392      _api_GetFileAttributesExW = _api_GetFileAttributesW
    393
    394      # Registry stuff.
    395
    396 -    def _api_RegOpenKeyExA(self, return_value, arguments, flags):
    397          return single("regkey_opened", arguments["regkey"])
    398
    399      _api_RegOpenKeyExW = _api_RegOpenKeyExA
    400      _api_RegCreateKeyExA = _api_RegOpenKeyExA
    401      _api_RegCreateKeyExW = _api_RegOpenKeyExA
    402
    403 -    def _api_RegDeleteKeyA(self, return_value, arguments, flags):
    404          return single("regkey_deleted", arguments["regkey"])
    405
    406      _api_RegDeleteKeyW = _api_RegDeleteKeyA
    407      _api_RegDeleteValueA = _api_RegDeleteKeyA
    408      _api_RegDeleteValueW = _api_RegDeleteKeyA
    409      _api_NtDeleteValueKey = _api_RegDeleteKeyA
    410
    411 -    def _api_RegQueryValueExA(self, return_value, arguments, flags):
    412          return single("regkey_read", arguments["regkey"])
    413
    414      _api_RegQueryValueExW = _api_RegQueryValueExA
    415      _api_NtQueryValueKey = _api_RegQueryValueExA
    416
    417 -    def _api_RegSetValueExA(self, return_value, arguments, flags):
    418          return single("regkey_written", arguments["regkey"])
    419
    420      _api_RegSetValueExW = _api_RegSetValueExA
    421      _api_NtSetValueKey = _api_RegSetValueExA
    422
    423 -    def _api_NtClose(self, return_value, arguments, flags):
    424          self.files.pop(arguments["handle"], None)
    425
    426      # Network stuff.
    427
    428 -    def _api_URLDownloadToFileW(self, return_value, arguments, flags):
    429          return multiple(
    430              ("downloads_file", arguments["url"]),
    431              ("file_opened", arguments["filepath"]),
    432              ("file_written", arguments["filepath"]),
    433          )
    434
    435 -    def _api_InternetConnectA(self, return_value, arguments, flags):
    436          return single("connects_host", arguments["hostname"])
    437
    438      _api_InternetConnectW = _api_InternetConnectA
    439
    440 -    def _api_InternetOpenUrlA(self, return_value, arguments, flags):
    441          return single("fetches_url", arguments["url"])
    442
    443      _api_InternetOpenUrlW = _api_InternetOpenUrlA
    444
    445 -    def _api_DnsQuery_A(self, return_value, arguments, flags):
    446          if arguments["hostname"]:
    447              return single("resolves_host", arguments["hostname"])
    448
    449      _api_DnsQuery_W = _api_DnsQuery_A
    450      _api_DnsQuery_UTF8 = _api_DnsQuery_A
    451      _api_getaddrinfo = _api_DnsQuery_A
    452      _api_GetAddrInfoW = _api_DnsQuery_A
    453      _api_gethostbyname = _api_DnsQuery_A
    454
    455 -    def _api_connect(self, return_value, arguments, flags):
    456          return single("connects_ip", arguments["ip_address"])
    457
    458      # Mutex stuff
    459
    460 -    def _api_NtCreateMutant(self, return_value, arguments, flags):
    461          if arguments["mutant_name"]:
    462              return single("mutex", arguments["mutant_name"])
    463
    464      _api_ConnectEx = _api_connect
    465
    466      # Process stuff.
    467
    468 -    def _api_CreateProcessInternalW(self, return_value, arguments, flags):
    469          if arguments.get("track", True):
    470              cmdline = arguments["command_line"] or arguments["filepath"]
    471              return single("command_line", cmdline)
    472
    473 -    def _api_ShellExecuteExW(self, return_value, arguments, flags):
    474          if arguments["parameters"]:
    475              cmdline = "%s %s" % (arguments["filepath"], arguments["parameters"])
    476          else:
    477              cmdline = arguments["filepath"]
    478          return single("command_line", cmdline)
    479
    480 -    def _api_system(self, return_value, arguments, flags):
    481          return single("command_line", arguments["command"])
    482
    483      # WMI stuff.
    484
    485 -    def _api_IWbemServices_ExecQuery(self, return_value, arguments, flags):
    486          return single("wmi_query", arguments["query"])
    487
    488 -    def _api_IWbemServices_ExecQueryAsync(self, return_value, arguments, flags):
    489          return single("wmi_query", arguments["query"])
    490
    491      # GUIDs.
    492
    493 -    def _api_CoCreateInstance(self, return_value, arguments, flags):
    494          return multiple(
    495              ("guid", arguments["clsid"]),
    496              ("guid", arguments["iid"]),
    497          )
    498
    499 -    def _api_CoCreateInstanceEx(self, return_value, arguments, flags):
    500          ret = [
    501              ("guid", arguments["clsid"]),
    502          ]
    503          for iid in arguments["iid"]:
    504              ret.append(("guid", iid))
    505          return multiple(*ret)
    506
    507 -    def _api_CoGetClassObject(self, return_value, arguments, flags):
    508          return multiple(
    509              ("guid", arguments["clsid"]),
    510              ("guid", arguments["iid"]),
    511          )
    512
    513      # SSLv3 & TLS Master Secrets.
    514
    515 -    def _api_Ssl3GenerateKeyMaterial(self, return_value, arguments, flags):
    516          if arguments["client_random"] and arguments["server_random"]:
    517              return single("tls_master", (
    518                  arguments["client_random"],
    519                  arguments["server_random"],
    520                  arguments["master_secret"],
    521              ))
    522
    523 -    def _api_PRF(self, return_value, arguments, flags):
    524          if arguments["type"] == "key expansion":
    525              return single("tls_master", (
    526                  arguments["client_random"],
    527                  arguments["server_random"],
    528                  arguments["master_secret"],
    529              ))
    530
    531 -class RebootReconstructor(object):
    532      """Reconstructs the behavior as would be seen after a reboot."""
    533
    534 -    def process_apicall(self, event):
    535          fn = getattr(self, "_api_%s" % event["api"], None)
    536          if fn is not None:
    537              ret = fn(
    538                  event["return_value"], event["arguments"], event.get("flags")
    539              )
    540              return ret or []
    541          return []
    542
    543 -    def _api_delete_regkey(self, return_value, arguments, flags):
    544          return single("regkey_deleted", arguments["regkey"])
    545
    546      _api_RegDeleteKeyA = _api_delete_regkey
    547      _api_RegDeleteKeyW = _api_delete_regkey
    548      _api_RegDeleteValueA = _api_delete_regkey
    549      _api_RegDeleteValueW = _api_delete_regkey
    550      _api_NtDeleteValueKey = _api_delete_regkey
    551
    552 -    def parse_cmdline(self, command_line):
    553          """Extract the filepath and arguments from the full commandline."""
    554          components = shlex.split(command_line, posix=False)
    555          return components[0].strip('"'), components[1:]
    556
    557 -    def _handle_run(self, arguments, flags):
    558          """Handle Run registry keys."""
    559          reg_type = flags.get("reg_type", arguments["reg_type"])
    560          filepath, args = self.parse_cmdline(arguments["value"])
    561          return multiple(
    562              ("regkey_written", (
    563                  arguments["regkey"], reg_type, arguments["value"]
    564              )),
    565              ("create_process", (
    566                  filepath, args, "explorer.exe"
    567              )),
    568          )
    569
    570 -    def _handle_runonce(self, arguments, flags):
    571          """For RunOnce there is no registry key persistence."""
    572          filepath, args = self.parse_cmdline(arguments["value"])
    573          return single("create_process", (
    574              filepath, args, "explorer.exe",
    575          ))
    576
    577      # TODO In an optimal world we would move this logic to a Signature, but
    578      # unfortunately the reboot information is already written away during the
    579      # Processing stage and thus the Signature stage is too late in the chain.
    580      _reg_regexes = [
    581          (_handle_run, ".*\\\\Software\\\\(Wow6432Node\\\\)?Microsoft\\\\Windows\\\\CurrentVersion\\\\Run"),
    582          (_handle_runonce, ".*\\\\Software\\\\(Wow6432Node\\\\)?Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnce"),
    583      ]
    584
    585 -    def _api_set_regkey(self, return_value, arguments, flags):
    586          # Is this a registry key that directly affects reboot persistence?
    587          for fn, regex in self._reg_regexes:
    588              if re.match(regex, arguments["regkey"], re.I):
    589                  return fn(self, arguments, flags)
    590
    591          reg_type = flags.get("reg_type", arguments["reg_type"])
    592          return single("regkey_written", (
    593              arguments["regkey"], reg_type, arguments["value"]
    594          ))
    595
    596      _api_RegSetValueExA = _api_set_regkey
    597      _api_RegSetValueExW = _api_set_regkey
    598      _api_NtSetValueKey = _api_set_regkey
    599