cpus.me

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:

Mounting it readonly

mkdir -p /tmp/dmg-mnt
hdiutil attach -readonly -nobrowse -noautoopen -mountpoint /tmp/dmg-mnt sample.dmg

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:

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!