A few notes on DMG files
Hi internet friends!
Even for the regular macOS user, that doesn’t have interest in computer things, I expect that DMG files will not be
a totally strange thing. Ok, at least the action of dragging an icon over Applications will be familiar.
I found myself looking into DMG files from time to time, one layer lower than just dragging the icons. After some time
and repeated use, most of the commands on how to inspect these files became residents in my memory. But, memory is of such a
nature that it needs to be refreshed.
Thus this is just a sequence of useful commands that I want to keep here so I quickly and easily find them again (without having to browse unknown internet spaces or waste precious tokens ;> )
Provenance
sha256sum sample.dmg
xattr -l sample.dmg
mdls -name kMDItemWhereFroms sample.dmg
The quarantine attribute looks like 0281;6a5eff82;Chrome;<UUID>: flags, a hex Unix timestamp, the agent that downloaded it, and an event UUID. The timestamp decodes with:
date -r $((0x6a5eff82))
Tue Jul 21 07:11:30 CEST 2026
kMDItemWhereFroms usually has the download URL and the page it came from. The UUID also joins against the LaunchServices quarantine database:
sqlite3 ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 \
"select datetime(LSQuarantineTimeStamp + 978307200, 'unixepoch'),
LSQuarantineAgentBundleIdentifier,
LSQuarantineDataURLString,
LSQuarantineOriginURLString
from LSQuarantineEvent
where LSQuarantineEventIdentifier = '<UUID>'"
Timestamps in that database use the Cocoa epoch (2001-01-01), hence the + 978307200.
Image type
file or file -k is kind of useless here:
file sample.dmg
sample.dmg: zlib compressed data
A UDIF image (aka DMG) is identified by its trailer. The last 512 bytes start with koly:
tail -c 512 sample.dmg | xxd | head -1
00000000: 6b6f 6c79 0000 0004 0000 0200 0000 0001 koly............
The trailer points at the data fork, at an XML plist (the blkx table that maps compressed chunks to disk sectors) and, for signed images, at the code signature. hdiutil imageinfo parses all of it without attaching anything:
hdiutil isencrypted sample.dmg
hdiutil imageinfo sample.dmg
Trimmed output:
Format Description: UDIF read-only compressed (zlib)
Format: UDZO
partitions:
partition-scheme: GUID
...
4:
partition-name: disk image
partition-hint: Apple_HFS
partition-filesystems:
HFS+:
Properties:
Encrypted: false
Kernel Compatible: true
Checksummed: true
Software License Agreement: false
Compressed: true
Worth noting: the format (UDZO zlib, ULFO lzfse, ULMO lzma, UDBZ bzip2, UDRO/UDRW uncompressed), the filesystem (HFS+ or APFS), whether there’s a license agreement (attaching will stop and ask you to accept it) and whether it’s encrypted. hdiutil verify sample.dmg checks the embedded checksums, which catches truncated or modified files.
A dive into the actual file format might be interesting.
Signature and notarization
codesign -dvvv sample.dmg
spctl -a -t open --context context:primary-signature -vv sample.dmg
stapler validate sample.dmg
syspolicy_check distribution sample.dmg
A signed and notarized DMG looks like this (trimmed):
Format=disk image
Authority=Developer ID Application: Example Corp (ABCDE12345)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Notarization Ticket=stapled
TeamIdentifier=ABCDE12345
and spctl says accepted with source=Notarized Developer ID. An unsigned one gives code object is not signed at all, and spctl rejects it with source=no usable signature.
Browsing WITHOUT mounting
I use 7-Zip. Although, it doesn’t always work (see caveats). I didn’t dig for each caveat, so I kind of ignored most of the times and moved ahead. It is possible that I was just doing something wrong. Later, I should come back to this section and update it with precise details and understand the causes.
7zz l sample.dmg
7zz x -o./extracted sample.dmg
Caveats I ran into:
- Extracted files don’t get the quarantine attribute. Treat the output folder as live and don’t open anything from it.
- Absolute symlinks get rewritten to point inside the output folder. The usual
Applications -> /Applicationsended up pointing at./extracted/Applications.7zz x -so sample.dmg <Volume>/Applicationsprints the real target. - BSD flags like
hiddenare dropped, so you can’t tell what Finder would have hidden. - HFS+ internals such as
[HFS+ Private Data]show up as folders. - Not everything can be extracted sometimes (or maybe I did something wrong)
Mounting it readonly
mkdir -p /tmp/dmg-mnt
hdiutil attach -readonly -nobrowse -noautoopen -mountpoint /tmp/dmg-mnt sample.dmg
-readonly: nothing gets written to the image.-nobrowse: the volume doesn’t show up in Finder, so there’s no window and no icon or thumbnail rendering.-noautoopen: don’t auto-open the volume’s root folder.-mountpoint: a known path instead of/Volumes/<whatever the author picked>.
The resulting mount:
mount | grep dmg-mnt
/dev/disk8s1 on /private/tmp/dmg-mnt (hfs, local, nodev, nosuid, read-only, noowners, quarantine, nobrowse, mounted by cpu)
Note the quarantine flag. Because the DMG was quarantined, the whole volume is. The files on it don’t have the attribute themselves (xattr -l shows nothing), but anything copied off with cp or ditto gets the DMG’s quarantine attribute.
When you only care about the partition map, attach without mounting:
hdiutil attach -nomount -readonly sample.dmg
diskutil list /dev/diskN
hdiutil detach /dev/diskN
Inventory
cd /tmp/dmg-mnt
ls -laO@ .
find . -not -path "./.fseventsd*" | sort
find . -type l -exec ls -l {} \;
find . -type f -perm +111
find . -type f -exec file {} + | grep -i mach-o
ls -laO@ shows dotfiles (malware likes that!), BSD flags (-O) and extended attributes (@):
drwxr-xr-x 3 cpu staff - 102 Oct 4 22:23 .background
-rwxr-xr-x 1 cpu staff - 20 Oct 4 22:23 .hidden.command
lrwxr-xr-x 1 cpu staff - 13 Oct 4 22:23 Applications -> /Applications
drwxr-xr-x 3 cpu staff - 102 Oct 4 22:23 Demo.app
-rw-r--r-- 1 cpu staff - 2082 Oct 4 22:23 Installer.pkg
-rw-r--r--@ 1 cpu staff hidden 1 Oct 4 22:23 visible-but-hidden.txt
There are a few things to look for in such cases that can help understand if a package has additional interesting payloads, ex: hidden files, icons, thumbnails.
The app bundle
plutil -p Demo.app/Contents/Info.plist
Which keys I usually care about:
CFBundleExecutable: which binary actually runs.CFBundleIdentifier: does it match what the app claims to be.LSUIElement/LSBackgroundOnly: no Dock icon. Normal for menu bar apps, and also handy for malware.NSAppleEventsUsageDescription: it wants to automate other apps.CFBundleURLTypes,CFBundleDocumentTypes: URL schemes and file types it registers.
Signature:
codesign -dvvv Demo.app
codesign --verify --deep --strict -vvv Demo.app
spctl -a -t exec -vv Demo.app
syspolicy_check distribution Demo.app
From the adhoc signed test app:
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20400 size=261 flags=0x2(adhoc) hashes=2+3 location=embedded
Signature=adhoc
TeamIdentifier=not set
Signature=adhoc with no team ID means there’s no developer identity at all. spctl rejects it, and the only way it runs is if the user gets talked into overriding Gatekeeper. That’s the typical stealer approach. A legitimate app has a Developer ID Application authority, a team ID, flags=0x10000(runtime) (hardened runtime) and a notarization ticket.
end
I’ll close here. Each packaged item from the DMG is another opportunity to go into interesting details
and perform analysis. Just think about machO, all kind of scripts that are delivered and examples of how malware uses
DMG for distribution.
As I will use this as a sort of DMG / UDIF notebook, updates will follow.
Bye!