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