Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/dmg.py: 100%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

15 statements  

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 )