1
2
3
4
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
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
28 self.services = {}
29 self.vbe6_ptrs = {}
30 self.vbe6_func = {}
31
33 event["raw"] = "script",
34 event["arguments"]["script"] = \
35 jsbeautify(event["arguments"]["script"])
36
38 event["raw"] = "code",
39 event["arguments"]["code"] = jsbeautify(event["arguments"]["code"])
40
42 event["raw"] = "html",
43 event["arguments"]["html"] = htmlprettify(event["arguments"]["html"])
44
46 event["raw"] = "lines",
47 for idx, line in enumerate(event["arguments"]["lines"]):
48 event["arguments"]["lines"][idx] = htmlprettify(line)
49
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
59 """Keep track of the name of this service."""
60 service_name = event["arguments"]["service_name"]
61
62
63
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
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
82
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
95
97
98 args = event["arguments"]
99 if args["library"] == "VBE6.DLL" and not args["function"]:
100 return False
101
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
114 """Only used by the monitor for administrative uses."""
115 return False
116
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
131
133 event["raw"] = "script",
134 event["arguments"]["script"] = \
135 jsbeautify(event["arguments"]["script"])
136
138 event["raw"] = "string", "unescaped"
139
140
141
142
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
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
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
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
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
188 del event["type"]
189
190
191 del event["pid"]
192
193
194
195 del event["uniqhash"]
196
197
198 apiname = "_api_%s" % event["api"]
199 r = getattr(self, apiname, lambda _: None)(event)
200
201
202 self._api_modifier(event)
203
204
205
206 if r is not False:
207 yield event
208
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
226 """Parses monitor generated logs."""
227 key = "processes"
228
235
237 if path.endswith(".bson"):
238 self.matched = True
239 return True
240
242
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
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
271 for category, args in reboot.process_apicall(event):
272
273
274
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
286
287 process["calls"].has_apicalls = True
288
289 yield event
290
292 if not self.matched:
293 return
294
295 self.processes.sort(key=lambda process: process["first_seen"])
296 return self.processes
297
299 return value % 2**32 < 0x80000000
300
302 return [(key, value)]
303
306
308 """Reconstructs the behavior of behavioral API logs."""
311
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
322
324 return single("directory_created", arguments["dirpath"])
325
326 _api_CreateDirectoryExW = _api_CreateDirectoryW
327
329 return single("directory_removed", arguments["dirpath"])
330
331 _api_RemoveDirectoryW = _api_RemoveDirectoryA
332
334 return single("file_moved", (
335 arguments["oldfilepath"], arguments["newfilepath"]
336 ))
337
339 return single("file_copied", (
340 arguments["oldfilepath"], arguments["newfilepath"]
341 ))
342
343 _api_CopyFileW = _api_CopyFileA
344 _api_CopyFileExW = _api_CopyFileA
345
347 return single("file_deleted", arguments["filepath"])
348
349 _api_DeleteFileW = _api_DeleteFileA
350 _api_NtDeleteFile = _api_DeleteFileA
351
353 return single("directory_enumerated", arguments["filepath"])
354
355 _api_FindFirstFileExW = _api_FindFirstFileExA
356
358 return single("dll_loaded", arguments["module_name"])
359
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
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
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
390 return single("file_exists", arguments["filepath"])
391
392 _api_GetFileAttributesExW = _api_GetFileAttributesW
393
394
395
397 return single("regkey_opened", arguments["regkey"])
398
399 _api_RegOpenKeyExW = _api_RegOpenKeyExA
400 _api_RegCreateKeyExA = _api_RegOpenKeyExA
401 _api_RegCreateKeyExW = _api_RegOpenKeyExA
402
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
412 return single("regkey_read", arguments["regkey"])
413
414 _api_RegQueryValueExW = _api_RegQueryValueExA
415 _api_NtQueryValueKey = _api_RegQueryValueExA
416
418 return single("regkey_written", arguments["regkey"])
419
420 _api_RegSetValueExW = _api_RegSetValueExA
421 _api_NtSetValueKey = _api_RegSetValueExA
422
424 self.files.pop(arguments["handle"], None)
425
426
427
429 return multiple(
430 ("downloads_file", arguments["url"]),
431 ("file_opened", arguments["filepath"]),
432 ("file_written", arguments["filepath"]),
433 )
434
436 return single("connects_host", arguments["hostname"])
437
438 _api_InternetConnectW = _api_InternetConnectA
439
441 return single("fetches_url", arguments["url"])
442
443 _api_InternetOpenUrlW = _api_InternetOpenUrlA
444
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
456 return single("connects_ip", arguments["ip_address"])
457
458
459
461 if arguments["mutant_name"]:
462 return single("mutex", arguments["mutant_name"])
463
464 _api_ConnectEx = _api_connect
465
466
467
469 if arguments.get("track", True):
470 cmdline = arguments["command_line"] or arguments["filepath"]
471 return single("command_line", cmdline)
472
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
484
486 return single("wmi_query", arguments["query"])
487
489 return single("wmi_query", arguments["query"])
490
491
492
494 return multiple(
495 ("guid", arguments["clsid"]),
496 ("guid", arguments["iid"]),
497 )
498
500 ret = [
501 ("guid", arguments["clsid"]),
502 ]
503 for iid in arguments["iid"]:
504 ret.append(("guid", iid))
505 return multiple(*ret)
506
508 return multiple(
509 ("guid", arguments["clsid"]),
510 ("guid", arguments["iid"]),
511 )
512
513
514
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
532 """Reconstructs the behavior as would be seen after a reboot."""
533
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
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
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
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
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
578
579
580 _reg_regexes = [
581 (_handle_run, ".*\\\\Software\\\\(Wow6432Node\\\\)?Microsoft\\\\Windows\\\\CurrentVersion\\\\Run"),
582 (_handle_runonce, ".*\\\\Software\\\\(Wow6432Node\\\\)?Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnce"),
583 ]
584
586
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