Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/tesla/sbfh.py: 48%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

40 statements  

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 )