From 2e788d91b73efa756c984165d2d459a9331dd114 Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Mon, 19 Feb 2024 19:18:49 +0000 Subject: [PATCH 1/9] Use filename alone in the default component Signed-off-by: Prabhu Subramanian --- blint/sbom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blint/sbom.py b/blint/sbom.py index 90803d8..facb101 100644 --- a/blint/sbom.py +++ b/blint/sbom.py @@ -36,7 +36,7 @@ def default_parent(src_dirs: list[str]) -> Component: """ if not src_dirs: raise ValueError("No source directories provided") - name = src_dirs[0] + name = os.path.basename(src_dirs[0]) purl = f"pkg:generic/{name}@latest" component = Component(type=Type.application, name=name, version="latest", purl=purl) component.bom_ref = RefType(purl) From 2b65f34e65f4459d0be70cc94dcf380fba1284ef Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Mon, 19 Feb 2024 19:33:52 +0000 Subject: [PATCH 2/9] Added method to ignore symbols. Trim output by disabling indentation in deep mode Signed-off-by: Prabhu Subramanian --- blint/binary.py | 11 ++++++++++- blint/sbom.py | 7 ++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/blint/binary.py b/blint/binary.py index a4abed5..f73cb1b 100644 --- a/blint/binary.py +++ b/blint/binary.py @@ -213,6 +213,15 @@ def parse_strings(parsed_obj): return strings_list +def ignorable_symbol(symbol_name: str | None) -> bool: + if not symbol_name: + return True + for pref in ("$f64.", "__"): + if symbol_name.startswith(pref): + return True + return False + + def parse_symbols(symbols): """ Parse symbols from a list of symbols. @@ -238,7 +247,7 @@ def parse_symbols(symbols): symbol_name = symbol.demangled_name if isinstance(symbol_name, lief.lief_errors): symbol_name = symbol.name - if symbol_name: + if not ignorable_symbol(symbol_name): exe_type = guess_exe_type(symbol_name) symbols_list.append( { diff --git a/blint/sbom.py b/blint/sbom.py index facb101..e649bc0 100644 --- a/blint/sbom.py +++ b/blint/sbom.py @@ -127,11 +127,11 @@ def generate(src_dirs: list[str], output_file: str, deep_mode: bool) -> bool: for f in android_files: progress.update(task, description=f"Processing [bold]{f}[/bold]") components.extend(process_android_file(components, deep_mode, dependencies, f, sbom)) - return create_sbom(components, dependencies, output_file, sbom) + return create_sbom(components, dependencies, output_file, sbom, deep_mode) def create_sbom( - components: list[Component], dependencies: list[dict], output_file: str, sbom: CycloneDX + components: list[Component], dependencies: list[dict], output_file: str, sbom: CycloneDX, deep_mode: bool ) -> bool: """ Creates a Software Bill of Materials (SBOM) with the provided components, @@ -142,6 +142,7 @@ def create_sbom( dependencies (list): A list of dependencies. output_file (str): The path to the output file. sbom: The SBOM object representing the SBOM. + deep_mode (bool): Flag indicating whether to perform deep analysis. Returns: bool: True if the SBOM generation is successful, False otherwise. @@ -170,7 +171,7 @@ def create_sbom( with open(output_file, mode="w", encoding="utf-8") as fp: fp.write( sbom.model_dump_json( - indent=2, + indent=None if deep_mode else 2, exclude_none=True, exclude_defaults=True, warnings=False, From d33acaea159c472b29daf98f1abfe262b9477a1c Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Mon, 19 Feb 2024 21:14:00 +0000 Subject: [PATCH 3/9] Track symbols version and dynamic symbols Signed-off-by: Prabhu Subramanian --- blint/config.py | 2 ++ blint/sbom.py | 70 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/blint/config.py b/blint/config.py index 600a766..468f6f8 100644 --- a/blint/config.py +++ b/blint/config.py @@ -1245,3 +1245,5 @@ ) ], } + +SYMBOL_DELIMITER = "~~" diff --git a/blint/sbom.py b/blint/sbom.py index e649bc0..9540d99 100644 --- a/blint/sbom.py +++ b/blint/sbom.py @@ -7,6 +7,7 @@ from blint.android import collect_app_metadata from blint.binary import parse +from blint.config import SYMBOL_DELIMITER from blint.cyclonedx.spec import ( BomFormat, Component, @@ -181,6 +182,49 @@ def create_sbom( return True +def components_from_symbols_version(symbols_version: list[dict]) -> list[Component]: + """ + Creates a list of Component objects from symbols version. + This style of detection is quite imprecise since the version is just a min specifier. + + Args: + symbols_version (list[dict]): A list of symbols version. + + Returns: + list[Component]: list of components + """ + lib_components: list[Component] = [] + for symbol in symbols_version: + group = "" + name = symbol["name"] + version = "latest" + if "_" in name: + tmp_a = name.split("_") + if len(tmp_a) == 2: + version = tmp_a[-1] + name = tmp_a[0].lower() + if name.startswith("glib"): + name = name.removeprefix("g") + group = "gnu" + purl = f"pkg:generic/{group}/{name}@{version}" if group else f"pkg:generic/{name}@{version}" + if symbol.get("hash"): + purl = f"{purl}?hash={symbol.get('hash')}" + comp = Component( + type=Type.library, + group=group, + name=name, + version=version, + purl=purl, + evidence=create_component_evidence(symbol["name"], 0.5), + properties=[ + Property(name="internal:symbol_version", value=symbol["name"]) + ] + ) + comp.bom_ref = RefType(purl) + lib_components.append(comp) + return lib_components + + def process_exe_file( components: list[Component], deep_mode: bool, @@ -206,6 +250,7 @@ def process_exe_file( parent_component: Component = default_parent([exe]) metadata: Dict[str, Any] = parse(exe) parent_component.properties = [] + lib_components: list[Component] = [] for prop in ( "binary_type", "magic", @@ -236,26 +281,41 @@ def process_exe_file( value = str(metadata.get(prop)) if isinstance(metadata.get(prop), bool): value = value.lower() - parent_component.properties.append(Property(name=f"internal:{prop}", value=value)) + if value: + parent_component.properties.append(Property(name=f"internal:{prop}", value=value)) if deep_mode: + symbols_version: list[dict] = metadata.get("symbols_version", []) + # Attempt to detect library components from the symbols version block + # If this is unsuccessful then store the information as a property + lib_components += components_from_symbols_version(symbols_version) + if not lib_components: + parent_component.properties += [ + Property( + name="internal:symbols_version", + value=", ".join([f["name"] for f in symbols_version]), + ) + ] parent_component.properties += [ Property( name="internal:functions", - value="~~".join([f["name"] for f in metadata.get("functions", [])]), + value=SYMBOL_DELIMITER.join([f["name"] for f in metadata.get("functions", [])]), ), Property( name="internal:symtab_symbols", - value="~~".join([f["name"] for f in metadata.get("symtab_symbols", [])]), + value=SYMBOL_DELIMITER.join([f["name"] for f in metadata.get("symtab_symbols", [])]), ), Property( name="internal:imports", - value="~~".join([f["name"] for f in metadata.get("imports", [])]), + value=SYMBOL_DELIMITER.join([f["name"] for f in metadata.get("imports", [])]), + ), + Property( + name="internal:dynamic_symbols", + value=SYMBOL_DELIMITER.join([f["name"] for f in metadata.get("dynamic_symbols", [])]), ), ] if not sbom.metadata.component.components: sbom.metadata.component.components = [] sbom.metadata.component.components.append(parent_component) - lib_components: list[Component] = [] if metadata.get("libraries"): for entry in metadata.get("libraries"): comp = create_library_component(entry, exe) From b7430c087fe36af60eeaf195e559c87f523366d9 Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Mon, 19 Feb 2024 21:33:52 +0000 Subject: [PATCH 4/9] Capture build id from notes Signed-off-by: Prabhu Subramanian --- blint/binary.py | 8 +++++--- blint/sbom.py | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/blint/binary.py b/blint/binary.py index f73cb1b..0171093 100644 --- a/blint/binary.py +++ b/blint/binary.py @@ -77,9 +77,11 @@ def extract_note_data(idx, note): if "ID Hash" in note_str: build_id = note_str.rsplit("ID Hash:", maxsplit=1)[-1].strip() description = note.description - description_str = " ".join(map(integer_to_hex_str, description[:16])) - if len(description) > 16: + description_str = " ".join(map(integer_to_hex_str, description[:64])) + if len(description) > 64: description_str += " ..." + if note.type == lief.ELF.Note.TYPE.GNU_BUILD_ID: + build_id = description_str.replace(" ", "") type_str = note.type type_str = str(type_str).rsplit(".", maxsplit=1)[-1] note_details = "" @@ -101,7 +103,7 @@ def extract_note_data(idx, note): version = note_details.version abi = str(note_details.abi) version_str = f"{version[0]}.{version[1]}.{version[2]}" - if not version_str and type_str == "BUILD_ID" and build_id: + if not version_str and build_id: version_str = build_id return { "index": idx, diff --git a/blint/sbom.py b/blint/sbom.py index 9540d99..fdaacc2 100644 --- a/blint/sbom.py +++ b/blint/sbom.py @@ -283,6 +283,10 @@ def process_exe_file( value = value.lower() if value: parent_component.properties.append(Property(name=f"internal:{prop}", value=value)) + if metadata.get("notes"): + for note in metadata.get("notes"): + if note.get("version"): + parent_component.properties.append(Property(name=f"internal:{note.get('type')}", value=note.get('version'))) if deep_mode: symbols_version: list[dict] = metadata.get("symbols_version", []) # Attempt to detect library components from the symbols version block From 68b20115fe0af0930cdeb1a9ef105ef6a2a2d2c7 Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Mon, 19 Feb 2024 21:37:27 +0000 Subject: [PATCH 5/9] Capture build id from notes Signed-off-by: Prabhu Subramanian --- blint/sbom.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/blint/sbom.py b/blint/sbom.py index fdaacc2..2182adf 100644 --- a/blint/sbom.py +++ b/blint/sbom.py @@ -286,7 +286,8 @@ def process_exe_file( if metadata.get("notes"): for note in metadata.get("notes"): if note.get("version"): - parent_component.properties.append(Property(name=f"internal:{note.get('type')}", value=note.get('version'))) + parent_component.properties.append( + Property(name=f"internal:{note.get('type')}", value=note.get('version'))) if deep_mode: symbols_version: list[dict] = metadata.get("symbols_version", []) # Attempt to detect library components from the symbols version block @@ -302,7 +303,8 @@ def process_exe_file( parent_component.properties += [ Property( name="internal:functions", - value=SYMBOL_DELIMITER.join([f["name"] for f in metadata.get("functions", [])]), + value=SYMBOL_DELIMITER.join( + [f["name"] for f in metadata.get("functions", []) if not f["name"].startswith("__")]), ), Property( name="internal:symtab_symbols", From efafe3dd6dbeaeb2318a6213a9d910f8316a762f Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Mon, 19 Feb 2024 21:39:35 +0000 Subject: [PATCH 6/9] Update version Signed-off-by: Prabhu Subramanian --- poetry.lock | 1 - pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0bbb795..ed84ca8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -776,7 +776,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, diff --git a/pyproject.toml b/pyproject.toml index b61ec21..77ebe9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "blint" -version = "2.0.0" +version = "2.0.1" description = "Linter and SBOM generator for binary files." authors = ["Prabhu Subramanian ", "Caroline Russell "] license = "Apache-2.0" From cd23f78f033cc0f65c0f4d7fdfaaef4ca29f0751 Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Mon, 19 Feb 2024 21:44:41 +0000 Subject: [PATCH 7/9] Update version Signed-off-by: Prabhu Subramanian --- Info.plist | 4 ++-- blint/cli.py | 9 ++++----- file_version_info.txt | 8 ++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Info.plist b/Info.plist index b052df8..4477b7b 100644 --- a/Info.plist +++ b/Info.plist @@ -3,12 +3,12 @@ CFBundleDevelopmentRegion English CFBundleIdentifier - io.owasp-dep-scan.blint + io.owasp-depscan.blint CFBundleInfoDictionaryVersion 6.0 CFBundleName blint CFBundleVersion - 2.0.0 + 2.0.1 diff --git a/blint/cli.py b/blint/cli.py index d230674..fca03e0 100644 --- a/blint/cli.py +++ b/blint/cli.py @@ -5,9 +5,9 @@ import os import sys -from blint.sbom import generate -from blint.logger import LOG from blint.analysis import AnalysisRunner, report +from blint.logger import LOG +from blint.sbom import generate from blint.utils import gen_file_list BLINT_LOGO = """ @@ -148,9 +148,6 @@ def handle_args(): # Create reports directory reports_dir = args.reports_dir - if not os.path.exists(reports_dir): - os.makedirs(reports_dir) - for src in src_dirs: if not os.path.exists(src): LOG.error(f"{src} is an invalid file or directory!") @@ -171,6 +168,8 @@ def main(): generate(src_dirs, sbom_output, args.deep_mode) # Default case else: + if not os.path.exists(reports_dir): + os.makedirs(reports_dir) files = gen_file_list(src_dirs) analyzer = AnalysisRunner() findings, reviews, fuzzables = analyzer.start( diff --git a/file_version_info.txt b/file_version_info.txt index 696b70f..01afdc0 100644 --- a/file_version_info.txt +++ b/file_version_info.txt @@ -7,8 +7,8 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. Must always contain 4 elements. - filevers=(2,0,0,0), - prodvers=(2,0,0,0), + filevers=(2,0,1,0), + prodvers=(2,0,1,0), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -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.0.0.0'), + StringStruct(u'FileVersion', u'2.0.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.0.0.0')]) + StringStruct(u'ProductVersion', u'2.0.1.0')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) ] From db873672e82cfec1183c696eb1a65b95815d848f Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Mon, 19 Feb 2024 22:02:03 +0000 Subject: [PATCH 8/9] Advance progress display Signed-off-by: Prabhu Subramanian --- blint/sbom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blint/sbom.py b/blint/sbom.py index 2182adf..e4a29ca 100644 --- a/blint/sbom.py +++ b/blint/sbom.py @@ -117,7 +117,7 @@ def generate(src_dirs: list[str], output_file: str, deep_mode: bool) -> bool: start=True, ) for exe in exe_files: - progress.update(task, description=f"Processing [bold]{exe}[/bold]") + progress.update(task, description=f"Processing [bold]{exe}[/bold]", advance=1) components.extend(process_exe_file(components, deep_mode, dependencies, exe, sbom)) if android_files: task = progress.add_task( @@ -126,7 +126,7 @@ def generate(src_dirs: list[str], output_file: str, deep_mode: bool) -> bool: start=True, ) for f in android_files: - progress.update(task, description=f"Processing [bold]{f}[/bold]") + progress.update(task, description=f"Processing [bold]{f}[/bold]", advance=1) components.extend(process_android_file(components, deep_mode, dependencies, f, sbom)) return create_sbom(components, dependencies, output_file, sbom, deep_mode) From bb13cd427dacd51acddec202a5674e99b5f219ad Mon Sep 17 00:00:00 2001 From: Prabhu Subramanian Date: Tue, 20 Feb 2024 08:40:29 +0000 Subject: [PATCH 9/9] Use Symbol delimiter for android as well Signed-off-by: Prabhu Subramanian --- blint/android.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/blint/android.py b/blint/android.py index 5fb7c1c..a9f43e7 100644 --- a/blint/android.py +++ b/blint/android.py @@ -5,6 +5,7 @@ import tempfile from blint.binary import parse, parse_dex +from blint.config import SYMBOL_DELIMITER from blint.cyclonedx.spec import ( Component, Property, @@ -271,7 +272,7 @@ def parse_so_file(app_file, app_temp_dir, sof): properties=[ Property(name="internal:srcFile", value=rel_path), Property(name="internal:appFile", value=app_file), - Property(name="internal:functions", value=", ".join(set(functions))), + Property(name="internal:functions", value=SYMBOL_DELIMITER.join(set(functions))), ], ) component.bom_ref = RefType(purl) @@ -355,7 +356,7 @@ def create_dex_component(app_file, dex_metadata, group, name, rel_path, version) Property(name="internal:appFile", value=app_file), Property( name="internal:functions", - value=", ".join( + value=SYMBOL_DELIMITER.join( { f"""{m.name}({','.join([_clean_type(p.underlying_array_type) for p in m.prototype.parameters_type])}):{_clean_type(m.prototype.return_type.underlying_array_type)}""" for m in dex_metadata.get("methods") @@ -364,7 +365,7 @@ def create_dex_component(app_file, dex_metadata, group, name, rel_path, version) ), Property( name="internal:classes", - value=", ".join( + value=SYMBOL_DELIMITER.join( set(sorted([_clean_type(c.fullname) for c in dex_metadata.get("classes")])) ), ),