Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/autel/ecc.py: 59%

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

37 statements  

1from pathlib import Path 

2from typing import Optional 

3 

4from structlog import get_logger 

5 

6from unblob.file_utils import File, InvalidInputFormat, iterate_file 

7from unblob.models import ( 

8 Endian, 

9 Extractor, 

10 HandlerDoc, 

11 HandlerType, 

12 HexString, 

13 Reference, 

14 StructHandler, 

15 StructParser, 

16 ValidChunk, 

17) 

18 

19logger = get_logger() 

20 

21C_DEFINITIONS = r""" 

22 

23 typedef struct autel_header { 

24 char magic[8]; 

25 uint32 file_size; 

26 uint32 header_size; 

27 char copyright[16]; 

28 } autel_header_t; 

29""" 

30 

31KEYS = [ 

32 (54, 147), 

33 (96, 129), 

34 (59, 193), 

35 (191, 0), 

36 (45, 130), 

37 (96, 144), 

38 (27, 129), 

39 (152, 0), 

40 (44, 180), 

41 (118, 141), 

42 (115, 129), 

43 (210, 0), 

44 (13, 164), 

45 (27, 133), 

46 (20, 192), 

47 (139, 0), 

48 (28, 166), 

49 (17, 133), 

50 (19, 193), 

51 (224, 0), 

52 (20, 161), 

53 (145, 0), 

54 (14, 193), 

55 (12, 132), 

56 (18, 161), 

57 (17, 140), 

58 (29, 192), 

59 (246, 0), 

60 (115, 178), 

61 (28, 132), 

62 (155, 0), 

63 (12, 132), 

64 (31, 165), 

65 (20, 136), 

66 (27, 193), 

67 (142, 0), 

68 (96, 164), 

69 (18, 133), 

70 (145, 0), 

71 (23, 132), 

72 (13, 165), 

73 (13, 148), 

74 (23, 193), 

75 (19, 132), 

76 (27, 178), 

77 (83, 137), 

78 (146, 0), 

79 (145, 0), 

80 (18, 166), 

81 (96, 148), 

82 (13, 193), 

83 (159, 0), 

84 (96, 166), 

85 (20, 129), 

86 (20, 193), 

87 (27, 132), 

88 (9, 160), 

89 (96, 148), 

90 (13, 192), 

91 (159, 0), 

92 (96, 180), 

93 (142, 0), 

94 (31, 193), 

95 (155, 0), 

96 (7, 166), 

97 (224, 0), 

98 (20, 192), 

99 (27, 132), 

100 (28, 160), 

101 (17, 149), 

102 (19, 193), 

103 (96, 132), 

104 (76, 164), 

105 (208, 0), 

106 (80, 192), 

107 (78, 132), 

108 (96, 160), 

109 (27, 144), 

110 (24, 193), 

111 (140, 0), 

112 (96, 178), 

113 (17, 141), 

114 (12, 193), 

115 (224, 0), 

116 (14, 161), 

117 (17, 141), 

118 (151, 0), 

119 (14, 132), 

120 (16, 165), 

121 (96, 137), 

122 (13, 193), 

123 (155, 0), 

124 (20, 161), 

125 (29, 141), 

126 (23, 192), 

127 (24, 132), 

128 (27, 178), 

129 (10, 133), 

130 (96, 192), 

131 (140, 0), 

132 (14, 180), 

133 (17, 133), 

134 (16, 192), 

135 (144, 0), 

136 (11, 163), 

137 (13, 141), 

138 (96, 192), 

139 (17, 132), 

140 (12, 178), 

141 (96, 141), 

142 (28, 192), 

143 (27, 132), 

144 (27, 130), 

145 (18, 141), 

146 (96, 193), 

147 (31, 132), 

148 (96, 181), 

149 (13, 140), 

150 (23, 193), 

151 (224, 0), 

152 (27, 166), 

153 (142, 0), 

154 (27, 192), 

155 (24, 132), 

156 (12, 183), 

157 (96, 133), 

158 (84, 192), 

159 (14, 132), 

160 (27, 178), 

161 (10, 140), 

162 (155, 0), 

163 (9, 132), 

164 (17, 160), 

165 (56, 133), 

166 (96, 192), 

167 (82, 132), 

168 (13, 160), 

169 (27, 137), 

170 (20, 193), 

171 (139, 0), 

172 (28, 161), 

173 (145, 0), 

174 (19, 192), 

175 (118, 132), 

176 (115, 165), 

177 (20, 132), 

178 (145, 0), 

179 (14, 132), 

180 (12, 167), 

181 (146, 0), 

182 (17, 193), 

183 (29, 132), 

184 (96, 176), 

185 (28, 144), 

186 (27, 193), 

187 (140, 0), 

188 (31, 180), 

189 (148, 0), 

190 (27, 192), 

191 (14, 132), 

192 (83, 160), 

193 (18, 137), 

194 (17, 193), 

195 (23, 132), 

196 (13, 165), 

197 (13, 145), 

198 (151, 0), 

199 (147, 0), 

200 (27, 178), 

201 (96, 137), 

202 (19, 193), 

203 (159, 0), 

204 (14, 160), 

205 (25, 148), 

206 (17, 193), 

207 (142, 0), 

208 (16, 180), 

209 (27, 136), 

210 (14, 193), 

211 (224, 0), 

212 (17, 178), 

213 (12, 144), 

214 (224, 0), 

215 (28, 132), 

216 (27, 160), 

217 (13, 141), 

218 (11, 193), 

219 (96, 132), 

220 (27, 165), 

221 (30, 140), 

222 (224, 0), 

223 (146, 0), 

224 (31, 165), 

225 (29, 129), 

226 (96, 192), 

227 (140, 0), 

228 (31, 161), 

229 (24, 145), 

230 (140, 0), 

231 (96, 132), 

232 (27, 165), 

233 (29, 140), 

234 (31, 192), 

235 (154, 0), 

236 (14, 161), 

237 (27, 145), 

238 (140, 0), 

239 (18, 132), 

240 (23, 167), 

241 (96, 140), 

242 (21, 129), 

243 (14, 132), 

244 (17, 165), 

245 (9, 137), 

246 (12, 193), 

247 (155, 0), 

248 (18, 161), 

249 (96, 141), 

250 (27, 192), 

251 (148, 0), 

252 (29, 178), 

253 (23, 133), 

254 (24, 192), 

255 (155, 0), 

256 (10, 180), 

257 (96, 133), 

258 (28, 192), 

259 (14, 132), 

260 (31, 130), 

261 (28, 129), 

262 (18, 193), 

263 (31, 132), 

264 (12, 180), 

265 (13, 144), 

266 (96, 193), 

267 (31, 132), 

268 (96, 160), 

269 (13, 141), 

270 (27, 193), 

271 (18, 132), 

272 (23, 181), 

273 (26, 140), 

274 (27, 193), 

275 (156, 0), 

276 (96, 166), 

277 (79, 141), 

278 (211, 0), 

279 (76, 132), 

280 (77, 160), 

281 (75, 133), 

282 (206, 0), 

283 (182, 0), 

284 (96, 129), 

285 (59, 133), 

286 (191, 0), 

287 (173, 0), 

288] 

289 

290 

291def ecc_decode(data: bytes): 

292 """Decode a 256 byte data block.""" 

293 assert len(data) <= 256 

294 for i, value in enumerate(data): 

295 a, b = KEYS[i] 

296 yield ((value + a) ^ b) % 256 

297 

298 

299class ECCExtractor(Extractor): 

300 def __init__(self): 

301 self._struct_parser = StructParser(C_DEFINITIONS) 

302 

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

304 outpath = outdir.joinpath(f"{inpath.name}.decrypted") 

305 with File.from_path(inpath) as file: 

306 header = self._struct_parser.parse( 

307 "autel_header_t", file, endian=Endian.LITTLE 

308 ) 

309 with outpath.open("wb") as outfile: 

310 for data in iterate_file( 

311 file, header.header_size, header.file_size, 256 

312 ): 

313 outfile.write(bytes(ecc_decode(data))) 

314 

315 

316class AutelECCHandler(StructHandler): 

317 NAME = "ecc" 

318 

319 PATTERNS = [HexString("45 43 43 30 31 30 31 00")] 

320 

321 C_DEFINITIONS = C_DEFINITIONS 

322 HEADER_STRUCT = "autel_header_t" 

323 EXTRACTOR = ECCExtractor() 

324 

325 DOC = HandlerDoc( 

326 name="Autel ECC", 

327 description="Autel ECC files consist of a custom header followed by encrypted data blocks. The header includes metadata such as magic bytes, file size, and copyright information.", 

328 handler_type=HandlerType.ARCHIVE, 

329 vendor="Autel", 

330 references=[ 

331 Reference( 

332 title="Autel ECC Decryption Script (Sector7)", 

333 url="https://gist.github.com/sector7-nl/3fc815cd2497817ad461bfbd393294cb", # Replace with actual reference if available 

334 ) 

335 ], 

336 limitations=[], 

337 ) 

338 

339 def is_valid_header(self, header) -> bool: 

340 return header.header_size == 0x20 

341 

342 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]: 

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

344 

345 if not self.is_valid_header(header): 

346 raise InvalidInputFormat("Invalid ECC header.") 

347 

348 return ValidChunk( 

349 start_offset=start_offset, 

350 end_offset=start_offset + header.header_size + header.file_size, 

351 )