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) == self.POWEROFF:
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
305
307 """Initialize machine manager module. Override default to set proper
308 connection string.
309 @param module: machine manager module
310 """
311 super(LibVirtMachinery, self).initialize(module)
312
328
380
381 - def stop(self, label):
382 """Stops a virtual machine. Kill them all.
383 @param label: virtual machine name.
384 @raise CuckooMachineError: if unable to stop virtual machine.
385 """
386 log.debug("Stopping machine %s", label)
387
388 if self._status(label) == self.POWEROFF:
389 raise CuckooMachineError("Trying to stop an already stopped "
390 "machine {0}".format(label))
391
392
393 conn = self._connect()
394 try:
395 if not self.vms[label].isActive():
396 log.debug("Trying to stop an already stopped machine %s. "
397 "Skip", label)
398 else:
399 self.vms[label].destroy()
400 except libvirt.libvirtError as e:
401 raise CuckooMachineError("Error stopping virtual machine "
402 "{0}: {1}".format(label, e))
403 finally:
404 self._disconnect(conn)
405
406 self._wait_status(label, self.POWEROFF)
407
409 """Override shutdown to free libvirt handlers - they print errors."""
410 super(LibVirtMachinery, self).shutdown()
411
412
413 self.vms = None
414
416 """Takes a memory dump.
417 @param path: path to where to store the memory dump.
418 """
419 log.debug("Dumping memory for machine %s", label)
420
421 conn = self._connect()
422 try:
423 self.vms[label].coreDump(path, flags=libvirt.VIR_DUMP_MEMORY_ONLY)
424 except libvirt.libvirtError as e:
425 raise CuckooMachineError("Error dumping memory virtual machine "
426 "{0}: {1}".format(label, e))
427 finally:
428 self._disconnect(conn)
429
431 """Gets current status of a vm.
432 @param label: virtual machine name.
433 @return: status string.
434 """
435 log.debug("Getting status for %s", label)
436
437
438
439
440
441
442
443
444
445
446
447
448 conn = self._connect()
449 try:
450 state = self.vms[label].state(flags=0)
451 except libvirt.libvirtError as e:
452 raise CuckooMachineError("Error getting status for virtual "
453 "machine {0}: {1}".format(label, e))
454 finally:
455 self._disconnect(conn)
456
457 if state:
458 if state[0] == 1:
459 status = self.RUNNING
460 elif state[0] == 3:
461 status = self.PAUSED
462 elif state[0] == 4 or state[0] == 5:
463 status = self.POWEROFF
464 else:
465 status = self.ERROR
466
467
468 if status:
469 self.set_status(label, status)
470 return status
471 else:
472 raise CuckooMachineError("Unable to get status for "
473 "{0}".format(label))
474
476 """Connects to libvirt subsystem.
477 @raise CuckooMachineError: when unable to connect to libvirt.
478 """
479
480 if not self.dsn:
481 raise CuckooMachineError("You must provide a proper "
482 "connection string")
483
484 try:
485 return libvirt.open(self.dsn)
486 except libvirt.libvirtError:
487 raise CuckooMachineError("Cannot connect to libvirt")
488
490 """Disconnects to libvirt subsystem.
491 @raise CuckooMachineError: if cannot disconnect from libvirt.
492 """
493 try:
494 conn.close()
495 except libvirt.libvirtError:
496 raise CuckooMachineError("Cannot disconnect from libvirt")
497
499 """Fetch machines handlers.
500 @return: dict with machine label as key and handle as value.
501 """
502 vms = {}
503 for vm in self.machines():
504 vms[vm.label] = self._lookup(vm.label)
505 return vms
506
508 """Search for a virtual machine.
509 @param conn: libvirt connection handle.
510 @param label: virtual machine name.
511 @raise CuckooMachineError: if virtual machine is not found.
512 """
513 conn = self._connect()
514 try:
515 vm = conn.lookupByName(label)
516 except libvirt.libvirtError:
517 raise CuckooMachineError("Cannot find machine "
518 "{0}".format(label))
519 finally:
520 self._disconnect(conn)
521 return vm
522
524 """List available virtual machines.
525 @raise CuckooMachineError: if unable to list virtual machines.
526 """
527 conn = self._connect()
528 try:
529 names = conn.listDefinedDomains()
530 except libvirt.libvirtError:
531 raise CuckooMachineError("Cannot list domains")
532 finally:
533 self._disconnect(conn)
534 return names
535
537 """Check if libvirt release supports snapshots.
538 @return: True or false.
539 """
540 if libvirt.getVersion() >= 8000:
541 return True
542 else:
543 return False
544
546 """Get current snapshot for virtual machine
547 @param label: virtual machine name
548 @return None or current snapshot
549 @raise CuckooMachineError: if cannot find current snapshot or
550 when there are too many snapshots available
551 """
552
553 conn = self._connect()
554 try:
555 vm = self.vms[label]
556 snap = vm.hasCurrentSnapshot(flags=0)
557 except libvirt.libvirtError:
558 self._disconnect(conn)
559 raise CuckooMachineError("Unable to get current snapshot for "
560 "virtual machine {0}".format(label))
561 finally:
562 self._disconnect(conn)
563
564 if snap:
565 return vm.snapshotCurrent(flags=0)
566
567
568 conn = self._connect()
569 try:
570 snaps = vm[label].snapshotListNames(flags=0)
571
572 def get_create(sn):
573 xml_desc = sn.getXMLDesc(flags=0)
574 return ET.fromstring(xml_desc).findtext("./creationTime")
575
576 return max(get_create(vm.snapshotLookupByName(name, flags=0))
577 for name in snaps)
578 except libvirt.libvirtError:
579 return None
580 except ValueError:
581 return None
582 finally:
583 self._disconnect(conn)
584
586 """Base abstract class for processing module."""
587 order = 1
588 enabled = True
589
591 self.analysis_path = ""
592 self.logs_path = ""
593 self.task = None
594 self.options = None
595
597 """Set report options.
598 @param options: report options dict.
599 """
600 self.options = options
601
603 """Add task information.
604 @param task: task dictionary.
605 """
606 self.task = task
607
609 """Set paths.
610 @param analysis_path: analysis folder path.
611 """
612 self.analysis_path = analysis_path
613 self.log_path = os.path.join(self.analysis_path, "analysis.log")
614 self.file_path = os.path.realpath(os.path.join(self.analysis_path,
615 "binary"))
616 self.dropped_path = os.path.join(self.analysis_path, "files")
617 self.logs_path = os.path.join(self.analysis_path, "logs")
618 self.shots_path = os.path.join(self.analysis_path, "shots")
619 self.pcap_path = os.path.join(self.analysis_path, "dump.pcap")
620 self.pmemory_path = os.path.join(self.analysis_path, "memory")
621 self.memory_path = os.path.join(self.analysis_path, "memory.dmp")
622
624 """Start processing.
625 @raise NotImplementedError: this method is abstract.
626 """
627 raise NotImplementedError
628
630 """Base class for Cuckoo signatures."""
631
632 name = ""
633 description = ""
634 severity = 1
635 categories = []
636 families = []
637 authors = []
638 references = []
639 alert = False
640 enabled = True
641 minimum = None
642 maximum = None
643
644 evented = False
645 filter_processnames = set()
646 filter_apinames = set()
647 filter_categories = set()
648
650 self.data = []
651 self.results = results
652 self._current_call_cache = None
653 self._current_call_dict = None
654
656 """Checks a pattern against a given subject.
657 @param pattern: string or expression to check for.
658 @param subject: target of the check.
659 @param regex: boolean representing if the pattern is a regular
660 expression or not and therefore should be compiled.
661 @return: boolean with the result of the check.
662 """
663 if regex:
664 exp = re.compile(pattern, re.IGNORECASE)
665 if isinstance(subject, list):
666 for item in subject:
667 if exp.match(item):
668 return item
669 else:
670 if exp.match(subject):
671 return subject
672 else:
673 if isinstance(subject, list):
674 for item in subject:
675 if item == pattern:
676 return item
677 else:
678 if subject == pattern:
679 return subject
680
681 return None
682
684 """Checks for a file being opened.
685 @param pattern: string or expression to check for.
686 @param regex: boolean representing if the pattern is a regular
687 expression or not and therefore should be compiled.
688 @return: boolean with the result of the check.
689 """
690 subject = self.results["behavior"]["summary"]["files"]
691 return self._check_value(pattern=pattern,
692 subject=subject,
693 regex=regex)
694
696 """Checks for a registry key being opened.
697 @param pattern: string or expression to check for.
698 @param regex: boolean representing if the pattern is a regular
699 expression or not and therefore should be compiled.
700 @return: boolean with the result of the check.
701 """
702 subject = self.results["behavior"]["summary"]["keys"]
703 return self._check_value(pattern=pattern,
704 subject=subject,
705 regex=regex)
706
708 """Checks for a mutex being opened.
709 @param pattern: string or expression to check for.
710 @param regex: boolean representing if the pattern is a regular
711 expression or not and therefore should be compiled.
712 @return: boolean with the result of the check.
713 """
714 subject = self.results["behavior"]["summary"]["mutexes"]
715 return self._check_value(pattern=pattern,
716 subject=subject,
717 regex=regex)
718
719 - def check_api(self, pattern, process=None, regex=False):
720 """Checks for an API being called.
721 @param pattern: string or expression to check for.
722 @param process: optional filter for a specific process name.
723 @param regex: boolean representing if the pattern is a regular
724 expression or not and therefore should be compiled.
725 @return: boolean with the result of the check.
726 """
727
728 for item in self.results["behavior"]["processes"]:
729
730 if process:
731 if item["process_name"] != process:
732 continue
733
734
735 for call in item["calls"]:
736
737 if self._check_value(pattern=pattern,
738 subject=call["api"],
739 regex=regex):
740 return call["api"]
741
742 return None
743
744 - def check_argument_call(self,
745 call,
746 pattern,
747 name=None,
748 api=None,
749 category=None,
750 regex=False):
751 """Checks for a specific argument of an invoked API.
752 @param call: API call information.
753 @param pattern: string or expression to check for.
754 @param name: optional filter for the argument name.
755 @param api: optional filter for the API function name.
756 @param category: optional filter for a category name.
757 @param regex: boolean representing if the pattern is a regular
758 expression or not and therefore should be compiled.
759 @return: boolean with the result of the check.
760 """
761
762 if api:
763 if call["api"] != api:
764 return False
765
766
767 if category:
768 if call["category"] != category:
769 return False
770
771
772 for argument in call["arguments"]:
773
774 if name:
775 if argument["name"] != name:
776 return False
777
778
779 if self._check_value(pattern=pattern,
780 subject=argument["value"],
781 regex=regex):
782 return argument["value"]
783
784 return False
785
786 - def check_argument(self,
787 pattern,
788 name=None,
789 api=None,
790 category=None,
791 process=None,
792 regex=False):
793 """Checks for a specific argument of an invoked API.
794 @param pattern: string or expression to check for.
795 @param name: optional filter for the argument name.
796 @param api: optional filter for the API function name.
797 @param category: optional filter for a category name.
798 @param process: optional filter for a specific process name.
799 @param regex: boolean representing if the pattern is a regular
800 expression or not and therefore should be compiled.
801 @return: boolean with the result of the check.
802 """
803
804 for item in self.results["behavior"]["processes"]:
805
806 if process:
807 if item["process_name"] != process:
808 continue
809
810
811 for call in item["calls"]:
812 r = self.check_argument_call(call, pattern, name,
813 api, category, regex)
814 if r:
815 return r
816
817 return None
818
819 - def check_ip(self, pattern, regex=False):
820 """Checks for an IP address being contacted.
821 @param pattern: string or expression to check for.
822 @param regex: boolean representing if the pattern is a regular
823 expression or not and therefore should be compiled.
824 @return: boolean with the result of the check.
825 """
826 return self._check_value(pattern=pattern,
827 subject=self.results["network"]["hosts"],
828 regex=regex)
829
830 - def check_domain(self, pattern, regex=False):
831 """Checks for a domain being contacted.
832 @param pattern: string or expression to check for.
833 @param regex: boolean representing if the pattern is a regular
834 expression or not and therefore should be compiled.
835 @return: boolean with the result of the check.
836 """
837 for item in self.results["network"]["domains"]:
838 if self._check_value(pattern=pattern,
839 subject=item["domain"],
840 regex=regex):
841 return item
842
843 return None
844
846 """Checks for a URL being contacted.
847 @param pattern: string or expression to check for.
848 @param regex: boolean representing if the pattern is a regular
849 expression or not and therefore should be compiled.
850 @return: boolean with the result of the check.
851 """
852 for item in self.results["network"]["http"]:
853 if self._check_value(pattern=pattern,
854 subject=item["uri"],
855 regex=regex):
856 return item
857
858 return None
859
861 """Retrieves the value of a specific argument from an API call.
862 @param call: API call object.
863 @param name: name of the argument to retrieve.
864 @return: value of the requried argument.
865 """
866
867
868 if call is not self._current_call_cache:
869 self._current_call_cache = call
870 self._current_call_dict = dict()
871
872 for argument in call["arguments"]:
873 self._current_call_dict[argument["name"]] = argument["value"]
874
875
876 if name in self._current_call_dict:
877 return self._current_call_dict[name]
878
879 return None
880
882 """Notify signature about API call. Return value determines
883 if this signature is done or could still match.
884 @param call: logged API call.
885 @param process: process doing API call.
886 @raise NotImplementedError: this method is abstract.
887 """
888 raise NotImplementedError
889
891 """Evented signature is notified when all API calls are done.
892 @return: Match state.
893 @raise NotImplementedError: this method is abstract.
894 """
895 raise NotImplementedError
896
898 """Start signature processing.
899 @param results: analysis results.
900 @raise NotImplementedError: this method is abstract.
901 """
902 raise NotImplementedError
903
917
919 """Base abstract class for reporting module."""
920 order = 1
921
923 self.analysis_path = ""
924 self.reports_path = ""
925 self.task = None
926 self.options = None
927
929 """Set analysis folder path.
930 @param analysis_path: analysis folder path.
931 """
932 self.analysis_path = analysis_path
933 self.conf_path = os.path.join(self.analysis_path, "analysis.conf")
934 self.file_path = os.path.realpath(os.path.join(self.analysis_path,
935 "binary"))
936 self.reports_path = os.path.join(self.analysis_path, "reports")
937 self.shots_path = os.path.join(self.analysis_path, "shots")
938 self.pcap_path = os.path.join(self.analysis_path, "dump.pcap")
939
940 try:
941 create_folder(folder=self.reports_path)
942 except CuckooOperationalError as e:
943 CuckooReportError(e)
944
946 """Set report options.
947 @param options: report options dict.
948 """
949 self.options = options
950
952 """Add task information.
953 @param task: task dictionary.
954 """
955 self.task = task
956
958 """Start report processing.
959 @raise NotImplementedError: this method is abstract.
960 """
961 raise NotImplementedError
962