NAME

What's inside the macOS installer I just downloaded?

METADATA

DESCRIPTION

I usually expect a macOS download to be an application bundled in a DMG, or a PKG hiding in one. The official Oracle VirtualBox installer gives us the second case. That is useful because a DMG and a PKG are often treated as the same kind of installer. They are not.

Here, Installer means Apple’s Installer application: the macOS program that opens a .pkg, interprets its distribution definition and presents the installation choices. Apple describes that role in its Installer documentation.

Oracle’s instructions say to mount the disk image and then open VirtualBox.pkg. I wanted to know what happens between those clicks, which files the package contains and which scripts that application would run. None of that requires starting Installer or executing extracted programs.

I used the Apple Silicon build of VirtualBox 7.2.12 on an arm64 Mac running macOS 26.6.2. Oracle publishes the download and its checksum in the 7.2.12 release directory:

$ curl --fail --location --remote-name \
    https://download.virtualbox.org/virtualbox/7.2.12/VirtualBox-7.2.12-174389-macOSArm64.dmg
$ curl --fail --location --remote-name \
    https://download.virtualbox.org/virtualbox/7.2.12/SHA256SUMS
$ grep 'VirtualBox-7.2.12-174389-macOSArm64.dmg$' SHA256SUMS
e6e7158592392486974f312ce85a8a2fff3673eca2c643cbd6a0a9bbf8fac07b *VirtualBox-7.2.12-174389-macOSArm64.dmg
$ shasum -a 256 VirtualBox-7.2.12-174389-macOSArm64.dmg
e6e7158592392486974f312ce85a8a2fff3673eca2c643cbd6a0a9bbf8fac07b  VirtualBox-7.2.12-174389-macOSArm64.dmg

The hashes match, so the rest refers to that exact file.

The first magic is misleading

The first result from file is correct, but not especially helpful:

$ file VirtualBox-7.2.12-174389-macOSArm64.dmg
VirtualBox-7.2.12-174389-macOSArm64.dmg: bzip2 compressed data, block size = 100k

The disk image uses bzip2 compression, but it is not merely a .bz2 file. Apple’s UDIF format keeps its identifying koly structure in the final 512 bytes rather than at offset zero.

$ tail -c 512 VirtualBox-7.2.12-174389-macOSArm64.dmg | \
    /usr/bin/hexdump -C -n 32
00000000  6b 6f 6c 79 00 00 00 04  00 00 02 00 00 00 00 01  |koly............|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020

hdiutil knows how to interpret the whole file and gives the less surprising answer:

$ hdiutil imageinfo VirtualBox-7.2.12-174389-macOSArm64.dmg | \
    grep -E '^(Format Description|Class Name|Checksum Type|Format:)'
Format Description: UDIF read-only compressed (bzip2)
Class Name: CUDIFDiskImage
Checksum Type: CRC32
Format: UDBZ

The download is a read-only compressed disk image containing a filesystem. Apple also uses UDIF images in its current Mac software distribution guidance.

Checking and mounting the DMG

The SHA-256 pins the download, while the CRC stored in the image checks its internal chunks and partition structures. hdiutil verify walks those structures without mounting the volume:

$ hdiutil verify VirtualBox-7.2.12-174389-macOSArm64.dmg 2>&1 | tail -3
  GPT Header (Backup GPT Header : 7): verified CRC32 $18631006
verified CRC32 $39E33C33
hdiutil: verify: checksum of "VirtualBox-7.2.12-174389-macOSArm64.dmg" is VALID

The image itself is code-signed as well. I filtered the verbose output to the fields that matter here:

$ codesign --display --verbose=4 VirtualBox-7.2.12-174389-macOSArm64.dmg 2>&1 | \
    grep -E '^(Identifier|Format|Authority|Timestamp|Notarization)'
Identifier=VirtualBox-7.2.12-r174389
Format=disk image
Authority=Developer ID Application: Oracle America, Inc. (VB5E2TV963)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=26 Jun 2026 at 18:15:02
Notarization Ticket=stapled
$ spctl --assess --verbose=4 --type open --context context:primary-signature \
    VirtualBox-7.2.12-174389-macOSArm64.dmg
VirtualBox-7.2.12-174389-macOSArm64.dmg: accepted
source=Notarized Developer ID

This establishes who signed the DMG and that Gatekeeper accepts its notarization. To see the files, I mounted it read-only and told Finder not to browse it automatically:

$ hdiutil attach -readonly -nobrowse VirtualBox-7.2.12-174389-macOSArm64.dmg
expected CRC32 $39E33C33
/dev/disk8           GUID_partition_scheme
/dev/disk8s1         Apple_APFS
/dev/disk9           EF57347C-0000-11AA-AA11-0030654
/dev/disk9s1         41504653-0000-11AA-AA11-0030654 /Volumes/VirtualBox
$ ls -1 /Volumes/VirtualBox
Applications
UserManual.pdf
VirtualBox.pkg
VirtualBox_Uninstall.tool
$ readlink /Volumes/VirtualBox/Applications
/Applications/
$ cp /Volumes/VirtualBox/VirtualBox.pkg .
$ hdiutil detach /Volumes/VirtualBox
"disk8" ejected.

Besides the PKG, the APFS volume contains a manual, an Applications symlink and a standalone shell-script uninstaller. The DMG is the delivery container; VirtualBox.pkg is the installer inside it.

VirtualBox-7.2.12-macOSArm64.dmg
|
+-- UDIF + APFS
+-- UserManual.pdf
+-- VirtualBox_Uninstall.tool
`-- VirtualBox.pkg (XAR)
    +-- Distribution + Resources/
    +-- app: metadata, BOM, payload, scripts
    `-- CLI: metadata, BOM, payload

Nothing has been installed at this point.

The PKG inside the image

The package has its own signature, made with a different kind of Developer ID certificate:

$ pkgutil --check-signature VirtualBox.pkg | sed -n '1,7p'
Package "VirtualBox.pkg":
   Status: signed by a developer certificate issued by Apple for distribution
   Notarization: trusted by the Apple notary service
   Signed with a trusted timestamp on: 2026-06-26 16:11:32 +0000
   Certificate Chain:
    1. Developer ID Installer: Oracle America, Inc. (VB5E2TV963)
       Expires: 2027-04-05 15:09:01 +0000

Now file reports a XAR archive. This time the identifying bytes really are at the front:

00000000  78 61 72 21 00 1c 00 01  00 00 00 00 00 00 14 ce  |xar!............|
00000010  00 00 00 00 00 00 4e ae  00 00 00 01 78 da ec 5c  |......N.....x..\|
00000020

The dump came from /usr/bin/hexdump -C -n 32 VirtualBox.pkg. Read as big-endian values, the header describes XAR version 1, a 5,326-byte compressed table of contents, 20,142 bytes after decompression and checksum algorithm 1. Apple’s open source XAR header defines the fields and identifies algorithm 1 as SHA-1.

$ xar --dump-header -f VirtualBox.pkg
magic:                  0x78617221 (OK)
size:                   28
version:                1
Compressed TOC length:  5326
Uncompressed TOC length: 20142
Checksum algorithm:     1 (SHA1)

Apple marks the XAR API as deprecated, but the tool still takes this package apart. Its table of contents is small enough to show in full:

$ xar -tf VirtualBox.pkg
VirtualBox.pkg
VirtualBox.pkg/Bom
VirtualBox.pkg/Payload
VirtualBox.pkg/Scripts
VirtualBox.pkg/PackageInfo
VirtualBoxCLI.pkg
VirtualBoxCLI.pkg/Bom
VirtualBoxCLI.pkg/Payload
VirtualBoxCLI.pkg/PackageInfo
Resources
Resources/en.lproj
Resources/en.lproj/Welcome.rtf
Resources/en.lproj/Localizable.strings
Resources/background.tiff
Distribution

This is a flat product package containing two component packages. pkgutil expands the XAR into ordinary directories without processing either payload:

$ pkgutil --expand VirtualBox.pkg virtualbox-pkg
$ find virtualbox-pkg -maxdepth 3 -type f | sort
virtualbox-pkg/Distribution
virtualbox-pkg/Resources/background.tiff
virtualbox-pkg/Resources/en.lproj/Localizable.strings
virtualbox-pkg/Resources/en.lproj/Welcome.rtf
virtualbox-pkg/VirtualBox.pkg/Bom
virtualbox-pkg/VirtualBox.pkg/PackageInfo
virtualbox-pkg/VirtualBox.pkg/Payload
virtualbox-pkg/VirtualBox.pkg/Scripts/postflight
virtualbox-pkg/VirtualBox.pkg/Scripts/preflight
virtualbox-pkg/VirtualBoxCLI.pkg/Bom
virtualbox-pkg/VirtualBoxCLI.pkg/PackageInfo
virtualbox-pkg/VirtualBoxCLI.pkg/Payload

The top-level Distribution file supplies the Installer choices and connects them to the two components. It also restricts this product to ARM64 hosts:

<options customize="allow" rootVolumeOnly="true" hostArchitectures="arm64"/>
<choice id="choiceVBox">
  <pkg-ref id="org.virtualbox.pkg.virtualbox"/>
</choice>
<choice id="choiceVBoxCLI">
  <pkg-ref id="org.virtualbox.pkg.virtualboxcli"/>
</choice>
<pkg-ref id="org.virtualbox.pkg.virtualbox">#VirtualBox.pkg</pkg-ref>
<pkg-ref id="org.virtualbox.pkg.virtualboxcli">#VirtualBoxCLI.pkg</pkg-ref>

I formatted the original XML and removed the presentation attributes. The relationship between choice and pkg-ref follows Apple’s distribution XML schema.

Where the files will land

The component PackageInfo files answer two immediate questions: where does each payload go, and does installation require root?

<pkg-info identifier="org.virtualbox.pkg.virtualbox" version="7.2.12"
  install-location="/Applications/" auth="root">
  <payload numberOfFiles="488" installKBytes="338060"/>
  <bundle path="./VirtualBox.app" id="org.virtualbox.app.VirtualBox"/>
</pkg-info>

<pkg-info identifier="org.virtualbox.pkg.virtualboxcli" version="7.2.12"
  install-location="/usr/local/bin" auth="root">
  <payload numberOfFiles="13" installKBytes="6"/>
</pkg-info>

Both request root authorization. The large component installs the application; the tiny one installs command-line wrappers. Their BOMs, or Bills of Materials, record the individual paths and file metadata. The CLI BOM begins with another useful magic value:

00000000  42 4f 4d 53 74 6f 72 65  00 00 00 01 00 00 00 31  |BOMStore.......1|
00000010  00 00 36 48 00 00 55 90  00 00 23 22 00 00 00 3c  |..6H..U...#"...<|
00000020

lsbom turns the binary inventory into something more convenient:

$ lsbom virtualbox-pkg/VirtualBoxCLI.pkg/Bom | sed -n '1,8p' | column -t
.                  40755   0/0
./VBoxAudioTest    100755  0/0  80   2322252598
./VBoxAutostart    100755  0/0  80   2068167003
./VBoxBalloonCtrl  100755  0/0  82   2983924263
./VBoxBugReport    100755  0/0  80   4051432796
./VBoxHeadless     100755  0/0  115  3914748263
./VBoxManage       100755  0/0  77   325582486
./VBoxVRDP         100755  0/0  115  3914748263

After each path come the mode and UID/GID. Regular files also have a size and a 32-bit CRC. The application BOM records group 80 and, notably, a setuid mode for VBoxNetAdpCtl:

$ lsbom virtualbox-pkg/VirtualBox.pkg/Bom | \
    grep -E '^\./VirtualBox\.app/Contents/(Info\.plist|MacOS/(VirtualBox|VBoxNetAdpCtl))[[:space:]]' | \
    column -t
./VirtualBox.app/Contents/Info.plist           100644  0/80  4107     771930153
./VirtualBox.app/Contents/MacOS/VBoxNetAdpCtl  104755  0/80  71040    4203402627
./VirtualBox.app/Contents/MacOS/VirtualBox     100755  0/80  2422880  3706879293

Joining a BOM path to the component’s installation location gives the destination:

PackageInfo: install-location="/Applications/"
                              +
BOM:         ./VirtualBox.app/Contents/MacOS/VirtualBox
                              =
             /Applications/VirtualBox.app/Contents/MacOS/VirtualBox

Peeling open a payload

pkgutil --payload-files gives us a quick look at the 488 application entries:

$ pkgutil --payload-files VirtualBox.pkg | sed -n '1,8p'
.
./VirtualBox.app
./VirtualBox.app/Contents
./VirtualBox.app/Contents/_CodeSignature
./VirtualBox.app/Contents/_CodeSignature/CodeResources
./VirtualBox.app/Contents/MacOS
./VirtualBox.app/Contents/MacOS/VBoxXPCOMIPCD.dylib
./VirtualBox.app/Contents/MacOS/VBoxSharedFolders.dylib

The component BOMs are better when permissions, ownership and CRCs matter. To inspect actual file contents, we need Payload.

The CLI payload is the nicer example because it expands to only 2,560 bytes:

$ file virtualbox-pkg/VirtualBoxCLI.pkg/Payload
virtualbox-pkg/VirtualBoxCLI.pkg/Payload: gzip compressed data, from Unix, original size modulo 2^32 2560
00000000  1f 8b 08 00 00 00 00 00  00 03 e5 95 41 6f 82 30  |............Ao.0|
00000010  14 80 3d fb 2b c4 ed aa  6d c1 c2 75 ea 96 6c c9  |..=.+...m..u..l.|
00000020

The 1f 8b prefix is gzip. After decompression, another archive header appears:

$ gzip -dc virtualbox-pkg/VirtualBoxCLI.pkg/Payload | file -
/dev/stdin: ASCII cpio archive (pre-SVR4 or odc)
00000000  30 37 30 37 30 37 30 30  30 30 30 30 30 30 30 30  |0707070000000000|
00000010  30 30 30 34 30 37 35 35  30 30 30 30 30 30 30 30  |0004075500000000|
00000020  30 30 30 30 30 30 30 30  31 36 30 30 30 30 30 30  |0000000016000000|

070707 identifies old ASCII CPIO. ditto understands the gzip-compressed archive directly:

$ mkdir cli-payload
$ ditto -x virtualbox-pkg/VirtualBoxCLI.pkg/Payload cli-payload
$ find cli-payload -mindepth 1 -maxdepth 1 -print | sort | sed -n '1,6p'
cli-payload/VBoxAudioTest
cli-payload/VBoxAutostart
cli-payload/VBoxBalloonCtrl
cli-payload/VBoxBugReport
cli-payload/VBoxHeadless
cli-payload/VBoxManage
$ sed -n '1,2p' cli-payload/VBoxManage
#!/bin/bash
exec /Applications/VirtualBox.app/Contents/MacOS/VBoxManage "$@"

So /usr/local/bin/VBoxManage is not the main binary. It is a 77-byte dispatcher into the application bundle.

The scripts are the interesting part

Payload inspection tells us what the package supplies. It does not cover changes made by installer scripts, and this package has two of them. PackageInfo assigns their execution roles even though their filenames use the older flight terminology:

<scripts>
  <preinstall file="./preflight" component-id="org.virtualbox.app.VirtualBox"/>
  <postinstall file="./postflight" component-id="org.virtualbox.app.VirtualBox"/>
</scripts>

There are three kinds of executable text in this download, and they run at different times:

downloaded DMG
|
+-- VirtualBox_Uninstall.tool
|   `-- runs only when explicitly launched
|
`-- VirtualBox.pkg
    +-- preinstall  -> Scripts/preflight
    |   `-- preserve existing Extension Packs
    +-- Payload
    |   `-- copy the application and CLI wrappers
    `-- postinstall -> Scripts/postflight
        +-- install Python bindings when available
        +-- install a per-user LaunchAgent
        +-- register file extensions
        +-- restore Extension Packs
        +-- reset ownership and the VBoxNetAdpCtl setuid bit
        `-- install a provisioning profile when present

The standalone uninstaller belongs to the DMG, not the PKG. It runs only if somebody launches it. The CLI wrappers are payload files and run later when a user invokes their commands. preflight and postflight, on the other hand, are run by Installer around the application payload.

The preinstall script is short. If an older VirtualBox installation contains Extension Packs, it copies them into Installer’s temporary directory before the old application is replaced. The postinstall script restores them afterward, then performs the integration work that does not fit neatly into the BOM.

That second script searches for local Python interpreters and may install VirtualBox bindings. It can also copy a vboxwebsrv LaunchAgent into the user’s library, register file extensions, change the application ownership to root:admin, restore the setuid bit on VBoxNetAdpCtl and install an embedded provisioning profile.

Takeaways

A DMG is a disk image containing a filesystem, not another spelling of PKG. In this download it is the outer, signed delivery container, while the inner flat PKG is a XAR containing Distribution, two component packages, their BOMs, payloads and installer scripts. The system tools make each layer visible without starting an installation, but payload listings only tell half the story: any useful review also has to account for the code Installer will run around those files.

SEE ALSO

FEEDBACK

Report a correction on GitHub send a correction by email

FOLLOW