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 )