From d574ea18b16318a075344b17f83938742b0ee2f2 Mon Sep 17 00:00:00 2001 From: prabhu Date: Mon, 29 Jul 2024 23:01:39 +0100 Subject: [PATCH] Support for detecting some loaders (#113) * Support for detecting some loaders Signed-off-by: Prabhu Subramanian * Support for detecting some loaders Signed-off-by: Prabhu Subramanian * Support for detecting some loaders Signed-off-by: Prabhu Subramanian --------- Signed-off-by: Prabhu Subramanian --- Info.plist | 2 +- blint/analysis.py | 46 +++++++++--- blint/binary.py | 71 ++++++++++++++----- blint/config.py | 43 ++++++++++- blint/data/annotations/review_exe_go.yml | 1 + blint/data/annotations/review_imports_pe.yml | 39 ++++++++++ blint/data/annotations/review_monero_go.yml | 1 + .../data/annotations/review_rootkits_win.yml | 2 +- .../review_symbols_antiforensic.yml | 25 +++++-- .../data/annotations/review_symbols_hooka.yml | 1 + blint/data/rules.yml | 7 ++ blint/utils.py | 4 +- file_version_info.txt | 4 +- pyproject.toml | 2 +- 14 files changed, 208 insertions(+), 40 deletions(-) diff --git a/Info.plist b/Info.plist index 045c6c8..9f16c1c 100644 --- a/Info.plist +++ b/Info.plist @@ -9,6 +9,6 @@ CFBundleName blint CFBundleVersion - 2.2.0 + 2.2.1 diff --git a/blint/analysis.py b/blint/analysis.py index 879dea8..66399ff 100644 --- a/blint/analysis.py +++ b/blint/analysis.py @@ -21,7 +21,7 @@ check_virtual_size, check_authenticode, check_dll_characteristics, check_codesign, check_trust_info) -from blint.config import PII_WORDS, get_int_from_env +from blint.config import FIRST_STAGE_WORDS, PII_WORDS, get_int_from_env from blint.logger import LOG, console from blint.utils import (create_findings_table, is_fuzzable_name, print_findings_table) @@ -54,12 +54,20 @@ review_symbols_dict = defaultdict(list) review_imports_dict = defaultdict(list) review_entries_dict = defaultdict(list) -review_rules_cache = {"PII_READ": { - "title": "Detect PII Read Operations", - "summary": "Can Retrieve Sensitive PII data", - "description": "Contains logic to retrieve sensitive data such as names, email, passwords etc.", - "patterns": PII_WORDS -}} +review_rules_cache = { + "PII_READ": { + "title": "Detect PII Read Operations", + "summary": "Can Retrieve Sensitive PII data", + "description": "Contains logic to retrieve sensitive data such as names, email, passwords etc.", + "patterns": PII_WORDS + }, + "LOADER_SYMBOLS": { + "title": "Detect Initial Loader", + "summary": "Behaves like a loader", + "description": "The binary behaves like a loader by downloading and executing additional payloads.", + "patterns": FIRST_STAGE_WORDS + }, +} # Debug mode DEBUG_MODE = os.getenv("SCAN_DEBUG_MODE") == "debug" @@ -309,7 +317,10 @@ def print_reviews_table(reviews, files): def json_serializer(obj): """JSON serializer to help serialize problematic types such as bytes""" if isinstance(obj, bytes): - return obj.decode('utf-8') + try: + return obj.decode('utf-8') + except UnicodeDecodeError: + return "" return obj @@ -494,7 +505,7 @@ def run_review(self, metadata): or self.review_entries_list ): return self._review_lists(metadata) - return {} + return self._review_loader_symbols(metadata) def _review_lists(self, metadata): """ @@ -516,6 +527,7 @@ def _review_lists(self, metadata): if self.review_entries_list: self._review_entries(metadata) self._review_pii(metadata) + self._review_loader_symbols(metadata) return self.results def _review_imports(self, metadata): @@ -562,6 +574,22 @@ def _review_pii(self, metadata): results["PII_READ"].append({"pattern": e, "function": e}) self.results |= results + def _review_loader_symbols(self, metadata): + """ + Reviews loader symbols. + + Args: + metadata (dict): The metadata to review. + + Returns: + dict: The results of the review. + """ + entries_list = [f.get("name", "") for f in metadata.get("first_stage_symbols", [])] + results = defaultdict(list) + for e in entries_list[0:EVIDENCE_LIMIT]: + results["LOADER_SYMBOLS"].append({"pattern": e, "function": e}) + self.results |= results + def _review_symbols_exe(self, metadata): """ Reviews symbols in the metadata. diff --git a/blint/binary.py b/blint/binary.py index ee069da..4e31641 100644 --- a/blint/binary.py +++ b/blint/binary.py @@ -9,7 +9,7 @@ import lief -from blint.config import PII_WORDS, get_float_from_env, get_int_from_env +from blint.config import FIRST_STAGE_WORDS, PII_WORDS, get_float_from_env, get_int_from_env from blint.logger import DEBUG, LOG from blint.utils import camel_to_snake, calculate_entropy, check_secret, cleanup_dict_lief_errors, decode_base64 @@ -270,7 +270,7 @@ def parse_strings(parsed_obj): if (entropy and (entropy > MIN_ENTROPY or len(s) > MIN_LENGTH)) or secret_type: strings_list.append( { - "value": (decode_base64(s) if s.endswith("==") else s), + "value": decode_base64(s) if s.endswith("==") else s, "entropy": entropy, "secret_type": secret_type, } @@ -788,7 +788,6 @@ def add_elf_metadata(exe_file, metadata, parsed_obj): metadata["has_runpath"] = False elif runpath: metadata["has_runpath"] = True - # This is getting renamed to symtab_symbols in lief 0.15.0 symtab_symbols = parsed_obj.symtab_symbols metadata["static"] = bool(symtab_symbols and not isinstance(symtab_symbols, lief.lief_errors)) dynamic_entries = parsed_obj.dynamic_entries @@ -797,6 +796,10 @@ def add_elf_metadata(exe_file, metadata, parsed_obj): metadata["notes"] = parse_notes(parsed_obj) metadata["strings"] = parse_strings(parsed_obj) metadata["symtab_symbols"], exe_type = parse_symbols(symtab_symbols) + rdata_section = parsed_obj.get_section(".rodata") + text_section = parsed_obj.get_section(".text") + if not metadata["symtab_symbols"]: + add_elf_rdata_symbols(metadata, rdata_section, text_section) if exe_type: metadata["exe_type"] = exe_type metadata["dynamic_symbols"], exe_type = parse_symbols(parsed_obj.dynamic_symbols) @@ -1114,10 +1117,16 @@ def add_pe_metadata(exe_file: str, metadata: dict, parsed_obj: lief.PE.Binary): break rdata_section = parsed_obj.get_section(".rdata") text_section = parsed_obj.get_section(".text") - if not rdata_section and text_section: - rdata_section = text_section - if (not metadata["symtab_symbols"] or metadata["exe_type"] != "gobinary") and rdata_section: - add_pe_rdata_symbols(metadata, rdata_section) + # If there are no .rdata and .text section, then attempt to look for two alphanumeric sections + if not rdata_section and not text_section: + for section in parsed_obj.sections: + if str(section.name).removeprefix(".").isalnum(): + if not rdata_section: + rdata_section = section + else: + text_section = section + if rdata_section or text_section: + add_pe_rdata_symbols(metadata, rdata_section, text_section) metadata["exports"] = parse_pe_exports(parsed_obj.get_export()) metadata["functions"] = parse_functions(parsed_obj.functions) metadata["ctor_functions"] = parse_functions(parsed_obj.ctor_functions) @@ -1247,33 +1256,39 @@ def add_pe_optional_headers(metadata, optional_header): return metadata -def add_pe_rdata_symbols(metadata, rdata_section: lief.PE.Section): +def add_pe_rdata_symbols(metadata, rdata_section: lief.PE.Section, text_section: lief.PE.Section): """Adds PE rdata symbols to the metadata dictionary. Args: metadata: The dictionary to store the metadata. rdata_section: .rdata section of the PE binary. + text_section: .text section of the PE binary. Returns: The updated metadata dictionary. """ - if not rdata_section or not rdata_section.content: - return metadata + file_extns_from_rdata = r".*\.(go|s|dll|exe|pdb)" rdata_symbols = set() pii_symbols = [] + first_stage_symbols = [] for pii in PII_WORDS: - for vari in (f"get{pii}", f"get_{camel_to_snake(pii)}"): - if rdata_section.search_all(vari): + for vari in (f"get{pii}", f"get_{pii}", f"get_{camel_to_snake(pii)}", f"Get{pii}"): + if (rdata_section and rdata_section.search_all(vari)) or (text_section and text_section.search_all(vari)): pii_symbols.append( {"name": vari.lower(), "type": "FUNCTION", "is_function": True, "is_imported": False}) continue - str_content = codecs.decode(rdata_section.content.tobytes("A"), encoding="utf-8", errors="ignore") + for sw in FIRST_STAGE_WORDS: + if (rdata_section and rdata_section.search_all(sw)) or (text_section and text_section.search_all(sw)): + first_stage_symbols.append( + {"name": sw, "type": "FUNCTION", "is_function": True, "is_imported": True}) + str_content = codecs.decode(rdata_section.content.tobytes("A"), encoding="utf-8", + errors="ignore") if rdata_section and rdata_section.content else "" for block in str_content.split(" "): - if "runtime." in block or "internal/" in block or ".go" in block or ".dll" in block: + if "runtime." in block or "internal/" in block or re.match(file_extns_from_rdata, block): if ".go" in block: metadata["exe_type"] = "gobinary" for asym in block.split("\x00"): - if re.match(r".*\.(go|s|dll)$", asym): + if re.match(file_extns_from_rdata + "$", asym): rdata_symbols.add(asym) if not metadata["symtab_symbols"]: metadata["symtab_symbols"] = [] @@ -1285,7 +1300,31 @@ def add_pe_rdata_symbols(metadata, rdata_section: lief.PE.Section): "is_imported": True } for s in sorted(rdata_symbols) ] - metadata["pii_symbols"] = pii_symbols + if pii_symbols: + metadata["pii_symbols"] = pii_symbols + if first_stage_symbols: + metadata["first_stage_symbols"] = first_stage_symbols + return metadata + + +def add_elf_rdata_symbols(metadata, rdata_section: lief.PE.Section, text_section: lief.PE.Section): + """Adds ELF rdata symbols to the metadata dictionary. + + Args: + metadata: The dictionary to store the metadata. + rdata_section: .data section of the ELF binary. + text_section: .text section of the ELF binary. + + Returns: + The updated metadata dictionary. + """ + first_stage_symbols = [] + for sw in FIRST_STAGE_WORDS: + if (rdata_section and rdata_section.search_all(sw)) or (text_section and text_section.search_all(sw)): + first_stage_symbols.append( + {"name": sw, "type": "FUNCTION", "is_function": True, "is_imported": True}) + if first_stage_symbols: + metadata["first_stage_symbols"] = first_stage_symbols return metadata diff --git a/blint/config.py b/blint/config.py index 8879a52..411b6fe 100644 --- a/blint/config.py +++ b/blint/config.py @@ -1230,7 +1230,7 @@ "email": [re.compile(r"(?<=mailto:)[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9.-]+")], "ip": [ re.compile( - r"^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]).){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$" + r"^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]).){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(:[0-9]+)?$" ) ], } @@ -1268,6 +1268,7 @@ def get_int_from_env(name, default): return int(get_float_from_env(name, default)) +# PII related symbols PII_WORDS = ( "FirstName", "LastName", @@ -1293,3 +1294,43 @@ def get_int_from_env(name, default): "AgentStatus", "LastLoginTime" ) + +# Some symbols to look for in a first-stage payload +FIRST_STAGE_WORDS = ( + "System.ServiceProcess", + "System.IO.Compression", + "System.Reflection.Emit", + "ICryptoTransform", + "LoadAssembly", + "GetEncodedData", + "add_AssemblyResolve", + "CreateDecryptor", + "GetExecutingAssembly", + "GetModules", + "get_IsFamilyOrAssembly", + "/proc/%d/cmdline", + "/proc/%s/exe", + "/proc/self/exe", + "/proc/net/route", + "/etc/resolv.conf", + "/usr/lib/systemd/systemd", + "/usr/compress/bin/", + "/usr/libexec/openssh/sftp-server", + "/usr/sbin/reboot", + "/usr/bin/reboot", + "/usr/sbin/shutdown", + "/usr/bin/shutdown", + "/usr/sbin/poweroff", + "/usr/bin/poweroff", + "/usr/sbin/halt", + "/usr/bin/halt", + "virtualboxemulunit", + "virtualboximportunit", + "virtualboxunit", + "qvirtualboxglobalsunit", + "Uvirtualboxdisasm", + "loaderx86.dll", + "ZwProtectVirtualMemory", + "shlwapi.dll", + "DeleteCriticalSection" +) diff --git a/blint/data/annotations/review_exe_go.yml b/blint/data/annotations/review_exe_go.yml index 82939d8..0589e1e 100644 --- a/blint/data/annotations/review_exe_go.yml +++ b/blint/data/annotations/review_exe_go.yml @@ -4,6 +4,7 @@ group: EXE_REVIEWS exe_type: - gobinary - x86_64-executable + - x86_64-exec rules: - id: FILE_IOUTIL title: IO util functions used diff --git a/blint/data/annotations/review_imports_pe.yml b/blint/data/annotations/review_imports_pe.yml index 6998c79..34061f3 100644 --- a/blint/data/annotations/review_imports_pe.yml +++ b/blint/data/annotations/review_imports_pe.yml @@ -41,6 +41,11 @@ rules: - CreateSocketHandle - CreateThread - CreateThreadpool + - GetCurrentThread + - GetThreadLocale + - ExitProcess + - FreeLibrary + - ExitThread - id: PROCESS_STATUS_API title: Process status api used summary: Queries about processes and device drivers @@ -464,6 +469,8 @@ rules: - CreateHardLink - BackupEventLog - CreateFileMapping + - SetFileAttributes + - SetFilePointer - id: WIN_BCRYPT_API title: Win bcrypt api functions used summary: Can Encrypt Files @@ -1973,6 +1980,14 @@ rules: description: | Shell Windows API allows applications to access functions provided by the operating system shell, and to change and enhance it. patterns: + - DllInstall + - GetProcessReference + - ParseURLA + - ParseURLW + - PathFindFileName + - PathFindOnPath + - PathIsSystemFolder + - PathIsUNCServerShare - FindExecutableA - FindExecutableW - InitNetworkAddressControl @@ -1994,6 +2009,30 @@ rules: - GetManagedApplications - InstallApplication - UninstallApplication + - SHCreateThread + - SHCreateStreamOnFile + - SHOpenRegStream + - SHRegCreateUSKey + - SHQueryValueEx + - SHQueryInfoKey + - UrlCreateFromPath + - PathMatchSpec + - SHBindToFolderIDListParent + - SHBrowseForFolder + - SHCreateDefaultContextMenu + - SHCreateShellItem + - SHFormatDrive + - SHGetDesktopFolder + - SHGetFolderPath + - SHGetFolderPathAndSubDir + - SHGetKnownFolder + - SHGetSpecialFolderLocation + - SHILCreateFromPath + - SHPathPrepareForWrite + - SHSetKnownFolderPath + - SignalFileOpen + - Win32DeleteFile + - shlwapi.dll - id: KERNEL_API title: Kernel api functions used summary: Manipulates Windows Kernel & Drivers diff --git a/blint/data/annotations/review_monero_go.yml b/blint/data/annotations/review_monero_go.yml index d5462d0..c0e17d9 100644 --- a/blint/data/annotations/review_monero_go.yml +++ b/blint/data/annotations/review_monero_go.yml @@ -4,6 +4,7 @@ group: SYMBOL_REVIEWS exe_type: - gobinary - x86_64-executable + - x86_64-exec rules: - id: MONERO_API_GO title: Detect use of Monero wallet diff --git a/blint/data/annotations/review_rootkits_win.yml b/blint/data/annotations/review_rootkits_win.yml index b1b92bf..1da182d 100644 --- a/blint/data/annotations/review_rootkits_win.yml +++ b/blint/data/annotations/review_rootkits_win.yml @@ -3,6 +3,7 @@ text: Review for Windows rootkits group: METHOD_REVIEWS exe_type: - x86_64-executable + - x86_64-exec - PE32 - PE64 rules: @@ -67,7 +68,6 @@ rules: - BeIsStringNull - BeIsStringTerminated - BeUnSupportedFunction - - id: NIDHOGG title: Detect Nidhogg summary: Provides Tools for Gaining Privileged Access and Injecting Malicious Code diff --git a/blint/data/annotations/review_symbols_antiforensic.yml b/blint/data/annotations/review_symbols_antiforensic.yml index f6d8dbe..9d0ef89 100644 --- a/blint/data/annotations/review_symbols_antiforensic.yml +++ b/blint/data/annotations/review_symbols_antiforensic.yml @@ -4,6 +4,7 @@ group: SYMBOL_REVIEWS exe_type: - genericbinary - x86_64-executable + - x86_64-exec - gobinary - PE32 - PE64 @@ -29,7 +30,6 @@ rules: - MeltFile - SysmonUnload - _RtlAdjustPrivilege - - id: PUPY title: Pupy Post-Exploitation Framework summary: Detect Erasing of Evidence of Exploitation @@ -129,9 +129,20 @@ rules: - pupycompile - pupygen - run_pupy - - - - - - + - id: RAW_NET_ACCESS + title: Raw Network API + summary: Detect use of Network API + description: | + Low-level network and dns API functions are used to prevent detection. + patterns: + - opennameservers.c + - closenameservers.c + - read_etc_hosts_r.c + - dnslookup.c + - __read_etc_hosts_r + - __open_nameservers + - getRandomIP + - __libc_connect + - __GI_gethostbyname2_r + - gethostbyname.c + - connect.c diff --git a/blint/data/annotations/review_symbols_hooka.yml b/blint/data/annotations/review_symbols_hooka.yml index 0db72af..eca3829 100644 --- a/blint/data/annotations/review_symbols_hooka.yml +++ b/blint/data/annotations/review_symbols_hooka.yml @@ -4,6 +4,7 @@ group: SYMBOL_REVIEWS exe_type: - gobinary - x86_64-executable + - x86_64-exec - genericbinary rules: - id: HOOKA diff --git a/blint/data/rules.yml b/blint/data/rules.yml index ab8b065..d0fd83b 100644 --- a/blint/data/rules.yml +++ b/blint/data/rules.yml @@ -13,6 +13,7 @@ exe_types: - genericbinary - x86_64-executable + - x86_64-exec - gobinary - PE32 - PE64 @@ -34,6 +35,7 @@ exe_types: - genericbinary - x86_64-executable + - x86_64-exec - gobinary - PE32 - PE64 @@ -61,6 +63,7 @@ exe_types: - genericbinary - x86_64-executable + - x86_64-exec - gobinary - mips-executable - id: CHECK_CANARY @@ -79,6 +82,7 @@ exe_types: - genericbinary - x86_64-executable + - x86_64-exec - dotnetbinary - mips-executable - id: CHECK_RPATH @@ -123,6 +127,7 @@ - gobinary - dotnetbinary - x86_64-executable + - x86_64-exec - id: CHECK_AUTHENTICODE title: Missing Authenticode description: | @@ -138,6 +143,7 @@ - gobinary - dotnetbinary - x86_64-executable + - x86_64-exec - id: CHECK_DLL_CHARACTERISTICS title: Missing dll Security Characteristics description: | @@ -174,6 +180,7 @@ - genericbinary - gobinary - x86_64-executable + - x86_64-exec - id: CHECK_TRUST_INFO title: Requires Elevated Execution description: | diff --git a/blint/utils.py b/blint/utils.py index 7a50b52..1015d16 100644 --- a/blint/utils.py +++ b/blint/utils.py @@ -12,7 +12,7 @@ from ar import Archive import lief -from defusedxml.ElementTree import fromstring +from defusedxml.ElementTree import fromstring, ParseError from rich import box from rich.table import Table @@ -322,7 +322,7 @@ def parse_pe_manifest(manifest): for ele in child.iter(): attribs_dict[ele.tag.rpartition("}")[-1]] = ele.attrib return attribs_dict - except (TypeError, AttributeError, IndexError) as e: + except (TypeError, AttributeError, IndexError, ParseError) as e: LOG.debug(f"Caught {type(e)}: {e} while parsing PE manifest.") return {} diff --git a/file_version_info.txt b/file_version_info.txt index 09e7149..4909b9a 100644 --- a/file_version_info.txt +++ b/file_version_info.txt @@ -32,12 +32,12 @@ VSVersionInfo( u'040904B0', [StringStruct(u'CompanyName', u'OWASP Foundation'), StringStruct(u'FileDescription', u'blint - The Binary Linter'), - StringStruct(u'FileVersion', u'2.2.0.0'), + StringStruct(u'FileVersion', u'2.2.1.0'), StringStruct(u'InternalName', u'blint'), StringStruct(u'LegalCopyright', u'© OWASP Foundation. All rights reserved.'), StringStruct(u'OriginalFilename', u'blint.exe'), StringStruct(u'ProductName', u'blint'), - StringStruct(u'ProductVersion', u'2.2.0.0')]) + StringStruct(u'ProductVersion', u'2.2.1.0')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) ] diff --git a/pyproject.toml b/pyproject.toml index 65d82bd..d1d0216 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "blint" -version = "2.2.0" +version = "2.2.1" description = "Linter and SBOM generator for binary files." authors = ["Prabhu Subramanian ", "Caroline Russell "] license = "MIT"