Skip to content

Commit d574ea1

Browse files
authored
Support for detecting some loaders (#113)
* Support for detecting some loaders Signed-off-by: Prabhu Subramanian <[email protected]> * Support for detecting some loaders Signed-off-by: Prabhu Subramanian <[email protected]> * Support for detecting some loaders Signed-off-by: Prabhu Subramanian <[email protected]> --------- Signed-off-by: Prabhu Subramanian <[email protected]>
1 parent 9759641 commit d574ea1

14 files changed

+208
-40
lines changed

Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
<key>CFBundleName</key>
1010
<string>blint</string>
1111
<key>CFBundleVersion</key>
12-
<string>2.2.0</string>
12+
<string>2.2.1</string>
1313
</dict>
1414
</plist>

blint/analysis.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
check_virtual_size, check_authenticode,
2222
check_dll_characteristics, check_codesign,
2323
check_trust_info)
24-
from blint.config import PII_WORDS, get_int_from_env
24+
from blint.config import FIRST_STAGE_WORDS, PII_WORDS, get_int_from_env
2525
from blint.logger import LOG, console
2626
from blint.utils import (create_findings_table, is_fuzzable_name, print_findings_table)
2727

@@ -54,12 +54,20 @@
5454
review_symbols_dict = defaultdict(list)
5555
review_imports_dict = defaultdict(list)
5656
review_entries_dict = defaultdict(list)
57-
review_rules_cache = {"PII_READ": {
58-
"title": "Detect PII Read Operations",
59-
"summary": "Can Retrieve Sensitive PII data",
60-
"description": "Contains logic to retrieve sensitive data such as names, email, passwords etc.",
61-
"patterns": PII_WORDS
62-
}}
57+
review_rules_cache = {
58+
"PII_READ": {
59+
"title": "Detect PII Read Operations",
60+
"summary": "Can Retrieve Sensitive PII data",
61+
"description": "Contains logic to retrieve sensitive data such as names, email, passwords etc.",
62+
"patterns": PII_WORDS
63+
},
64+
"LOADER_SYMBOLS": {
65+
"title": "Detect Initial Loader",
66+
"summary": "Behaves like a loader",
67+
"description": "The binary behaves like a loader by downloading and executing additional payloads.",
68+
"patterns": FIRST_STAGE_WORDS
69+
},
70+
}
6371

6472
# Debug mode
6573
DEBUG_MODE = os.getenv("SCAN_DEBUG_MODE") == "debug"
@@ -309,7 +317,10 @@ def print_reviews_table(reviews, files):
309317
def json_serializer(obj):
310318
"""JSON serializer to help serialize problematic types such as bytes"""
311319
if isinstance(obj, bytes):
312-
return obj.decode('utf-8')
320+
try:
321+
return obj.decode('utf-8')
322+
except UnicodeDecodeError:
323+
return ""
313324

314325
return obj
315326

@@ -494,7 +505,7 @@ def run_review(self, metadata):
494505
or self.review_entries_list
495506
):
496507
return self._review_lists(metadata)
497-
return {}
508+
return self._review_loader_symbols(metadata)
498509

499510
def _review_lists(self, metadata):
500511
"""
@@ -516,6 +527,7 @@ def _review_lists(self, metadata):
516527
if self.review_entries_list:
517528
self._review_entries(metadata)
518529
self._review_pii(metadata)
530+
self._review_loader_symbols(metadata)
519531
return self.results
520532

521533
def _review_imports(self, metadata):
@@ -562,6 +574,22 @@ def _review_pii(self, metadata):
562574
results["PII_READ"].append({"pattern": e, "function": e})
563575
self.results |= results
564576

577+
def _review_loader_symbols(self, metadata):
578+
"""
579+
Reviews loader symbols.
580+
581+
Args:
582+
metadata (dict): The metadata to review.
583+
584+
Returns:
585+
dict: The results of the review.
586+
"""
587+
entries_list = [f.get("name", "") for f in metadata.get("first_stage_symbols", [])]
588+
results = defaultdict(list)
589+
for e in entries_list[0:EVIDENCE_LIMIT]:
590+
results["LOADER_SYMBOLS"].append({"pattern": e, "function": e})
591+
self.results |= results
592+
565593
def _review_symbols_exe(self, metadata):
566594
"""
567595
Reviews symbols in the metadata.

blint/binary.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import lief
1111

12-
from blint.config import PII_WORDS, get_float_from_env, get_int_from_env
12+
from blint.config import FIRST_STAGE_WORDS, PII_WORDS, get_float_from_env, get_int_from_env
1313
from blint.logger import DEBUG, LOG
1414
from blint.utils import camel_to_snake, calculate_entropy, check_secret, cleanup_dict_lief_errors, decode_base64
1515

@@ -270,7 +270,7 @@ def parse_strings(parsed_obj):
270270
if (entropy and (entropy > MIN_ENTROPY or len(s) > MIN_LENGTH)) or secret_type:
271271
strings_list.append(
272272
{
273-
"value": (decode_base64(s) if s.endswith("==") else s),
273+
"value": decode_base64(s) if s.endswith("==") else s,
274274
"entropy": entropy,
275275
"secret_type": secret_type,
276276
}
@@ -788,7 +788,6 @@ def add_elf_metadata(exe_file, metadata, parsed_obj):
788788
metadata["has_runpath"] = False
789789
elif runpath:
790790
metadata["has_runpath"] = True
791-
# This is getting renamed to symtab_symbols in lief 0.15.0
792791
symtab_symbols = parsed_obj.symtab_symbols
793792
metadata["static"] = bool(symtab_symbols and not isinstance(symtab_symbols, lief.lief_errors))
794793
dynamic_entries = parsed_obj.dynamic_entries
@@ -797,6 +796,10 @@ def add_elf_metadata(exe_file, metadata, parsed_obj):
797796
metadata["notes"] = parse_notes(parsed_obj)
798797
metadata["strings"] = parse_strings(parsed_obj)
799798
metadata["symtab_symbols"], exe_type = parse_symbols(symtab_symbols)
799+
rdata_section = parsed_obj.get_section(".rodata")
800+
text_section = parsed_obj.get_section(".text")
801+
if not metadata["symtab_symbols"]:
802+
add_elf_rdata_symbols(metadata, rdata_section, text_section)
800803
if exe_type:
801804
metadata["exe_type"] = exe_type
802805
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):
11141117
break
11151118
rdata_section = parsed_obj.get_section(".rdata")
11161119
text_section = parsed_obj.get_section(".text")
1117-
if not rdata_section and text_section:
1118-
rdata_section = text_section
1119-
if (not metadata["symtab_symbols"] or metadata["exe_type"] != "gobinary") and rdata_section:
1120-
add_pe_rdata_symbols(metadata, rdata_section)
1120+
# If there are no .rdata and .text section, then attempt to look for two alphanumeric sections
1121+
if not rdata_section and not text_section:
1122+
for section in parsed_obj.sections:
1123+
if str(section.name).removeprefix(".").isalnum():
1124+
if not rdata_section:
1125+
rdata_section = section
1126+
else:
1127+
text_section = section
1128+
if rdata_section or text_section:
1129+
add_pe_rdata_symbols(metadata, rdata_section, text_section)
11211130
metadata["exports"] = parse_pe_exports(parsed_obj.get_export())
11221131
metadata["functions"] = parse_functions(parsed_obj.functions)
11231132
metadata["ctor_functions"] = parse_functions(parsed_obj.ctor_functions)
@@ -1247,33 +1256,39 @@ def add_pe_optional_headers(metadata, optional_header):
12471256
return metadata
12481257

12491258

1250-
def add_pe_rdata_symbols(metadata, rdata_section: lief.PE.Section):
1259+
def add_pe_rdata_symbols(metadata, rdata_section: lief.PE.Section, text_section: lief.PE.Section):
12511260
"""Adds PE rdata symbols to the metadata dictionary.
12521261
12531262
Args:
12541263
metadata: The dictionary to store the metadata.
12551264
rdata_section: .rdata section of the PE binary.
1265+
text_section: .text section of the PE binary.
12561266
12571267
Returns:
12581268
The updated metadata dictionary.
12591269
"""
1260-
if not rdata_section or not rdata_section.content:
1261-
return metadata
1270+
file_extns_from_rdata = r".*\.(go|s|dll|exe|pdb)"
12621271
rdata_symbols = set()
12631272
pii_symbols = []
1273+
first_stage_symbols = []
12641274
for pii in PII_WORDS:
1265-
for vari in (f"get{pii}", f"get_{camel_to_snake(pii)}"):
1266-
if rdata_section.search_all(vari):
1275+
for vari in (f"get{pii}", f"get_{pii}", f"get_{camel_to_snake(pii)}", f"Get{pii}"):
1276+
if (rdata_section and rdata_section.search_all(vari)) or (text_section and text_section.search_all(vari)):
12671277
pii_symbols.append(
12681278
{"name": vari.lower(), "type": "FUNCTION", "is_function": True, "is_imported": False})
12691279
continue
1270-
str_content = codecs.decode(rdata_section.content.tobytes("A"), encoding="utf-8", errors="ignore")
1280+
for sw in FIRST_STAGE_WORDS:
1281+
if (rdata_section and rdata_section.search_all(sw)) or (text_section and text_section.search_all(sw)):
1282+
first_stage_symbols.append(
1283+
{"name": sw, "type": "FUNCTION", "is_function": True, "is_imported": True})
1284+
str_content = codecs.decode(rdata_section.content.tobytes("A"), encoding="utf-8",
1285+
errors="ignore") if rdata_section and rdata_section.content else ""
12711286
for block in str_content.split(" "):
1272-
if "runtime." in block or "internal/" in block or ".go" in block or ".dll" in block:
1287+
if "runtime." in block or "internal/" in block or re.match(file_extns_from_rdata, block):
12731288
if ".go" in block:
12741289
metadata["exe_type"] = "gobinary"
12751290
for asym in block.split("\x00"):
1276-
if re.match(r".*\.(go|s|dll)$", asym):
1291+
if re.match(file_extns_from_rdata + "$", asym):
12771292
rdata_symbols.add(asym)
12781293
if not metadata["symtab_symbols"]:
12791294
metadata["symtab_symbols"] = []
@@ -1285,7 +1300,31 @@ def add_pe_rdata_symbols(metadata, rdata_section: lief.PE.Section):
12851300
"is_imported": True
12861301
} for s in sorted(rdata_symbols)
12871302
]
1288-
metadata["pii_symbols"] = pii_symbols
1303+
if pii_symbols:
1304+
metadata["pii_symbols"] = pii_symbols
1305+
if first_stage_symbols:
1306+
metadata["first_stage_symbols"] = first_stage_symbols
1307+
return metadata
1308+
1309+
1310+
def add_elf_rdata_symbols(metadata, rdata_section: lief.PE.Section, text_section: lief.PE.Section):
1311+
"""Adds ELF rdata symbols to the metadata dictionary.
1312+
1313+
Args:
1314+
metadata: The dictionary to store the metadata.
1315+
rdata_section: .data section of the ELF binary.
1316+
text_section: .text section of the ELF binary.
1317+
1318+
Returns:
1319+
The updated metadata dictionary.
1320+
"""
1321+
first_stage_symbols = []
1322+
for sw in FIRST_STAGE_WORDS:
1323+
if (rdata_section and rdata_section.search_all(sw)) or (text_section and text_section.search_all(sw)):
1324+
first_stage_symbols.append(
1325+
{"name": sw, "type": "FUNCTION", "is_function": True, "is_imported": True})
1326+
if first_stage_symbols:
1327+
metadata["first_stage_symbols"] = first_stage_symbols
12891328
return metadata
12901329

12911330

blint/config.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1230,7 +1230,7 @@
12301230
"email": [re.compile(r"(?<=mailto:)[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9.-]+")],
12311231
"ip": [
12321232
re.compile(
1233-
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])$"
1233+
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]+)?$"
12341234
)
12351235
],
12361236
}
@@ -1268,6 +1268,7 @@ def get_int_from_env(name, default):
12681268
return int(get_float_from_env(name, default))
12691269

12701270

1271+
# PII related symbols
12711272
PII_WORDS = (
12721273
"FirstName",
12731274
"LastName",
@@ -1293,3 +1294,43 @@ def get_int_from_env(name, default):
12931294
"AgentStatus",
12941295
"LastLoginTime"
12951296
)
1297+
1298+
# Some symbols to look for in a first-stage payload
1299+
FIRST_STAGE_WORDS = (
1300+
"System.ServiceProcess",
1301+
"System.IO.Compression",
1302+
"System.Reflection.Emit",
1303+
"ICryptoTransform",
1304+
"LoadAssembly",
1305+
"GetEncodedData",
1306+
"add_AssemblyResolve",
1307+
"CreateDecryptor",
1308+
"GetExecutingAssembly",
1309+
"GetModules",
1310+
"get_IsFamilyOrAssembly",
1311+
"/proc/%d/cmdline",
1312+
"/proc/%s/exe",
1313+
"/proc/self/exe",
1314+
"/proc/net/route",
1315+
"/etc/resolv.conf",
1316+
"/usr/lib/systemd/systemd",
1317+
"/usr/compress/bin/",
1318+
"/usr/libexec/openssh/sftp-server",
1319+
"/usr/sbin/reboot",
1320+
"/usr/bin/reboot",
1321+
"/usr/sbin/shutdown",
1322+
"/usr/bin/shutdown",
1323+
"/usr/sbin/poweroff",
1324+
"/usr/bin/poweroff",
1325+
"/usr/sbin/halt",
1326+
"/usr/bin/halt",
1327+
"virtualboxemulunit",
1328+
"virtualboximportunit",
1329+
"virtualboxunit",
1330+
"qvirtualboxglobalsunit",
1331+
"Uvirtualboxdisasm",
1332+
"loaderx86.dll",
1333+
"ZwProtectVirtualMemory",
1334+
"shlwapi.dll",
1335+
"DeleteCriticalSection"
1336+
)

blint/data/annotations/review_exe_go.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ group: EXE_REVIEWS
44
exe_type:
55
- gobinary
66
- x86_64-executable
7+
- x86_64-exec
78
rules:
89
- id: FILE_IOUTIL
910
title: IO util functions used

blint/data/annotations/review_imports_pe.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ rules:
4141
- CreateSocketHandle
4242
- CreateThread
4343
- CreateThreadpool
44+
- GetCurrentThread
45+
- GetThreadLocale
46+
- ExitProcess
47+
- FreeLibrary
48+
- ExitThread
4449
- id: PROCESS_STATUS_API
4550
title: Process status api used
4651
summary: Queries about processes and device drivers
@@ -464,6 +469,8 @@ rules:
464469
- CreateHardLink
465470
- BackupEventLog
466471
- CreateFileMapping
472+
- SetFileAttributes
473+
- SetFilePointer
467474
- id: WIN_BCRYPT_API
468475
title: Win bcrypt api functions used
469476
summary: Can Encrypt Files
@@ -1973,6 +1980,14 @@ rules:
19731980
description: |
19741981
Shell Windows API allows applications to access functions provided by the operating system shell, and to change and enhance it.
19751982
patterns:
1983+
- DllInstall
1984+
- GetProcessReference
1985+
- ParseURLA
1986+
- ParseURLW
1987+
- PathFindFileName
1988+
- PathFindOnPath
1989+
- PathIsSystemFolder
1990+
- PathIsUNCServerShare
19761991
- FindExecutableA
19771992
- FindExecutableW
19781993
- InitNetworkAddressControl
@@ -1994,6 +2009,30 @@ rules:
19942009
- GetManagedApplications
19952010
- InstallApplication
19962011
- UninstallApplication
2012+
- SHCreateThread
2013+
- SHCreateStreamOnFile
2014+
- SHOpenRegStream
2015+
- SHRegCreateUSKey
2016+
- SHQueryValueEx
2017+
- SHQueryInfoKey
2018+
- UrlCreateFromPath
2019+
- PathMatchSpec
2020+
- SHBindToFolderIDListParent
2021+
- SHBrowseForFolder
2022+
- SHCreateDefaultContextMenu
2023+
- SHCreateShellItem
2024+
- SHFormatDrive
2025+
- SHGetDesktopFolder
2026+
- SHGetFolderPath
2027+
- SHGetFolderPathAndSubDir
2028+
- SHGetKnownFolder
2029+
- SHGetSpecialFolderLocation
2030+
- SHILCreateFromPath
2031+
- SHPathPrepareForWrite
2032+
- SHSetKnownFolderPath
2033+
- SignalFileOpen
2034+
- Win32DeleteFile
2035+
- shlwapi.dll
19972036
- id: KERNEL_API
19982037
title: Kernel api functions used
19992038
summary: Manipulates Windows Kernel & Drivers

blint/data/annotations/review_monero_go.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ group: SYMBOL_REVIEWS
44
exe_type:
55
- gobinary
66
- x86_64-executable
7+
- x86_64-exec
78
rules:
89
- id: MONERO_API_GO
910
title: Detect use of Monero wallet

blint/data/annotations/review_rootkits_win.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ text: Review for Windows rootkits
33
group: METHOD_REVIEWS
44
exe_type:
55
- x86_64-executable
6+
- x86_64-exec
67
- PE32
78
- PE64
89
rules:
@@ -67,7 +68,6 @@ rules:
6768
- BeIsStringNull
6869
- BeIsStringTerminated
6970
- BeUnSupportedFunction
70-
7171
- id: NIDHOGG
7272
title: Detect Nidhogg
7373
summary: Provides Tools for Gaining Privileged Access and Injecting Malicious Code

0 commit comments

Comments
 (0)