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