1import io
2from pathlib import Path
3from typing import Optional
4
5from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
6from structlog import get_logger
7
8from unblob.file_utils import File, InvalidInputFormat
9from unblob.models import (
10 Endian,
11 Extractor,
12 HandlerDoc,
13 HandlerType,
14 Reference,
15 Regex,
16 StructHandler,
17 ValidChunk,
18)
19
20logger = get_logger()
21
22C_DEFINITIONS = r"""
23 typedef struct encrpted_img_header {
24 char magic[12]; /* encrpted_img */
25 uint32 size; /* total size of file */
26 } dlink_header_t;
27"""
28
29HEADER_LEN = 16
30PEB_SIZE = 0x20000
31UBI_HEAD = b"\x55\x42\x49\x23\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
32UBI_HEAD_LEN = len(UBI_HEAD)
33KEY = b"he9-4+M!)d6=m~we1,q2a3d1n&2*Z^%8"
34IV = b"J%1iQl8$=lm-;8AE"
35
36
37class EncrptedExtractor(Extractor):
38 def extract(self, inpath: Path, outdir: Path):
39 cipher = Cipher(algorithms.AES(KEY), modes.CBC(IV))
40 decryptor = cipher.decryptor()
41 outpath = outdir.joinpath(f"{inpath.name}.decrypted")
42 outfile = outpath.open("wb")
43 with inpath.open("rb") as f:
44 f.seek(HEADER_LEN, io.SEEK_SET) # skip header
45 ciphertext = f.read(PEB_SIZE)
46 while ciphertext and len(ciphertext) % 16 == 0:
47 plaintext = decryptor.update(ciphertext)
48 outfile.write(UBI_HEAD + plaintext[UBI_HEAD_LEN:])
49 ciphertext = f.read(PEB_SIZE)
50 outfile.write(decryptor.finalize())
51 outfile.close()
52
53
54class EncrptedHandler(StructHandler):
55 NAME = "encrpted_img"
56
57 PATTERNS = [
58 Regex(r"encrpted_img"),
59 ]
60 C_DEFINITIONS = C_DEFINITIONS
61 HEADER_STRUCT = "dlink_header_t"
62 EXTRACTOR = EncrptedExtractor()
63
64 DOC = HandlerDoc(
65 name="D-Link encrpted_img",
66 description="A binary format used by D-Link to store encrypted firmware or data. It consists of a custom 12-byte magic header followed by the encrypted payload.",
67 handler_type=HandlerType.ARCHIVE,
68 vendor="D-Link",
69 references=[
70 Reference(
71 title="How-To: Extracting Decryption Keys for D-Link",
72 url="https://www.onekey.com/resource/extracting-decryption-keys-dlink", # Replace with actual reference if available
73 )
74 ],
75 limitations=[],
76 )
77
78 def is_valid_header(self, header) -> bool:
79 return header.size >= len(header)
80
81 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
82 header = self.parse_header(file, endian=Endian.BIG)
83
84 if not self.is_valid_header(header):
85 raise InvalidInputFormat("Invalid header length")
86
87 return ValidChunk(
88 start_offset=start_offset,
89 end_offset=start_offset + len(header) + header.size,
90 )