1import binascii
2import io
3from pathlib import Path
4
5from unblob.file_utils import (
6 Endian,
7 FileSystem,
8 InvalidInputFormat,
9 StructParser,
10 iterate_file,
11)
12from unblob.models import (
13 Extractor,
14 ExtractResult,
15 File,
16 HandlerDoc,
17 HandlerType,
18 HexString,
19 Reference,
20 StructHandler,
21 ValidChunk,
22)
23from unblob.report import ExtractionProblem
24
25C_DEFINITIONS = """
26 typedef struct sbfh_header {
27 char magic[4]; /* "SBFH" */
28 uint32 header_size;
29 char unk[7];
30 uint32 firmware_size;
31 char padding[265];
32 } sbfh_header_t;
33
34 typedef struct mrvl_header {
35 char magic[4]; /* "MRVL" */
36 uint32 unk_const; /* 0x2E9CF17B */
37 uint32 creation_time;
38 uint32 num_segments; /* <= 9 */
39 uint32 elf_version;
40 } mrvl_header_t;
41
42 typedef struct mrvl_segment_header {
43 uint32 segment_type; /* always 0x2 */
44 uint32 offset; /* relative to MRVL area start */
45 uint32 seg_size;
46 uint32 virtual_address;
47 uint32 crc_checksum;
48 } mrvl_segment_header_t;
49"""
50
51
52class SBFHExtractor(Extractor):
53 def __init__(self):
54 self._struct_parser = StructParser(C_DEFINITIONS)
55
56 def extract(self, inpath: Path, outdir: Path) -> ExtractResult:
57 fs = FileSystem(outdir)
58 with File.from_path(inpath) as file:
59 sbfh = self._struct_parser.parse("sbfh_header_t", file, Endian.LITTLE)
60
61 mrvl = self._struct_parser.parse("mrvl_header_t", file, Endian.LITTLE)
62
63 segments = [
64 self._struct_parser.parse("mrvl_segment_header_t", file, Endian.LITTLE)
65 for _ in range(mrvl.num_segments)
66 ]
67
68 base_vaddr = min(seg.virtual_address for seg in segments)
69 image_path = Path(f"firmware_{base_vaddr:08x}.bin")
70
71 with fs.open(image_path, "wb+") as outfile:
72 for seg in segments:
73 crc = 0xFFFFFFFF
74 outfile.seek(seg.virtual_address - base_vaddr, io.SEEK_SET)
75 for chunk in iterate_file(
76 file, sbfh.header_size + seg.offset, seg.seg_size
77 ):
78 crc = binascii.crc32(chunk, crc)
79 outfile.write(chunk)
80 if (crc ^ -1) & 0xFFFFFFFF != seg.crc_checksum:
81 fs.record_problem(
82 ExtractionProblem(
83 problem=f"CRC mismatch in MRVL segment at vaddr 0x{seg.virtual_address:08x}",
84 resolution="Segment written anyway",
85 )
86 )
87
88 return ExtractResult(reports=fs.problems)
89
90
91class SBFHHandler(StructHandler):
92 NAME = "sbfh"
93 C_DEFINITIONS = C_DEFINITIONS
94 HEADER_STRUCT = "sbfh_header_t"
95 PATTERNS = [
96 HexString(
97 """53 42 46 48 // SBFH magic
98 1C 01 00 00 // header_size
99 [7] //unknown
100 [4] // firmware_size
101 [265] // padding
102 4D 52 56 4C // MRVL magic
103 7B F1 9C 2E // unknown constant
104 [4] // creation_time
105 (01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09) 00 00 00 // num_segments
106 [2] 00 1F // elf version"""
107 ),
108 ]
109 EXTRACTOR = SBFHExtractor()
110 DOC = HandlerDoc(
111 name="Tesla Wall Connector SBFH",
112 description=(
113 "SBFH format is used in Tesla Wall Connector firmware, contains also a Marvell MRVL blob of ARM V7 segments "
114 ),
115 handler_type=HandlerType.ARCHIVE,
116 vendor="Tesla",
117 references=[
118 Reference(
119 title="Tesla Wall Connector Firmware File Structure",
120 url="https://akrutsinger.github.io/2023/10/08/tesla-wall-connector-firmware-file-structure.html",
121 ),
122 Reference(
123 title="Marvell 88MW30x Firmware Tools",
124 url="https://github.com/wfr/mrvl-88mw30x-firmware-tools",
125 ),
126 Reference(
127 title="Exploiting the Tesla Wall Connector",
128 url="https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector.html",
129 ),
130 ],
131 limitations=[],
132 )
133
134 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None:
135 sbfh_header = self.parse_header(file, Endian.LITTLE)
136
137 if sbfh_header.firmware_size == 0:
138 raise InvalidInputFormat("Invalid SBFH header")
139
140 return ValidChunk(
141 start_offset=start_offset,
142 end_offset=start_offset
143 + sbfh_header.header_size
144 + sbfh_header.firmware_size,
145 )