def build_options(self): """Generate analysis options. @return: options dict. """ options = {} options["id"] = self.task.id options["ip"] = self.cfg.resultserver.ip options["port"] = self.cfg.resultserver.port options["category"] = self.task.category options["target"] = self.task.target options["package"] = self.task.package options["options"] = self.task.options options["enforce_timeout"] = self.task.enforce_timeout if not self.task.timeout or self.task.timeout == 0: options["timeout"] = self.cfg.timeouts.default else: options["timeout"] = self.task.timeout if self.task.category == "file": options["file_name"] = File(self.task.target).get_name() options["file_type"] = File(self.task.target).get_type() return options
def run(self): """Run file information gathering. @return: information dict. """ self.key = "target" target_info = {"category": self.task["category"]} if self.task["category"] == "file": target_info["file"] = File(self.file_path).get_all() target_info["file"]["name"] = File(self.task["target"]).get_name() elif self.task["category"] == "url": target_info["url"] = self.task["target"] return target_info
def add_path(self, file_path, timeout=0, package="", options="", priority=1, custom="", machine="", platform="", memory=False, enforce_timeout=False): """Add a task to database from file path. @param file_path: sample path. @param timeout: selected timeout. @param options: analysis options. @param priority: analysis priority. @param custom: custom options. @param machine: selected machine. @param platform: platform. @param memory: toggle full memory dump. @param enforce_timeout: toggle full timeout execution. @return: cursor or None. """ if not file_path or not os.path.exists(file_path): return None return self.add(File(file_path), timeout, package, options, priority, custom, machine, platform, memory, enforce_timeout)
def run(self): self.key = "network" results = Pcap(self.pcap_path).run() # Save PCAP file hash. if os.path.exists(self.pcap_path): results["pcap_sha256"] = File(self.pcap_path).get_sha256() return results
def run(self, results): """Writes report. @param results: Cuckoo results dict. @raise CuckooReportError: if fails to write report. """ if not HAVE_JINJA2: raise CuckooReportError( "Failed to generate HTML report: Jinja2 Python library is not installed" ) shots_path = os.path.join(self.analysis_path, "shots") if os.path.exists(shots_path): shots = [] counter = 1 for shot_name in os.listdir(shots_path): if not shot_name.endswith(".jpg"): continue shot_path = os.path.join(shots_path, shot_name) if os.path.getsize(shot_path) == 0: continue shot = {} shot["id"] = os.path.splitext(File(shot_path).get_name())[0] shot["data"] = base64.b64encode(open(shot_path, "rb").read()) shots.append(shot) counter += 1 shots.sort(key=lambda shot: shot["id"]) results["screenshots"] = shots else: results["screenshots"] = [] results["apt"] = random.choice(HAX0RS) env = Environment(autoescape=True) env.loader = FileSystemLoader(os.path.join(CUCKOO_ROOT, "data", "html")) try: tpl = env.get_template("report.html") html = tpl.render({"results": results}) except Exception as e: raise CuckooReportError("Failed to generate HTML report: %s" % e) try: report = open(os.path.join(self.reports_path, "report.html"), "w") report.write(html) report.close() except (TypeError, IOError) as e: raise CuckooReportError("Failed to write HTML report: %s" % e) return True
def run(self): """Run analysis. @return: results dict. """ self.key = "static" static = {} if HAVE_PEFILE: if self.task["category"] == "file": if "PE32" in File(self.file_path).get_type(): static = PortableExecutable(self.file_path).run() return static
def run(self): """Run analysis. @return: list of dropped files with related information. """ self.key = "dropped" dropped_files = [] for dir_name, dir_names, file_names in os.walk(self.dropped_path): for file_name in file_names: file_path = os.path.join(dir_name, file_name) file_info = File(file_path=file_path, strip_name=True).get_all() dropped_files.append(file_info) return dropped_files
def run(self): """Runs VirusTotal processing @return: full VirusTotal report. """ self.key = "virustotal" virustotal = [] if not VIRUSTOTAL_KEY: raise CuckooProcessingError("VirusTotal API key not configured, skip") if self.task["category"] == "file": if not os.path.exists(self.file_path): raise CuckooProcessingError("File {0} not found, skip".format(self.file_path)) resource = File(self.file_path).get_md5() url = VIRUSTOTAL_FILE_URL elif self.task["category"] == "url": resource = self.task.target url = VIRUSTOTAL_URL_URL data = urllib.urlencode({"resource" : resource, "apikey" : VIRUSTOTAL_KEY}) try: request = urllib2.Request(url, data) response = urllib2.urlopen(request) response_data = response.read() except urllib2.URLError as e: raise CuckooProcessingError("Unable to establish connection to VirusTotal: {0}".format(e)) except urllib2.HTTPError as e: raise CuckooProcessingError("Unable to perform HTTP request to VirusTotal (http code={0})".format(e.code)) try: virustotal = json.loads(response_data) except ValueError as e: raise CuckooProcessingError("Unable to convert response to JSON: {0}".format(e)) return virustotal
def store_file(self): """Store a copy of the file being analyzed.""" if not os.path.exists(self.task.target): log.error( "The file to analyze does not exist at path \"%s\", " "analysis aborted", self.task.target) return False sha256 = File(self.task.target).get_sha256() self.binary = os.path.join(CUCKOO_ROOT, "storage", "binaries", sha256) if os.path.exists(self.binary): log.info("File already exists at \"%s\"", self.binary) else: # TODO: do we really need to abort the analysis in case we are not # able to store a copy of the file? try: shutil.copy(self.task.target, self.binary) except (IOError, shutil.Error) as e: log.error( "Unable to store file from \"%s\" to \"%s\", " "analysis aborted", self.task.target, self.binary) return False try: new_binary_path = os.path.join(self.storage, "binary") if hasattr(os, "symlink"): os.symlink(self.binary, new_binary_path) else: shutil.copy(self.binary, new_binary_path) except (AttributeError, OSError) as e: log.error("Unable to create symlink/copy from \"%s\" to \"%s\"", self.binary, self.storage) return True
def setUp(self): self.tmp = tempfile.mkstemp() self.file = File(self.tmp[1])
class TestFile: def setUp(self): self.tmp = tempfile.mkstemp() self.file = File(self.tmp[1]) def test_get_name(self): assert_equal(self.tmp[1].split("/")[-1], self.file.get_name()) def test_get_data(self): assert_equal("", self.file.get_data()) def test_get_size(self): assert_equal(0, self.file.get_size()) def test_get_crc32(self): assert_equal("00000000", self.file.get_crc32()) def test_get_md5(self): assert_equal("d41d8cd98f00b204e9800998ecf8427e", self.file.get_md5()) def test_get_sha1(self): assert_equal("da39a3ee5e6b4b0d3255bfef95601890afd80709", self.file.get_sha1()) def test_get_sha256(self): assert_equal("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", self.file.get_sha256()) def test_get_sha512(self): assert_equal("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", self.file.get_sha512()) def test_get_ssdeep(self): try: import pydeep assert_not_equal(None, self.file.get_ssdeep()) except ImportError: assert_equal(None, self.file.get_ssdeep()) def test_get_type(self): assert_equal("empty", self.file.get_type()) def test_get_all_type(self): assert isinstance(self.file.get_all(), dict) def test_get_all_keys(self): for key in ["name", "size", "crc32", "md5", "sha1", "sha256", "sha512", "ssdeep", "type"]: assert key in self.file.get_all() def tearDown(self): os.remove(self.tmp[1])
class TestFile: def setUp(self): self.tmp = tempfile.mkstemp() self.file = File(self.tmp[1]) def test_get_name(self): assert_equal(self.tmp[1].split("/")[-1], self.file.get_name()) def test_get_data(self): assert_equal("", self.file.get_data()) def test_get_size(self): assert_equal(0, self.file.get_size()) def test_get_crc32(self): assert_equal("00000000", self.file.get_crc32()) def test_get_md5(self): assert_equal("d41d8cd98f00b204e9800998ecf8427e", self.file.get_md5()) def test_get_sha1(self): assert_equal("da39a3ee5e6b4b0d3255bfef95601890afd80709", self.file.get_sha1()) def test_get_sha256(self): assert_equal( "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", self.file.get_sha256()) def test_get_sha512(self): assert_equal( "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", self.file.get_sha512()) def test_get_ssdeep(self): try: import pydeep assert_not_equal(None, self.file.get_ssdeep()) except ImportError: assert_equal(None, self.file.get_ssdeep()) def test_get_type(self): assert_equal("empty", self.file.get_type()) def test_get_all_type(self): assert isinstance(self.file.get_all(), dict) def test_get_all_keys(self): for key in [ "name", "size", "crc32", "md5", "sha1", "sha256", "sha512", "ssdeep", "type" ]: assert key in self.file.get_all() def tearDown(self): os.remove(self.tmp[1])
def run(self, results): """Writes report. @param results: analysis results dictionary. @raise CuckooReportError: if fails to connect or write to MongoDB. """ self.connect() # Create a copy of the dictionary. This is done in order to not modify # the original dictionary and possibly compromise the following # reporting modules. report = dict(results) # Set an unique index on stored files, to avoid duplicates. # From pymongo docs: # Returns the name of the created index if an index is actually created. # Returns None if the index already exists. self.db.fs.files.ensure_index("md5", unique=True, name="md5_unique") # Store the PCAP file in GridFS and reference it back in the report. pcap_path = os.path.join(self.analysis_path, "dump.pcap") pcap = File(pcap_path) if pcap.valid(): pcap_id = self.store_file(pcap) report["network"] = {"pcap_id": pcap_id} report["network"].update(results["network"]) # Walk through the dropped files, store them in GridFS and update the # report with the ObjectIds. new_dropped = [] for dropped in report["dropped"]: new_drop = dict(dropped) drop = File(dropped["path"]) if drop.valid(): dropped_id = self.store_file(drop, filename=dropped["name"]) new_drop["object_id"] = dropped_id new_dropped.append(new_drop) report["dropped"] = new_dropped # Add screenshots. report["shots"] = [] shots_path = os.path.join(self.analysis_path, "shots") if os.path.exists(shots_path): # Walk through the files and select the JPGs. shots = [shot for shot in os.listdir(shots_path) if shot.endswith(".jpg")] for shot_file in sorted(shots): shot_path = os.path.join(self.analysis_path, "shots", shot_file) shot = File(shot_path) # If the screenshot path is a valid file, store it and # reference it back in the report. if shot.valid(): shot_id = self.store_file(shot) report["shots"].append(shot_id) # Store chunks of API calls in a different collection and reference # those chunks back in the report. In this way we should defeat the # issue with the oversized reports exceeding MongoDB's boundaries. # Also allows paging of the reports. new_processes = [] for process in report["behavior"]["processes"]: new_process = dict(process) chunk = [] chunks_ids = [] # Loop on each process call. for index, call in enumerate(process["calls"]): # If the chunk size is 100 or if the loop is completed then # store the chunk in MongoDB. if len(chunk) == 100: chunk_id = self.db.calls.insert({"pid" : process["process_id"], "calls" : chunk}) chunks_ids.append(chunk_id) # Reset the chunk. chunk = [] # Append call to the chunk. chunk.append(call) # Store leftovers. if chunk: chunk_id = self.db.calls.insert({"pid" : process["process_id"], "calls" : chunk}) chunks_ids.append(chunk_id) # Add list of chunks. new_process["calls"] = chunks_ids new_processes.append(new_process) # Store the results in the report. report["behavior"] = dict(report["behavior"]) report["behavior"]["processes"] = new_processes # Store the report and retrieve its object id. self.db.analysis.insert(report) self.conn.disconnect()