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