1
2
3
4
5 import os
6 import re
7 import logging
8 import time
9
10 import xml.etree.ElementTree as ET
11
12 from lib.cuckoo.common.config import Config
13 from lib.cuckoo.common.constants import CUCKOO_ROOT
14 from lib.cuckoo.common.exceptions import CuckooCriticalError
15 from lib.cuckoo.common.exceptions import CuckooMachineError
16 from lib.cuckoo.common.exceptions import CuckooOperationalError
17 from lib.cuckoo.common.exceptions import CuckooReportError
18 from lib.cuckoo.common.exceptions import CuckooDependencyError
19 from lib.cuckoo.common.objects import Dictionary
20 from lib.cuckoo.common.utils import create_folder
21 from lib.cuckoo.core.database import Database
22
23 try:
24 import libvirt
25 HAVE_LIBVIRT = True
26 except ImportError:
27 HAVE_LIBVIRT = False
28
29 log = logging.getLogger(__name__)
30
32 """Base abstract class for auxiliary modules."""
33
38
41
44
47
49 raise NotImplementedError
50
52 raise NotImplementedError
53
54
56 """Base abstract class for machinery modules."""
57
68
70 """Set machine manager options.
71 @param options: machine manager options dict.
72 """
73 self.options = options
74
76 """Read, load, and verify machines configuration.
77 @param module_name: module name.
78 """
79
80 self._initialize(module_name)
81
82
83 self._initialize_check()
84
86 """Read configuration.
87 @param module_name: module name.
88 """
89 self.module_name = module_name
90 mmanager_opts = self.options.get(module_name)
91
92 for machine_id in mmanager_opts["machines"].strip().split(","):
93 try:
94 machine_opts = self.options.get(machine_id.strip())
95 machine = Dictionary()
96 machine.id = machine_id.strip()
97 machine.label = machine_opts["label"]
98 machine.platform = machine_opts["platform"]
99 machine.tags = machine_opts.get("tags", None)
100 machine.ip = machine_opts["ip"]
101
102
103
104 machine.interface = machine_opts.get("interface", None)
105
106
107
108 machine.snapshot = machine_opts.get("snapshot", None)
109
110
111
112 opt_resultserver = self.options_globals.resultserver
113 ip = machine_opts.get("resultserver_ip", opt_resultserver.ip)
114 port = machine_opts.get("resultserver_port", opt_resultserver.port)
115
116 machine.resultserver_ip = ip
117 machine.resultserver_port = port
118
119
120 for key in machine.keys():
121 if machine[key]:
122
123 if isinstance(machine[key], (str, unicode)):
124 machine[key] = machine[key].strip()
125
126 self.db.add_machine(name=machine.id,
127 label=machine.label,
128 ip=machine.ip,
129 platform=machine.platform,
130 tags=machine.tags,
131 interface=machine.interface,
132 snapshot=machine.snapshot,
133 resultserver_ip=ip,
134 resultserver_port=port)
135 except (AttributeError, CuckooOperationalError) as e:
136 log.warning("Configuration details about machine %s "
137 "are missing: %s", machine_id, e)
138 continue
139
141 """Runs checks against virtualization software when a machine manager
142 is initialized.
143 @note: in machine manager modules you may override or superclass
144 his method.
145 @raise CuckooMachineError: if a misconfiguration or a unkown vm state
146 is found.
147 """
148 try:
149 configured_vms = self._list()
150 except NotImplementedError:
151 return
152
153 for machine in self.machines():
154
155
156 if machine.label in configured_vms and \
157 self._status(machine.label) in [self.POWEROFF, self.ABORTED]:
158 continue
159
160
161
162 try:
163 self.stop(machine.label)
164 except CuckooMachineError as e:
165 msg = "Please update your configuration. Unable to shut " \
166 "'{0}' down or find the machine in its proper state:" \
167 " {1}".format(machine.label, e)
168 raise CuckooCriticalError(msg)
169
170 if not self.options_globals.timeouts.vm_state:
171 raise CuckooCriticalError("Virtual machine state change timeout "
172 "setting not found, please add it to "
173 "the config file")
174
176 """List virtual machines.
177 @return: virtual machines list
178 """
179 return self.db.list_machines()
180
182 """How many machines are free.
183 @return: free machines count.
184 """
185 return self.db.count_machines_available()
186
187 - def acquire(self, machine_id=None, platform=None, tags=None):
200
202 """Release a machine.
203 @param label: machine name.
204 """
205 self.db.unlock_machine(label)
206
208 """Returns running virtual machines.
209 @return: running virtual machines list.
210 """
211 return self.db.list_machines(locked=True)
212
214 """Shutdown the machine manager. Kills all alive machines.
215 @raise CuckooMachineError: if unable to stop machine.
216 """
217 if len(self.running()) > 0:
218 log.info("Still %s guests alive. Shutting down...",
219 len(self.running()))
220 for machine in self.running():
221 try:
222 self.stop(machine.label)
223 except CuckooMachineError as e:
224 log.warning("Unable to shutdown machine %s, please check "
225 "manually. Error: %s", machine.label, e)
226
228 """Set status for a virtual machine.
229 @param label: virtual machine label
230 @param status: new virtual machine status
231 """
232 self.db.set_machine_status(label, status)
233
234 - def start(self, label=None):
235 """Start a machine.
236 @param label: machine name.
237 @raise NotImplementedError: this method is abstract.
238 """
239 raise NotImplementedError
240
241 - def stop(self, label=None):
242 """Stop a machine.
243 @param label: machine name.
244 @raise NotImplementedError: this method is abstract.
245 """
246 raise NotImplementedError
247
249 """Lists virtual machines configured.
250 @raise NotImplementedError: this method is abstract.
251 """
252 raise NotImplementedError
253
255 """Takes a memory dump of a machine.
256 @param path: path to where to store the memory dump.
257 """
258 raise NotImplementedError
259
261 """Waits for a vm status.
262 @param label: virtual machine name.
263 @param state: virtual machine status, accepts multiple states as list.
264 @raise CuckooMachineError: if default waiting timeout expire.
265 """
266
267 waitme = 0
268 try:
269 current = self._status(label)
270 except NameError:
271 return
272
273 if isinstance(state, str):
274 state = [state]
275 while current not in state:
276 log.debug("Waiting %i cuckooseconds for machine %s to switch "
277 "to status %s", waitme, label, state)
278 if waitme > int(self.options_globals.timeouts.vm_state):
279 raise CuckooMachineError("Timeout hit while for machine {0} "
280 "to change status".format(label))
281 time.sleep(1)
282 waitme += 1
283 current = self._status(label)
284
285
287 """Libvirt based machine manager.
288
289 If you want to write a custom module for a virtualization software
290 supported by libvirt you have just to inherit this machine manager and
291 change the connection string.
292 """
293
294
295 RUNNING = "running"
296 PAUSED = "paused"
297 POWEROFF = "poweroff"
298 ERROR = "machete"
299 ABORTED = "abort"
300
306
308 """Initialize machine manager module. Override default to set proper
309 connection string.
310 @param module: machine manager module
311 """
312 super(LibVirtMachinery, self).initialize(module)
313
329
381
382 - def stop(self, label):
383 """Stops a virtual machine. Kill them all.
384 @param label: virtual machine name.
385 @raise CuckooMachineError: if unable to stop virtual machine.
386 """
387 log.debug("Stopping machine %s", label)
388
389 if self._status(label) == self.POWEROFF:
390 raise CuckooMachineError("Trying to stop an already stopped "
391 "machine {0}".format(label))
392
393
394 conn = self._connect()
395 try:
396 if not self.vms[label].isActive():
397 log.debug("Trying to stop an already stopped machine %s. "
398 "Skip", label)
399 else:
400 self.vms[label].destroy()
401 except libvirt.libvirtError as e:
402 raise CuckooMachineError("Error stopping virtual machine "
403 "{0}: {1}".format(label, e))
404 finally:
405 self._disconnect(conn)
406
407 self._wait_status(label, self.POWEROFF)
408
410 """Override shutdown to free libvirt handlers - they print errors."""
411 super(LibVirtMachinery, self).shutdown()
412
413
414 self.vms = None
415
417 """Takes a memory dump.
418 @param path: path to where to store the memory dump.
419 """
420 log.debug("Dumping memory for machine %s", label)
421
422 conn = self._connect()
423 try:
424 self.vms[label].coreDump(path, flags=libvirt.VIR_DUMP_MEMORY_ONLY)
425 except libvirt.libvirtError as e:
426 raise CuckooMachineError("Error dumping memory virtual machine "
427 "{0}: {1}".format(label, e))
428 finally:
429 self._disconnect(conn)
430
432 """Gets current status of a vm.
433 @param label: virtual machine name.
434 @return: status string.
435 """
436 log.debug("Getting status for %s", label)
437
438
439
440
441
442
443
444
445
446
447
448
449 conn = self._connect()
450 try:
451 state = self.vms[label].state(flags=0)
452 except libvirt.libvirtError as e:
453 raise CuckooMachineError("Error getting status for virtual "
454 "machine {0}: {1}".format(label, e))
455 finally:
456 self._disconnect(conn)
457
458 if state:
459 if state[0] == 1:
460 status = self.RUNNING
461 elif state[0] == 3:
462 status = self.PAUSED
463 elif state[0] == 4 or state[0] == 5:
464 status = self.POWEROFF
465 else:
466 status = self.ERROR
467
468
469 if status:
470 self.set_status(label, status)
471 return status
472 else:
473 raise CuckooMachineError("Unable to get status for "
474 "{0}".format(label))
475
477 """Connects to libvirt subsystem.
478 @raise CuckooMachineError: when unable to connect to libvirt.
479 """
480
481 if not self.dsn:
482 raise CuckooMachineError("You must provide a proper "
483 "connection string")
484
485 try:
486 return libvirt.open(self.dsn)
487 except libvirt.libvirtError:
488 raise CuckooMachineError("Cannot connect to libvirt")
489
491 """Disconnects to libvirt subsystem.
492 @raise CuckooMachineError: if cannot disconnect from libvirt.
493 """
494 try:
495 conn.close()
496 except libvirt.libvirtError:
497 raise CuckooMachineError("Cannot disconnect from libvirt")
498
500 """Fetch machines handlers.
501 @return: dict with machine label as key and handle as value.
502 """
503 vms = {}
504 for vm in self.machines():
505 vms[vm.label] = self._lookup(vm.label)
506 return vms
507
509 """Search for a virtual machine.
510 @param conn: libvirt connection handle.
511 @param label: virtual machine name.
512 @raise CuckooMachineError: if virtual machine is not found.
513 """
514 conn = self._connect()
515 try:
516 vm = conn.lookupByName(label)
517 except libvirt.libvirtError:
518 raise CuckooMachineError("Cannot find machine "
519 "{0}".format(label))
520 finally:
521 self._disconnect(conn)
522 return vm
523
525 """List available virtual machines.
526 @raise CuckooMachineError: if unable to list virtual machines.
527 """
528 conn = self._connect()
529 try:
530 names = conn.listDefinedDomains()
531 except libvirt.libvirtError:
532 raise CuckooMachineError("Cannot list domains")
533 finally:
534 self._disconnect(conn)
535 return names
536
538 """Check if libvirt release supports snapshots.
539 @return: True or false.
540 """
541 if libvirt.getVersion() >= 8000:
542 return True
543 else:
544 return False
545
547 """Get current snapshot for virtual machine
548 @param label: virtual machine name
549 @return None or current snapshot
550 @raise CuckooMachineError: if cannot find current snapshot or
551 when there are too many snapshots available
552 """
553
554 conn = self._connect()
555 try:
556 vm = self.vms[label]
557 snap = vm.hasCurrentSnapshot(flags=0)
558 except libvirt.libvirtError:
559 self._disconnect(conn)
560 raise CuckooMachineError("Unable to get current snapshot for "
561 "virtual machine {0}".format(label))
562 finally:
563 self._disconnect(conn)
564
565 if snap:
566 return vm.snapshotCurrent(flags=0)
567
568
569 conn = self._connect()
570 try:
571 snaps = vm[label].snapshotListNames(flags=0)
572
573 def get_create(sn):
574 xml_desc = sn.getXMLDesc(flags=0)
575 return ET.fromstring(xml_desc).findtext("./creationTime")
576
577 return max(get_create(vm.snapshotLookupByName(name, flags=0))
578 for name in snaps)
579 except libvirt.libvirtError:
580 return None
581 except ValueError:
582 return None
583 finally:
584 self._disconnect(conn)
585
587 """Base abstract class for processing module."""
588 order = 1
589 enabled = True
590
592 self.analysis_path = ""
593 self.logs_path = ""
594 self.task = None
595 self.options = None
596
598 """Set report options.
599 @param options: report options dict.
600 """
601 self.options = options
602
604 """Add task information.
605 @param task: task dictionary.
606 """
607 self.task = task
608
610 """Set paths.
611 @param analysis_path: analysis folder path.
612 """
613 self.analysis_path = analysis_path
614 self.log_path = os.path.join(self.analysis_path, "analysis.log")
615 self.file_path = os.path.realpath(os.path.join(self.analysis_path,
616 "binary"))
617 self.dropped_path = os.path.join(self.analysis_path, "files")
618 self.logs_path = os.path.join(self.analysis_path, "logs")
619 self.shots_path = os.path.join(self.analysis_path, "shots")
620 self.pcap_path = os.path.join(self.analysis_path, "dump.pcap")
621 self.pmemory_path = os.path.join(self.analysis_path, "memory")
622 self.memory_path = os.path.join(self.analysis_path, "memory.dmp")
623
625 """Start processing.
626 @raise NotImplementedError: this method is abstract.
627 """
628 raise NotImplementedError
629
631 """Base class for Cuckoo signatures."""
632
633 name = ""
634 description = ""
635 severity = 1
636 categories = []
637 families = []
638 authors = []
639 references = []
640 alert = False
641 enabled = True
642 minimum = None
643 maximum = None
644
645
646
647
648 order = 0
649
650 evented = False
651 filter_processnames = set()
652 filter_apinames = set()
653 filter_categories = set()
654
656 self.data = []
657 self.results = results
658 self._current_call_cache = None
659 self._current_call_dict = None
660
662 """Checks a pattern against a given subject.
663 @param pattern: string or expression to check for.
664 @param subject: target of the check.
665 @param regex: boolean representing if the pattern is a regular
666 expression or not and therefore should be compiled.
667 @return: boolean with the result of the check.
668 """
669 if regex:
670 exp = re.compile(pattern, re.IGNORECASE)
671 if isinstance(subject, list):
672 for item in subject:
673 if exp.match(item):
674 return item
675 else:
676 if exp.match(subject):
677 return subject
678 else:
679 if isinstance(subject, list):
680 for item in subject:
681 if item == pattern:
682 return item
683 else:
684 if subject == pattern:
685 return subject
686
687 return None
688
690 """Checks for a file being opened.
691 @param pattern: string or expression to check for.
692 @param regex: boolean representing if the pattern is a regular
693 expression or not and therefore should be compiled.
694 @return: boolean with the result of the check.
695 """
696 subject = self.results["behavior"]["summary"]["files"]
697 return self._check_value(pattern=pattern,
698 subject=subject,
699 regex=regex)
700
702 """Checks for a registry key being opened.
703 @param pattern: string or expression to check for.
704 @param regex: boolean representing if the pattern is a regular
705 expression or not and therefore should be compiled.
706 @return: boolean with the result of the check.
707 """
708 subject = self.results["behavior"]["summary"]["keys"]
709 return self._check_value(pattern=pattern,
710 subject=subject,
711 regex=regex)
712
714 """Checks for a mutex being opened.
715 @param pattern: string or expression to check for.
716 @param regex: boolean representing if the pattern is a regular
717 expression or not and therefore should be compiled.
718 @return: boolean with the result of the check.
719 """
720 subject = self.results["behavior"]["summary"]["mutexes"]
721 return self._check_value(pattern=pattern,
722 subject=subject,
723 regex=regex)
724
725 - def check_api(self, pattern, process=None, regex=False):
726 """Checks for an API being called.
727 @param pattern: string or expression to check for.
728 @param process: optional filter for a specific process name.
729 @param regex: boolean representing if the pattern is a regular
730 expression or not and therefore should be compiled.
731 @return: boolean with the result of the check.
732 """
733
734 for item in self.results["behavior"]["processes"]:
735
736 if process:
737 if item["process_name"] != process:
738 continue
739
740
741 for call in item["calls"]:
742
743 if self._check_value(pattern=pattern,
744 subject=call["api"],
745 regex=regex):
746 return call["api"]
747
748 return None
749
750 - def check_argument_call(self,
751 call,
752 pattern,
753 name=None,
754 api=None,
755 category=None,
756 regex=False):
757 """Checks for a specific argument of an invoked API.
758 @param call: API call information.
759 @param pattern: string or expression to check for.
760 @param name: optional filter for the argument name.
761 @param api: optional filter for the API function name.
762 @param category: optional filter for a category name.
763 @param regex: boolean representing if the pattern is a regular
764 expression or not and therefore should be compiled.
765 @return: boolean with the result of the check.
766 """
767
768 if api:
769 if call["api"] != api:
770 return False
771
772
773 if category:
774 if call["category"] != category:
775 return False
776
777
778 for argument in call["arguments"]:
779
780 if name:
781 if argument["name"] != name:
782 continue
783
784
785 if self._check_value(pattern=pattern,
786 subject=argument["value"],
787 regex=regex):
788 return argument["value"]
789
790 return False
791
792 - def check_argument(self,
793 pattern,
794 name=None,
795 api=None,
796 category=None,
797 process=None,
798 regex=False):
799 """Checks for a specific argument of an invoked API.
800 @param pattern: string or expression to check for.
801 @param name: optional filter for the argument name.
802 @param api: optional filter for the API function name.
803 @param category: optional filter for a category name.
804 @param process: optional filter for a specific process name.
805 @param regex: boolean representing if the pattern is a regular
806 expression or not and therefore should be compiled.
807 @return: boolean with the result of the check.
808 """
809
810 for item in self.results["behavior"]["processes"]:
811
812 if process:
813 if item["process_name"] != process:
814 continue
815
816
817 for call in item["calls"]:
818 r = self.check_argument_call(call, pattern, name,
819 api, category, regex)
820 if r:
821 return r
822
823 return None
824
825 - def check_ip(self, pattern, regex=False):
826 """Checks for an IP address being contacted.
827 @param pattern: string or expression to check for.
828 @param regex: boolean representing if the pattern is a regular
829 expression or not and therefore should be compiled.
830 @return: boolean with the result of the check.
831 """
832 return self._check_value(pattern=pattern,
833 subject=self.results["network"]["hosts"],
834 regex=regex)
835
836 - def check_domain(self, pattern, regex=False):
837 """Checks for a domain being contacted.
838 @param pattern: string or expression to check for.
839 @param regex: boolean representing if the pattern is a regular
840 expression or not and therefore should be compiled.
841 @return: boolean with the result of the check.
842 """
843 for item in self.results["network"]["domains"]:
844 if self._check_value(pattern=pattern,
845 subject=item["domain"],
846 regex=regex):
847 return item
848
849 return None
850
852 """Checks for a URL being contacted.
853 @param pattern: string or expression to check for.
854 @param regex: boolean representing if the pattern is a regular
855 expression or not and therefore should be compiled.
856 @return: boolean with the result of the check.
857 """
858 for item in self.results["network"]["http"]:
859 if self._check_value(pattern=pattern,
860 subject=item["uri"],
861 regex=regex):
862 return item
863
864 return None
865
867 """Retrieves the value of a specific argument from an API call.
868 @param call: API call object.
869 @param name: name of the argument to retrieve.
870 @return: value of the requried argument.
871 """
872
873
874 if call is not self._current_call_cache:
875 self._current_call_cache = call
876 self._current_call_dict = dict()
877
878 for argument in call["arguments"]:
879 self._current_call_dict[argument["name"]] = argument["value"]
880
881
882 if name in self._current_call_dict:
883 return self._current_call_dict[name]
884
885 return None
886
888 """Notify signature about API call. Return value determines
889 if this signature is done or could still match.
890 @param call: logged API call.
891 @param process: process doing API call.
892 @raise NotImplementedError: this method is abstract.
893 """
894 raise NotImplementedError
895
897 """Evented signature is notified when all API calls are done.
898 @return: Match state.
899 @raise NotImplementedError: this method is abstract.
900 """
901 raise NotImplementedError
902
904 """Start signature processing.
905 @param results: analysis results.
906 @raise NotImplementedError: this method is abstract.
907 """
908 raise NotImplementedError
909
923
925 """Base abstract class for reporting module."""
926 order = 1
927
929 self.analysis_path = ""
930 self.reports_path = ""
931 self.task = None
932 self.options = None
933
935 """Set analysis folder path.
936 @param analysis_path: analysis folder path.
937 """
938 self.analysis_path = analysis_path
939 self.conf_path = os.path.join(self.analysis_path, "analysis.conf")
940 self.file_path = os.path.realpath(os.path.join(self.analysis_path,
941 "binary"))
942 self.reports_path = os.path.join(self.analysis_path, "reports")
943 self.shots_path = os.path.join(self.analysis_path, "shots")
944 self.pcap_path = os.path.join(self.analysis_path, "dump.pcap")
945
946 try:
947 create_folder(folder=self.reports_path)
948 except CuckooOperationalError as e:
949 CuckooReportError(e)
950
952 """Set report options.
953 @param options: report options dict.
954 """
955 self.options = options
956
958 """Add task information.
959 @param task: task dictionary.
960 """
961 self.task = task
962
964 """Start report processing.
965 @raise NotImplementedError: this method is abstract.
966 """
967 raise NotImplementedError
968