diff --git a/BadEntryPointSectionDetector.py b/BadEntryPointSectionDetector.py index 95558db..9f50481 100644 --- a/BadEntryPointSectionDetector.py +++ b/BadEntryPointSectionDetector.py @@ -2,53 +2,53 @@ from PackerDetector import * class BadEntryPointSectionDetector(PackerDetector): - def __init__(self, config): - super().__init__(config) - self.acceptableEntrySections = [".text"] - self.alternativeEntrySections = [".code", "text", ".text0", ".text1", ".text2", ".text3"] - self.driverEntrySection = ["INIT"] - self.delphiBssSections = [".BSS", "BSS", ".bss"] - self.delphiEntrySections = [".itext", "CODE"] + def __init__(self, config): + super(BadEntryPointSectionDetector, self).__init__(config) + self.acceptableEntrySections = [".text"] + self.alternativeEntrySections = [".code", "text", ".text0", ".text1", ".text2", ".text3"] + self.driverEntrySection = ["INIT"] + self.delphiBssSections = [".BSS", "BSS", ".bss"] + self.delphiEntrySections = [".itext", "CODE"] - def Run(self, pe, report): # TODO test - if (not self.config["CheckForBadEntryPointSections"]): - return + def Run(self, pe, report): # TODO test + if (not self.config["CheckForBadEntryPointSections"]): + return - entryPoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint - if (entryPoint == 0): - report.IndicateSuspicion("Null entry point") - return + entryPoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint + if (entryPoint == 0): + report.IndicateSuspicion("Null entry point") + return - allSectionNames = [] - entryPointSectionNames = [] - for section in pe.sections: - try: - secName = GetCleanSectionName(section) - allSectionNames.append(secName) - if (entryPoint >= section.VirtualAddress and entryPoint <= (section.VirtualAddress + section.Misc_VirtualSize)): - entryPointSectionNames.append(secName) - except UnicodeDecodeError: - report.IndicateSuspicion("Section name with invalid characters") + allSectionNames = [] + entryPointSectionNames = [] + for section in pe.sections: + try: + secName = GetCleanSectionName(section) + allSectionNames.append(secName) + if (entryPoint >= section.VirtualAddress and entryPoint <= (section.VirtualAddress + section.Misc_VirtualSize)): + entryPointSectionNames.append(secName) + except UnicodeDecodeError: + report.IndicateSuspicion("Section name with invalid characters") - entryPointSectionCount = len(entryPointSectionNames) - if (entryPointSectionCount == 0): - report.IndicateSuspicion("Entry point 0x%x doesn't fall in valid section" % entryPoint) - else: - if (entryPointSectionCount > 1): - report.IndicateDetection("Entry point 0x%x falls in overlapping sections: %s" % (entryPoint, FormatStringList(entryPointSectionNames))) + entryPointSectionCount = len(entryPointSectionNames) + if (entryPointSectionCount == 0): + report.IndicateSuspicion("Entry point 0x%x doesn't fall in valid section" % entryPoint) + else: + if (entryPointSectionCount > 1): + report.IndicateDetection("Entry point 0x%x falls in overlapping sections: %s" % (entryPoint, FormatStringList(entryPointSectionNames))) - if (not DoListsIntersect(self.acceptableEntrySections, entryPointSectionNames)): - badEpSec = False - if (DoListsIntersect(self.delphiBssSections, allSectionNames)): - # has bss, see if we have a delphi ep section - badEpSec = not DoListsIntersect(self.delphiEntrySections, entryPointSectionNames) - elif (not DoListsIntersect(self.acceptableEntrySections, allSectionNames)): - # normal entry section doesn't exist anywhere, so check for alternatives - badEpSec = not DoListsIntersect(self.alternativeEntrySections, entryPointSectionNames) - else: - # not regular ep section, not a delphi entry section, not an alternative entry section, and regular entry section name exists, - # so the only possibility left is a driver entry section - badEpSec = not DoListsIntersect(self.driverEntrySection, entryPointSectionNames) + if (not DoListsIntersect(self.acceptableEntrySections, entryPointSectionNames)): + badEpSec = False + if (DoListsIntersect(self.delphiBssSections, allSectionNames)): + # has bss, see if we have a delphi ep section + badEpSec = not DoListsIntersect(self.delphiEntrySections, entryPointSectionNames) + elif (not DoListsIntersect(self.acceptableEntrySections, allSectionNames)): + # normal entry section doesn't exist anywhere, so check for alternatives + badEpSec = not DoListsIntersect(self.alternativeEntrySections, entryPointSectionNames) + else: + # not regular ep section, not a delphi entry section, not an alternative entry section, and regular entry section name exists, + # so the only possibility left is a driver entry section + badEpSec = not DoListsIntersect(self.driverEntrySection, entryPointSectionNames) - if (badEpSec): - report.IndicateDetection("Entry point 0x%x in irregular section(s): %s" % (entryPoint, FormatStringList(entryPointSectionNames))) \ No newline at end of file + if (badEpSec): + report.IndicateDetection("Entry point 0x%x in irregular section(s): %s" % (entryPoint, FormatStringList(entryPointSectionNames))) diff --git a/LowImportCountDetector.py b/LowImportCountDetector.py index a5bc441..4f96c6f 100644 --- a/LowImportCountDetector.py +++ b/LowImportCountDetector.py @@ -2,19 +2,19 @@ from PackerDetector import * class LowImportCountDetector(PackerDetector): - def __init__(self, config): - super().__init__(config) + def __init__(self, config): + super(LowImportCountDetector, self).__init__(config) - def Run(self, pe, report): # TODO test - if (not self.config["CheckForLowImportCount"]): - return - try: - importCount = 0 - for library in pe.DIRECTORY_ENTRY_IMPORT: - if (GetCleanStringFromBytes(library.dll).lower() == "mscoree.dll"): - return # .NET assembly, counting imports is misleading as they will have a low number - importCount += len(library.imports) - if (importCount <= self.config["LowImportThreshold"]): - report.IndicateDetection("Too few imports (total: %d)" % importCount) - except AttributeError: - pass \ No newline at end of file + def Run(self, pe, report): # TODO test + if (not self.config["CheckForLowImportCount"]): + return + try: + importCount = 0 + for library in pe.DIRECTORY_ENTRY_IMPORT: + if (GetCleanStringFromBytes(library.dll).lower() == "mscoree.dll"): + return # .NET assembly, counting imports is misleading as they will have a low number + importCount += len(library.imports) + if (importCount <= self.config["LowImportThreshold"]): + report.IndicateDetection("Too few imports (total: %d)" % importCount) + except AttributeError: + pass diff --git a/NonStandardSectionNameDetector.py b/NonStandardSectionNameDetector.py index 2efad2e..eff6261 100644 --- a/NonStandardSectionNameDetector.py +++ b/NonStandardSectionNameDetector.py @@ -2,42 +2,43 @@ from PackerDetector import * class NonStandardSectionNameDetector(PackerDetector): - def __init__(self, config): - super().__init__(config) - self.knownSectionNames = [ - ".00cfg", ".arch", ".autoload_text", ".bindat", ".bootdat", ".bss", ".BSS", - ".buildid", ".CLR_UEF", ".code", ".cormeta", ".complua", ".CRT", ".cygwin_dll_common", - ".data", ".DATA", ".data1", ".data2", ".data3", ".debug", ".debug$F", - ".debug$P", ".debug$S", ".debug$T", ".drectve ", ".didat", ".didata", ".edata", - ".eh_fram", ".export", ".fasm", ".flat", ".gfids", ".giats", ".gljmp", - ".glue_7t", ".glue_7", ".idata", ".idlsym", ".impdata", ".itext", ".ndata", - ".orpc", ".pdata", ".rdata", ".reloc", ".rodata", ".rsrc", ".sbss", - ".script", ".shared", ".sdata", ".srdata", ".stab", ".stabstr", ".sxdata", - ".text", ".text0", ".text1", ".text2", ".text3", ".textbss", ".tls", - ".tls$", ".udata", ".vsdata", ".xdata", ".wixburn", ".wpp_sf ", "BSS", - "CODE", "DATA", "DGROUP", "edata", "idata", "INIT", "minATL", - "PAGE", "rdata", "sdata", "shared", "Shared", "testdata", "text", - "nv_fatb", "nv_FatBi" - ] + def __init__(self, config): + super(NonStandardSectionNameDetector, self).__init__(config) + + self.knownSectionNames = [ + ".00cfg", ".arch", ".autoload_text", ".bindat", ".bootdat", ".bss", ".BSS", + ".buildid", ".CLR_UEF", ".code", ".cormeta", ".complua", ".CRT", ".cygwin_dll_common", + ".data", ".DATA", ".data1", ".data2", ".data3", ".debug", ".debug$F", + ".debug$P", ".debug$S", ".debug$T", ".drectve ", ".didat", ".didata", ".edata", + ".eh_fram", ".export", ".fasm", ".flat", ".gfids", ".giats", ".gljmp", + ".glue_7t", ".glue_7", ".idata", ".idlsym", ".impdata", ".itext", ".ndata", + ".orpc", ".pdata", ".rdata", ".reloc", ".rodata", ".rsrc", ".sbss", + ".script", ".shared", ".sdata", ".srdata", ".stab", ".stabstr", ".sxdata", + ".text", ".text0", ".text1", ".text2", ".text3", ".textbss", ".tls", + ".tls$", ".udata", ".vsdata", ".xdata", ".wixburn", ".wpp_sf ", "BSS", + "CODE", "DATA", "DGROUP", "edata", "idata", "INIT", "minATL", + "PAGE", "rdata", "sdata", "shared", "Shared", "testdata", "text", + "nv_fatb", "nv_FatBi" + ] - def Run(self, pe, report): # TODO test - if (not self.config["CheckForNonStandardSections"]): - return - unknownSections = [] - badSectionNameCount = 0 - for section in pe.sections: - try: - secName = GetCleanSectionName(section) - if (secName not in self.knownSectionNames): - unknownSections.append(secName) - except UnicodeDecodeError: - badSectionNameCount += 1 - report.IndicateSuspicion("Section name with invalid characters") + def Run(self, pe, report): # TODO test + if (not self.config["CheckForNonStandardSections"]): + return + unknownSections = [] + badSectionNameCount = 0 + for section in pe.sections: + try: + secName = GetCleanSectionName(section) + if (secName not in self.knownSectionNames): + unknownSections.append(secName) + except UnicodeDecodeError: + badSectionNameCount += 1 + report.IndicateSuspicion("Section name with invalid characters") - unknownSectionCount = len(unknownSections) - if (unknownSectionCount >= self.config["NonStandardSectionThreshold"]): - report.IndicateDetection("Detected %d non-standard sections: %s" % (unknownSectionCount, FormatStringList(unknownSections))) - if (badSectionNameCount >= self.config["BadSectionNameThreshold"]): - report.IndicateDetection("Detected %d sections with invalid names" % badSectionNameCount); + unknownSectionCount = len(unknownSections) + if (unknownSectionCount >= self.config["NonStandardSectionThreshold"]): + report.IndicateDetection("Detected %d non-standard sections: %s" % (unknownSectionCount, FormatStringList(unknownSections))) + if (badSectionNameCount >= self.config["BadSectionNameThreshold"]): + report.IndicateDetection("Detected %d sections with invalid names" % badSectionNameCount); diff --git a/PEIDDetector.py b/PEIDDetector.py index 76425da..e37ccdc 100644 --- a/PEIDDetector.py +++ b/PEIDDetector.py @@ -3,19 +3,22 @@ from PackerDetector import * class PEIDDetector(PackerDetector): - def __init__(self, config): - super().__init__(config) - if (self.config["UseLargePEIDDatabase"]): - self.signatures = peutils.SignatureDatabase('deps/peid/signatures_long.txt') - else: - self.signatures = peutils.SignatureDatabase('deps/peid/signatures_short.txt') + def __init__(self, config): - def Run(self, pe, report): - if (not self.config["CheckForPEIDSignatures"]): - return + super(PEIDDetector, self).__init__(config) + + if (self.config["UseLargePEIDDatabase"]): + self.signatures = peutils.SignatureDatabase('deps/peid/signatures_long.txt') + else: + self.signatures = peutils.SignatureDatabase('deps/peid/signatures_short.txt') - matches = self.signatures.match_all(pe, ep_only=self.config["OnlyPEIDEntryPointSignatures"]) - if (not matches): - return - for match in matches: - report.IndicateDetection("Found PEID signature: %s" % match) \ No newline at end of file + def Run(self, pe, report): + if (not self.config["CheckForPEIDSignatures"]): + return + + matches = self.signatures.match_all(pe, ep_only=self.config["OnlyPEIDEntryPointSignatures"]) + if (not matches): + return + + for match in matches: + report.IndicateDetection("Found PEID signature: %s" % match) diff --git a/PackerDetector.py b/PackerDetector.py index 529a392..5c371cb 100644 --- a/PackerDetector.py +++ b/PackerDetector.py @@ -1,8 +1,8 @@ from Utils import * -class PackerDetector: - def __init__(self, config): - self.config = config +class PackerDetector(object): + def __init__(self, config): + self.config = config - def Run(self, pe, report): - raise NotImplementedError \ No newline at end of file + def Run(self, pe, report): + raise NotImplementedError diff --git a/PackerReport.py b/PackerReport.py index 7eaf163..6a0f5b4 100644 --- a/PackerReport.py +++ b/PackerReport.py @@ -1,43 +1,48 @@ +from __future__ import print_function from Utils import * -class PackerReport: - def __init__(self, name): - self.name = name - self.detections = 0 - self.suspicions = 0 - self.failed = False - self.error = "" - self.logs = [] - - def IndicateDetection(self, message): - self.logs.append("[DETECTION] %s" % message) - self.detections += 1 - - def IndicateSuspicion(self, message): - self.logs.append("[SUSPICION] %s" % message) - self.suspicions += 1 - - def IndicateParseFailed(self, message): - self.error = message - self.failed = True - - def GetDetections(self): - return self.detections - - def GetSuspicions(self): - return self.suspicions - - def GetParseFailed(self): - return self.failed - - def Print(self, outfn=print): - outfn("Packer report for: %s" % self.name) - if (self.failed): - outfn("\tError: %s" % self.error) - else: - outfn("\tDetections: %d" % self.detections) - outfn("\tSuspicions: %d" % self.suspicions) - outfn("\tLog:") - - for log in self.logs: - outfn("\t\t%s" % log) \ No newline at end of file + +def default_outfn(msg): + print(msg) + +class PackerReport(object): + def __init__(self, name): + self.name = name + self.detections = 0 + self.suspicions = 0 + self.failed = False + self.error = "" + self.logs = [] + + def IndicateDetection(self, message): + self.logs.append("[DETECTION] %s" % message) + self.detections += 1 + + def IndicateSuspicion(self, message): + self.logs.append("[SUSPICION] %s" % message) + self.suspicions += 1 + + def IndicateParseFailed(self, message): + self.error = message + self.failed = True + + def GetDetections(self): + return self.detections + + def GetSuspicions(self): + return self.suspicions + + def GetParseFailed(self): + return self.failed + + def Print(self, outfn=default_outfn): + outfn("Packer report for: %s" % self.name) + if (self.failed): + outfn("\tError: %s" % self.error) + else: + outfn("\tDetections: %d" % self.detections) + outfn("\tSuspicions: %d" % self.suspicions) + outfn("\tLog:") + + for log in self.logs: + outfn("\t\t%s" % log) diff --git a/PackerSectionNameDetector.py b/PackerSectionNameDetector.py index 8def9bc..01e98a7 100644 --- a/PackerSectionNameDetector.py +++ b/PackerSectionNameDetector.py @@ -2,37 +2,38 @@ from PackerDetector import * class PackerSectionNameDetector(PackerDetector): - def __init__(self, config): - super().__init__(config) - self.packerSectionNames = { - ".aspack": "Aspack packer", ".adata": "Aspack packer/Armadillo packer", "ASPack": "Aspack packer", ".ASPack": "ASPAck Protector", - ".boom": "The Boomerang List Builder (config+exe xored with a single byte key 0x77)", ".ccg": "CCG Packer (Chinese Packer)", ".charmve": "Added by the PIN tool", "BitArts": "Crunch 2.0 Packer", - "DAStub": "DAStub Dragon Armor protector", "!EPack": "Epack packer", "FSG!": "FSG packer (not a section name, but a good identifier)", ".gentee": "Gentee installer", - "kkrunchy": "kkrunchy Packer", ".mackt": "ImpRec-created section", ".MaskPE": "MaskPE Packer", "MEW": "MEW packer", - ".MPRESS1": "Mpress Packer", ".MPRESS2": "Mpress Packer", ".neolite": "Neolite Packer", ".neolit": "Neolite Packer", - ".nsp1": "NsPack packer", ".nsp0": "NsPack packer", ".nsp2": "NsPack packer", "nsp1": "NsPack packer", - "nsp0": "NsPack packer", "nsp2": "NsPack packer", ".packed": "RLPack Packer (first section)", "pebundle": "PEBundle Packer", - "PEBundle": "PEBundle Packer", "PEC2TO": "PECompact packer", "PECompact2": "PECompact packer (not a section name, but a good identifier)", "PEC2": "PECompact packer", - "pec1": "PECompact packer", "pec2": "PECompact packer", "PEC2MO": "PECompact packer", "PELOCKnt": "PELock Protector", - ".perplex": "Perplex PE-Protector", "PESHiELD": "PEShield Packer", ".petite": "Petite Packer", ".pinclie": "Added by the PIN tool", - "ProCrypt": "ProCrypt Packer", ".RLPack": "RLPack Packer (second section)", ".rmnet": "Ramnit virus marker", "RCryptor": "RPCrypt Packer", - ".RPCrypt": "RPCrypt Packer", ".seau": "SeauSFX Packer", ".sforce3": "StarForce Protection", ".spack": "Simple Pack (by bagie)", - ".svkp": "SVKP packer", "Themida": "Themida Packer", ".Themida": "Themida Packer", ".taz": "Some version os PESpin", - ".tsuarch": "TSULoader", ".tsustub": "TSULoader", ".packed": "Unknown Packer", "PEPACK!!": "Pepack", - ".Upack": "Upack packer", ".ByDwing": "Upack Packer", "UPX0": "UPX packer", "UPX1": "UPX packer", - "UPX2": "UPX packer", "UPX!": "UPX packer", ".UPX0": "UPX Packer", ".UPX1": "UPX Packer", - ".UPX2": "UPX Packer", ".vmp0": "VMProtect packer", ".vmp1": "VMProtect packer", ".vmp2": "VMProtect packer", - "VProtect": "Vprotect Packer", ".winapi": "Added by API Override tool", "WinLicen": "WinLicense (Themida) Protector", "_winzip_": "WinZip Self-Extractor", - ".WWPACK": "WWPACK Packer", ".yP": "Y0da Protector", ".y0da": "Y0da Protector", - } + def __init__(self, config): + super(PackerSectionNameDetector, self).__init__(config) + + self.packerSectionNames = { + ".aspack": "Aspack packer", ".adata": "Aspack packer/Armadillo packer", "ASPack": "Aspack packer", ".ASPack": "ASPAck Protector", + ".boom": "The Boomerang List Builder (config+exe xored with a single byte key 0x77)", ".ccg": "CCG Packer (Chinese Packer)", ".charmve": "Added by the PIN tool", "BitArts": "Crunch 2.0 Packer", + "DAStub": "DAStub Dragon Armor protector", "!EPack": "Epack packer", "FSG!": "FSG packer (not a section name, but a good identifier)", ".gentee": "Gentee installer", + "kkrunchy": "kkrunchy Packer", ".mackt": "ImpRec-created section", ".MaskPE": "MaskPE Packer", "MEW": "MEW packer", + ".MPRESS1": "Mpress Packer", ".MPRESS2": "Mpress Packer", ".neolite": "Neolite Packer", ".neolit": "Neolite Packer", + ".nsp1": "NsPack packer", ".nsp0": "NsPack packer", ".nsp2": "NsPack packer", "nsp1": "NsPack packer", + "nsp0": "NsPack packer", "nsp2": "NsPack packer", ".packed": "RLPack Packer (first section)", "pebundle": "PEBundle Packer", + "PEBundle": "PEBundle Packer", "PEC2TO": "PECompact packer", "PECompact2": "PECompact packer (not a section name, but a good identifier)", "PEC2": "PECompact packer", + "pec1": "PECompact packer", "pec2": "PECompact packer", "PEC2MO": "PECompact packer", "PELOCKnt": "PELock Protector", + ".perplex": "Perplex PE-Protector", "PESHiELD": "PEShield Packer", ".petite": "Petite Packer", ".pinclie": "Added by the PIN tool", + "ProCrypt": "ProCrypt Packer", ".RLPack": "RLPack Packer (second section)", ".rmnet": "Ramnit virus marker", "RCryptor": "RPCrypt Packer", + ".RPCrypt": "RPCrypt Packer", ".seau": "SeauSFX Packer", ".sforce3": "StarForce Protection", ".spack": "Simple Pack (by bagie)", + ".svkp": "SVKP packer", "Themida": "Themida Packer", ".Themida": "Themida Packer", ".taz": "Some version os PESpin", + ".tsuarch": "TSULoader", ".tsustub": "TSULoader", ".packed": "Unknown Packer", "PEPACK!!": "Pepack", + ".Upack": "Upack packer", ".ByDwing": "Upack Packer", "UPX0": "UPX packer", "UPX1": "UPX packer", + "UPX2": "UPX packer", "UPX!": "UPX packer", ".UPX0": "UPX Packer", ".UPX1": "UPX Packer", + ".UPX2": "UPX Packer", ".vmp0": "VMProtect packer", ".vmp1": "VMProtect packer", ".vmp2": "VMProtect packer", + "VProtect": "Vprotect Packer", ".winapi": "Added by API Override tool", "WinLicen": "WinLicense (Themida) Protector", "_winzip_": "WinZip Self-Extractor", + ".WWPACK": "WWPACK Packer", ".yP": "Y0da Protector", ".y0da": "Y0da Protector", + } - def Run(self, pe, report): # TODO test - if (not self.config["CheckForPackerSections"]): - return - for section in pe.sections: - try: - secName = GetCleanSectionName(section) - if (secName in self.packerSectionNames): - report.IndicateDetection("Section name '%s' matches known packer: [%s]" % (secName, self.packerSectionNames[secName])) - except UnicodeDecodeError: - report.IndicateSuspicion("Section name with invalid characters") \ No newline at end of file + def Run(self, pe, report): # TODO test + if (not self.config["CheckForPackerSections"]): + return + for section in pe.sections: + try: + secName = GetCleanSectionName(section) + if (secName in self.packerSectionNames): + report.IndicateDetection("Section name '%s' matches known packer: [%s]" % (secName, self.packerSectionNames[secName])) + except UnicodeDecodeError: + report.IndicateSuspicion("Section name with invalid characters") diff --git a/README.MD b/README.MD index 6bb82dc..8403910 100644 --- a/README.MD +++ b/README.MD @@ -1,10 +1,12 @@ # PyPackerDetect +## Overview + A small python script/library to detect whether an executable is packed. This is one of many tools we use for dataset curation within the ARG team at Cylance. Accuracy is not perfect, but is sufficient in accomplishing what we need. -Tested and devloped using Python 3. +Tested and devloped using Python 3, but should also work on Python 2.7. [pefile](https://github.com/erocarrera/pefile) is used for PE parsing, found in `./deps/libpefile`. @@ -27,4 +29,5 @@ Example usage is in `DetectPacker.py`. Can be run via command line. ## Resources -Big thanks to [Hexacorn](http://www.hexacorn.com/blog/2016/12/15/pe-section-names-re-visited/), a good portion of the known PE section names come from there. \ No newline at end of file +Big thanks to [Hexacorn](http://www.hexacorn.com/blog/2016/12/15/pe-section-names-re-visited/), a good portion of the known PE section names come from there. + diff --git a/deps/libpefile/peutils.py b/deps/libpefile/peutils.py index 95f5d4a..3666826 100644 --- a/deps/libpefile/peutils.py +++ b/deps/libpefile/peutils.py @@ -1,4 +1,4 @@ -# -*- coding: Latin-1 -*- +# -*- coding: utf-8 -*- """peutils, Portable Executable utilities module