Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/xiaomi/hdr.py: 68%

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

85 statements  

1import binascii 

2import io 

3from collections.abc import Iterable 

4from pathlib import Path 

5from typing import cast 

6 

7from structlog import get_logger 

8 

9from unblob.file_utils import ( 

10 File, 

11 FileSystem, 

12 InvalidInputFormat, 

13 iterate_file, 

14 snull, 

15) 

16from unblob.models import ( 

17 Endian, 

18 Extractor, 

19 ExtractResult, 

20 HandlerDoc, 

21 HandlerType, 

22 HexString, 

23 StructHandler, 

24 StructParser, 

25 ValidChunk, 

26) 

27 

28logger = get_logger() 

29CRC_CONTENT_OFFSET = 12 # The CRC32 value is located after 12 byte in the header 

30SIGNATURE_LEN = 272 # signature_header_t contains 4 bytes of size + 12 bytes for padding x3 + 0x100 is 256 in decimal 

31BLOB_MAGIC = 0x0000BABE # Blob header magic (little-endian: be ba 00 00) 

32 

33# https://lxr.openwrt.org/source/firmware-utils/src/xiaomifw.c 

34C_DEFINITIONS = r""" 

35 struct hdr1_header { 

36 char magic[4]; /* HDR1 */ 

37 uint32 signature_offset; 

38 uint32 crc32; 

39 uint16 unused; 

40 uint16 device_id; /* RA70 */ 

41 uint32 blob_offsets[8]; 

42 } hdr1_header_t; 

43 

44 struct hdr2_header { 

45 char magic[4]; /* HDR1 */ 

46 uint32 signature_offset; 

47 uint32 crc32; 

48 uint32 unused1; 

49 uint64 device_id; /* RA70 */ 

50 uint64 region; /* EU */ 

51 uint64 unused2[2]; 

52 uint32 blob_offsets[8]; 

53 } hdr2_header_t; 

54 

55 struct xiaomi_blob_header { 

56 uint32 magic; /* 0x0000babe */ 

57 uint32 flash_offset; 

58 uint32 blob_size; /* Size of blob */ 

59 uint16 type; /* Type of blob */ 

60 uint16 unused; 

61 char name[32]; /* Name of blob */ 

62 } blob_header_t; 

63 

64 struct xiaomi_signature_header { 

65 uint32 signature_size; 

66 uint32 padding[3]; 

67 uint8 content[0x100]; 

68 } signature_header_t; 

69 """ 

70 

71 

72def calculate_crc(file: File, start_offset: int, size: int) -> int: 

73 digest = 0 

74 for chunk in iterate_file(file, start_offset, size): 

75 digest = binascii.crc32(chunk, digest) 

76 return (digest ^ -1) & 0xFFFFFFFF 

77 

78 

79def is_valid_blob_header(blob_header) -> bool: 

80 if blob_header.magic != BLOB_MAGIC: 

81 return False 

82 if not blob_header.blob_size: 

83 return False 

84 try: 

85 snull(blob_header.name).decode("utf-8") 

86 except UnicodeDecodeError: 

87 return False 

88 return True 

89 

90 

91def is_valid_header(header) -> bool: 

92 if header.signature_offset < len(header): 

93 return False 

94 if not header.blob_offsets[0]: # noqa: SIM103 

95 return False 

96 return True 

97 

98 

99class HDRExtractor(Extractor): 

100 def __init__(self, header_struct: str): 

101 self.header_struct = header_struct 

102 self._struct_parser = StructParser(C_DEFINITIONS) 

103 

104 def extract(self, inpath: Path, outdir: Path): 

105 fs = FileSystem(outdir) 

106 with File.from_path(inpath) as file: 

107 for output_path, start_offset, size in self.parse(file): 

108 fs.carve(output_path, file, start_offset, size) 

109 return ExtractResult(reports=fs.problems) 

110 

111 def parse(self, file: File) -> Iterable[tuple[Path, int, int]]: 

112 header = self._struct_parser.parse(self.header_struct, file, Endian.LITTLE) 

113 for offset in cast("Iterable", header.blob_offsets): 

114 if not offset: 

115 break 

116 

117 file.seek(offset, io.SEEK_SET) 

118 blob_header = self._struct_parser.parse( 

119 "blob_header_t", file, Endian.LITTLE 

120 ) 

121 logger.debug("blob_header_t", blob_header_t=blob_header, _verbosity=3) 

122 if not is_valid_blob_header(blob_header): 

123 raise InvalidInputFormat("Invalid HDR blob header.") 

124 

125 # file.tell() points to right after the blob_header == start_offset 

126 blob_start = file.tell() 

127 if blob_start + blob_header.blob_size > header.signature_offset: 

128 raise InvalidInputFormat("HDR blob data extends past the signature.") 

129 

130 yield ( 

131 ( 

132 Path(snull(blob_header.name).decode("utf-8")), 

133 blob_start, 

134 blob_header.blob_size, 

135 ) 

136 ) 

137 

138 

139class HDRHandlerBase(StructHandler): 

140 HEADER_STRUCT = "hdr1_header_t" 

141 

142 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None: 

143 header = self.parse_header(file, endian=Endian.LITTLE) 

144 

145 if not is_valid_header(header): 

146 raise InvalidInputFormat("Invalid HDR header.") 

147 

148 end_offset = start_offset + header.signature_offset + SIGNATURE_LEN 

149 

150 if not self._is_crc_valid(file, header, start_offset, end_offset): 

151 raise InvalidInputFormat("CRC32 does not match in HDR header.") 

152 

153 return ValidChunk(start_offset=start_offset, end_offset=end_offset) 

154 

155 def _is_crc_valid( 

156 self, file: File, header, start_offset: int, end_offset: int 

157 ) -> bool: 

158 computed_crc = calculate_crc( 

159 file, 

160 start_offset=start_offset + CRC_CONTENT_OFFSET, 

161 size=end_offset - start_offset - CRC_CONTENT_OFFSET, 

162 ) 

163 return header.crc32 == computed_crc 

164 

165 

166class HDR1Handler(HDRHandlerBase): 

167 NAME = "hdr1" 

168 PATTERNS = [ 

169 HexString( 

170 """ 

171 // 48 44 52 31 90 32 e2 00 02 2a 5b 6a 00 00 11 00 HDR1.2...*[j.... 

172 // 30 00 00 00 70 02 00 00 a4 02 e0 00 00 00 00 00 0...p........... 

173 48 44 52 31 

174 """ 

175 ), 

176 ] 

177 C_DEFINITIONS = C_DEFINITIONS 

178 HEADER_STRUCT = "hdr1_header_t" 

179 EXTRACTOR = HDRExtractor("hdr1_header_t") 

180 

181 DOC = HandlerDoc( 

182 name="Xiaomi HDR1", 

183 description="Xiaomi HDR1 firmware files feature a custom header containing metadata, CRC32 checksum, and blob offsets for embedded data. These files are used in Xiaomi devices for firmware updates.", 

184 handler_type=HandlerType.ARCHIVE, 

185 vendor="Xiaomi", 

186 references=[], 

187 limitations=[], 

188 ) 

189 

190 

191class HDR2Handler(HDRHandlerBase): 

192 NAME = "hdr2" 

193 PATTERNS = [ 

194 HexString( 

195 """ 

196 // 48 44 52 32 d4 02 78 02 68 54 e8 fa 00 00 00 00 HDR2..x.hT...... 

197 // 52 41 37 30 00 00 00 00 45 55 00 00 00 00 00 00 RA70....EU...... 

198 48 44 52 32 

199 """ 

200 ), 

201 ] 

202 C_DEFINITIONS = C_DEFINITIONS 

203 HEADER_STRUCT = "hdr2_header_t" 

204 EXTRACTOR = HDRExtractor("hdr2_header_t") 

205 

206 DOC = HandlerDoc( 

207 name="Xiaomi HDR2", 

208 description="Xiaomi HDR2 firmware files feature a custom header with metadata, CRC32 checksum, and blob offsets for embedded data. These files also include additional fields for device ID and region information.", 

209 handler_type=HandlerType.ARCHIVE, 

210 vendor="Xiaomi", 

211 references=[], 

212 limitations=[], 

213 )