From a17810b1199ca8223657a2494d53ea7def8e93a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Mon, 12 Aug 2024 15:50:34 +0200 Subject: [PATCH] added unpacking plugin for tenvis pk2 container --- .../plugins/unpacking/tenvis_pk2/__init__.py | 0 .../unpacking/tenvis_pk2/code/__init__.py | 0 .../unpacking/tenvis_pk2/code/tenvis_pk2.py | 174 ++++++++++++++++++ .../unpacking/tenvis_pk2/test/__init__.py | 0 .../unpacking/tenvis_pk2/test/data/broken.pk2 | Bin 0 -> 100 bytes .../unpacking/tenvis_pk2/test/data/test.pk2 | Bin 0 -> 158 bytes .../tenvis_pk2/test/test_tenvis_pk2.py | 36 ++++ pyproject.toml | 2 +- 8 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 fact_extractor/plugins/unpacking/tenvis_pk2/__init__.py create mode 100644 fact_extractor/plugins/unpacking/tenvis_pk2/code/__init__.py create mode 100644 fact_extractor/plugins/unpacking/tenvis_pk2/code/tenvis_pk2.py create mode 100644 fact_extractor/plugins/unpacking/tenvis_pk2/test/__init__.py create mode 100644 fact_extractor/plugins/unpacking/tenvis_pk2/test/data/broken.pk2 create mode 100644 fact_extractor/plugins/unpacking/tenvis_pk2/test/data/test.pk2 create mode 100644 fact_extractor/plugins/unpacking/tenvis_pk2/test/test_tenvis_pk2.py diff --git a/fact_extractor/plugins/unpacking/tenvis_pk2/__init__.py b/fact_extractor/plugins/unpacking/tenvis_pk2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fact_extractor/plugins/unpacking/tenvis_pk2/code/__init__.py b/fact_extractor/plugins/unpacking/tenvis_pk2/code/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fact_extractor/plugins/unpacking/tenvis_pk2/code/tenvis_pk2.py b/fact_extractor/plugins/unpacking/tenvis_pk2/code/tenvis_pk2.py new file mode 100644 index 00000000..60b01150 --- /dev/null +++ b/fact_extractor/plugins/unpacking/tenvis_pk2/code/tenvis_pk2.py @@ -0,0 +1,174 @@ +import struct +from datetime import datetime +from pathlib import Path +from typing import BinaryIO + +NAME = 'tenvis_pk2' +MIME_PATTERNS = ['firmware/pk2'] +VERSION = '0.1.0' + +PK2_MAGIC = b'PK2\x00' +XOR_KEY = [0xA1, 0x83, 0x24, 0x78, 0xB3, 0x41, 0x43, 0x56] +KEY_LEN = len(XOR_KEY) + + +class Pk2FileHeader: + """ + Header struct: + 0 | 4 | uint32 magic + 4 | 4 | uint32 camera type + 8 | 4 | uint32 creation time + 12 | 4 | char[4] version + 16 | 8 | char[8] reserved + 24 | 4 | uint32 section count + total size: 28 bytes + """ + + size = 28 + + def __init__(self, fp: BinaryIO): + file_hdr_data = fp.read(self.size) + self.magic = file_hdr_data[:4] # we parse the magic as bytes + self.camera_type, self.creation_time = struct.unpack(' 0: + chunk_size = min(self._BLOCK_SIZE, remaining) + data = self._fp.read(chunk_size) + if not data: + break + output = _decrypt(data) + out_fp.write(output) + remaining -= chunk_size + + def to_dict(self): + return {k: v for k, v in self.__dict__.items() if not k.startswith('_')} + + +class Pk2Cmd: + """ + 0 | x | char[x] command (XOR encrypted) + total size: x bytes (== section payload size) + """ + + def __init__(self, fp: BinaryIO, offset: int, size: int): + fp.seek(offset) + self.command = _decrypt(fp.read(size)).rstrip(b'\x00').decode('ascii', errors='replace') + self.size = size + + def __str__(self): + cmd = repr(self.command) + return f'CMD: {cmd}' + + +def _decrypt(data: bytes) -> bytearray: + output = bytearray() + for index, char in enumerate(data): + output.append(char ^ XOR_KEY[index % KEY_LEN]) + return output + + +def unpack_function(file_path: str, tmp_dir: str): + input_path, output_dir = Path(file_path), Path(tmp_dir) + meta = {'sections': []} + + if input_path.stat().st_size < Pk2FileHeader.size + Pk2SectionHeader.size: + meta['errors'] = ['file too small'] + return meta + + with input_path.open('rb') as fp: + file_header = Pk2FileHeader(fp) + offset = file_header.size + meta['header'] = file_header.to_dict() + for _ in range(file_header.section_count): + try: + section_header = Pk2SectionHeader(fp, offset) + section_meta = section_header.to_dict() + offset += section_header.size + if section_header.type == 'FILE': + pk2_file = Pk2File(fp, offset, section_header.payload_size) + pk2_file.save(output_dir) + offset += pk2_file.size + section_meta['file'] = pk2_file.to_dict() + elif section_header.type == 'CMD': + pk2_command = Pk2Cmd(fp, offset, section_header.payload_size) + offset += pk2_command.size + section_meta['command'] = pk2_command.command + else: + meta.setdefault('errors', []).append(f'unknown section type: {section_header.type}') + break + meta['sections'].append(section_meta) + except struct.error: + meta.setdefault('errors', []).append(f'error while parsing section at offset {offset}') + break + + return {'output': meta} + + +# ----> Do not edit below this line <---- +def setup(unpack_tool): + for item in MIME_PATTERNS: + unpack_tool.register_plugin(item, (unpack_function, NAME, VERSION)) diff --git a/fact_extractor/plugins/unpacking/tenvis_pk2/test/__init__.py b/fact_extractor/plugins/unpacking/tenvis_pk2/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fact_extractor/plugins/unpacking/tenvis_pk2/test/data/broken.pk2 b/fact_extractor/plugins/unpacking/tenvis_pk2/test/data/broken.pk2 new file mode 100644 index 0000000000000000000000000000000000000000..7bcae751f794b17d7f8968c1b4339ae77bac8e08 GIT binary patch literal 100 zcmWIWHe$HM!oZOKe`gv45?}_hoqb&xrrz6S=WG_J`p9c(n?Q53H$PDP$O9ko$;qmg r=N`IpP4sql^Yn2Qvb3w6yWS{DH2R`ne`9K$2v8X-5bLMq=Q98RF;E+= literal 0 HcmV?d00001 diff --git a/fact_extractor/plugins/unpacking/tenvis_pk2/test/data/test.pk2 b/fact_extractor/plugins/unpacking/tenvis_pk2/test/data/test.pk2 new file mode 100644 index 0000000000000000000000000000000000000000..2bd7801cfd0039a0127995568c6a2ff484ce19d0 GIT binary patch literal 158 zcmWIWHe$HM!oZOKe`gv45?}_hoqb&xrrz6S=WG_J`p9c(n?Q53H$PDP$O9ko$;qmg z=N`IpP4sql^Yn2Qvb3w6yWS{DH2R`ne`9K$2v8X-5bLMq=QFSa8OPsvOI