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 )