def richpe_info(self, pe, cli_mode=False): res = {} if pe.RICH_HEADER: info = get_richpe_info(pe) cli_out("ProdId\tVersion\tCount\tProduct", cli_mode) for i in info: res = { "product_id": i["prodid"], "version": i["version"], "count": i["count"] } if i['product']: res["product"] = i["product"] cli_out( "{}\t{}\t{}\t{}".format(i['prodid'], i['version'], i['count'], i['product']), cli_mode) else: cli_out( "{}\t{}\t{}".format(i['prodid'], i['version'], i['count']), cli_mode) rich_hash = get_richpe_hash(pe) cli_out("\nRichPE Hash: {}".format(rich_hash), cli_mode) res["md5"] = rich_hash else: cli_out("No RichPE Header", cli_mode) return {"richpe": res} if res else {}
def get_results(self, data, min_len=4, wide_only=False, ascii_only=False, cli_mode=False): # regular expressions from flare-floss: # https://github.com/fireeye/flare-floss/blob/master/floss/strings.py#L7-L9 re_narrow = re.compile(b'([%s]{%d,})' % (ASCII_BYTE, min_len)) re_wide = re.compile(b'((?:[%s]\x00){%d,})' % (ASCII_BYTE, min_len)) strings = [] # print ascii strings unless we only want wide strings if not wide_only: for match in re_narrow.finditer(data): s = match.group().decode('ascii') strings.append(s) cli_out(s, cli_mode) # print wide strings unless we only want ascii strings if not ascii_only: for match in re_wide.finditer(data): try: s = match.group().decode('utf-16') cli_out(s, cli_mode) strings.append(s) except UnicodeDecodeError: pass return {"strings": strings}
def check_timestamp(self, pe, cli_mode=False): """check for suspicious timestamps""" date = datetime.datetime.utcfromtimestamp(pe.FILE_HEADER.TimeDateStamp) if (date.year < 2005) or (date > datetime.datetime.now()): cli_out("[+] Suspicious timestamp : %s" % str(date), cli_mode) return {"suspicious_timestamp": str(date)} return {}
def get_results(self, pe, cli_mode=False): res = {} res.update(self.get_sig_info(pe, cli_mode)) if res: data = bytes(pe.write()[self.get_sig_address(pe)+8:]) signature = cms.ContentInfo.load(data) cli_out("", cli_mode) res.update(self.get_cert_info(signature, cli_mode)) return {"signature": res} if res else res
def check_imphash(self, pe, cli_mode=False): """Check imphash in a list of known import hashes""" ih = pe.get_imphash() if ih in self.imphashes: cli_out( "[+] Known suspicious import hash: %s" % (self.imphashes[ih]), cli_mode) return {"sus_imp_hash": self.imphashes[ih]} return {}
def check_resource(pe, resource, parents, cli_mode=False): """ Recursive cecking/printing of suspicious resources. A resource is suspicious if it itself contains a PE header or the resource is a directory and it's name is a known name we're tracking as suspicious. """ if hasattr(resource, "data"): # Resource offset = resource.data.struct.OffsetToData size = resource.data.struct.Size data = pe.get_memory_mapped_image()[offset:offset + size] if data.startswith(b'\x4d\x5a\x90\x00\x03\x00'): if resource.name: name = '/'.join(parents) + '/' + str(resource.name) else: name = '/'.join(parents) + '/' + str(resource.id) cli_out('[+] PE header in resource {}'.format(name), cli_mode) return {"embedded_pe_resources": [name]} else: return {} else: # directory result = {} parents = copy.copy(parents) suspicious = False if resource.id is not None: parents.append(str(resource.id)) # resources with no IDs are sus else: # TODO test this part. haven't found a sample yet with one of these name = resource.name.string.decode('utf-8') parents.append(name) if name in self.resource_names: cli_out( "[+] resource with no ID: {} -> {}".format( name, self.resource_names[name]), cli_mode) sus_resource = { "resource": name, "description": self.resource_names[name] } if "no_id_resources" not in result: result["no_id_resources"] = [sus_resource] else: result["no_id_resources"].append(sus_resource) # recurse and merge the downstream results into this one for child_resource in resource.directory.entries: merge_sus_resources( result, check_resource(pe, child_resource, parents, cli_mode)) return result
def check_pe_size(self, pe, data, cli_mode=False): """Check for extra data in the PE file by comparing PE info and data size""" length = max( map(lambda x: x.PointerToRawData + x.SizeOfRawData, pe.sections)) if length < len(data): cli_out("[+] %i extra bytes in the file" % (len(data) - length), cli_mode) return {"extra_bytes": len(data) - length} else: return {}
def check_abnormal_section_name(self, pe, cli_mode=False): res = [ x.Name.decode('utf-8', 'ignore').strip('\x00') for x in pe.sections if not self._normal_section_name(x.Name) ] if len(res) > 0: cli_out("[+] Abnormal section names: %s" % " ".join(res), cli_mode) return {"abnormal_section_names": res} else: return {}
def resource_info(pe, r, parents): """Recursive info of resources""" # resource if hasattr(r, "data"): # gather all the info offset = r.data.struct.OffsetToData size = r.data.struct.Size data = pe.get_memory_mapped_image()[offset:offset + size] parents_path = "/".join(parents) + "/" path = parents_path + str( r.id) if not r.name else parents_path + r.name m = hashlib.md5() m.update(data) md5 = m.hexdigest() magic_type = magic.from_buffer(data) lang = pefile.LANG.get(r.data.lang, 'UNKNOWN') sublang = pefile.get_sublang_name_for_lang( r.data.lang, r.data.sublang) # display it if we want cli_out( "%-19s %-9s %-14s %-17s %-14s %-9s" % (path, "%i B" % size, lang, sublang, magic_type, md5), cli_mode) # return it in a dict return [{ "path": path, "size": size, "md5": md5, "magic_type": magic_type, "lang": lang, "sublang": sublang }] # directory else: res = [] # append this name or ID to the parents list parents = copy.copy(parents) if r.id is not None: parents.append(str(r.id)) else: parents.append(r.name.string.decode('utf-8')) for r2 in r.directory.entries: res += resource_info(pe, r2, parents) return res
def entry_point_info(self, pe, cli_mode=False): entry_point = pe.OPTIONAL_HEADER.AddressOfEntryPoint + pe.OPTIONAL_HEADER.ImageBase section = search_section(pe, entry_point, physical=False) entry_point = hex(entry_point) cli_out("Entry point:\t%s (section %s)" % (entry_point, section), cli_mode) return { "entry_point": { "address": entry_point, "section_name": section } }
def check_pe_sections(self, pe, cli_mode=False): """Search for PE headers at the beginning of sections""" res = [] for section in pe.sections: if b"!This program cannot be run in DOS mode" in section.get_data()[:400] or \ b"This program must be run under Win32" in section.get_data()[:400]: res.append(section.Name.decode('utf-8').strip('\x00')) if len(res) > 0: cli_out("[+] PE header in sections %s" % " ".join(res), cli_mode) return {"pe_header_in_sections": res} return {}
def check_peid(self, data, cli_mode=False): """Check on PEid signatures""" peid_db = os.path.dirname( os.path.realpath(__file__))[:-7] + "data/PeID.yar" rules = yara.compile(filepath=peid_db) matches = rules.match(data=data) if len(matches) > 0: cli_out( "[+] PeID packer: %s" % ", ".join([a.rule for a in matches]), cli_mode) return {"peid_packer": [a.rule for a in matches]} return {}
def get_name_attr_values(x509_name, sub_object_friendly, res): cli_out(sub_object_friendly, cli_mode) sub_object = sub_object_friendly.lower() if sub_object not in res: res[sub_object] = {} for name_attr in x509_name: friendly_name = OID_NAME_MAP[name_attr.oid] key_name = friendly_name.lower().replace(" ", "_") value = str(name_attr.value) cli_out("\t{:<35} {}".format(friendly_name + ": ", value), cli_mode) res[sub_object][key_name] = value
def headers_info(seld, pe, cli_mode=False): """Display header information""" res = {} # check if file is a DLL if pe.FILE_HEADER.IMAGE_FILE_DLL: cli_out("DLL File!", cli_mode) res["is_dll"] = True # gather compile time timestamp = pe.FILE_HEADER.TimeDateStamp compile_time = str(datetime.datetime.utcfromtimestamp(timestamp)) cli_out("Compile Time:\t%s (UTC - 0x%-8X)" % (compile_time, timestamp), cli_mode) res["compile_time"] = compile_time return res
def hashes_info(self, pe, data, cli_mode=False): """Display md5, sha1, sh256, and imphash of the data given""" res = {} # compute md5, sha1, sha256 for algo in ["md5", "sha1", "sha256"]: m = getattr(hashlib, algo)() m.update(data) h = m.hexdigest() cli_out("%-15s %s" % (algo.upper() + ":", h), cli_mode) res[algo.lower()] = h # get imphash imphash = pe.get_imphash() cli_out("%-15s %s" % ("Imphash:", pe.get_imphash()), cli_mode) if imphash: res["imphash"] = imphash return res
def exports_info(self, pe, cli_mode=False): """exports""" exports = [] try: for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols: address = hex(pe.OPTIONAL_HEADER.ImageBase + exp.address) name = exp.name.decode('utf-8', 'ignore') ordinal = exp.ordinal cli_out("%s %s %s" % (address, name, ordinal), cli_mode) exports.append({ "address": address, "name": name, "ordinal": ordinal }) except AttributeError: pass return {"exports": exports} if exports else {}
def check_known_suspicious_sections(self, pe, cli_mode=False): names = [ x.Name.decode('utf-8', 'ignore').strip('\x00') for x in pe.sections ] res = [] for name in names: sus_section = self.know_suspicious_sections.get(name) if sus_section: res.append({"section_name": name, "description": sus_section}) if len(res) > 0: cli_out("[+] Known suspicious sections", cli_mode) for r in res: cli_out("\t-%s: %s" % (r["section_name"], r["description"]), cli_mode) return {"suspicious_sections": res} else: return {}
def get_results(self, pe, data, cli_mode=False): crypto_db = os.path.dirname( os.path.realpath(__file__))[:-7] + "data/yara-crypto.yar" if not os.path.isfile(crypto_db): if cli_mode: print("Problem accessing the yara database") else: raise Exception("Problem accessing the yara database") rules = yara.compile(filepath=crypto_db) matches = rules.match(data=data) results = [] if len(matches) > 0: for match in matches: paddr = match.strings[0][0] results.append({"rule": match.rule, "address": hex(paddr)}) # try to pin down the virtual/logical address if we can # TODO add to non-cli mode? section, vaddr = self.convert_physical_addr(pe, paddr) if section: cli_out( "Found : {} at {} ({} - {})".format( match.rule, hex(paddr), section, hex(vaddr)), cli_mode) else: cli_out( "Found : {} at {} (Virtual Address and section not found)" .format(match.rule, hex(paddr)), cli_mode) else: cli_out("No cryptographic data found!", cli_mode) return {} if not results else {"crypto_matches": results}
def check_section_entropy(self, pe, cli_mode=False): res = [] for s in pe.sections: if s.get_entropy() < 1 or s.get_entropy() > 7: res.append({ "section_name": s.Name.decode('utf-8', 'ignore').strip('\x00'), "entropy": s.get_entropy() }) if len(res) > 0: if len(res) == 1: cli_out( "[+] Suspicious section's entropy: %s - %3f" % (res[0]["section_name"], res[0]["entropy"]), cli_mode) else: cli_out("[+] Suspicious entropy in the following sections:", cli_mode) for r in res: cli_out("\t- %s - %3f" % (r["section_name"], r["entropy"]), cli_mode) return {"suspicious_entropy_sections": res} else: return {}
def sections_info(self, pe, cli_mode=False): """information about the PE sections""" sections = [] cli_out( "{:9} {:4} {:10} {:10} {:9} {:9} {:8} {}".format( "Name", "RWX", "VirtSize", "VirtAddr", "RawAddr", "RawSize", "Entropy", "md5"), cli_mode) for section in pe.sections: name = section.Name.decode('utf-8', 'ignore').strip('\x00') m = hashlib.md5() m.update(section.get_data()) md5 = m.hexdigest() permissions = "" if section.IMAGE_SCN_MEM_READ: permissions += "R" else: permissions += "-" if section.IMAGE_SCN_MEM_WRITE: permissions += "W" else: permissions += "-" if section.IMAGE_SCN_MEM_EXECUTE: permissions += "X" else: permissions += "-" vsize = hex(section.Misc_VirtualSize) vaddr = hex(section.VirtualAddress) raw_addr = hex(section.PointerToRawData) size = hex(section.SizeOfRawData) entropy = section.get_entropy() cli_out( "{:9} {:4} {:10} {:10} {:9} {:9} {:6.2f} {}".format( name, permissions, vsize, vaddr, raw_addr, size, entropy, md5), cli_mode) sections.append({ "name": name, "permissions": permissions, "virtual_size": vsize, "virtual_address": vaddr, "raw_address": raw_addr, "raw_size": size, "entropy": entropy, "md5": md5 }) cli_out("", cli_mode) return {"sections": sections} if sections else {}
def get_sig_info(self, pe, cli_mode=False): res = {} address = self.get_sig_address(pe) if address: address = hex(address) cli_out("This PE file is signed", cli_mode) cli_out("Signature Address: " + address, cli_mode) res["address"] = address else: cli_out("This PE file is not signed", cli_mode) return res
def dotnet_guid_info(self, pe, data, cli_mode=False): res = {} if is_dot_net_assembly(pe): try: r = get_guid(pe, data) if "mvid" in r: cli_out(".NET MVid\t{}".format(r["mvid"]), cli_mode) res["module_version_id"] = r["mvid"] if "typelib_id" in r: cli_out(".NET TypeLib\t{}".format(r['typelib_id']), cli_mode) res["typelib_id"] = r["typelib_id"] except: cli_out("Impossible to parse .NET GUID", cli_mode) return res
def imports_info(self, pe, cli_mode=False): """Display imports""" res = {} if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'): for entry in pe.DIRECTORY_ENTRY_IMPORT: # get the dll name dll = entry.dll.decode('utf-8') cli_out(dll, cli_mode) for i in entry.imports: # get the address, ordinal, and function name addr = str(hex(i.address)) ordinal = str(i.ordinal) if i.ordinal else None name = i.name.decode('utf-8') if i.name else None if dll not in res: res[dll] = [] import_res = {"address": addr} if name: import_res["name"] = name if ordinal: import_res["ordinal"] = ordinal res[dll].append(import_res) # display # TODO allow for both ordinal and name being present in cli mode if name: cli_out('\t%s %s' % (addr, name), cli_mode) else: cli_out('\t%s ordinal: %s' % (addr, ordinal), cli_mode) # flatten the non-key-descriptive res into one that is new_res = [] for dll in res: new_res.append({"library": dll, "imports": res[dll]}) return {"library_imports": new_res} if new_res else {}
def check_tls(self, pe, cli_mode=False): """Check if there are TLS callbacks""" res = {} callbacks = [] if (hasattr(pe, 'DIRECTORY_ENTRY_TLS') and \ pe.DIRECTORY_ENTRY_TLS and \ pe.DIRECTORY_ENTRY_TLS.struct and \ pe.DIRECTORY_ENTRY_TLS.struct.AddressOfCallBacks): callback_array_rva = pe.DIRECTORY_ENTRY_TLS.struct.AddressOfCallBacks - pe.OPTIONAL_HEADER.ImageBase idx = 0 while True: func = pe.get_dword_from_data( pe.get_data(callback_array_rva + 4 * idx, 4), 0) if func == 0: break callbacks.append({ "address": "0x%x" % func, "section": search_section(pe, func, physical=False) }) idx += 1 if callbacks: res["tls_callbacks"] = callbacks # display the results if len(callbacks) == 1: cli_out( "TLS Callback:\t%s (section %s)" % (callbacks[0]["address"], callbacks[0]["section"]), cli_mode) else: cli_out("TLS Callbacks:", cli_mode) for c in callbacks: cli_out( "\t\t%s (section %s)" % (c["address"], c["section"]), cli_mode) return res
def magic_type_info(self, data, cli_mode=False): magic_type = magic.from_buffer(data) cli_out("Type:\t\t%s" % magic_type, cli_mode) return {"magic_type": magic_type}
def get_cert_info(self, signature, cli_mode=False): def get_name_attr_values(x509_name, sub_object_friendly, res): cli_out(sub_object_friendly, cli_mode) sub_object = sub_object_friendly.lower() if sub_object not in res: res[sub_object] = {} for name_attr in x509_name: friendly_name = OID_NAME_MAP[name_attr.oid] key_name = friendly_name.lower().replace(" ", "_") value = str(name_attr.value) cli_out("\t{:<35} {}".format(friendly_name + ": ", value), cli_mode) res[sub_object][key_name] = value certificates = [] for cert in signature["content"]["certificates"]: cert_res = {} parsed_cert = x509.load_der_x509_certificate(cert.dump(), default_backend()) # general certificate information cli_out(("=" * 100) + "\nCertificate\n" + ("=" * 100) + "\n", cli_mode) # certificate version version = str(parsed_cert.version) cert_res["cert_version"] = version cli_out("{:<35} {}".format("Version:", version), cli_mode) # validity timestamp boundaries not_valid_before = str(parsed_cert.not_valid_before) not_valid_after = str(parsed_cert.not_valid_after) cert_res["not_valid_before"] = not_valid_before cert_res["not_valid_after"] = not_valid_after cli_out("{:<35} {}".format("Not Valid Before:", not_valid_before), cli_mode) cli_out("{:<35} {}".format("Not Valid After:", not_valid_after), cli_mode) cli_out("", cli_mode) # issuer information get_name_attr_values(parsed_cert.issuer, "Issuer", cert_res) cli_out("", cli_mode) # subject information get_name_attr_values(parsed_cert.subject, "Subject", cert_res) cli_out("", cli_mode) certificates.append(cert_res) # TODO check if cert is valid return {"certificates": certificates}
def size_info(self, data, cli_mode=False): size = len(data) cli_out("Size:\t\t%d bytes" % size, cli_mode) return {"size": str(size)}