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

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

36 statements  

1from pathlib import Path 

2 

3from structlog import get_logger 

4 

5from unblob.file_utils import File, InvalidInputFormat, iterate_file 

6from unblob.models import ( 

7 Endian, 

8 Extractor, 

9 HandlerDoc, 

10 HandlerType, 

11 HexString, 

12 Reference, 

13 StructHandler, 

14 StructParser, 

15 ValidChunk, 

16) 

17 

18logger = get_logger() 

19 

20C_DEFINITIONS = r""" 

21 

22 typedef struct autel_header { 

23 char magic[8]; 

24 uint32 file_size; 

25 uint32 header_size; 

26 char copyright[16]; 

27 } autel_header_t; 

28""" 

29 

30KEYS = [ 

31 (54, 147), 

32 (96, 129), 

33 (59, 193), 

34 (191, 0), 

35 (45, 130), 

36 (96, 144), 

37 (27, 129), 

38 (152, 0), 

39 (44, 180), 

40 (118, 141), 

41 (115, 129), 

42 (210, 0), 

43 (13, 164), 

44 (27, 133), 

45 (20, 192), 

46 (139, 0), 

47 (28, 166), 

48 (17, 133), 

49 (19, 193), 

50 (224, 0), 

51 (20, 161), 

52 (145, 0), 

53 (14, 193), 

54 (12, 132), 

55 (18, 161), 

56 (17, 140), 

57 (29, 192), 

58 (246, 0), 

59 (115, 178), 

60 (28, 132), 

61 (155, 0), 

62 (12, 132), 

63 (31, 165), 

64 (20, 136), 

65 (27, 193), 

66 (142, 0), 

67 (96, 164), 

68 (18, 133), 

69 (145, 0), 

70 (23, 132), 

71 (13, 165), 

72 (13, 148), 

73 (23, 193), 

74 (19, 132), 

75 (27, 178), 

76 (83, 137), 

77 (146, 0), 

78 (145, 0), 

79 (18, 166), 

80 (96, 148), 

81 (13, 193), 

82 (159, 0), 

83 (96, 166), 

84 (20, 129), 

85 (20, 193), 

86 (27, 132), 

87 (9, 160), 

88 (96, 148), 

89 (13, 192), 

90 (159, 0), 

91 (96, 180), 

92 (142, 0), 

93 (31, 193), 

94 (155, 0), 

95 (7, 166), 

96 (224, 0), 

97 (20, 192), 

98 (27, 132), 

99 (28, 160), 

100 (17, 149), 

101 (19, 193), 

102 (96, 132), 

103 (76, 164), 

104 (208, 0), 

105 (80, 192), 

106 (78, 132), 

107 (96, 160), 

108 (27, 144), 

109 (24, 193), 

110 (140, 0), 

111 (96, 178), 

112 (17, 141), 

113 (12, 193), 

114 (224, 0), 

115 (14, 161), 

116 (17, 141), 

117 (151, 0), 

118 (14, 132), 

119 (16, 165), 

120 (96, 137), 

121 (13, 193), 

122 (155, 0), 

123 (20, 161), 

124 (29, 141), 

125 (23, 192), 

126 (24, 132), 

127 (27, 178), 

128 (10, 133), 

129 (96, 192), 

130 (140, 0), 

131 (14, 180), 

132 (17, 133), 

133 (16, 192), 

134 (144, 0), 

135 (11, 163), 

136 (13, 141), 

137 (96, 192), 

138 (17, 132), 

139 (12, 178), 

140 (96, 141), 

141 (28, 192), 

142 (27, 132), 

143 (27, 130), 

144 (18, 141), 

145 (96, 193), 

146 (31, 132), 

147 (96, 181), 

148 (13, 140), 

149 (23, 193), 

150 (224, 0), 

151 (27, 166), 

152 (142, 0), 

153 (27, 192), 

154 (24, 132), 

155 (12, 183), 

156 (96, 133), 

157 (84, 192), 

158 (14, 132), 

159 (27, 178), 

160 (10, 140), 

161 (155, 0), 

162 (9, 132), 

163 (17, 160), 

164 (56, 133), 

165 (96, 192), 

166 (82, 132), 

167 (13, 160), 

168 (27, 137), 

169 (20, 193), 

170 (139, 0), 

171 (28, 161), 

172 (145, 0), 

173 (19, 192), 

174 (118, 132), 

175 (115, 165), 

176 (20, 132), 

177 (145, 0), 

178 (14, 132), 

179 (12, 167), 

180 (146, 0), 

181 (17, 193), 

182 (29, 132), 

183 (96, 176), 

184 (28, 144), 

185 (27, 193), 

186 (140, 0), 

187 (31, 180), 

188 (148, 0), 

189 (27, 192), 

190 (14, 132), 

191 (83, 160), 

192 (18, 137), 

193 (17, 193), 

194 (23, 132), 

195 (13, 165), 

196 (13, 145), 

197 (151, 0), 

198 (147, 0), 

199 (27, 178), 

200 (96, 137), 

201 (19, 193), 

202 (159, 0), 

203 (14, 160), 

204 (25, 148), 

205 (17, 193), 

206 (142, 0), 

207 (16, 180), 

208 (27, 136), 

209 (14, 193), 

210 (224, 0), 

211 (17, 178), 

212 (12, 144), 

213 (224, 0), 

214 (28, 132), 

215 (27, 160), 

216 (13, 141), 

217 (11, 193), 

218 (96, 132), 

219 (27, 165), 

220 (30, 140), 

221 (224, 0), 

222 (146, 0), 

223 (31, 165), 

224 (29, 129), 

225 (96, 192), 

226 (140, 0), 

227 (31, 161), 

228 (24, 145), 

229 (140, 0), 

230 (96, 132), 

231 (27, 165), 

232 (29, 140), 

233 (31, 192), 

234 (154, 0), 

235 (14, 161), 

236 (27, 145), 

237 (140, 0), 

238 (18, 132), 

239 (23, 167), 

240 (96, 140), 

241 (21, 129), 

242 (14, 132), 

243 (17, 165), 

244 (9, 137), 

245 (12, 193), 

246 (155, 0), 

247 (18, 161), 

248 (96, 141), 

249 (27, 192), 

250 (148, 0), 

251 (29, 178), 

252 (23, 133), 

253 (24, 192), 

254 (155, 0), 

255 (10, 180), 

256 (96, 133), 

257 (28, 192), 

258 (14, 132), 

259 (31, 130), 

260 (28, 129), 

261 (18, 193), 

262 (31, 132), 

263 (12, 180), 

264 (13, 144), 

265 (96, 193), 

266 (31, 132), 

267 (96, 160), 

268 (13, 141), 

269 (27, 193), 

270 (18, 132), 

271 (23, 181), 

272 (26, 140), 

273 (27, 193), 

274 (156, 0), 

275 (96, 166), 

276 (79, 141), 

277 (211, 0), 

278 (76, 132), 

279 (77, 160), 

280 (75, 133), 

281 (206, 0), 

282 (182, 0), 

283 (96, 129), 

284 (59, 133), 

285 (191, 0), 

286 (173, 0), 

287] 

288 

289 

290def ecc_decode(data: bytes): 

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

292 assert len(data) <= 256 

293 for i, value in enumerate(data): 

294 a, b = KEYS[i] 

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

296 

297 

298class ECCExtractor(Extractor): 

299 def __init__(self): 

300 self._struct_parser = StructParser(C_DEFINITIONS) 

301 

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

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

304 with File.from_path(inpath) as file: 

305 header = self._struct_parser.parse( 

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

307 ) 

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

309 for data in iterate_file( 

310 file, header.header_size, header.file_size, 256 

311 ): 

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

313 

314 

315class AutelECCHandler(StructHandler): 

316 NAME = "ecc" 

317 

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

319 

320 C_DEFINITIONS = C_DEFINITIONS 

321 HEADER_STRUCT = "autel_header_t" 

322 EXTRACTOR = ECCExtractor() 

323 

324 DOC = HandlerDoc( 

325 name="Autel ECC", 

326 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.", 

327 handler_type=HandlerType.ARCHIVE, 

328 vendor="Autel", 

329 references=[ 

330 Reference( 

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

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

333 ) 

334 ], 

335 limitations=[], 

336 ) 

337 

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

339 return header.header_size == 0x20 

340 

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

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

343 

344 if not self.is_valid_header(header): 

345 raise InvalidInputFormat("Invalid ECC header.") 

346 

347 return ValidChunk( 

348 start_offset=start_offset, 

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

350 )