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"