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 # The segment data lives in the firmware region the header declares
72 # (header_size + firmware_size), which is also the chunk boundary.
73 data_end = sbfh.header_size + sbfh.firmware_size
74
75 with fs.open(image_path, "wb+") as outfile:
76 for seg in segments:
77 crc = 0xFFFFFFFF
78 seg_start = sbfh.header_size + seg.offset
79 seg_size = max(0, min(seg.seg_size, data_end - seg_start))
80 outfile.seek(seg.virtual_address - base_vaddr, io.SEEK_SET)
81 for chunk in iterate_file(file, seg_start, seg_size):
82 crc = binascii.crc32(chunk, crc)
83 outfile.write(chunk)
84 if (crc ^ -1) & 0xFFFFFFFF != seg.crc_checksum:
85 fs.record_problem(
86 ExtractionProblem(
87 problem=f"CRC mismatch in MRVL segment at vaddr 0x{seg.virtual_address:08x}",
88 resolution="Segment written anyway",
89 )
90 )
91
92 return ExtractResult(reports=fs.problems)
93
94
95class SBFHHandler(StructHandler):
96 NAME = "sbfh"
97 C_DEFINITIONS = C_DEFINITIONS
98 HEADER_STRUCT = "sbfh_header_t"
99 PATTERNS = [
100 HexString(
101 """53 42 46 48 // SBFH magic
102 1C 01 00 00 // header_size
103 [7] //unknown
104 [4] // firmware_size
105 [265] // padding
106 4D 52 56 4C // MRVL magic
107 7B F1 9C 2E // unknown constant
108 [4] // creation_time
109 (01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09) 00 00 00 // num_segments
110 [2] 00 1F // elf version"""
111 ),
112 ]
113 EXTRACTOR = SBFHExtractor()
114 DOC = HandlerDoc(
115 name="Tesla Wall Connector SBFH",
116 description=(
117 "SBFH format is used in Tesla Wall Connector firmware, contains also a Marvell MRVL blob of ARM V7 segments "
118 ),
119 handler_type=HandlerType.ARCHIVE,
120 vendor="Tesla",
121 references=[
122 Reference(
123 title="Tesla Wall Connector Firmware File Structure",
124 url="https://akrutsinger.github.io/2023/10/08/tesla-wall-connector-firmware-file-structure.html",
125 ),
126 Reference(
127 title="Marvell 88MW30x Firmware Tools",
128 url="https://github.com/wfr/mrvl-88mw30x-firmware-tools",
129 ),
130 Reference(
131 title="Exploiting the Tesla Wall Connector",
132 url="https://www.synacktiv.com/en/publications/exploiting-the-tesla-wall-connector-from-its-charge-port-connector.html",
133 ),
134 ],
135 limitations=[],
136 )
137
138 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None:
139 sbfh_header = self.parse_header(file, Endian.LITTLE)
140
141 if sbfh_header.firmware_size == 0:
142 raise InvalidInputFormat("Invalid SBFH header")
143
144 return ValidChunk(
145 start_offset=start_offset,
146 end_offset=start_offset
147 + sbfh_header.header_size
148 + sbfh_header.firmware_size,
149 )