签名

cuckoosandbox/cuckoo/common/abstracts.py

  1. # Copyright (C) 2012-2013 Claudio Guarnieri.
  2. # Copyright (C) 2014-2019 Cuckoo Foundation.
  3. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  4. # See the file 'docs/LICENSE' for copying permission.
  5. import logging
  6. import os
  7. import re
  8. import time
  9. import xml.etree.ElementTree as ET
  10. from cuckoo.common.config import config
  11. from cuckoo.common.exceptions import CuckooCriticalError
  12. from cuckoo.common.exceptions import CuckooDependencyError
  13. from cuckoo.common.exceptions import CuckooMachineError
  14. from cuckoo.common.exceptions import CuckooOperationalError
  15. from cuckoo.common.exceptions import CuckooReportError
  16. from cuckoo.common.files import Folders
  17. from cuckoo.common.objects import Dictionary
  18. from cuckoo.core.database import Database
  19. from cuckoo.misc import cwd, make_list
  20. try:
  21. import libvirt
  22. HAVE_LIBVIRT = True
  23. except ImportError:
  24. HAVE_LIBVIRT = False
  25. log = logging.getLogger(__name__)
  26. class Configuration(object):
  27. skip = (
  28. "family", "extra",
  29. )
  30. # Single entry values.
  31. keywords1 = (
  32. "type", "version", "magic", "campaign",
  33. )
  34. # Multiple entry values.
  35. keywords2 = (
  36. "cnc", "url", "mutex", "user_agent", "referrer",
  37. )
  38. # Encryption key values.
  39. keywords3 = (
  40. "des3key", "rc4key", "xorkey", "pubkey", "privkey", "iv",
  41. )
  42. # Normalize keys.
  43. mapping = {
  44. "cncs": "cnc",
  45. "urls": "url",
  46. "user-agent": "user_agent",
  47. }
  48. def __init__(self):
  49. self.entries = []
  50. self.order = []
  51. self.families = {}
  52. def add(self, entry):
  53. self.entries.append(entry)
  54. if entry["family"] not in self.families:
  55. self.families[entry["family"]] = {
  56. "family": entry["family"],
  57. }
  58. self.order.append(entry["family"])
  59. family = self.families[entry["family"]]
  60. for key, value in entry.items():
  61. if key in self.skip or not value:
  62. continue
  63. key = self.mapping.get(key, key)
  64. if key in self.keywords1:
  65. if family.get(key) and family[key] != value:
  66. log.error(
  67. "Duplicate value for %s => %r vs %r",
  68. key, family[key], value
  69. )
  70. continue
  71. family[key] = value
  72. elif key in self.keywords2:
  73. if key not in family:
  74. family[key] = []
  75. for value in make_list(value):
  76. if value and value not in family[key]:
  77. family[key].append(value)
  78. elif key in self.keywords3:
  79. if "key" not in family:
  80. family["key"] = {}
  81. if key not in family["key"]:
  82. family["key"][key] = []
  83. if value not in family["key"][key]:
  84. family["key"][key].append(value)
  85. elif key not in family.get("extra", {}):
  86. if "extra" not in family:
  87. family["extra"] = {}
  88. family["extra"][key] = [value]
  89. elif value not in family["extra"][key]:
  90. family["extra"][key].append(value)
  91. def get(self, family, *keys):
  92. r = self.families.get(family, {})
  93. for key in keys:
  94. r = r.get(key, {})
  95. return r or None
  96. def family(self, name):
  97. return self.families.get(name) or {}
  98. def results(self):
  99. ret = []
  100. for family in self.order:
  101. ret.append(self.families[family])
  102. return ret
  103. class Auxiliary(object):
  104. """Base abstract class for auxiliary modules."""
  105. def __init__(self):
  106. self.task = None
  107. self.machine = None
  108. self.guest_manager = None
  109. self.options = None
  110. @classmethod
  111. def init_once(cls):
  112. pass
  113. def set_task(self, task):
  114. self.task = task
  115. def set_machine(self, machine):
  116. self.machine = machine
  117. def set_guest_manager(self, guest_manager):
  118. self.guest_manager = guest_manager
  119. def set_options(self, options):
  120. self.options = Dictionary(options)
  121. def start(self):
  122. raise NotImplementedError
  123. def stop(self):
  124. raise NotImplementedError
  125. class Machinery(object):
  126. """Base abstract class for machinery modules."""
  127. # Default label used in machinery configuration file to supply virtual
  128. # machine name/label/vmx path. Override it if you dubbed it in another
  129. # way.
  130. LABEL = "label"
  131. def __init__(self):
  132. self.options = None
  133. self.db = Database()
  134. self.remote_control = False
  135. # Machine table is cleaned to be filled from configuration file
  136. # at each start.
  137. self.db.clean_machines()
  138. @classmethod
  139. def init_once(cls):
  140. pass
  141. def pcap_path(self, task_id):
  142. """Return the .pcap path for this task id."""
  143. return cwd("storage", "analyses", "%s" % task_id, "dump.pcap")
  144. def set_options(self, options):
  145. """Set machine manager options.
  146. @param options: machine manager options dict.
  147. """
  148. self.options = options
  149. def initialize(self, module_name):
  150. """Read, load, and verify machines configuration.
  151. @param module_name: module name.
  152. """
  153. # Load.
  154. self._initialize(module_name)
  155. # Run initialization checks.
  156. self._initialize_check()
  157. def _initialize(self, module_name):
  158. """Read configuration.
  159. @param module_name: module name.
  160. """
  161. machinery = self.options.get(module_name)
  162. for vmname in machinery["machines"]:
  163. options = self.options.get(vmname)
  164. # If configured, use specific network interface for this
  165. # machine, else use the default value.
  166. if options.get("interface"):
  167. interface = options["interface"]
  168. else:
  169. interface = machinery.get("interface")
  170. if options.get("resultserver_ip"):
  171. ip = options["resultserver_ip"]
  172. else:
  173. ip = config("cuckoo:resultserver:ip")
  174. if options.get("resultserver_port"):
  175. port = options["resultserver_port"]
  176. else:
  177. # The ResultServer port might have been dynamically changed,
  178. # get it from the ResultServer singleton. Also avoid import
  179. # recursion issues by importing ResultServer here.
  180. from cuckoo.core.resultserver import ResultServer
  181. port = ResultServer().port
  182. self.db.add_machine(
  183. name=vmname,
  184. label=options[self.LABEL],
  185. ip=options.ip,
  186. platform=options.platform,
  187. options=options.get("options", ""),
  188. tags=options.tags,
  189. interface=interface,
  190. snapshot=options.snapshot,
  191. resultserver_ip=ip,
  192. resultserver_port=port
  193. )
  194. def _initialize_check(self):
  195. """Run checks against virtualization software when a machine manager
  196. is initialized.
  197. @note: in machine manager modules you may override or superclass
  198. his method.
  199. @raise CuckooMachineError: if a misconfiguration or a unkown vm state
  200. is found.
  201. """
  202. try:
  203. configured_vms = self._list()
  204. except NotImplementedError:
  205. return
  206. for machine in self.machines():
  207. # If this machine is already in the "correct" state, then we
  208. # go on to the next machine.
  209. if machine.label in configured_vms and \
  210. self._status(machine.label) in [self.POWEROFF, self.ABORTED]:
  211. continue
  212. # This machine is currently not in its correct state, we're going
  213. # to try to shut it down. If that works, then the machine is fine.
  214. try:
  215. self.stop(machine.label)
  216. except CuckooMachineError as e:
  217. raise CuckooCriticalError(
  218. "Please update your configuration. Unable to shut '%s' "
  219. "down or find the machine in its proper state: %s" %
  220. (machine.label, e)
  221. )
  222. if not config("cuckoo:timeouts:vm_state"):
  223. raise CuckooCriticalError(
  224. "Virtual machine state change timeout has not been set "
  225. "properly, please update it to be non-null."
  226. )
  227. def machines(self):
  228. """List virtual machines.
  229. @return: virtual machines list
  230. """
  231. return self.db.list_machines()
  232. def availables(self):
  233. """Return how many machines are free.
  234. @return: free machines count.
  235. """
  236. return self.db.count_machines_available()
  237. def acquire(self, machine_id=None, platform=None, tags=None):
  238. """Acquire a machine to start analysis.
  239. @param machine_id: machine ID.
  240. @param platform: machine platform.
  241. @param tags: machine tags
  242. @return: machine or None.
  243. """
  244. if machine_id:
  245. return self.db.lock_machine(label=machine_id)
  246. elif platform:
  247. return self.db.lock_machine(platform=platform, tags=tags)
  248. else:
  249. return self.db.lock_machine(tags=tags)
  250. def release(self, label=None):
  251. """Release a machine.
  252. @param label: machine name.
  253. """
  254. self.db.unlock_machine(label)
  255. def running(self):
  256. """Return running virtual machines.
  257. @return: running virtual machines list.
  258. """
  259. return self.db.list_machines(locked=True)
  260. def shutdown(self):
  261. """Shutdown the machine manager and kill all alive machines.
  262. @raise CuckooMachineError: if unable to stop machine.
  263. """
  264. if len(self.running()) > 0:
  265. log.info("Still %s guests alive. Shutting down...",
  266. len(self.running()))
  267. for machine in self.running():
  268. try:
  269. self.stop(machine.label)
  270. except CuckooMachineError as e:
  271. log.warning("Unable to shutdown machine %s, please check "
  272. "manually. Error: %s", machine.label, e)
  273. def set_status(self, label, status):
  274. """Set status for a virtual machine.
  275. @param label: virtual machine label
  276. @param status: new virtual machine status
  277. """
  278. self.db.set_machine_status(label, status)
  279. def start(self, label, task):
  280. """Start a machine.
  281. @param label: machine name.
  282. @param task: task object.
  283. @raise NotImplementedError: this method is abstract.
  284. """
  285. raise NotImplementedError
  286. def stop(self, label=None):
  287. """Stop a machine.
  288. @param label: machine name.
  289. @raise NotImplementedError: this method is abstract.
  290. """
  291. raise NotImplementedError
  292. def _list(self):
  293. """List virtual machines configured.
  294. @raise NotImplementedError: this method is abstract.
  295. """
  296. raise NotImplementedError
  297. def dump_memory(self, label, path):
  298. """Take a memory dump of a machine.
  299. @param path: path to where to store the memory dump.
  300. """
  301. raise NotImplementedError
  302. def enable_remote_control(self, label):
  303. """Enable remote control interface (RDP/VNC/SSH).
  304. @param label: machine name.
  305. @return: None
  306. """
  307. raise NotImplementedError
  308. def disable_remote_control(self, label):
  309. """Disable remote control interface (RDP/VNC/SSH).
  310. @param label: machine name.
  311. @return: None
  312. """
  313. raise NotImplementedError
  314. def get_remote_control_params(self, label):
  315. """Return connection details for remote control.
  316. @param label: machine name.
  317. @return: dict with keys: protocol, host, port
  318. """
  319. raise NotImplementedError
  320. def _wait_status(self, label, *states):
  321. """Wait for a vm status.
  322. @param label: virtual machine name.
  323. @param state: virtual machine status, accepts multiple states as list.
  324. @raise CuckooMachineError: if default waiting timeout expire.
  325. """
  326. # This block was originally suggested by Loic Jaquemet.
  327. waitme = 0
  328. try:
  329. current = self._status(label)
  330. except NameError:
  331. return
  332. while current not in states:
  333. log.debug("Waiting %i cuckooseconds for machine %s to switch "
  334. "to status %s", waitme, label, states)
  335. if waitme > config("cuckoo:timeouts:vm_state"):
  336. raise CuckooMachineError(
  337. "Timeout hit while for machine %s to change status" % label
  338. )
  339. time.sleep(1)
  340. waitme += 1
  341. current = self._status(label)
  342. @staticmethod
  343. def version():
  344. """Return the version of the virtualization software"""
  345. return None
  346. class LibVirtMachinery(Machinery):
  347. """Libvirt based machine manager.
  348. If you want to write a custom module for a virtualization software
  349. supported by libvirt you have just to inherit this machine manager and
  350. change the connection string.
  351. """
  352. # VM states.
  353. RUNNING = "running"
  354. PAUSED = "paused"
  355. POWEROFF = "poweroff"
  356. ERROR = "machete"
  357. ABORTED = "abort"
  358. def __init__(self):
  359. if not HAVE_LIBVIRT:
  360. raise CuckooDependencyError(
  361. "The libvirt package has not been installed "
  362. "(`pip install libvirt-python`)"
  363. )
  364. super(LibVirtMachinery, self).__init__()
  365. def initialize(self, module):
  366. """Initialize machine manager module. Override default to set proper
  367. connection string.
  368. @param module: machine manager module
  369. """
  370. super(LibVirtMachinery, self).initialize(module)
  371. def _initialize_check(self):
  372. """Run all checks when a machine manager is initialized.
  373. @raise CuckooMachineError: if libvirt version is not supported.
  374. """
  375. # Version checks.
  376. if not self._version_check():
  377. raise CuckooMachineError("Libvirt version is not supported, "
  378. "please get an updated version")
  379. # Preload VMs
  380. self.vms = self._fetch_machines()
  381. # Base checks. Also attempts to shutdown any machines which are
  382. # currently still active.
  383. super(LibVirtMachinery, self)._initialize_check()
  384. def start(self, label, task):
  385. """Start a virtual machine.
  386. @param label: virtual machine name.
  387. @param task: task object.
  388. @raise CuckooMachineError: if unable to start virtual machine.
  389. """
  390. log.debug("Starting machine %s", label)
  391. if self._status(label) != self.POWEROFF:
  392. msg = "Trying to start a virtual machine that has not " \
  393. "been turned off {0}".format(label)
  394. raise CuckooMachineError(msg)
  395. conn = self._connect()
  396. vm_info = self.db.view_machine_by_label(label)
  397. snapshot_list = self.vms[label].snapshotListNames(flags=0)
  398. # If a snapshot is configured try to use it.
  399. if vm_info.snapshot and vm_info.snapshot in snapshot_list:
  400. # Revert to desired snapshot, if it exists.
  401. log.debug("Using snapshot {0} for virtual machine "
  402. "{1}".format(vm_info.snapshot, label))
  403. try:
  404. vm = self.vms[label]
  405. snapshot = vm.snapshotLookupByName(vm_info.snapshot, flags=0)
  406. self.vms[label].revertToSnapshot(snapshot, flags=0)
  407. except libvirt.libvirtError:
  408. msg = "Unable to restore snapshot {0} on " \
  409. "virtual machine {1}".format(vm_info.snapshot, label)
  410. raise CuckooMachineError(msg)
  411. finally:
  412. self._disconnect(conn)
  413. elif self._get_snapshot(label):
  414. snapshot = self._get_snapshot(label)
  415. log.debug("Using snapshot {0} for virtual machine "
  416. "{1}".format(snapshot.getName(), label))
  417. try:
  418. self.vms[label].revertToSnapshot(snapshot, flags=0)
  419. except libvirt.libvirtError:
  420. raise CuckooMachineError("Unable to restore snapshot on "
  421. "virtual machine {0}".format(label))
  422. finally:
  423. self._disconnect(conn)
  424. else:
  425. self._disconnect(conn)
  426. raise CuckooMachineError("No snapshot found for virtual machine "
  427. "{0}".format(label))
  428. # Check state.
  429. self._wait_status(label, self.RUNNING)
  430. def stop(self, label):
  431. """Stop a virtual machine. Kill them all.
  432. @param label: virtual machine name.
  433. @raise CuckooMachineError: if unable to stop virtual machine.
  434. """
  435. log.debug("Stopping machine %s", label)
  436. if self._status(label) == self.POWEROFF:
  437. raise CuckooMachineError("Trying to stop an already stopped "
  438. "machine {0}".format(label))
  439. # Force virtual machine shutdown.
  440. conn = self._connect()
  441. try:
  442. if not self.vms[label].isActive():
  443. log.debug("Trying to stop an already stopped machine %s. "
  444. "Skip", label)
  445. else:
  446. self.vms[label].destroy() # Machete's way!
  447. except libvirt.libvirtError as e:
  448. raise CuckooMachineError("Error stopping virtual machine "
  449. "{0}: {1}".format(label, e))
  450. finally:
  451. self._disconnect(conn)
  452. # Check state.
  453. self._wait_status(label, self.POWEROFF)
  454. def shutdown(self):
  455. """Override shutdown to free libvirt handlers - they print errors."""
  456. super(LibVirtMachinery, self).shutdown()
  457. # Free handlers.
  458. self.vms = None
  459. def dump_memory(self, label, path):
  460. """Take a memory dump.
  461. @param path: path to where to store the memory dump.
  462. """
  463. log.debug("Dumping memory for machine %s", label)
  464. conn = self._connect()
  465. try:
  466. # Resolve permission issue as libvirt creates the file as
  467. # root/root in mode 0600, preventing us from reading it. This
  468. # supposedly still doesn't allow us to remove it, though..
  469. open(path, "wb").close()
  470. self.vms[label].coreDump(path, flags=libvirt.VIR_DUMP_MEMORY_ONLY)
  471. except libvirt.libvirtError as e:
  472. raise CuckooMachineError("Error dumping memory virtual machine "
  473. "{0}: {1}".format(label, e))
  474. finally:
  475. self._disconnect(conn)
  476. def _status(self, label):
  477. """Get current status of a vm.
  478. @param label: virtual machine name.
  479. @return: status string.
  480. """
  481. log.debug("Getting status for %s", label)
  482. # Stetes mapping of python-libvirt.
  483. # virDomainState
  484. # VIR_DOMAIN_NOSTATE = 0
  485. # VIR_DOMAIN_RUNNING = 1
  486. # VIR_DOMAIN_BLOCKED = 2
  487. # VIR_DOMAIN_PAUSED = 3
  488. # VIR_DOMAIN_SHUTDOWN = 4
  489. # VIR_DOMAIN_SHUTOFF = 5
  490. # VIR_DOMAIN_CRASHED = 6
  491. # VIR_DOMAIN_PMSUSPENDED = 7
  492. conn = self._connect()
  493. try:
  494. state = self.vms[label].state(flags=0)
  495. except libvirt.libvirtError as e:
  496. raise CuckooMachineError("Error getting status for virtual "
  497. "machine {0}: {1}".format(label, e))
  498. finally:
  499. self._disconnect(conn)
  500. if state:
  501. if state[0] == 1:
  502. status = self.RUNNING
  503. elif state[0] == 3:
  504. status = self.PAUSED
  505. elif state[0] == 4 or state[0] == 5:
  506. status = self.POWEROFF
  507. else:
  508. status = self.ERROR
  509. # Report back status.
  510. if status:
  511. self.set_status(label, status)
  512. return status
  513. else:
  514. raise CuckooMachineError("Unable to get status for "
  515. "{0}".format(label))
  516. def _connect(self):
  517. """Connect to libvirt subsystem.
  518. @raise CuckooMachineError: when unable to connect to libvirt.
  519. """
  520. # Check if a connection string is available.
  521. if not self.dsn:
  522. raise CuckooMachineError("You must provide a proper "
  523. "connection string")
  524. try:
  525. return libvirt.open(self.dsn)
  526. except libvirt.libvirtError:
  527. raise CuckooMachineError("Cannot connect to libvirt")
  528. def _disconnect(self, conn):
  529. """Disconnect from libvirt subsystem.
  530. @raise CuckooMachineError: if cannot disconnect from libvirt.
  531. """
  532. try:
  533. conn.close()
  534. except libvirt.libvirtError:
  535. raise CuckooMachineError("Cannot disconnect from libvirt")
  536. def _fetch_machines(self):
  537. """Fetch machines handlers.
  538. @return: dict with machine label as key and handle as value.
  539. """
  540. vms = {}
  541. for vm in self.machines():
  542. vms[vm.label] = self._lookup(vm.label)
  543. return vms
  544. def _lookup(self, label):
  545. """Search for a virtual machine.
  546. @param conn: libvirt connection handle.
  547. @param label: virtual machine name.
  548. @raise CuckooMachineError: if virtual machine is not found.
  549. """
  550. conn = self._connect()
  551. try:
  552. vm = conn.lookupByName(label)
  553. except libvirt.libvirtError:
  554. raise CuckooMachineError("Cannot find machine "
  555. "{0}".format(label))
  556. finally:
  557. self._disconnect(conn)
  558. return vm
  559. def _list(self):
  560. """List available virtual machines.
  561. @raise CuckooMachineError: if unable to list virtual machines.
  562. """
  563. conn = self._connect()
  564. try:
  565. names = conn.listDefinedDomains()
  566. except libvirt.libvirtError:
  567. raise CuckooMachineError("Cannot list domains")
  568. finally:
  569. self._disconnect(conn)
  570. return names
  571. def _version_check(self):
  572. """Check if libvirt release supports snapshots.
  573. @return: True or false.
  574. """
  575. if libvirt.getVersion() >= 8000:
  576. return True
  577. else:
  578. return False
  579. def _get_snapshot(self, label):
  580. """Get current snapshot for virtual machine
  581. @param label: virtual machine name
  582. @return None or current snapshot
  583. @raise CuckooMachineError: if cannot find current snapshot or
  584. when there are too many snapshots available
  585. """
  586. def _extract_creation_time(node):
  587. """Extracts creation time from a KVM vm config file.
  588. @param node: config file node
  589. @return: extracted creation time
  590. """
  591. xml = ET.fromstring(node.getXMLDesc(flags=0))
  592. return xml.findtext("./creationTime")
  593. snapshot = None
  594. conn = self._connect()
  595. try:
  596. vm = self.vms[label]
  597. # Try to get the currrent snapshot, otherwise fallback on the latest
  598. # from config file.
  599. if vm.hasCurrentSnapshot(flags=0):
  600. snapshot = vm.snapshotCurrent(flags=0)
  601. else:
  602. log.debug("No current snapshot, using latest snapshot")
  603. # No current snapshot, try to get the last one from config file.
  604. snapshot = sorted(vm.listAllSnapshots(flags=0),
  605. key=_extract_creation_time,
  606. reverse=True)[0]
  607. except libvirt.libvirtError:
  608. raise CuckooMachineError("Unable to get snapshot for "
  609. "virtual machine {0}".format(label))
  610. finally:
  611. self._disconnect(conn)
  612. return snapshot
  613. def enable_remote_control(self, label):
  614. # TODO: we can't dynamically enable/disable this right now
  615. pass
  616. def disable_remote_control(self, label):
  617. pass
  618. def get_remote_control_params(self, label):
  619. conn = self._connect()
  620. try:
  621. vm = conn.lookupByName(label)
  622. if not vm:
  623. log.warning("No such VM: %s", label)
  624. return {}
  625. port = 0
  626. desc = ET.fromstring(vm.XMLDesc())
  627. for elem in desc.findall("./devices/graphics"):
  628. if elem.attrib.get("type") == "vnc":
  629. # Future work: passwd, listen, socket (addr:port)
  630. port = elem.attrib.get("port")
  631. if port:
  632. port = int(port)
  633. break
  634. finally:
  635. self._disconnect(conn)
  636. if port <= 0:
  637. log.error("VM %s does not have a valid VNC port", label)
  638. return {}
  639. # TODO The Cuckoo Web Interface may be running at a different host
  640. # than the actual Cuckoo daemon (and as such, the VMs).
  641. return {
  642. "protocol": "vnc",
  643. "host": "127.0.0.1",
  644. "port": port,
  645. }
  646. class Processing(object):
  647. """Base abstract class for processing module."""
  648. order = 1
  649. enabled = True
  650. def __init__(self):
  651. self.analysis_path = ""
  652. self.baseline_path = ""
  653. self.logs_path = ""
  654. self.task = None
  655. self.machine = None
  656. self.options = None
  657. self.results = {}
  658. @classmethod
  659. def init_once(cls):
  660. pass
  661. def set_options(self, options):
  662. """Set processing options.
  663. @param options: processing options dict.
  664. """
  665. self.options = Dictionary(options)
  666. def set_task(self, task):
  667. """Add task information.
  668. @param task: task dictionary.
  669. """
  670. self.task = task
  671. def set_machine(self, machine):
  672. """Add machine information."""
  673. self.machine = machine
  674. def set_baseline(self, baseline_path):
  675. """Set the path to the baseline directory."""
  676. self.baseline_path = baseline_path
  677. def set_path(self, analysis_path):
  678. """Set paths.
  679. @param analysis_path: analysis folder path.
  680. """
  681. self.analysis_path = analysis_path
  682. self.log_path = os.path.join(self.analysis_path, "analysis.log")
  683. self.cuckoolog_path = os.path.join(self.analysis_path, "cuckoo.log")
  684. self.file_path = os.path.realpath(os.path.join(self.analysis_path,
  685. "binary"))
  686. self.dropped_path = os.path.join(self.analysis_path, "files")
  687. self.dropped_meta_path = os.path.join(self.analysis_path, "files.json")
  688. self.extracted_path = os.path.join(self.analysis_path, "extracted")
  689. self.package_files = os.path.join(self.analysis_path, "package_files")
  690. self.buffer_path = os.path.join(self.analysis_path, "buffer")
  691. self.logs_path = os.path.join(self.analysis_path, "logs")
  692. self.shots_path = os.path.join(self.analysis_path, "shots")
  693. self.pcap_path = os.path.join(self.analysis_path, "dump.pcap")
  694. self.pmemory_path = os.path.join(self.analysis_path, "memory")
  695. self.memory_path = os.path.join(self.analysis_path, "memory.dmp")
  696. self.mitmout_path = os.path.join(self.analysis_path, "mitm.log")
  697. self.mitmerr_path = os.path.join(self.analysis_path, "mitm.err")
  698. self.tlsmaster_path = os.path.join(self.analysis_path, "tlsmaster.txt")
  699. self.suricata_path = os.path.join(self.analysis_path, "suricata")
  700. self.network_path = os.path.join(self.analysis_path, "network")
  701. self.taskinfo_path = os.path.join(self.analysis_path, "task.json")
  702. def set_results(self, results):
  703. """Set the results - the fat dictionary."""
  704. self.results = results
  705. def run(self):
  706. """Start processing.
  707. @raise NotImplementedError: this method is abstract.
  708. """
  709. raise NotImplementedError
  710. class Signature(object):
  711. """Base class for Cuckoo signatures."""
  712. name = ""
  713. description = ""
  714. severity = 1
  715. order = 1
  716. categories = []
  717. families = []
  718. authors = []
  719. references = []
  720. platform = None
  721. alert = False
  722. enabled = True
  723. minimum = None
  724. maximum = None
  725. ttp = []
  726. # Maximum amount of marks to record.
  727. markcount = 50
  728. # Basic filters to reduce the amount of events sent to this signature.
  729. filter_apinames = []
  730. filter_categories = []
  731. def __init__(self, caller):
  732. """
  733. @param caller: calling object. Stores results in caller.results
  734. """
  735. self.marks = []
  736. self.matched = False
  737. self._caller = caller
  738. # These are set by the caller, they represent the process identifier
  739. # and call index respectively.
  740. self.pid = None
  741. self.cid = None
  742. self.call = None
  743. @classmethod
  744. def init_once(cls):
  745. pass
  746. def _check_value(self, pattern, subject, regex=False, all=False):
  747. """Check a pattern against a given subject.
  748. @param pattern: string or expression to check for.
  749. @param subject: target of the check.
  750. @param regex: boolean representing if the pattern is a regular
  751. expression or not and therefore should be compiled.
  752. @return: boolean with the result of the check.
  753. """
  754. ret = set()
  755. if regex:
  756. exp = re.compile(pattern, re.IGNORECASE)
  757. if isinstance(subject, list):
  758. for item in subject:
  759. if exp.match(item):
  760. ret.add(item)
  761. else:
  762. if exp.match(subject):
  763. ret.add(subject)
  764. else:
  765. if isinstance(subject, list):
  766. for item in subject:
  767. if item.lower() == pattern.lower():
  768. ret.add(item)
  769. else:
  770. if subject == pattern:
  771. ret.add(subject)
  772. # Return all elements.
  773. if all:
  774. return list(ret)
  775. # Return only the first element, if available. Otherwise return None.
  776. elif ret:
  777. return ret.pop()
  778. def get_results(self, key=None, default=None):
  779. if key:
  780. return self._caller.results.get(key, default)
  781. return self._caller.results
  782. def get_processes(self, name=None):
  783. """Get a list of processes.
  784. @param name: If set only return processes with that name.
  785. @return: List of processes or empty list
  786. """
  787. for item in self.get_results("behavior", {}).get("processes", []):
  788. if name is None or item["process_name"] == name:
  789. yield item
  790. def get_process_by_pid(self, pid=None):
  791. """Get a process by its process identifier.
  792. @param pid: pid to search for.
  793. @return: process.
  794. """
  795. for item in self.get_results("behavior", {}).get("processes", []):
  796. if item["pid"] == pid:
  797. return item
  798. def get_summary(self, key=None, default=[]):
  799. """Get one or all values related to the global summary."""
  800. summary = self.get_results("behavior", {}).get("summary", {})
  801. return summary.get(key, default) if key else summary
  802. def get_summary_generic(self, pid, actions):
  803. """Get generic info from summary.
  804. @param pid: pid of the process. None for all
  805. @param actions: A list of actions to get
  806. """
  807. ret = []
  808. for process in self.get_results("behavior", {}).get("generic", []):
  809. if pid is not None and process["pid"] != pid:
  810. continue
  811. for action in actions:
  812. if action in process["summary"]:
  813. ret += process["summary"][action]
  814. return ret
  815. def get_files(self, pid=None, actions=None):
  816. """Get files read, queried, or written to optionally by a
  817. specific process.
  818. @param pid: the process or None for all
  819. @param actions: actions to search for. None is all
  820. @return: yields files
  821. """
  822. if actions is None:
  823. actions = [
  824. "file_opened", "file_written",
  825. "file_read", "file_deleted",
  826. "file_exists", "file_failed",
  827. ]
  828. return self.get_summary_generic(pid, actions)
  829. def get_dll_loaded(self, pid=None):
  830. """Get DLLs loaded by a specific process.
  831. @param pid: the process or None for all
  832. @return: yields DLLs loaded
  833. """
  834. return self.get_summary_generic(pid, ["dll_loaded"])
  835. def get_keys(self, pid=None, actions=None):
  836. """Get registry keys.
  837. @param pid: The pid to look in or None for all.
  838. @param actions: the actions as a list.
  839. @return: yields registry keys
  840. """
  841. if actions is None:
  842. actions = [
  843. "regkey_opened", "regkey_written",
  844. "regkey_read", "regkey_deleted",
  845. ]
  846. return self.get_summary_generic(pid, actions)
  847. def check_file(self, pattern, regex=False, actions=None, pid=None,
  848. all=False):
  849. """Check for a file being opened.
  850. @param pattern: string or expression to check for.
  851. @param regex: boolean representing if the pattern is a regular
  852. expression or not and therefore should be compiled.
  853. @param actions: a list of key actions to use.
  854. @param pid: The process id to check. If it is set to None, all
  855. processes will be checked.
  856. @return: boolean with the result of the check.
  857. """
  858. if actions is None:
  859. actions = [
  860. "file_opened", "file_written",
  861. "file_read", "file_deleted",
  862. "file_exists", "file_failed",
  863. ]
  864. return self._check_value(pattern=pattern,
  865. subject=self.get_files(pid, actions),
  866. regex=regex,
  867. all=all)
  868. def check_dll_loaded(self, pattern, regex=False, actions=None, pid=None,
  869. all=False):
  870. """Check for DLLs being loaded.
  871. @param pattern: string or expression to check for.
  872. @param regex: boolean representing if the pattern is a regular
  873. expression or not and therefore should be compiled.
  874. @param pid: The process id to check. If it is set to None, all
  875. processes will be checked.
  876. @return: boolean with the result of the check.
  877. """
  878. return self._check_value(pattern=pattern,
  879. subject=self.get_dll_loaded(pid),
  880. regex=regex,
  881. all=all)
  882. def check_command_line(self, pattern, regex=False, all=False):
  883. """Check for a command line being opened.
  884. @param pattern: string or expression to check for.
  885. @param regex: boolean representing if the pattern is a regular
  886. expression or not and therefore should be compiled.
  887. @return: boolean with the result of the check.
  888. """
  889. return self._check_value(pattern=pattern,
  890. subject=self.get_summary("command_line"),
  891. regex=regex,
  892. all=all)
  893. def check_key(self, pattern, regex=False, actions=None, pid=None,
  894. all=False):
  895. """Check for a registry key being accessed.
  896. @param pattern: string or expression to check for.
  897. @param regex: boolean representing if the pattern is a regular
  898. expression or not and therefore should be compiled.
  899. @param actions: a list of key actions to use.
  900. @param pid: The process id to check. If it is set to None, all
  901. processes will be checked.
  902. @return: boolean with the result of the check.
  903. """
  904. if actions is None:
  905. actions = [
  906. "regkey_written", "regkey_opened",
  907. "regkey_read", "regkey_deleted",
  908. ]
  909. return self._check_value(pattern=pattern,
  910. subject=self.get_keys(pid, actions),
  911. regex=regex,
  912. all=all)
  913. def get_mutexes(self, pid=None):
  914. """
  915. @param pid: Pid to filter for
  916. @return:List of mutexes
  917. """
  918. return self.get_summary_generic(pid, ["mutex"])
  919. def check_mutex(self, pattern, regex=False, all=False):
  920. """Check for a mutex being opened.
  921. @param pattern: string or expression to check for.
  922. @param regex: boolean representing if the pattern is a regular
  923. expression or not and therefore should be compiled.
  924. @return: boolean with the result of the check.
  925. """
  926. return self._check_value(pattern=pattern,
  927. subject=self.get_mutexes(),
  928. regex=regex,
  929. all=all)
  930. def get_command_lines(self):
  931. """Retrieve all command lines used."""
  932. return self.get_summary("command_line")
  933. def get_wmi_queries(self):
  934. """Retrieve all executed WMI queries."""
  935. return self.get_summary("wmi_query")
  936. def get_net_generic(self, subtype):
  937. """Generic getting network data.
  938. @param subtype: subtype string to search for.
  939. """
  940. return self.get_results("network", {}).get(subtype, [])
  941. def get_net_hosts(self):
  942. """Return a list of all hosts."""
  943. return self.get_net_generic("hosts")
  944. def get_net_domains(self):
  945. """Return a list of all domains."""
  946. return self.get_net_generic("domains")
  947. def get_net_http(self):
  948. """Return a list of all http data."""
  949. return self.get_net_generic("http")
  950. def get_net_http_ex(self):
  951. """Return a list of all http data."""
  952. return \
  953. self.get_net_generic("http_ex") + self.get_net_generic("https_ex")
  954. def get_net_udp(self):
  955. """Return a list of all udp data."""
  956. return self.get_net_generic("udp")
  957. def get_net_icmp(self):
  958. """Return a list of all icmp data."""
  959. return self.get_net_generic("icmp")
  960. def get_net_irc(self):
  961. """Return a list of all irc data."""
  962. return self.get_net_generic("irc")
  963. def get_net_smtp(self):
  964. """Return a list of all smtp data."""
  965. return self.get_net_generic("smtp")
  966. def get_net_smtp_ex(self):
  967. """"Return a list of all smtp data"""
  968. return self.get_net_generic("smtp_ex")
  969. def get_virustotal(self):
  970. """Return the information retrieved from virustotal."""
  971. return self.get_results("virustotal", {})
  972. def get_volatility(self, module=None):
  973. """Return the data that belongs to the given module."""
  974. volatility = self.get_results("memory", {})
  975. return volatility if module is None else volatility.get(module, {})
  976. def get_apkinfo(self, section=None, default={}):
  977. """Return the apkinfo results for this analysis."""
  978. apkinfo = self.get_results("apkinfo", {})
  979. return apkinfo if section is None else apkinfo.get(section, default)
  980. def get_droidmon(self, section=None, default={}):
  981. """Return the droidmon results for this analysis."""
  982. droidmon = self.get_results("droidmon", {})
  983. return droidmon if section is None else droidmon.get(section, default)
  984. def get_googleplay(self, section=None, default={}):
  985. """Return the Google Play results for this analysis."""
  986. googleplay = self.get_results("googleplay", {})
  987. return googleplay if section is None else googleplay.get(section, default)
  988. def check_ip(self, pattern, regex=False, all=False):
  989. """Check for an IP address being contacted.
  990. @param pattern: string or expression to check for.
  991. @param regex: boolean representing if the pattern is a regular
  992. expression or not and therefore should be compiled.
  993. @return: boolean with the result of the check.
  994. """
  995. return self._check_value(pattern=pattern,
  996. subject=self.get_net_hosts(),
  997. regex=regex,
  998. all=all)
  999. def check_domain(self, pattern, regex=False, all=False):
  1000. """Check for a domain being contacted.
  1001. @param pattern: string or expression to check for.
  1002. @param regex: boolean representing if the pattern is a regular
  1003. expression or not and therefore should be compiled.
  1004. @return: boolean with the result of the check.
  1005. """
  1006. domains = set()
  1007. for item in self.get_net_domains():
  1008. domains.add(item["domain"])
  1009. return self._check_value(pattern=pattern,
  1010. subject=list(domains),
  1011. regex=regex,
  1012. all=all)
  1013. def check_url(self, pattern, regex=False, all=False):
  1014. """Check for a URL being contacted.
  1015. @param pattern: string or expression to check for.
  1016. @param regex: boolean representing if the pattern is a regular
  1017. expression or not and therefore should be compiled.
  1018. @return: boolean with the result of the check.
  1019. """
  1020. urls = set()
  1021. for item in self.get_net_http():
  1022. urls.add(item["uri"])
  1023. return self._check_value(pattern=pattern,
  1024. subject=list(urls),
  1025. regex=regex,
  1026. all=all)
  1027. def check_suricata_alerts(self, pattern):
  1028. """Check for pattern in Suricata alert signature
  1029. @param pattern: string or expression to check for.
  1030. @return: True/False
  1031. """
  1032. for alert in self.get_results("suricata", {}).get("alerts", []):
  1033. if re.findall(pattern, alert.get("signature", ""), re.I):
  1034. return True
  1035. return False
  1036. def init(self):
  1037. """Allow signatures to initialize themselves."""
  1038. def mark_call(self, *args, **kwargs):
  1039. """Mark the current call as explanation as to why this signature
  1040. matched."""
  1041. mark = {
  1042. "type": "call",
  1043. "pid": self.pid,
  1044. "cid": self.cid,
  1045. "call": self.call,
  1046. }
  1047. if args or kwargs:
  1048. log.warning(
  1049. "You have provided extra arguments to the mark_call() method "
  1050. "which no longer supports doing so. Please report explicit "
  1051. "IOCs through mark_ioc()."
  1052. )
  1053. self.marks.append(mark)
  1054. def mark_ioc(self, category, ioc, description=None):
  1055. """Mark an IOC as explanation as to why the current signature
  1056. matched."""
  1057. mark = {
  1058. "type": "ioc",
  1059. "category": category,
  1060. "ioc": ioc,
  1061. "description": description,
  1062. }
  1063. # Prevent duplicates.
  1064. if mark not in self.marks:
  1065. self.marks.append(mark)
  1066. def mark_vol(self, plugin, **kwargs):
  1067. """Mark output of a Volatility plugin as explanation as to why the
  1068. current signature matched."""
  1069. mark = {
  1070. "type": "volatility",
  1071. "plugin": plugin,
  1072. }
  1073. mark.update(kwargs)
  1074. self.marks.append(mark)
  1075. def mark_config(self, config):
  1076. """Mark configuration from this malware family."""
  1077. if not isinstance(config, dict) or "family" not in config:
  1078. raise CuckooCriticalError("Invalid call to mark_config().")
  1079. self.marks.append({
  1080. "type": "config",
  1081. "config": config,
  1082. })
  1083. def mark(self, **kwargs):
  1084. """Mark arbitrary data."""
  1085. mark = {
  1086. "type": "generic",
  1087. }
  1088. mark.update(kwargs)
  1089. self.marks.append(mark)
  1090. def has_marks(self, count=None):
  1091. """Return true if this signature has one or more marks."""
  1092. if count is not None:
  1093. return len(self.marks) >= count
  1094. return not not self.marks
  1095. def on_call(self, call, process):
  1096. """Notify signature about API call. Return value determines
  1097. if this signature is done or could still match.
  1098. Only called if signature is "active".
  1099. @param call: logged API call.
  1100. @param process: proc object.
  1101. """
  1102. raise NotImplementedError
  1103. def on_signature(self, signature):
  1104. """Event yielded when another signatures has matched. Some signatures
  1105. only take effect when one or more other signatures have matched as
  1106. well.
  1107. @param signature: The signature that just matched
  1108. """
  1109. def on_process(self, process):
  1110. """Called on process change.
  1111. Can be used for cleanup of flags, re-activation of the signature, etc.
  1112. @param process: dictionary describing this process
  1113. """
  1114. def on_yara(self, category, filepath, match):
  1115. """Called on YARA match.
  1116. @param category: yara match category
  1117. @param filepath: path to the file that matched
  1118. @param match: yara match information
  1119. The Yara match category can be one of the following.
  1120. extracted: an extracted PE image from a process memory dump
  1121. procmem: a process memory dump
  1122. dropped: a dropped file
  1123. """
  1124. def on_extract(self, match):
  1125. """Called on an Extracted match.
  1126. @param match: extracted match information
  1127. """
  1128. def on_complete(self):
  1129. """Signature is notified when all API calls have been processed."""
  1130. def extend_ttp(self):
  1131. """Find the short and long descriptions for the TTPs of a signature"""
  1132. d = {}
  1133. for t in self.ttp:
  1134. d[t] = self._caller.ttp_descriptions.get(t)
  1135. return d
  1136. def results(self):
  1137. """Turn this signature into actionable results."""
  1138. return dict(name=self.name,
  1139. ttp=self.extend_ttp(),
  1140. description=self.description,
  1141. severity=self.severity,
  1142. families=self.families,
  1143. references=self.references,
  1144. marks=self.marks[:self.markcount],
  1145. markcount=len(self.marks))
  1146. @property
  1147. def cfgextr(self):
  1148. return self._caller.c
  1149. class Report(object):
  1150. """Base abstract class for reporting module."""
  1151. order = 1
  1152. def __init__(self):
  1153. self.analysis_path = ""
  1154. self.reports_path = ""
  1155. self.task = None
  1156. self.options = None
  1157. @classmethod
  1158. def init_once(cls):
  1159. pass
  1160. def _get_analysis_path(self, subpath):
  1161. return os.path.join(self.analysis_path, subpath)
  1162. def set_path(self, analysis_path):
  1163. """Set analysis folder path.
  1164. @param analysis_path: analysis folder path.
  1165. """
  1166. self.analysis_path = analysis_path
  1167. self.file_path = os.path.realpath(self._get_analysis_path("binary"))
  1168. self.reports_path = self._get_analysis_path("reports")
  1169. self.shots_path = self._get_analysis_path("shots")
  1170. self.pcap_path = self._get_analysis_path("dump.pcap")
  1171. try:
  1172. Folders.create(self.reports_path)
  1173. except CuckooOperationalError as e:
  1174. raise CuckooReportError(e)
  1175. def set_options(self, options):
  1176. """Set report options.
  1177. @param options: report options dict.
  1178. """
  1179. self.options = Dictionary(options)
  1180. def set_task(self, task):
  1181. """Add task information.
  1182. @param task: task dictionary.
  1183. """
  1184. self.task = task
  1185. def run(self, results):
  1186. """Start report processing.
  1187. @raise NotImplementedError: this method is abstract.
  1188. """
  1189. raise NotImplementedError
  1190. class BehaviorHandler(object):
  1191. """Base class for behavior handlers inside of BehaviorAnalysis."""
  1192. key = "undefined"
  1193. # Behavior event types this handler is interested in.
  1194. event_types = []
  1195. def __init__(self, behavior_analysis):
  1196. self.analysis = behavior_analysis
  1197. def handles_path(self, logpath):
  1198. """Needs to return True for the log files this handler wants to
  1199. process."""
  1200. return False
  1201. def parse(self, logpath):
  1202. """Called after handles_path succeeded, should generate behavior
  1203. events."""
  1204. raise NotImplementedError
  1205. def handle_event(self, event):
  1206. """Handle an event that gets passed down the stack."""
  1207. raise NotImplementedError
  1208. def run(self):
  1209. """Return the handler specific structure, gets placed into
  1210. behavior[self.key]."""
  1211. raise NotImplementedError
  1212. class ProtocolHandler(object):
  1213. """Abstract class for protocol handlers coming out of the analysis."""
  1214. def __init__(self, task_id, ctx, version=None):
  1215. self.task_id = task_id
  1216. self.handler = ctx
  1217. self.fd = None
  1218. self.version = version
  1219. def __enter__(self):
  1220. self.init()
  1221. def __exit__(self, type, value, traceback):
  1222. self.close()
  1223. def close(self):
  1224. if self.fd:
  1225. self.fd.close()
  1226. self.fd = None
  1227. def handle(self):
  1228. raise NotImplementedError
  1229. class Extractor(object):
  1230. """One piece in a series of recursive extractors & unpackers."""
  1231. yara_rules = []
  1232. # Minimum and maximum supported version in Cuckoo.
  1233. minimum = None
  1234. maximum = None
  1235. @classmethod
  1236. def init_once(cls):
  1237. pass
  1238. def __init__(self, parent):
  1239. self.parent = parent
  1240. def handle_yara(self, filepath, match):
  1241. raise NotImplementedError
  1242. def push_command_line(self, cmdline, process=None):
  1243. self.parent.push_command_line(cmdline, process)
  1244. def push_script(self, process, command):
  1245. self.parent.push_script(process, command)
  1246. def push_script_recursive(self, command):
  1247. self.parent.push_script_recursive(command)
  1248. def push_shellcode(self, sc):
  1249. self.parent.push_shellcode(sc)
  1250. def push_blob(self, blob, category, externals, info=None):
  1251. self.parent.push_blob(blob, category, externals, info)
  1252. def push_blob_noyara(self, blob, category, info=None):
  1253. self.parent.push_blob_noyara(blob, category, info)
  1254. def push_config(self, config):
  1255. self.parent.push_config(config)
  1256. def enhance(self, filepath, key, value):
  1257. self.parent.enhance(filepath, key, value)

创建您的新签名

为了让您更好地了解创建签名的过程,我们将一起创建一个非常简单的签名,并逐步介绍这些步骤和可用选项。为此,我们将创建一个签名来检查所分析的恶意软件是否打开了名为“i_am_a_malware”的互斥锁。
首先要做的是导入依赖项,创建骨架并定义一些初始属性。这些是您当前可以设置的:

  • name:签名的标识符。
  • description:对签名所代表的内容的简要描述。
  • severity:标识匹配事件严重性的数字(通常在 1 到 3 之间)。
  • categories:描述匹配事件类型的类别列表(例如“ banker ”、“ injection ”或“ anti-vm ”)。
  • families:恶意软件家族名称的列表,以防签名与已知的特定匹配。
  • authors:创作签名的人员列表。
  • references:为签名提供上下文的引用列表(URL)。
  • enable:如果设置为 False,则将跳过签名。
  • alert:如果设置为 True 可用于指定应报告签名(可能由专用报告模块)。
  • minimum:成功运行此签名所需的最低 Cuckoo 版本。
  • maximum:成功运行此签名所需的最大 Cuckoo 版本。

在我们的示例中,我们将创建以下框架:

  1. from cuckoo.common.abstracts import Signature
  2. class BadBadMalware(Signature): # We initialize the class inheriting Signature.
  3. name = "badbadmalware" # We define the name of the signature
  4. description = "Creates a mutex known to be associated with Win32.BadBadMalware" # We provide a description
  5. severity = 3 # We set the severity to maximum
  6. categories = ["trojan"] # We add a category
  7. families = ["badbadmalware"] # We add the name of our fictional malware family
  8. authors = ["Me"] # We specify the author
  9. minimum = "2.0" # We specify that in order to run the signature, the user will simply need Cuckoo 2.0
  10. def on_complete(self):
  11. return

这是一个完全有效的签名。它还没有真正做任何事情,所以现在我们需要定义签名匹配的条件。
正如我们所说,我们想要匹配一个特定的互斥锁名称,所以我们进行如下操作:

  1. from cuckoo.common.abstracts import Signature
  2. class BadBadMalware(Signature):
  3. name = "badbadmalware"
  4. description = "Creates a mutex known to be associated with Win32.BadBadMalware"
  5. severity = 3
  6. categories = ["trojan"]
  7. families = ["badbadmalware"]
  8. authors = ["Me"]
  9. minimum = "2.0"
  10. def on_complete(self):
  11. return self.check_mutex("i_am_a_malware")

就这么简单,现在我们的签名将返回True是否观察到分析的恶意软件打开了指定的互斥锁。
如果您想更明确并直接访问全局容器,可以通过以下方式转换先前的签名:

  1. from cuckoo.common.abstracts import Signature
  2. class BadBadMalware(Signature):
  3. name = "badbadmalware"
  4. description = "Creates a mutex known to be associated with Win32.BadBadMalware"
  5. severity = 3
  6. categories = ["trojan"]
  7. families = ["badbadmalware"]
  8. authors = ["Me"]
  9. minimum = "2.0"
  10. def on_complete(self):
  11. for process in self.get_processes_by_pid():
  12. if "summary" in process and "mutexes" in process["summary"]:
  13. for mutex in process["summary"]["mutexes"]:
  14. if mutex == "i_am_a_malware":
  15. return True
  16. return False

事件签名

从 1.0 版本开始,Cuckoo 提供了一种编写更高性能签名的方法。过去,每个签名都需要遍历分析期间收集的整个 API 调用集合。当此类集合规模较大时,这会不必要地导致性能问题。
从 1.2 开始,Cuckoo 只支持所谓的“事件签名”。基于该run功能的旧签名可以移植到使用 on_complete
主要区别在于,使用这种新格式,所有签名将并行执行,并且on_call()将通过 API 调用集合在一个循环内为每个签名调用一个回调函数。
使用此技术的示例签名如下:

  1. from cuckoo.common.abstracts import Signature
  2. class SystemMetrics(Signature):
  3. name = "generic_metrics"
  4. description = "Uses GetSystemMetrics"
  5. severity = 2
  6. categories = ["generic"]
  7. authors = ["Cuckoo Developers"]
  8. minimum = "2.0"
  9. # Evented signatures can specify filters that reduce the amount of
  10. # API calls that are streamed in. One can filter Process name, API
  11. # name/identifier and category. These should be sets for faster lookup.
  12. filter_processnames = set()
  13. filter_apinames = set(["GetSystemMetrics"])
  14. filter_categories = set()
  15. # This is a signature template. It should be used as a skeleton for
  16. # creating custom signatures, therefore is disabled by default.
  17. # on_call函数用于event签名。
  18. # 这些使用更有效的方式处理记录的API调用。
  19. enabled = False
  20. def on_complete(self):
  21. # In the on_complete method one can implement any cleanup code and
  22. # decide one last time if this signature matches or not.
  23. # Return True in case it matches.
  24. return False
  25. # This method will be called for every logged API call by the loop
  26. # in the RunSignatures plugin. The return value determines the "state"
  27. # of this signature. True means the signature matched and False it did not this time.
  28. # Use self.deactivate() to stop streaming in API calls.
  29. def on_call(self, call, pid, tid):
  30. # This check would in reality not be needed as we already make use
  31. # of filter_apinames above.
  32. if call["api"] == "GetSystemMetrics":
  33. # Signature matched, return True.
  34. return True
  35. # continue
  36. return None

内联注释已经不言自明。
当签名匹配时触发另一个事件:

  1. required = ["creates_exe", "badmalware"]
  2. def on_signature(self, matched_sig):
  3. if matched_sig in self.required:
  4. self.required.remove(matched_sig)
  5. if not self.required:
  6. return True
  7. return False

这种签名可用于将识别异常的多个签名组合成一个对样本进行分类的签名(恶意软件警报)。

cuckoosandbox/community/modules/signatures

android

android_antivirus_virustotal.py
android_dangerous_permissions.py
android_dynamic_code.py
android_embedded_apk.py
android_google_play_diff.py
android_native_code.py
android_reflection_code.py

Application

application_aborted_broadcast_receiver.py
application_deleted_app.py
application_executed_shell_command.py
application_installed_app.py
application_queried_account_info.py
application_queried_installed_apps.py
application_queried_phone_number.py
application_queried_private_information.py
application_recording_audio.py
application_registered_receiver_runtime.py
application_sent_sms_messages.py
application_stopped_processes.py
application_uses_location.py
application_using_the_camera.py

cross

buffer.py
buffer2.py
html_flash.py
js_eval.py
js_iframe.py
js_suspicious.py
keys.py
static_pdf.py

darwin

code_injection.py
task_for_pid.py

extractor

dde.py
filetypes.py
ole.py
powerfun.py
unicorn.py

linux

network

dead_host.py
dns_cnc.py
dns_tld.py

network_bind.py:一套API调用

  1. from lib.cuckoo.common.abstracts import Signature
  2. class NetworkBIND(Signature):
  3. name = "network_bind"
  4. description = "Starts servers listening"
  5. severity = 2
  6. categories = ["bind"]
  7. authors = ["nex", "Accuvant"]
  8. minimum = "2.0"
  9. filter_apinames = "bind", "listen", "accept"
  10. def init(self):
  11. self.mask = 0
  12. def on_call(self, call, process):
  13. if call["api"] == "bind":
  14. self.mark_call()
  15. self.mask |= 1
  16. if call["api"] == "listen":
  17. self.mark_call()
  18. self.mask |= 2
  19. if call["api"] == "accept":
  20. self.mark_call()
  21. self.mask |= 4
  22. def on_complete(self):
  23. return self.mask == 7

network_cnc_http.py
network_dyndns.py
network_http.py
network_icmp.py
network_irc.py
network_smtp.py
network_torgateway.py
network_wscript.py
nolookup_communication.py
p2p_cnc.py
snort.py

suricata.py

  1. import re
  2. from lib.cuckoo.common.abstracts import Signature
  3. mapping = {
  4. "lokibot": "loki",
  5. }
  6. # Obviously needs some more work.
  7. protocols = {
  8. 80: "http",
  9. 443: "https",
  10. }
  11. class SuricataAlert(Signature):
  12. name = "suricata_alert"
  13. description = "Raised Suricata alerts"
  14. severity = 3
  15. categories = ["network"]
  16. authors = ["Cuckoo Technologies"]
  17. minimum = "2.0"
  18. et_trojan = "ET TROJAN", "ETPRO TROJAN"
  19. blocklist = (
  20. "executable", "potential", "likely", "rogue", "supicious", "generic",
  21. "possible", "known", "common", "troj", "trojan", "team", "probably",
  22. "w2km", "http", "abuse", "win32", "unknown", "single", "filename",
  23. "worm", "fake", "malicious", "observed", "windows",
  24. )
  25. family_next = (
  26. "win32", "win64", "w32", "ransomware",
  27. )
  28. def extract_family(self, signature):
  29. words = re.findall("[A-Za-z0-9]+", signature)
  30. if len(words) < 3:
  31. return
  32. family = words[2].lower()
  33. if family in self.family_next and len(words) > 3:
  34. family = words[3].lower()
  35. if family in self.blocklist or len(family) < 4:
  36. return
  37. # If it exists in our mapping, normalize the name.
  38. return mapping.get(family, family)
  39. def on_complete(self):
  40. alerts = []
  41. for alert in self.get_results("suricata", {}).get("alerts", []):
  42. if alert["signature"] in alerts:
  43. continue
  44. if not alert["signature"].startswith(self.et_trojan):
  45. continue
  46. # Extract text between parentheses.
  47. reg_type = re.search("\\(([A-Za-z0-9_]+)\\)", alert["signature"])
  48. reg_type = reg_type.group(1) if reg_type else None
  49. family = self.extract_family(alert["signature"])
  50. if not family:
  51. continue
  52. self.mark_config({
  53. "family": family.title(),
  54. "cnc": "%s://%s:%s" % (
  55. protocols.get(alert["dst_port"], "tcp"),
  56. alert["dst_ip"], alert["dst_port"]
  57. ),
  58. "type": reg_type,
  59. })
  60. self.mark_ioc("suricata", alert["signature"])
  61. alerts.append(alert["signature"])
  62. return self.has_marks()

windows

Anti

Anti-AV

antiav_avast_libs.py:通过模块名和参数名,确定调用的模块
  1. from lib.cuckoo.common.abstracts import Signature
  2. class AvastDetectLibs(Signature):
  3. name = "antiav_avast_libs"
  4. description = "Detects Avast Antivirus through the presence of a library"
  5. severity = 3
  6. categories = ["anti-av"]
  7. authors = ["Optiv"]
  8. minimum = "2.0"
  9. ttp = ["T1063"]
  10. filter_apinames = set(["LdrLoadDll", "LdrGetDllHandle"])
  11. def on_call(self, call, process):
  12. dllname = call["arguments"]["module_name"]
  13. if "snxhk" in dllname.lower():
  14. self.mark_call()
  15. def on_complete(self):
  16. return self.has_marks()

antiav_bitdefender_libs.py
antiav_detectfile.py
antiav_detectreg.py
antiav_servicestop.py
antiav_srp.py

Anti-DBG

antidbg_debuggercheck.py
antidbg_devices.py
antidbg_windows.py

🙋‍♀️🙋‍♀️🙋‍♀️Anti-SandBox🙋‍♀️🙋‍♀️🙋‍♀️

antisandbox_clipboard.py
antisandbox_cuckoo_files.py
antisandbox_file.py
antisandbox_forehwnd.py
antisandbox_fortinet_files.py
antisandbox_idletime.py
antisandbox_joe_anubis_files.py
antisandbox_mouse_hook.py
antisandbox_restart.py
antisandbox_sleep.py
antisandbox_sunbelt.py
antisandbox_sunbelt_files.py
antisandbox_threattrack_files.py
antisandbox_unhook.py

Anti-Virus

antivirus_detection_cn.py
antivirus_irma.py
antivirus_virustotal.py

Anti-VM

antivm_bochs_keys.py
antivm_computername.py
antivm_disksize.py
antivm_generic_bios.py
antivm_generic_cpu.py
antivm_generic_disk.py
antivm_generic_firmware.py
antivm_generic_ide.py
antivm_generic_scsi.py
antivm_generic_services.py
antivm_hyperv_keys.py
antivm_memory_available.py
antivm_network_adapter.py
antivm_parallels_keys.py
antivm_parallels_window.py
antivm_psuedo_device.py
antivm_sandboxie.py
antivm_vbox_acpi.py
antivm_vbox_devices.py
antivm_vbox_files.py
antivm_vbox_keys.py
antivm_vbox_provname.py
antivm_vbox_window.py
antivm_virtualpc.py
antivm_virtualpc_magic.py
antivm_virtualpc_window.py
antivm_vmware_files.py
antivm_vmware_in_insn.py
antivm_vmware_keys.py
antivm_vmware_window.py
antivm_vpc_keys.py
antivm_xen_keys.py

APT

apt_carbunak.py:互斥体字符串+正则

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # This signature was contributed by RedSocks - http://redsocks.nl
  4. # See the file 'docs/LICENSE' for copying permission.
  5. from lib.cuckoo.common.abstracts import Signature
  6. class APT_Carbunak(Signature):
  7. name = "apt_carbunak"
  8. description = "Creates known Carbunak/Anunak APT files, registry keys and/or mutexes"
  9. severity = 3
  10. categories = ["apt"]
  11. families = ["carbunak"]
  12. authors = ["RedSocks"]
  13. minimum = "2.0"
  14. mutexes_re = [
  15. ".*Uw1HDFMKPAlFRFYZ.*",
  16. ".*VVpHVlcJbQtFQVNP.*",
  17. ".*BAgXCgAIbQtFFwRP.*",
  18. ".*WApFWgRebQtFRFZP.*",
  19. ".*VlxDV1lSbQtFTVdP.*",
  20. ".*AwxMCwcIbQtFTQBP.*",
  21. ".*UghMW1BbbQtFRlNP.*",
  22. ".*Vl5FCABfbQtFRQNP.*",
  23. ".*Vg9GC1FbbQtFQlRP.*",
  24. ".*BQ9EXgBbbQtFF1RP.*",
  25. ]
  26. def on_complete(self):
  27. for indicator in self.mutexes_re:
  28. for mutex in self.check_mutex(pattern=indicator, regex=True, all=True):
  29. self.mark_ioc("mutex", mutex)
  30. return self.has_marks()

apt_cloudatlas.py:文件名字符串+正则

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # See the file 'docs/LICENSE' for copying permission.
  4. from lib.cuckoo.common.abstracts import Signature
  5. class APT_CloudAtlas(Signature):
  6. name = "apt_cloudatlas"
  7. description = "Creates known CloudAtlas APT files, registry keys and/or mutexes"
  8. severity = 3
  9. categories = ["apt"]
  10. families = ["cloudatlas"]
  11. authors = ["RedSocks"]
  12. minimum = "2.0"
  13. files_re = [
  14. ".*steinheimman",
  15. ".*papersaving",
  16. ".*previliges",
  17. ".*fundamentive",
  18. ".*bicorporate",
  19. ".*miditiming",
  20. ".*damnatorily",
  21. ".*munnopsis",
  22. ".*arzner",
  23. ".*redtailed",
  24. ".*roodgoose",
  25. ".*acholias",
  26. ".*salefians",
  27. ".*wartworts",
  28. ".*frequencyuse",
  29. ".*nonmagyar",
  30. ".*shebir",
  31. ".*getgoing",
  32. ]
  33. def on_complete(self):
  34. for indicator in self.files_re:
  35. for filepath in self.check_file(pattern=indicator, regex=True, all=True):
  36. self.mark_ioc("file", filepath)
  37. return self.has_marks()

apt_flame.py:互斥体名+注册表路径+正则

  1. # Copyright (C) 2012 Claudio "nex" Guarnieri (@botherder)
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. from lib.cuckoo.common.abstracts import Signature
  16. class Flame(Signature):
  17. name = "targeted_flame"
  18. description = "Shows some indicators associated with the Flame malware"
  19. severity = 3
  20. categories = ["targeted"]
  21. families = ["flame", "skywiper"]
  22. authors = ["nex"]
  23. minimum = "2.0"
  24. references = [
  25. "http://www.crysys.hu/skywiper/skywiper.pdf",
  26. "http://www.securelist.com/en/blog/208193522/The_Flame_Questions_and_Answers",
  27. "http://www.certcc.ir/index.php?name=news&file=article&sid=1894",
  28. ]
  29. mutexes_re = [
  30. ".*__fajb",
  31. ".*DVAAccessGuard",
  32. ".*mssecuritymgr"
  33. ]
  34. regkeys_re = [
  35. ".*\\\\Microsoft\\ Shared\\\\MSSecurityMgr\\\\.*",
  36. ".*\\\\Ef_trace\\.log$"
  37. ]
  38. def on_complete(self):
  39. for indicator in self.mutexes_re:
  40. mutex = self.check_mutex(pattern=indicator, regex=True)
  41. if mutex:
  42. self.mark_ioc("mutex", mutex)
  43. for indicator in self.regkeys_re:
  44. filepath = self.check_file(pattern=indicator, regex=True)
  45. if filepath:
  46. self.mark_ioc("file", filepath)
  47. return self.has_marks()

apt_inception.py:文件名+正则

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # This signature was contributed by RedSocks - http://redsocks.nl
  4. # See the file 'docs/LICENSE' for copying permission.
  5. from lib.cuckoo.common.abstracts import Signature
  6. class InceptionAPT(Signature):
  7. name = "apt_inception"
  8. description = "Creates known Inception APT files, registry keys and/or mutexes"
  9. severity = 3
  10. categories = ["apt"]
  11. families = ["inception"]
  12. authors = ["RedSocks"]
  13. references = [
  14. "https://www.bluecoat.com/security-blog/2014-12-09/blue-coat-exposes-%E2%80%9C-inception-framework%E2%80%9D-very-sophisticated-layered-malware",
  15. ]
  16. minimum = "2.0"
  17. files_re = [
  18. ".*polymorphed.*dll",
  19. ]
  20. def on_complete(self):
  21. for indicator in self.files_re:
  22. for filepath in self.check_file(pattern=indicator, regex=True, all=True):
  23. self.mark_ioc("file", filepath)
  24. return self.has_marks()

apt_putter_panda.py:互斥体名+正则

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # This signature was contributed by RedSocks - http://redsocks.nl
  4. # See the file 'docs/LICENSE' for copying permission.
  5. from lib.cuckoo.common.abstracts import Signature
  6. class PutterpandaMutexes(Signature):
  7. name = "putterpanda_mutexes"
  8. description = "Creates known Putter Panda APT mutexes"
  9. severity = 3
  10. categories = ["rat"]
  11. families = ["panda"]
  12. authors = ["RedSocks"]
  13. minimum = "2.0"
  14. mutexes_re = [
  15. ".*__PDH_PLA_MUTEX__",
  16. ]
  17. def on_complete(self):
  18. for indicator in self.mutexes_re:
  19. if self.check_mutex(pattern=indicator, regex=True):
  20. return True

apt_sandworm_ip.py:固定IP(IoCs)

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # This signature was contributed by RedSocks - http://redsocks.nl
  4. # See the file 'docs/LICENSE' for copying permission.
  5. from lib.cuckoo.common.abstracts import Signature
  6. class apt_sandworm_ip(Signature):
  7. name = "apt_sandworm_ip"
  8. description = "Connects to Known Sandworm APT IP address"
  9. severity = 2
  10. categories = ["apt"]
  11. authors = ["RedSocks"]
  12. minimum = "2.0"
  13. ipaddrs = [
  14. "95.143.193.131",
  15. "46.165.222.6",
  16. "78.46.40.239",
  17. "144.76.119.48",
  18. "37.220.34.56",
  19. "46.4.28.218",
  20. "95.143.193.182",
  21. "5.61.38.31",
  22. "94.185.80.66",
  23. "95.211.122.36"
  24. ]
  25. def on_complete(self):
  26. for ipaddr in self.ipaddrs:
  27. if self.check_ip(pattern=ipaddr):
  28. self.mark_ioc("ipaddr", ipaddr)
  29. return self.has_marks()

apt_sandworm_url.py:URL+正则

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # This signature was contributed by RedSocks - http://redsocks.nl
  4. # See the file 'docs/LICENSE' for copying permission.
  5. from lib.cuckoo.common.abstracts import Signature
  6. class apt_sandworm_url(Signature):
  7. name = "apt_sandworm_url"
  8. description = "Uses Known Sandworm APT URL Indicator"
  9. severity = 2
  10. categories = ["apt"]
  11. authors = ["RedSocks"]
  12. minimum = "2.0"
  13. urls_re = [
  14. ".*YXJyYWtpczAy.*",
  15. ".*aG91c2VhdHJlaWRlczk0.*",
  16. ".*\/loadvers\/paramctrl\.php",
  17. ".*\/dirconf\/check\.php",
  18. ".*\/siteproperties\/viewframes\/dialog\.php",
  19. ]
  20. def on_complete(self):
  21. for url in self.urls_re:
  22. if self.check_url(pattern=url, regex=True):
  23. self.mark_ioc("url", url)
  24. return self.has_marks()

apt_turlacarbon.py

  1. # Copyright (C) 2015 Robby Zeitfuchs (@robbyFux)
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. from lib.cuckoo.common.abstracts import Signature
  16. class TurlaCarbon(Signature):
  17. name = "apt_turlacarbon"
  18. description = "Appears to be the targeted Turla Carbon malware"
  19. severity = 3
  20. alert = True
  21. categories = ["apt"]
  22. families = ["turla", "uroburos", "snake"]
  23. authors = ["Robby Zeitfuchs", "@robbyFux"]
  24. minimum = "2.0"
  25. references = [
  26. "https://blog.gdatasoftware.com/blog/article/analysis-of-project-cobra.html",
  27. "https://malwr.com/analysis/MTI2M2RjYTAyZmNmNDE4ZTk5MDBkZjA4MDA5ZTFjMDc/",
  28. ]
  29. filter_apinames = "NtWriteFile",
  30. regkey_indicator = ".*\\\\ActiveComputerName$"
  31. buffer_indicators = [
  32. "[NAME]",
  33. "[TIME]",
  34. "iproc",
  35. "user_winmin",
  36. "user_winmax",
  37. "object_id",
  38. ]
  39. def init(self):
  40. self.wrote = False
  41. def on_call(self, call, process):
  42. # Check whether each buffer indicator is in this buffer write.//检查每个缓冲区指示器是否在这个缓冲区写。
  43. for indicator in self.buffer_indicators:
  44. if indicator not in call["arguments"]["buffer"]:
  45. break
  46. else:
  47. self.wrote = True
  48. self.mark_call()
  49. def on_complete(self):
  50. if not self.check_key(self.regkey_indicator, regex=True):
  51. return
  52. if self.wrote:
  53. return True

apt_uroburos_file.py

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # This signature was contributed by RedSocks - http://redsocks.nl
  4. # See the file 'docs/LICENSE' for copying permission.
  5. from lib.cuckoo.common.abstracts import Signature
  6. class UroburosFile(Signature):
  7. name = "uroburos_file"
  8. description = "Creates known Turla/Uroburos APT files"
  9. severity = 3
  10. categories = ["rat"]
  11. families = ["uroburos"]
  12. authors = ["RedSocks"]
  13. minimum = "2.0"
  14. mutexes_re = [
  15. ".*turla10",
  16. ".*msdata\\\\.*",
  17. ".*1396695624",
  18. ]
  19. def on_complete(self):
  20. for mutex in self.mutexes_re:
  21. if self.check_mutex(pattern=mutex, regex=True):
  22. return True

apt_uroburos_mutex.py

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # This signature was contributed by RedSocks - http://redsocks.nl
  4. # See the file 'docs/LICENSE' for copying permission.
  5. from lib.cuckoo.common.abstracts import Signature
  6. class UroburosMutexes(Signature):
  7. name = "uroburos_mutexes"
  8. description = "Creates known Turla/Uroburos APT mutexes"
  9. severity = 3
  10. categories = ["rat"]
  11. families = ["uroburos"]
  12. authors = ["RedSocks"]
  13. minimum = "2.0"
  14. files_re = [
  15. ".*\\\\drivers\\\\wo2ifsl.sys",
  16. ".*\\\\drivers\\\\acpied.sys",
  17. ".*\\\\drivers\\\\atmarpd.sys",
  18. ".*\\\\temp\\\\msmsgsmon.exe",
  19. ".*\\\\temp\\\\msdattst.ocx",
  20. ]
  21. def on_complete(self):
  22. for indicator in self.files_re:
  23. if self.check_mutex(pattern=indicator, regex=True):
  24. return True
  25. if self.check_file(pattern=indicator, regex=True):
  26. return True

BackDoor

backdoor_lolbot.py
backdoor_sdbot.py
backdoor_tdss.py
backdoor_vanbot.py
backdoor_whimoo.py

Banker

banker_bancos.py
banker_cridex.py
banker_prinimalka.py
banker_spyeye_mutex.py
banker_spyeye_url.py
banker_tinba_mutex.py
banker_zeus_mutex.py
banker_zeus_p2p.py
banker_zeus_url.py
banking_mutex.py

BitCoin

bitcoin_opencl.py

Boot

bootconfig_modify.py
bootkit.py

Bot

bot_athena_url.py
bot_athenahttp.py
bot_betabot_url.py
bot_dirtjumper.py
bot_drive.py
bot_drive2.py
bot_kelihos.py
bot_kovter.py
bot_madness.py
bot_pony_url.py
bot_russkill.py
bot_solar_url.py
bot_vnloader.py
bot_warbot_url.py

Browser

browser_bho.py
browser_security.py
browser_startpage.py

ByPass

bypass_firewall.py

Cloud

cloud_dropbox.py
cloud_google.py
cloud_mediafire.py
cloud_mega.py
cloud_rapidshare.py
cloud_wetransfer.py
cloudflare.py

Create

creates_doc.py
creates_exe.py
creates_hidden_file.py
creates_largekey.py
creates_null_reg_entry.py
creates_service.py
creates_shortcut.py

Crypto

crypto_apis.py
cryptomining.py

DDOS

ddos_blackrev_mutex.py
ddos_darkddos_mutex.py
ddos_eclipse_mutex.py
ddos_ipkiller_mutex.py

Detect

detect_putty.py
detect_winscp.py

Disable

disables_app.py
disables_browserwarn.py
disables_security.py
disables_sysrestore.py
disables_wer.py
disables_windowsupdate.py

DNS

dns_dyndns_provider.py
dns_exp3322.py
dns_freehosting_domain.py

Exec

exec_bitsadmin.py
exec_crash.py
exec_waitfor.py

Exploit

exploit_blackhole_url.py
exploit_mutex.py
exploit_sweetorange_mutex.py
exploitation.py

IM

im_bittorrent_bleep.py
im_qq.py

InfoStealer

infostealer_bitcoin.py
infostealer_browser.py
infostealer_browser_modifications.py
infostealer_clipboard.py
infostealer_derusbi_file.py
infostealer_ftp.py
infostealer_im.py
infostealer_keylogger.py
infostealer_mail.py

Injection

injection_explorer.py
injection_memorymodify.py
injection_network_traffic.py
injection_runpe.py
injection_thread.py
injection_writememory.py

Keylogger

keylogger_ardamax_mutex.py
keylogger_jintor_mutex.py

Locate

locates_browser.py
locates_sniffer.py

Locker

locker_cmd.py
locker_regedit.py
locker_taskmgr.py

MemDump

memdump_urls.py
memdump_yara.py

Modify

modifies_certs.py
modifies_proxies.py
modifies_seccenter.py
modifies_uac_notify.py
modifies_wallpaper.py
modifies_zoneid.py

NetWork

network_rdp_mutex.py
network_service_mirc.py
network_tor.py
network_tor_service.py
network_urlshort_cn.py
network_vnc_mutex.py

Office

office.py

  1. # Copyright (C) 2016 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # See the file 'docs/LICENSE' for copying permission.
  4. import ntpath
  5. import re
  6. from lib.cuckoo.common.abstracts import Signature
  7. network_objects = [
  8. "microsoft.xmlhttp",
  9. "msxml2.serverxmlhttp",
  10. "msxml2.xmlhttp",
  11. "msxml2.serverxmlhttp.6.0",
  12. "winhttp.winhttprequest.5.1",
  13. ]
  14. class OfficeCreateObject(Signature):
  15. name = "office_create_object"
  16. description = "Creates suspicious VBA object"
  17. severity = 3
  18. categories = ["vba"]
  19. authors = ["Cuckoo Technologies"]
  20. minimum = "2.0"
  21. ttp = ["T1203"]
  22. filter_apinames = "vbe6_CreateObject", "vbe6_GetObject"
  23. objects = {
  24. "adodb.stream": "file",
  25. "scripting.filesystemobject": "file",
  26. "shell.application": "process",
  27. "wscript.shell": "process",
  28. }
  29. # Include all globally defined network objects.
  30. objects.update(dict((_, "network") for _ in network_objects))
  31. descriptions = {
  32. "network": "May attempt to connect to the outside world",
  33. "file": "May attempt to write one or more files to the harddisk",
  34. "process": "May attempt to create new processes",
  35. }
  36. def on_call(self, call, process):
  37. objname = call["arguments"]["object_name"]
  38. if objname.lower() not in self.objects:
  39. return
  40. description = self.descriptions[self.objects[objname.lower()]]
  41. self.mark_ioc("com_class", objname, description)
  42. return True
  43. class OfficeCheckProjectName(Signature):
  44. name = "office_check_project_name"
  45. description = "Office checks VB project name"
  46. severity = 1
  47. categories = ["vba"]
  48. authors = ["FDD", "Cuckoo Sandbox"]
  49. minimum = "2.0"
  50. filter_apinames = "vbe6_Invoke",
  51. def on_call(self, call, process):
  52. if call["arguments"]["funcname"] != "macroname":
  53. return
  54. self.mark_call()
  55. return True
  56. class OfficeCountDirectories(Signature):
  57. name = "office_count_dirs"
  58. description = "Office document invokes CountDirectories (possible anti-sandbox)"
  59. severity = 2
  60. categories = ["vba"]
  61. authors = ["FDD @ Cuckoo Technologies"]
  62. minimum = "2.0"
  63. ttp = ["T1203"]
  64. filter_apinames = "vbe6_Invoke",
  65. def on_call(self, call, process):
  66. if call["arguments"]["funcname"] != "CountDirectories":
  67. return
  68. self.mark_call()
  69. return True
  70. class OfficeCheckVersion(Signature):
  71. name = "office_appinfo_version"
  72. description = "Office document checks Office version (possible anti-sandbox)"
  73. severity = 2
  74. categories = ["vba"]
  75. authors = ["FDD", "Cuckoo Technologies"]
  76. minimum = "2.0"
  77. filter_apinames = "vbe6_Invoke",
  78. def on_call(self, call, process):
  79. if "args" not in call["arguments"]:
  80. return
  81. if call["arguments"]["funcname"] != "AppInfo":
  82. return
  83. if call["arguments"]["args"][0] != 2:
  84. return
  85. self.mark_call()
  86. return True
  87. class OfficeCheckWindow(Signature):
  88. name = "office_check_window"
  89. description = "Office document checks Office window size (possible anti-sandbox)"
  90. severity = 2
  91. categories = ["vba"]
  92. authors = ["FDD @ Cuckoo Technologies"]
  93. minimum = "2.0"
  94. filter_apinames = "vbe6_Invoke",
  95. def on_call(self, call, process):
  96. if "args" not in call["arguments"]:
  97. return
  98. if call["arguments"]["funcname"] != "AppInfo":
  99. return
  100. if call["arguments"]["args"][0] != 7:
  101. return
  102. self.mark_call()
  103. return True
  104. class OfficeHttpRequest(Signature):
  105. name = "office_http_request"
  106. description = "Office document performs HTTP request (possibly to download malware)"
  107. severity = 5
  108. categories = ["vba"]
  109. authors = ["Cuckoo Technologies"]
  110. minimum = "2.0"
  111. ttp = ["T1203", "T1071"]
  112. filter_apinames = "vbe6_Invoke",
  113. def on_call(self, call, process):
  114. # This checks if this instance method invocation belongs to
  115. # a known network class (e.g., "MSXML2.XMLHTTP").
  116. if call["flags"].get("this", "").lower() not in network_objects:
  117. return
  118. # The .Open method specifies the URL.
  119. if call["arguments"]["funcname"] != "Open":
  120. return
  121. # Usually ["GET", "url", False].
  122. if len(call["arguments"]["args"]) == 3:
  123. self.mark_ioc("payload_url", call["arguments"]["args"][1])
  124. return True
  125. class OfficeRecentFiles(Signature):
  126. name = "office_recent_files"
  127. description = "Uses RecentFiles to determine whether it is running in a sandbox"
  128. severity = 4
  129. categories = ["vba"]
  130. authors = ["Cuckoo Technologies"]
  131. minimum = "2.0"
  132. filter_apinames = "vbe6_Invoke",
  133. def on_call(self, call, process):
  134. if call["arguments"]["funcname"] == "RecentFiles":
  135. self.mark_call()
  136. return True
  137. class HasOfficeEps(Signature):
  138. name = "has_office_eps"
  139. description = "Located potentially malicious Encapsulated Post Script (EPS) file"
  140. severity = 3
  141. categories = ["office"]
  142. authors = ["Cuckoo Technologies"]
  143. minimum = "2.0"
  144. def on_complete(self):
  145. office = self.get_results("static", {}).get("office", {})
  146. if office.get("eps", []):
  147. return True
  148. class OfficeIndirectCall(Signature):
  149. name = "office_indirect_call"
  150. description = "Office document has indirect calls"
  151. severity = 1
  152. categories = ["office"]
  153. authors = ["FDD @ Cuckoo Technologies"]
  154. minimum = "2.0"
  155. ttp = ["T1203"]
  156. patterns = [
  157. "CallByName[^\r\n;']*",
  158. ]
  159. def on_complete(self):
  160. office = self.get_results("static", {}).get("office", {})
  161. if "macros" in office:
  162. for macro in office["macros"]:
  163. for pattern in self.patterns:
  164. matches = re.findall(pattern, macro["deobf"])
  165. for match in matches:
  166. self.mark_ioc("Statement", match)
  167. return self.has_marks()
  168. class OfficeCheckName(Signature):
  169. name = "office_check_doc_name"
  170. description = "Office document checks it's own name"
  171. severity = 2
  172. categories = ["office"]
  173. authors = ["FDD", "Cuckoo Technologies"]
  174. minimum = "2.0"
  175. patterns = [
  176. "[^\n\r;']*Me.Name[^\n\r;']*",
  177. ]
  178. def on_complete(self):
  179. office = self.get_results("static", {}).get("office", {})
  180. if "macros" in office:
  181. for macro in office["macros"]:
  182. for pattern in self.patterns:
  183. matches = re.findall(pattern, macro["deobf"])
  184. for match in matches:
  185. self.mark_ioc("Statement", match)
  186. return self.has_marks()
  187. class OfficePlatformDetect(Signature):
  188. name = "office_platform_detect"
  189. description = "Office document tries to detect platform"
  190. severity = 2
  191. categories = ["office"]
  192. authors = ["FDD @ Cuckoo Technologies"]
  193. minimum = "2.0"
  194. patterns = [
  195. "#If\s+(?:Not\s+)?Win32",
  196. "#If\s+Mac\s*=\s(?:1|0)"
  197. ]
  198. def on_complete(self):
  199. office = self.get_results("static", {}).get("office", {})
  200. if "macros" in office:
  201. for macro in office["macros"]:
  202. for pattern in self.patterns:
  203. matches = re.findall(pattern, macro["deobf"])
  204. for match in matches:
  205. self.mark_ioc("Statement", match)
  206. return self.has_marks()
  207. class DocumentClose(Signature):
  208. name = "document_close"
  209. description = "Word document hooks document close"
  210. severity = 2
  211. categories = ["office"]
  212. authors = ["FDD", "Cuckoo Technologies"]
  213. minimum = "2.0"
  214. ttp = ["T1179"]
  215. def on_complete(self):
  216. office = self.get_results("static", {}).get("office", {})
  217. if "macros" in office:
  218. for macro in office["macros"]:
  219. if "Sub Document_Close()" in macro["deobf"]:
  220. return True
  221. class DocumentOpen(Signature):
  222. name = "document_open"
  223. description = "Word document hooks document open"
  224. severity = 2
  225. categories = ["office"]
  226. authors = ["FDD", "Cuckoo Technologies"]
  227. minimum = "2.0"
  228. ttp = ["T1179"]
  229. def on_complete(self):
  230. office = self.get_results("static", {}).get("office", {})
  231. if "macros" in office:
  232. for macro in office["macros"]:
  233. if "Sub Document_Open()" in macro["deobf"]:
  234. return True
  235. class OfficeEpsStrings(Signature):
  236. name = "office_eps_strings"
  237. description = "Suspicious keywords embedded in an Encapsulated Post Script (EPS) file"
  238. severity = 3
  239. categories = ["office"]
  240. authors = ["Cuckoo Technologies"]
  241. minimum = "2.0"
  242. keywords = [
  243. "longjmp", "NtCreateEvent", "NtProtectVirtualMemory",
  244. ]
  245. def on_complete(self):
  246. office = self.get_results("static", {}).get("office", {})
  247. for s in office.get("eps", []):
  248. if s.strip() in self.keywords:
  249. self.mark_ioc("eps_string", s)
  250. return self.has_marks()
  251. class OfficeVulnerableGuid(Signature):
  252. name = "office_vuln_guid"
  253. description = "GUIDs known to be associated with a CVE were requested (may be False Positive)"
  254. severity = 3
  255. categories = ["office"]
  256. authors = ["Niels Warnars @ Cuckoo Technologies"]
  257. minimum = "2.0"
  258. ttp = ["T1203"]
  259. bad_guids = {
  260. "BDD1F04B-858B-11D1-B16A-00C0F0283628": "CVE-2012-0158",
  261. "996BF5E0-8044-4650-ADEB-0B013914E99C": "CVE-2012-0158",
  262. "C74190B6-8589-11d1-B16A-00C0F0283628": "CVE-2012-0158",
  263. "9181DC5F-E07D-418A-ACA6-8EEA1ECB8E9E": "CVE-2012-0158",
  264. "1EFB6596-857C-11D1-B16A-00C0F0283628": "CVE-2012-1856",
  265. "66833FE6-8583-11D1-B16A-00C0F0283628": "CVE-2012-1856",
  266. "1EFB6596-857C-11D1-B16A-00C0F0283628": "CVE-2013-3906",
  267. "DD9DA666-8594-11D1-B16A-00C0F0283628": "CVE-2014-1761",
  268. "00000535-0000-0010-8000-00AA006D2EA4": "CVE-2015-0097",
  269. "0E59F1D5-1FBE-11D0-8FF2-00A0D10038BC": "CVE-2015-0097",
  270. "05741520-C4EB-440A-AC3F-9643BBC9F847": "CVE-2015-1641",
  271. "A08A033D-1A75-4AB6-A166-EAD02F547959": "CVE-2015-1641",
  272. "F4754C9B-64F5-4B40-8AF4-679732AC0607": "CVE-2015-1641",
  273. "4C599241-6926-101B-9992-00000B65C6F9": "CVE-2015-2424",
  274. "44F9A03B-A3EC-4F3B-9364-08E0007F21DF": "CVE-2015-2424",
  275. }
  276. def on_complete(self):
  277. summary = self.get_results("behavior", {}).get("summary", {})
  278. for guid in summary.get("guid", []):
  279. if guid.upper() in self.bad_guids:
  280. self.mark_ioc("cve", self.bad_guids[guid.upper()])
  281. return self.has_marks()
  282. class OfficeVulnModules(Signature):
  283. name = "office_vuln_modules"
  284. description = "Libraries known to be associated with a CVE were requested (may be False Positive)"
  285. severity = 3
  286. categories = ["office"]
  287. authors = ["Niels Warnars @ Cuckoo Technologies"]
  288. minimum = "2.0"
  289. ttp = ["T1203"]
  290. bad_modules = {
  291. "ogl.dll": "CVE-2013-3906",
  292. "oart.dll": "CVE-2013-3906",
  293. "packager.dll": "CVE-2014-4114/6352",
  294. "olkloadr.dll": "CVE-2015-1641",
  295. "epsimp32.flt": "CVE-2015-2545",
  296. }
  297. def on_complete(self):
  298. summary = self.get_results("behavior", {}).get("summary", {})
  299. for module in summary.get("dll_loaded", []):
  300. module = ntpath.split(module)[1]
  301. if module.lower() in self.bad_modules:
  302. self.mark_ioc("cve", self.bad_modules[module.lower()])
  303. return self.has_marks()

office_packager.py

office_rtf.py

  1. # Copyright (C) 2018 Kevin Ross
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. from lib.cuckoo.common.abstracts import Signature
  16. class RTFUnknownVersion(Signature):
  17. name = "rtf_unknown_version"
  18. description = "RTF file has an unknown version"
  19. severity = 2
  20. categories = ["office"]
  21. authors = ["Kevin Ross"]
  22. minimum = "2.0"
  23. def on_complete(self):
  24. target = self.get_results("target", {})
  25. filetype = target.get("file", {}).get("type") or ""
  26. name = target.get("file", {}).get("name")
  27. if "Rich Text Format data" in filetype and "unknown version" in filetype:
  28. self.mark(
  29. filename=name,
  30. filetype_details=filetype,
  31. )
  32. for dropped in self.get_results("dropped", []):
  33. if "filepath" in dropped:
  34. droppedtype = dropped["type"]
  35. droppedname = dropped["name"]
  36. if "Rich Text Format data" in droppedtype and "unknown version" in droppedtype:
  37. self.mark(
  38. dropped_filename=droppedname,
  39. dropped_filetype_details=filetype,
  40. )
  41. return self.has_marks()
  42. class RTFCharacterSet(Signature):
  43. name = "rtf_unknown_character_set"
  44. description = "RTF file has an unknown character set"
  45. severity = 2
  46. categories = ["office"]
  47. authors = ["Kevin Ross"]
  48. minimum = "2.0"
  49. def on_complete(self):
  50. target = self.get_results("target", {})
  51. filetype = target.get("file", {}).get("type") or ""
  52. name = target.get("file", {}).get("name")
  53. if "Rich Text Format data" in filetype and "unknown character set" in filetype:
  54. self.mark(
  55. filename=name,
  56. filetype_details=filetype,
  57. )
  58. for dropped in self.get_results("dropped", []):
  59. if "filepath" in dropped:
  60. droppedtype = dropped["type"]
  61. droppedname = dropped["name"]
  62. if "Rich Text Format data" in droppedtype and "unknown character set" in droppedtype:
  63. self.mark(
  64. dropped_filename=droppedname,
  65. dropped_filetype_details=filetype,
  66. )
  67. return self.has_marks()

Packer

packer_entropy.py
packer_polymorphic.py
packer_upx.py
packer_vmprotect.py

Persistence

persistence_ads.py
persistence_autorun.py
persistence_bootexecute.py
persistence_registry_fileless.py

POS

pos_alina_file.py
pos_alina_url.py
pos_blackpos_url.py
pos_decebal_mutex.py
pos_dexter.py
pos_jackpos_file.py
pos_jackpos_url.py
pos_poscardstealer_url.py

PowerShell

powerfun.py
powershell.py
powershell_reg.py
powerworm.py

Process

process_interest.py
process_needed.py

Ransom

ransom_mutex.py
ransomware_bcdedit.py
ransomware_fileextensions.py
ransomware_filemodications.py
ransomware_files.py
ransomware_message.py
ransomware_recyclebin.py
ransomware_shadowcopy.py
ransomware_viruscoder.py
ransomware_wbadmin.py

RAT

rat_adzok.py
rat_bandook.py
rat_beastdoor.py
rat_beebus_mutex.py
rat_bifrose.py
rat_blackhole.py
rat_blackice.py
rat_blackshades.py
rat_bladabindi.py
rat_bottilda.py
rat_bozok.py
rat_buzus.py
rat_comRAT.py
rat_cybergate.py
rat_darkcloud.py
rat_darkshell.py
rat_delf.py
rat_dibik.py
rat_evilbot.py
rat_farfli.py
rat_fexel_ip.py
rat_flystudio.py
rat_fynloski.py
rat_ghostbot.py
rat_hesperbot.py
rat_hikit.py
rat_hupigon.py
rat_icepoint.py
rat_jewdo.py
rat_jorik.py
rat_karakum.py
rat_koutodoor.py
rat_kuluoz.py
rat_likseput.py
rat_madness.py
rat_madness_url.py
rat_magania_mutex.py
rat_minerbot.py
rat_mybot.py
rat_naid_ip.py
rat_nakbot.py
rat_netobserve.py
rat_netshadow.py
rat_netwire.py
rat_nitol.py
rat_njrat.py
rat_pasta.py
rat_pcclient.py
rat_plugx.py
rat_poebot.py
rat_poisonivy.py
rat_qakbot.py
rat_rbot.py
rat_renos.py
rat_sadbot.py
rat_senna.py
rat_shadowbot.py
rat_siggen.py
rat_spynet.py
rat_spyrecorder.py
rat_staser.py
rat_swrort.py
rat_teamviewer.py
rat_travnet.py
rat_trogbot.py
rat_turkojan.py
rat_urlspy.py
rat_urxbot.py
rat_vertexnet.py
rat_vertexnet_url.py
rat_wakbot.py
rat_xtreme.py
rat_zegost.py

Recon

recon_beacon.py
recon_checkip.py
recon_fingerprint.py
recon_programs.py
recon_systeminfo.py

RootKit

rootkit_blackenergy_mutex.py

  1. # Copyright (C) 2010-2015 Cuckoo Foundation.
  2. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  3. # This signature was contributed by RedSocks - http://redsocks.nl
  4. # See the file 'docs/LICENSE' for copying permission.
  5. from lib.cuckoo.common.abstracts import Signature
  6. class BlackEnergyMutexes(Signature):
  7. name = "blackenergy_mutexes"
  8. description = "Creates known BlackEnergy Rootkit mutexes"
  9. severity = 3
  10. categories = ["rootkit"]
  11. families = ["blackenergy"]
  12. authors = ["RedSocks"]
  13. minimum = "2.0"
  14. mutexes_re = [
  15. ".*\\{CD56173D-1A7D-4E99-8109-A71BB04263DF\\}",
  16. ]
  17. def on_complete(self):
  18. for indicator in self.mutexes_re:
  19. match = self.check_mutex(pattern=indicator, regex=True)
  20. if match:
  21. self.mark_ioc("mutex", match)
  22. return self.has_marks()

SMTP

smtp_gmail.py
smtp_live.py
smtp_mailru.py
smtp_yahoo.py

Stealth

stealth_childproc.py
stealth_hiddenextension.py
stealth_hiddenfile.py
stealth_hiddenicons.py
stealth_hidenotifications.py
stealth_systemprocname.py
stealth_window.py

Trojan

trojan_bublik.py
trojan_ceatrg.py
trojan_coinminer.py
trojan_dapato.py
trojan_emotet.py
trojan_jorik.py
trojan_kilim.py
trojan_lethic.py
trojan_lockscreen.py
trojan_mrblack.py
trojan_obfus_mutex.py
trojan_pincav.py
trojan_redosru.py
trojan_rovnix.py
trojan_sysn.py
trojan_tnega_mutex.py
trojan_vbinject.py
trojan_yoddos.py
trojandl_begseabug_mutex.py
trojandl_upatre_mutex.py

Virus

vir_andromeda.py
vir_bagle.py
vir_banload.py
vir_btc.py
vir_c24_url.py
vir_cryptolocker.py
vir_ddos556.py
vir_decay.py
vir_dofoil.py
vir_dyreza.py
vir_expiro.py
vir_fakeav2_mutex.py
vir_fakeav_mutex.py
vir_gaelicum.py
vir_infinity.py
vir_ircbrute.py
vir_isrstealer.py
vir_istealer_url.py
vir_karagany.py
vir_katusha.py
vir_killdisk.py
vir_koobface.py
vir_luder.py
vir_napolar.py
vir_nebuler.py
vir_oldrea.py
vir_perflogger.py
vir_pidief.py
vir_ponfoy.py
vir_pykse.py
vir_ragebot.py
vir_ramnit.py
vir_sharpstealer.py
vir_shiz.py
vir_shylock.py
vir_ufr3.py
vir_upatre.py
vir_virut.py
virus_jeefo_mutex.py
virus_tufik_mutex.py

Windows

windows_console.py
windows_utilities.py

Worm

worm_allaple.py
worm_fesber_mutex.py
worm_kolabc.py
worm_krepper_mutex.py
worm_palevo.py
worm_phorpiex.py
worm_psyokym.py
worm_puce_mutex.py
worm_renocide.py
worm_rungbu.py
worm_runouce_mutex.py
worm_winsxsbot.py
worm_xworm.py

其他

antiemu_wine.py
allocates_rwx.py
antianalysis_detectfile.py
appinit.py
applocker_bypass.py
bad_certs.py
carberp_mutex.py
clears_logs.py
clickfraud.py
credential_dump.py
dde.py
deepfreeze_mutex.py

deletes_executed.py

  1. # Copyright (C) 2014 Optiv Inc. (brad.spengler@optiv.com), Converted 2016 for Cuckoo 2.0
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. from lib.cuckoo.common.abstracts import Signature
  16. class DeletesExecutedFiles(Signature):
  17. name = "deletes_executed_files"
  18. description = "Deletes executed files from disk"
  19. severity = 3
  20. categories = ["persistence", "stealth"]
  21. authors = ["Optiv", "Kevin Ross"]
  22. minimum = "2.0"
  23. ttp = ["T1070"]
  24. evented = True
  25. def on_complete(self):
  26. processes = []
  27. for process in self.get_results("behavior", {}).get("generic", []):
  28. for cmdline in process.get("summary", {}).get("command_line", []):
  29. processes.append(cmdline)
  30. if processes:
  31. for deletedfile in self.get_files(actions=["file_deleted"]):
  32. if deletedfile in processes[0]:
  33. self.mark_ioc("file", deletedfile)
  34. return self.has_marks()

downloader_cabby.py
dridex_apis.py
driver_load.py

dropper.py

  1. # Copyright (C) 2014 Optiv Inc. (brad.spengler@optiv.com), Updated 2016 for Cuckoo 2.0
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. from lib.cuckoo.common.abstracts import Signature
  16. class Dropper(Signature):
  17. name = "dropper"
  18. description = "Drops a binary and executes it"
  19. severity = 2
  20. categories = ["dropper"]
  21. authors = ["Optiv"]
  22. minimum = "2.0"
  23. ttp = ["T1129"]
  24. def __init__(self, *args, **kwargs):
  25. Signature.__init__(self, *args, **kwargs)
  26. self.executed = []
  27. self.exe = False
  28. if self.get_results("target", {}).get("category") == "file":
  29. f = self.get_results("target", {}).get("file", {})
  30. if "PE32 executable" in f.get("type", ""):
  31. self.exe = True
  32. filter_apinames = "CreateProcessInternalW", "ShellExecuteExW"
  33. def on_call(self, call, process):
  34. filepath = call["arguments"]["filepath"]
  35. if filepath not in self.executed:
  36. self.executed.append(filepath)
  37. def on_complete(self):
  38. for executed in self.executed:
  39. for dropped in self.get_results("dropped", []):
  40. if "filepath" in dropped:
  41. filepath = dropped["filepath"]
  42. if executed == filepath:
  43. self.mark_ioc("file", executed)
  44. if not self.exe:
  45. self.severity = 3
  46. return self.has_marks()
  47. class ExeAppData(Signature):
  48. name = "exe_appdata"
  49. description = "Drops an executable to the user AppData folder"
  50. severity = 2
  51. categories = ["dropper", "persistence"]
  52. authors = ["Kevin Ross"]
  53. minimum = "2.0"
  54. ttp = ["T1129"]
  55. def on_complete(self):
  56. for dropped in self.get_results("dropped", []):
  57. if "filepath" in dropped and dropped["type"].startswith("PE32 executable"):
  58. filepath = dropped["filepath"]
  59. if "\\Users\\" in filepath and "\\AppData\\" in filepath:
  60. self.mark_ioc("file", filepath)
  61. return self.has_marks()

emotet_apis.py
emoves_zoneid_ads.py
excel_datalink_files.py
fraud_fakerean.py
hacktool_pwdump_file.py
has_authenticode.py
has_pdb.py
javascript_commandline.py
maldoc.py
martians.py
mining.py
moves_self.py
multiple_ua.py
nymaim_apis.py
origin_langid.py
payload_download.py
pe_features.py
privileges.py
protection_rx.py
raises_exception.py
reads_user_agent.py
self_delete_bat.py
sharing_rghost.py
shellcode.py
sipstun.py
sniffer_winpcap.py
spreading_autoruninf.py
stops_service.py
suspicious_process.py
tapi_mutex.py
terminates_process.py
volatility_sig.py
wmi.py
url_file.py