Skip to content

Commit

Permalink
feat(reporting): report meta-data information about chunks.
Browse files Browse the repository at this point in the history
Allow handlers to provide a dict value as part of a ValidChunk metadata
attribute. That dictionnary can contain any relevant metadata
information from the perspective of the handler, but we advise handler
writers to report parsed information such as header values.

This metadata dict is later reported as part of our ChunkReports and
available in the JSON report file if the user requested one.

The idea is to expose metadata to further analysis steps through the
unblob report. For example, a binary analysis toolkit would read the load
address and architecture from a uImage chunk to analyze the file
extracted from that chunk with the right settings.

A note on the 'as_dict' implementation.

The initial idea was to implement it in dissect.cstruct (see
fox-it/dissect.cstruct#29), but due to expected
changes in the project's API I chose to implement it in unblob so we're
not dependent on another project.
  • Loading branch information
qkaiser committed Apr 14, 2023
1 parent f7f32fa commit 0b150ba
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 4 deletions.
25 changes: 24 additions & 1 deletion unblob/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pathlib import Path
from typing import Iterator, Tuple

from dissect.cstruct import cstruct
from dissect.cstruct import Instance, cstruct
from pyperscan import Scan

from .logging import format_hex
Expand Down Expand Up @@ -311,3 +311,26 @@ def read_until_past(file: File, pattern: bytes):
return file.tell()
if next_byte not in pattern:
return file.tell() - 1


def as_dict(obj):
"""Convert a Python class instance to a dictionary."""
if isinstance(obj, dict):
return obj
if isinstance(obj, list):
return [as_dict(item) for item in obj]
if isinstance(obj, Instance):
result = {}
for k, v in obj._values.items(): # noqa: SLF001
result[k] = v
return result

result = {}
for key, value in obj.__dict__.items():
if key.startswith("_"):
continue
if isinstance(value, (list, tuple)):
result[key] = [as_dict(item) for item in value]
else:
result[key] = as_dict(value)
return result
5 changes: 4 additions & 1 deletion unblob/handlers/archive/sevenzip.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from structlog import get_logger

from unblob.extractors import Command
from unblob.file_utils import as_dict

from ...models import File, HexString, StructHandler, ValidChunk

Expand Down Expand Up @@ -70,4 +71,6 @@ def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]
# We read the signature header here to get the offset to the header database
first_db_header = start_offset + len(header) + header.next_header_offset
end_offset = first_db_header + header.next_header_size
return ValidChunk(start_offset=start_offset, end_offset=end_offset)
return ValidChunk(
start_offset=start_offset, end_offset=end_offset, metadata=as_dict(header)
)
4 changes: 3 additions & 1 deletion unblob/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class ValidChunk(Chunk):

handler: "Handler" = attr.ib(init=False, eq=False)
is_encrypted: bool = attr.ib(default=False)
metadata: dict = attr.ib(default={})

def extract(self, inpath: Path, outdir: Path):
if self.is_encrypted:
Expand All @@ -108,6 +109,7 @@ def as_report(self, extraction_reports: List[Report]) -> ChunkReport:
size=self.size,
handler_name=self.handler.NAME,
is_encrypted=self.is_encrypted,
metadata=self.metadata,
extraction_reports=extraction_reports,
)

Expand Down Expand Up @@ -188,7 +190,7 @@ def default(self, obj):

if isinstance(obj, bytes):
try:
return obj.decode()
return obj.decode("utf-8", errors="surrogateescape")
except UnicodeDecodeError:
return str(obj)

Expand Down
3 changes: 2 additions & 1 deletion unblob/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import traceback
from enum import Enum
from pathlib import Path
from typing import List, Optional, Union, final
from typing import Dict, List, Optional, Union, final

import attr

Expand Down Expand Up @@ -181,6 +181,7 @@ class ChunkReport(Report):
end_offset: int
size: int
is_encrypted: bool
metadata: Dict
extraction_reports: List[Report]


Expand Down

0 comments on commit 0b150ba

Please sign in to comment.