1from typing import Optional
2
3from structlog import get_logger
4
5from unblob.extractors.command import Command
6
7from ...file_utils import Endian
8from ...models import (
9 File,
10 HandlerDoc,
11 HandlerType,
12 HexString,
13 Reference,
14 StructHandler,
15 ValidChunk,
16)
17
18logger = get_logger()
19
20
21class DMGHandler(StructHandler):
22 NAME = "dmg"
23
24 PATTERNS = [
25 HexString(
26 """
27 // 'koly' magic, followed by version from 1 to 4, followed by fixed header size of 512
28 6b 6f 6c 79 00 00 00 04 00 00 02 00
29 """
30 )
31 ]
32
33 C_DEFINITIONS = r"""
34 // source: http://newosxbook.com/DMG.html
35 typedef struct dmg_header {
36 char Signature[4]; // Magic ('koly')
37 uint32 Version; // Current version is 4
38 uint32 HeaderSize; // sizeof(this), always 512
39 uint32 Flags; // Flags
40 uint64 RunningDataForkOffset; //
41 uint64 DataForkOffset; // Data fork offset (usually 0, beginning of file)
42 uint64 DataForkLength; // Size of data fork (usually up to the XMLOffset, below)
43 uint64 RsrcForkOffset; // Resource fork offset, if any
44 uint64 RsrcForkLength; // Resource fork length, if any
45 uint32 SegmentNumber; // Usually 1, may be 0
46 uint32 SegmentCount; // Usually 1, may be 0
47 uchar SegmentID[16]; // 128-bit GUID identifier of segment (if SegmentNumber !=0)
48
49 uint32 DataChecksumType; // Data fork
50 uint32 DataChecksumSize; // Checksum Information
51 uint32 DataChecksum[32]; // Up to 128-bytes (32 x 4) of checksum
52
53 uint64 XMLOffset; // Offset of property list in DMG, from beginning
54 uint64 XMLLength; // Length of property list
55 uint8 Reserved1[120]; // 120 reserved bytes - zeroed
56
57 uint32 ChecksumType; // Master
58 uint32 ChecksumSize; // Checksum information
59 uint32 Checksum[32]; // Up to 128-bytes (32 x 4) of checksum
60
61 uint32 ImageVariant; // Commonly 1
62 uint64 SectorCount; // Size of DMG when expanded, in sectors
63
64 uint32 reserved2; // 0
65 uint32 reserved3; // 0
66 uint32 reserved4; // 0
67
68 } dmg_header_t;
69 """
70 HEADER_STRUCT = "dmg_header_t"
71
72 # This directory is here for simulating hardlinks and other metadata
73 # but the files will be extracted anyway, and we only care about the content
74 # See more about this problem: https://newbedev.com/hfs-private-directory-data
75 EXTRACTOR = Command(
76 "7z", "x", "-xr!*HFS+ Private Data*", "-y", "{inpath}", "-o{outdir}"
77 )
78
79 DOC = HandlerDoc(
80 name="DMG",
81 description="Apple Disk Image (DMG) files are commonly used on macOS for software distribution and disk image storage.",
82 handler_type=HandlerType.ARCHIVE,
83 vendor="Apple",
84 references=[
85 Reference(
86 title="Apple Disk Image Format Documentation",
87 url="http://newosxbook.com/DMG.html",
88 ),
89 ],
90 limitations=[],
91 )
92
93 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
94 header = self.parse_header(file, endian=Endian.BIG)
95
96 # NOTE: the koly block is a trailer
97 # ┌─────────────┐ │
98 # │Data fork │ │DataForkLength
99 # │contains │ │
100 # │disk blocks │ │
101 # │ │ │
102 # │ │ ▼
103 # ├─────────────┤ │
104 # │XML plist │ │XMLLength
105 # ├─────────────┤ ▼
106 # │koly trailer │
107 # └─────────────┘
108 #
109
110 return ValidChunk(
111 start_offset=start_offset - header.XMLLength - header.DataForkLength,
112 end_offset=start_offset + len(header),
113 )