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

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

43 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 # 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 )