The macOS installation process installs a pkg file with root credentials. Because of this high level of privileges, it is essential for a Mac system administrator or security expert to be able to inspect the files and scripts.
macOS comes with several tools to work with package files. Most of them are command line tools. pkgutil
lets you examine a pkg file and its contents before the actual installation. It also lets you inspect which packages and files have already been installed on a given system.
Installed Packages
You can use the pkgutil
command to list packages that have been installed on the system.
$ pkgutil --pkgs
This will list all packages that have been installed on the system. On a freshly installed macOS 15.5 system the list is very short:
com.apple.files.data-template
But depending on the version of macOS and how long the system has been running the list may have hundreds of entries. You can use grep
to filter the output, but pkgutil
has its own filter option: (I ran this on a system with a few more things installed.)
> pkgutil --pkgs='com.scriptingosx.*'
com.scriptingosx.Installomator
com.scriptingosx.swift-prefs-tools
com.scriptingosx.utiluti
com.scriptingosx.desktoppr
Note that you need to quote the search term otherwise the shell will attempt to expand the wildcard.
Information for an Installed Package
pkgutil --pkgs
lists the identifiers of the packages. Identifiers are chosen by developer, but should generally follow the “reverse DNS notation” scheme.
When properly used, identifiers allow the installer process to distinguish between new installations, upgrades and packages that have already been installed. There is another piece of information necessary to determine this and that is the version of a package. To get the version and other information on a specific package run
> pkgutil --info com.scriptingosx.desktoppr
package-id: com.scriptingosx.desktoppr
version: 0.5-218
volume: /
location:
install-time: 1720421876
The install time is logged in epoch time (seconds since January 1, 1970). To convert it into something readable by humans you can use the date command:
> date -r 1720421876
Mon Jul 8 08:57:56 CEST 2024
Note: in earlier versions of Mac OS X the information on installed packages were stored in individual files called receipts. While the information is now stored in a database, the data is still referred to as a receipt.
Listing the Files a Package Installed
The --files
option lists all the files that were installed by a package. The file paths are given relative to the packages install-location. Usually, but not always, the install location is the root of the file system/
.
> pkgutil --files com.scriptingosx.desktoppr
usr
usr/local
usr/local/bin
usr/local/bin/desktoppr
The --file-info
option does the reverse and looks up which package installed a specific file. If a file was placed there by multiple packages with different package identifiers, you will get a list.
> pkgutil --file-info /usr/local/bin/desktoppr
volume: /
path: /usr/local/bin/desktoppr
pkgid: com.scriptingosx.desktoppr
pkg-version: 0.5-218
install-time: 1720421876
uid: 0
gid: 0
mode: 100755
The installer receipt remembers the file’s owner (uid, 0 is root) and group (gid, 0 is wheel) and the permission mode that was stored in the package. Along with the actual files, the installer package also contains the owner, group, and mode (access privileges) for each file.
The metadata in the receipt may not match the file’s metadata on the disk. This indicates that the file was changed since installation. But it is difficult, if not impossible to know whether the change was intentional, accidental or even malicious. You will have to use your good judgement.
Unfortunately, some developers do not know or understand that installation packages also set the metadata for payload files and you often see changes to the owner, group and file mode applied in a postinstall script
. When evaluating whether an installed file has been tampered with after installation, it is necessary to check postinstall
scripts for such actions.
When a file was not installed by a package installer the --file-info
option will return the path and volume, but no package information:
> pkgutil --file-info /Applications/Notes.app
volume: /
path: /Applications/Notes.app
This is also the case for files that were copied, moved or created by a package’s preinstall
or postinstall
script. You only get the package data for files that were placed from a package’s payload.
Forgetting an installed package
The installation system on macOS uses the package identifier and version in the receipt to determine if an installation is new to the system, a different or new version of an already installed package, or a re-installation of a package of the same version. The behavior of the installation may change between theses scenarios.
You may notice that when you delete files or apps that were installed from a package and then re-install the same version of the package, that the files may not re-appear on the system. This happens often when you are testing an installation workflow over and over again on the same system.
You can use pkgutil --forget
to remove the receipt of a package from the system. The --forget
option will not delete any files that were installed on the system. All it removes is the installation receipt. If you then install the same package again, the system will consider it a fresh, new installation and the payload should be installed correctly.
Uninstalling
The macOS installation system does not have an option to uninstall or remove files and apps that were installed with an installation package. You can get a list of files that were installed from the package payload or the receipt. This will be a good starting point, but an app or tool might also create daemons, agents, preferences, configuration files, and other resources in various places across the file system. All these files weren’t part of the package payload and wouldn’t be tracked in the receipt. You will have to inspect all of these and judge whether you need to remove them, as well. Daemon and agents will need to be properly unloaded and quit before deleting their files.
Once you have built a script that performs the un-installation to your satisfaction, you should also run pkgutil --forget
and remove the record of the package being installed to ensure a future re-installation will run smoothly.
Inspecting Package Payload Files
Sometimes you want to see what a package file will do without actually installing it. pkgutil
has some options for that, too.
Our example file will be the installer I provide for one of my projects: desktoppr. Desktoppr is a command line tool to set the desktop picture or wallpaper on macOS.
You don’t have to actually install desktoppr
to inspect the pkg. Though if you want to, you can install the pkg and use pkgutil
to determine what it installed and then delete that single file later.
You can download the latest package installer file for desktoppr
from the ‘Releases’ section on the GitHub repository. Note that, like many other projects, desktoppr has a pkg and a zip download. For this, we are only interesting in the pkg file.
The file
command reports a pkg file is a xar archive:
$ file ~/Downloads/desktoppr-0.5-218.pkg
/Users/armin/Downloads/desktoppr-0.5-218.pkg: xar archive compressed TOC: 4389, SHA-1 checksum
(The exact output of this command may vary depending on your version of macOS and the version of the pkg.)
Packages are compressed into a single archive file. This ‘flat’ package format was introduced in Mac OS X Leopard 10.5 in 2007, replacing and deprecating the previous ‘bundle-style’ packages. Bundle-style packages were finally made defunct in macOS Sequoia 15.0 in 2024. Unless you have to support legacy Macs you should only encounter flat packages.
To expand the package, we can use pkgutil --expand
> pkgutil --expand desktoppr-0.5-218.pkg Desktoppr
> ls Desktoppr
Distribution desktoppr.pkg
This will create a folder named Desktoppr
with the expanded contents of the package file.
Inside this folder, you will see a file named Distribution
. Open this file with a text editor. (open -e Desktoppr/Distribution
will open the file in TextEdit if you don’t have another editor available.)
This XML file contains the metadata for the installer process. The most interesting elements are pkg-ref
, which has the version
and the identifier
for the package and the components. It also shows the options or components that are available from the user in the Installer application.
There is a sub-folder called desktoppr.pkg
inside the expanded folder. The pkgutil --expand
command has already expanded this component, so we don’t need to expand it again.
> ls Desktoppr/desktoppr.pkg/
Bom PackageInfo Payload
Note: When you are inspecting the expanded file structure in Finder, it will show this subdirectory with the package icon. The folder name ends with
.pkg
which Finder erroneously interprets as a file extension for a bundle-style installation package. If you want to see the contents of this folder in Finder, choose ‘Show Package Contents’ from the context menu.
Inside the sub-directory or component, you will find three more files.
PackageInfo
is another XML file with metadata on the component. The most relevant information in here is right in the first pkg-info
tag, which has attributes for identifier
, version
and install-location
.
The Payload
file is another archive with the actual files inside it. If you wanted to extract the files manually you can do so with:
> tar xvf Desktoppr/desktoppr.pkg/Payload
x .
x ./usr
x ./usr/local
x ./usr/local/bin
x ./usr/local/bin/desktoppr
The folder structure of the payload is relative to the package’s install-location.
Bill of Materials
The last file is called Bom
which is short for ‘Bill of material’. It contains an entry for each file in the Payload
with additional metadata: owner, group, and file mode (access privileges). It is stored in a binary format, so it cannot be read with a text editor, but you can read the content with the lsbom
command.
> lsbom Desktoppr/desktoppr.pkg/Bom
. 40755 0/0
./usr 40755 0/0
./usr/local 40755 0/0
./usr/local/bin 40755 0/0
./usr/local/bin/desktoppr 100755 0/0 271792 550451430
This will output one line per item in the package. The entries or columns per line are: path, file mode, owner id/group id, file size and a CRC 32-bit checksum (only for files).
There are many options to control the output of the lsbom
command. You can find them all in its man page.
Since the bill of material (Bom
) is very interesting pkgutil
provides a shortcut to get it without having to expand the entire pkg file.
> pkgutil --bom desktoppr-0.5-218.pkg
/tmp/desktoppr-0.5-218.pkg.boms.vVNvMz/desktoppr.pkg/Bom
This command will extract the Bom
into a temporary file and output the path. You will use this most commonly together with lsbom
.
pkgutil
also has a --payload-files
option:
pkgutil --payload-files desktoppr-0.5-218.pkg
.
./usr
./usr/local
./usr/local/bin
./usr/local/bin/desktoppr
This output shows only the file path. If you require more information, use the --bom
option to export the Bom
file and use lsbom
.
More Complex Packages
The desktoppr installation package is a very simple package. It installs a single binary file.
For a slightly more complex package, you can download the installer pkg for Setup Manager. Setup Manager is an enrollment tool that works with Jamf Pro and Jamf Connect.
Again, you do not have to actually run the installer to inspect. In this case, the tool will only work on a Mac managed with a Jamf management server at enrollment. Nevertheless inspecting this package will be instructive.
First, use pkgutil
to list the payload files.
> pkgutil --payload-files Setup\ Manager-1.3.1-610.pkg
.
./Library
./Library/LaunchAgents
./Library/LaunchAgents/com.jamf.setupmanager.loginwindow.plist
./Library/LaunchDaemons
./Library/LaunchDaemons/com.jamf.setupmanager.plist
./Library/LaunchDaemons/com.jamf.setupmanager.finished.plist
./Applications
./Applications/Utilities
./Applications/Utilities/Setup Manager.app
There are more files that are listed, but they are all files and folders in the Setup Manager.app
bundle. This package installs two LaunchDaemons and a LaunchAgent, as well as the Setup Manager application in /Applications/Utilities
To learn more, expand the package file with pkgutil
:
> pkgutil --expand Setup\ Manager-1.3.1-610.pkg SetupManager
> ls SetupManager
Distribution Resources Setup Manager.pkg
> ls SetupManager/Resources
License.rtf Readme.rtf
We see a new subfolder named Resources
which contains two rich text files. These are shown in the respective panes when the pkg file is opened with the Installer application. You can double-click the Setup Manager pkg to open it in the Installer application and see the two panes. You don’t need to follow through with the installation.
When we dig further into the expanded Setup Manager we see another folder we did not have before:
ls SetupManager/Setup\ Manager.pkg/
Bom PackageInfo Payload Scripts
ls SetupManager/Setup\ Manager.pkg/Scripts
postinstall preinstall
The Scripts
folder in the component contains two scripts: preinstall
and postinstall
. The installation process will run these scripts before and after the payload files are installed on the system.
When you open the script files in a text editor, you can see that these unload and load the LaunchAgents and Daemons in the payload.
You can use pkgutil
and lsbom
to inspect all kinds of packages. If you want to practice, the Microsoft installers are a very good exercise.
Component Packages
There is a simpler type of packages. As an example, download the installer pkg for an early version of desktoppr.
When you expand this pkg file with pkgutil
, you will see no Distribution XML file or sub-component folders.
$ pkgutil --expand desktoppr-0.3.pkg Desktoppr0.3
ls Desktoppr0.3/
Bom PackageInfo Payload
Instead you see the three files we saw earlier in the component subfolder of the main pkg: Bom
, PackageInfo
, and Payload
. Nevertheless, if you were to install this package, it would work just fine and install its payload.
This is a component package. Generally, component packages are built as an intermediate step to assemble the distribution package format we saw earlier. Nevertheless, component package files will work fine on their own, as well.
Distribution Packages, Product Archives, and Component Packages
Most of the pkg files you will encounter are distribution packages. Distribution packages do not have a payload or installation scripts of their own. Distribution packages contain one or more components. Each component will have a payload and (possibly) installation scripts.
Distribution packages are wrappers for their components and can have some extra data, such as the License and ReadMe file we saw earlier.
Apple’s developer documentation often refers to “product archives.” Product Archives are a different name for distribution packages with a specific set of metadata. Most relevantly, product archives have an identifier and version set.
Distribution packages and product archives allow the developer to customize the interactive installation process in the Installer application. Product archives are also a requirement for publishing in the Mac App Store. For these reasons, product archives are the recommended choice for developers to distribute their software.
Component packages already provide the most relevant feature for package installers: they install files. They are quite simple to create, which makes them popular with Mac system administrators who often need to build custom installers that are installed silently from a management system. There are, however, some situations where distribution packages are required with management systems, too.
Suspicious Package
Understanding the command line tools and workflows to expand and inspect pkg files is a good exercise and an important foundation to building packages. Nevertheless, it can be tedious when all you want is to just to see the files inside or some metadata for the package.
The application ‘Suspicious Package‘ provides a powerful and useful graphical interface for inspecting installation packages and their payloads. It gives an overview of package’s metadata, including signature and notarization status. It will show a detailed graphical view of the payload, the metadata files and installations scripts. When necessary, you can preview or extract individual files for further analysis
There will still be situations where you will need pkgutil
, but Suspicious Package is an indispensable tool for any Mac Admin and Mac security professional. You can download Suspicious Package for free from Mothers Ruin Software.