Skip to content

Commit

Permalink
Merge pull request #59 from owasp-dep-scan/feature/filename-component
Browse files Browse the repository at this point in the history
Use filename alone in the default component
  • Loading branch information
prabhu authored Feb 20, 2024
2 parents ca5b8be + bb13cd4 commit ee8cd7f
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 31 deletions.
4 changes: 2 additions & 2 deletions Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleIdentifier</key>
<string>io.owasp-dep-scan.blint</string>
<string>io.owasp-depscan.blint</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>blint</string>
<key>CFBundleVersion</key>
<string>2.0.0</string>
<string>2.0.1</string>
</dict>
</plist>
7 changes: 4 additions & 3 deletions blint/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand All @@ -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")]))
),
),
Expand Down
19 changes: 15 additions & 4 deletions blint/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand All @@ -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,
Expand Down Expand Up @@ -213,6 +215,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.
Expand All @@ -238,7 +249,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(
{
Expand Down
9 changes: 4 additions & 5 deletions blint/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
Expand Down Expand Up @@ -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!")
Expand All @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions blint/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1245,3 +1245,5 @@
)
],
}

SYMBOL_DELIMITER = "~~"
89 changes: 78 additions & 11 deletions blint/sbom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -36,7 +37,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)
Expand Down Expand Up @@ -116,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(
Expand All @@ -125,13 +126,13 @@ 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)
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,
Expand All @@ -142,6 +143,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.
Expand Down Expand Up @@ -170,7 +172,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,
Expand All @@ -180,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,
Expand All @@ -205,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",
Expand Down Expand Up @@ -235,26 +281,47 @@ 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 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
# 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", []) if not f["name"].startswith("__")]),
),
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)
Expand Down
8 changes: 4 additions & 4 deletions file_version_info.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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])])
]
Expand Down
1 change: 0 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <prabhu@appthreat.com>", "Caroline Russell <caroline@appthreat.dev>"]
license = "Apache-2.0"
Expand Down

0 comments on commit ee8cd7f

Please sign in to comment.