Reverse Engineering Jamf Admin

Reverse Engineering Jamf Admin


If you are using Jamf Pro to manage your macOS client systems, the “default” option to upload, delete & modify properties of software installer packages & disk images, printer configurations, dock items, directory bindings, and scripts to your Jamf Pro infrastructure is using the native macOS Graphic User Interface (GUI) application called Jamf Admin. With Jamf supporting (and promoting) using and moving your Jamf Pro infrastructure to the cloud, some of the functionality of Jamf Admin application has moved to the web interface. And they have hinted at the plan to move its functionality entirely to the web interface, but currently, most of this administrative functionality is “only” available in Jamf Admin.

Jamf Admin provides decent functionality for managing installer packages & disks, but there are some bugs and poor implementation that can be annoying and painful for Jamf Pro administrators. When you want to streamline the steps of the process and move it into a framework to provide consistency & automation it does not meet this demand and requirement.  Also, you can work around some bugs and poor implementations by using more efficient code and discover hidden options that aren’t exposed in Jamf Admin native macOS Graphic User Interface (GUI) application using your own or others code or processes. Jamf does not provide any documentation on using APIs to provide Jamf Admin functionality using your own code and we have actually been told by Jamf technical support that it’s currently not possible but from the existence of tools like JSSImporter, Jamf Pro Upload Web Form, etc. and from our research and development that is far from the truth.

Image result for reverse engineering"


To prepare to reverse-engineer the “Jamf Admin” application, you will need to set up the following configuration modifications.

Jamf Pro User Privileges

To provide minimal access setup the following privileges for the user account or API user that you will be used to reverse engineer the “Jamf Admin” application. An API user is set up and tied to a script or process vs a particular user. Having a dedicated user-specific to a service also allows you to easily shutdown that application or script by disabling the user. Jamf provides a variety of tools to allow for programmatic access to data within the system and to allow for integrations with other systems. The primary tool is the Classic API. The base URL for the Classic API is located at /JSSResource on each Jamf Pro server.

Minimal Jamf Admin Privileges
  • Use Jamf Admin
  • Save With Jamf Admin

Settings > Jamf Pro User Accounts & Groups > [USERNAME] > Privileges > Jamf Admin

Debugging Proxy

Next, you will need to install and set up a proxy to analyze communication from Jamf Admin to the Jamf Pro and/or Jamf Distribution Point. What’s a Proxy? In this simple example, communication between the two computers (shown in grey) connected through a third computer (shown in red) which acts like a proxy server. Alice isn’t talking directly to Bob and Bob doesn’t see the request directly from Alice. The proxy is acting as a relay. Two computers connected via a proxy server. The first computer says to the proxy server: "ask the second computer what the time is". Proxy & Other Options

There are multiple options to set up a debugging proxy or similar methodologies for example:


Wireshark is a free and open-source packet analyzer. It is used for network troubleshooting, analysis, software and communications protocol development, and education.

Image result for what is Wireshark mac"

tcpdump is a data-network packet analyzer computer program that runs under a command-line interface. It allows the user to display TCP/IP and other packets being transmitted or received over a network to which the computer is attached. Here is an article that covers the process of “Recording a Packet Trace” with tcpdump on the Apple Developer site.

For this blog article, we will be covering using Charles debugging proxy application to reverse engineer the Jamf Admin application.

Charles Debugging Proxy Application

Next, you will need to download and configure a debugging proxy to analyze connections to and from the “Jamf Admin” application. One great solution that runs on Mac client systems is Charles. Charles is a cross-platform HTTP debugging proxy server application written in Java. It would relay the communication between Jamf Admin and the Jamf Pro server. It enables the user to view HTTP, HTTPS, HTTP/2 and enabled TCP port traffic accessed from, to, or via the local computer. This includes requests and responses including HTTP headers and metadata (e.g. cookies, caching and encoding information) with functionality targeted at assisting analyzing connections and messaging.

Charles Web Debugging Proxy Application

Grant Privileges

When you first launch Charles, you’ll see the message below appear where you can Grant Privileges and then enter your administrator user password to confirm. Charle - Automatic macOS Proxy Configutation

Charles Root Certificate

Next, you will need to install the Charles root certificate on the Mac system running Charles as well as the Jamf Admin application. This will allow us to access the SSL traffic that is being proxied through Charles. Download and import the Charles root certificate into your Keychain on macOS by selecting:

Help > SSL Proxying > Install Charles Root Certificate

Next, add the certificate to the current user or System keychain.

This certificate is not trusted by default so you will need to modify the trust settings.

Expand the “Trust” disclosure triangle and select “Always Trust” from the “When using this certificate” pop-up list. Close the certificate details window and enter the password to confirm these modifications.

Enable SSL Proxying

To enable SSL Proxying, navigate to the “Proxy” menu and select the “SSL Proxying Settings…” command and make sure that the “Enable SSL Proxying” checkbox is selected.

Enable SSL Proxying for all locations by clicking “Add” button, then enter a single wildcard character ( * ) in the Host field, leave the Port field empty, and then click the “OK” button.


After finishing the steps above, the “SSL Proxying Settings” window should appear like the following:

Allow Jamf Admin Use Invalid Certificate

Next, you need to modify settings to allow Jamf Admin to use an invalid certificate. This is required to allow Jamf Admin to use the Charles Root Certificate to view the SSL traffic from Jamf Admin, Jamf Pro Server, and the Jamf Distribution Server. Using the Jamf Admin, select the “Preferences…” command from the “Jamf Admin” menu.

Then unselect the checkbox to allow the untrusted SSL certificate.

Or using the command line you can enter the following command:

defaults write com.jamfsoftware.jss allowInvalidCertificate -bool yes

This modifies or creates the property list file located here:


With the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">
Supposedly this flag is deprecated but still works in our testing, but keep this in mind if it stops working in the future.

Jamf Distribution Point Credentials
Note, if you have other admins in your organization using Jamf Admin, your file distribution points username & password will be in the clear and readable. So, keep this in mind if you want to limit access to this username/password in your organization or include consider including it in a password policy when a former employee leave your organization.


Start Recording
Next, in the Charles application select “Start SSL Proxying” from the “Proxy” menu.

Then select “Start Recording” from the “Proxy” menu.

Jamf Admin

Next, launch Jamf Admin and Charles should collect the traffic for https:// your.jss.domain:8443 .

In Charles, you should see traffic regarding casper.jxml .

Right-click on it and select “Copy cURL Request” from the pop-up menu.

The content should look something like the following:
curl -H 'Host: your.jss.domain:8443' -H 'Content-Type: application/xml; charset=utf-8' -H 'Accept: */*' -H 'User-Agent: Jamf%20Admin/ CFNetwork/978.2 Darwin/18.7.0 (x86_64)' -H 'Accept-Language: en-us' --data-binary "&username=${USERNAME}&password=${PASSWORD}%21&casperAdminVersion=10.7.0&skipComputers=true" --compressed 'https://your.jss.domain:8443//casper.jxml'

However, the Jamf Admin application tends to be verbose and we have found only the following is necessary:

curl --data-binary"&username=${USERNAME}&password=${PASSWORD}&casperAdminVersion=&skipComputers=true" 'https://your.jss.domain:8443//casper.jxml'
At this point, a JSESSIONID cookie is also set which negates the need for the password for subsequent actions, though we aren’t positive how long the cookie will remain valid.



Analyze Jamf Admin Processes

Next, step is to pick processes in Jamf Admin that you want to analyze and reverse engineer the process and then test & implement with your own code. For example, here are Jamf Admin processes we analyzed and reverse engineered.

Uploading Packages
Uploading packages are done in two steps, File Copy, and Package Creation File Copy Jamf Admin simply performs a copy to your FileShare Distribution Point, all of the information needed to mount the FileShare is located in the XML response given back by the Authentication call above, but given an SMB FileShare, it can be mimicked with the following:
function urlencode {
echo $(perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$1")
open smb://$(urlencode "$USERNAME"):$(urlencode "$PASSWORD")@${FILESHARE}
Note, a package entry can be created in the Jamf Pro database with (or without) the file being present.

Add Installer Package Bash Script
The following bash script will add the install package to your Jamf Pro database.
URL=$(defaults read com.jamfsoftware.jss url)

# Add Installer Package
curl --data-binary "&username=$(urlencode "$USERNAME")\
&password=$(urlencode "$PASSWORD")\
&packageName=$(urlencode `basename "$PACKAGE"`)\
&packageFileName=$(urlencode `basename "$PACKAGE"`)\
&checksum=$(openssl dgst md5 "$PACKAGE")\
&hashValue=$(openssl dgst sha512 "$PACKAGE")\
&packagePriority=10" "${URL}/casperAdminAddObject.jxml"
Update Installer Package Information
The following bash script will update the installer package metadata in the Jamf Pro database.
URL=$(defaults read com.jamfsoftware.jss url)

curl --data-binary "&username=$(urlencode "$USERNAME")\
&password=$(urlencode "$PASSWORD")\
&packageName=$(urlencode `basename "$PACKAGE"`)\
&packageFileName=$(urlencode `basename "$PACKAGE"`)\
&checksum=$(openssl dgst md5 "$PACKAGE")\
&hashValue=$(openssl dgst sha512 "$PACKAGE")\
&packageInfo=$(urlencode "$INFO")\
&packageNotes=$(urlencode "$NOTES")\
&installLanguage=en_US" "${URL}/casperAdminAddObject.jxml"
Also, here is a python script that will update the installer package metadata in the Jamf Pro database.
#!/usr/bin/env python3
import requests
import xml.etree.ElementTree as ET
username = 'username'
password = 'password'
# set to False if Charles Proxy is running
verify = True 

session = requests.Session()
AUTH = {'username': username, 
        'password': passwd, 
        'casperAdminVersion': '', 
        'skipComputers': 'true'}
# verify=False allows the post to run while Charles Proxy is running (cookie is automatically handled)
auth_response ="https://{hostname}:8443//casper.jxml", data=AUTH, verify=verify)
# raw xml data for authentication
xml = auth_response.text

CREATE = {'username': username, 
			# 'password': password,  # not required if cookie is still valid
          'type': 'package', 
          'packageName': 'package_name.pkg',      # `/usr/bin/basename </path/to/package.pkg>`
          'packageFileName': 'package_name.pkg',  # `/usr/bin/basename </path/to/package.pkg>`
			'adobeInstall': 'false',
			'osInstall': 'false',
			'osInstallerVersion': '',
			'parentPackageID': '-1',
			'checksum': md5_checksum,		# `/usr/bin/openssl dgst md5 </path/to/package.pkg>`
			'hashType': '1',
			'hashValue': sha512_checksum,	# `/usr/bin/openssl dgst sha512 </path/to/package.pkg>`
			'reboot': 'false',
			'packagePriority': '10'}
create_response ="https://{hostname}:8443//casperAdminAddObject.jxml", data=CREATE, verify=verify)
jss_package_id = ET.fromstring(create_response.text).find('new_id').text

SAVE = {'username': username,
        # 'password': password,						# not necessary if cookie still valid
        'allScriptsMigratedToJSS': 'true',
        'packageID': jss_package_id,					# ID of the newly created package
        'packageName': 'package.pkg',
        'packageFileName': 'package.pkg',
        'checksum': md5_checksum,						# md5 digest
        'hashType': '1',
        'hashValue': sha512_checksum,				# sha512 digest
        'packageGroupID': group_id,					# package category id
        'packagePriority': '10',
        'packageInfo': 'Package Info Text',
        'packageNotes': 'Packge Notes Text',
        'packageFormat': 'Apple Package',
        'packageSize': 'n/a',
        'packageRequirements': '',
        'bootVolumeRequired': 'false',
        'fut': 'false',
        'feu': 'false',
        'ifswu': 'false',
        'reboot': 'false',
        'uninstall': 'false',
        'packageRequiredProcessor': 'None',
        'packageSwitchWithPackageID': '-1',
        'selfHealingAction': 'nothing',
        'selfHealingNotify': 'false',
        'adobeInstall': 'false',
        'adobeInstallerImage': 'false',
        'adobeUpdater': 'false',
        'osInstall': 'false',
        'osInstallerVersion': '',
        'packageSerialNumber': '',
        'parentPackageID': '-1',
        'basePath': '',
        'ignoreConflictingProcesses': 'false',
        'suppressFromDock': 'false',
        'suppressEULA': 'false',
        'suppressRegistration': 'false',
        'suppressUpdates': 'false',
        'installLanguage': 'en_US'}
update_response ="https://{hostname}:8443//casperAdminSave.jxml", data=SAVE, verify=verify) 

DELETE = {'username': username, 
          'password': passwd, 
          'allScriptsMigratedToJSS': 'true',
          'deletedPackageID': jss_package_id}
delete_response ="https://{hostname}:8443//casperAdminSave.jxml", data=DELETE, verify=verify)
Delete an Installer Package
Note, this will not delete the installer package from your Jamf Distribution Point, but only from the Jamf Pro database.
URL=$(defaults read com.jamfsoftware.jss url)
JSS_PACKAGE_ID="100000" # jss id of package

# Delete a package
curl --data-binary "&username=$(urlencode "PASSWORD")\
&password=$(urlencode "PASSWORD")\
&deletedPackageID=${JSS_PACKAGE_ID}" "${URL}/casperAdminSave.jxml"


Indexing like Rabbits & Such

Indexing an installer package with Jamf Admin creates a log of all the items contained within the installer package. This is supposed to allow you to uninstall the installer package contents and view the contents from Jamf Pro web interface. This option uploads a list of the contents of the installer package to your Jamf Pro database.

Image result for rabbit breeding joke"


Jamf Pro Installer Package Contents

For example, in Jamf Pro select the following to display contents:

Jamf Pro > Settings > Computer Management > Packages > [INSTALLER PACKAGE NAME].pkg

Then click the “Contents” button to display the installer package contents.


Expanding Installer Packages?

On the Mac client system, you are running Jamf Admin to perform the indexing of an installer package, you will get expanded install on the system and if you are using the optioninstall-location using a tool like pkgbuild where you are specifying a payload of single application bundle like with an install path like /Applications/Programming. Jamf Admin’s indexing process will expand the installer package at the root (/) of your file system and you will get multiple versions of your installer packages contents on the Mac system. Or if you include the full path of your installer package’s payload, it will get expanded based on your payload path structure on the Mac system doing the indexing with Jamf Admin.

Why is Jamf Admin expanding in production areas of the Mac system vs using /private/tmp and cleaning up after? If this is a Jamf Pro administrator’s production Mac system, it could cause issues with the client system by expanding in these production areas or causing unnecessary duplication & cleanup tasks. Jamf Admin and all the administrative tools include a copy of the jamf  binary, maybe in case, an administrator system doesn’t have the default jamf binary installed or is a dependency that previously existed with the administrative tools.

Jamf Pro/Jamf

One method to get more insight, into the process that Jamf Admin is using with the indexing process is to use a command-line tool like filemon which displays real-time file system activity on macOS and iOS. This utility relies on the FSEvents device, present in macOS, to follow file system-related events, such as creation and deletion of files. Or a GUI tool like FSMonitor,  a macOS application that can monitor and report all modifications to your file system as they are happening. Tracked changes include file creation, deletion, change of content, renames, and change of attributes (i.e. modifications to permissions or owner).

For example, here is file system activity gathered using FSMonitor:

Jamf Admin Indexing Process:

  • Creates [NAME].tmp file in /Library/Application Support/JAMF/tmp
  • Runs defaults command and stores to [NAME].tmp
  • Runs pkgutil and expands into /Library/Application Support/JAMF/tmp/expandedPackage with Bom, Payload and PackageInfo.
  • Runs lsbom and outputs /Library/Application Support/JAMF/tmp/
  • Runs gunzip and creates /Library/Application Support/JAMF/tmp/Archive.pax
  • Runs shell command/script and expands/installs installer package contents, maybe via Archive.pax
  • Creates /private/tmp/indexes directory and creates & updates “1-10” named files, /private/tmp/indexes/1-10


You could use the strings command on the jamf binary and other components of the Jamf Admin bundle to get hints of other functions and processes. The strings command returns each string of printable characters in files. Its main uses are to determine the contents of and to extract text from binary files (i.e., non-text files). A string is any finite sequence of characters, and it can be as few as one character.

strings "/Applications/Utilities/Admin/Jamf Pro/Jamf" > /path/to/jamf_strings_output.txt

For example, here are some notable strings…

Creating uninstall file...
Error: The payloads directory does not exist at the specified base path.
Process blocking
Results of installation: 
The JSS did not return information to uninstall this package
Creating uninstall BOM...
Save location not valid.
Indexing a Flat-File Apple Package...
Expanding to directory...
/usr/sbin/pkgutil --expand 
/ -name Bom
/usr/bin/lsbom -p fugMTc 
Extracting Contents of 
; /bin/pax -r -f 
Indexing an Apple Package...
 -name *.bom

You could take this investigation much deeper using a disassembly tool like Hopper, but that is out of the scope of this blog post.

Jamf Admin Indexing – Such Size Bug

Here is an example of the Jamf Admin’s indexing process using the cURL command (formatted for clarity):

    -H 'Host: ${JSSHOSTNAME}:8443'
    -H 'Content-Type: application/xml; charset=utf-8'
    -H 'Accept: */*'
    -H 'User-Agent: Jamf%20Admin (unknown version) CFNetwork/978.0.7 Darwin/18.7.0 (x86_64)'
    -H 'Accept-Language: en-us'
    --data-binary "&username=${USERNAME}
    --compressed "https://${JSSHOSTNAME}:8443//packageIndex.jxml"

Interestingly, there is a bug where size equals “such” (&size=such)? See the example lines above. Creating our own methodology of indexing outside Jamf Admin didn’t have this issue and worked properly.

Enable Debug Mode for Jamf Admin

To log more in-depth and details logs of Jamf Admin create a directory or file named “debug” within the following directory:


For example, then create a file named “debug”:

touch "/Applications/Jamf Pro/Jamf"

Or create a directory named “debug”:

mkdir "/Applications/Jamf Pro/Jamf"

The output for the Jamf Admin debug log is named CasperAdminDebug.log and will be created in the user’s directory launching Jamf Admin ~/Library/Logs/JAMF/” You can use a terminal command-line utility like tail , cat , less, etc. to view the log:

tail ~/Library/Logs/JAMF/CasperAdminDebug.log

Or use the macOS Console GUI application to view the debug log.

When you launch Jamf Admin, you will see the following mount log information:

'/private/var/folders/ft/jtkcdd4r8xgcty0059_sts980000gq/T/AppTranslocation/663D1E32-A662-4536-9CF8-17A10357B601/d/Jamf' mount -type afp -server '[JDS HOSTNAME]' -share '[JDS SHARE NAME]' -username '[USERNAME]' -passhash '[PASSWORD HASH]' -visible
1/20/20 10:34:55 AM - -----------------------------------------------------------------------------------------------------------------------Mounting [PASSWORD HASH]
Mounted file server
<mountpoint>/Volumes/[JDS SHARE NAME]</mountpoint>

Since the debug logs are verbose and can take up unnecessary disk space, it is recommended to disable debug logging after you have completed troubleshooting Jamf Admin removing the following directory or file. Example of remove debug file:

rm "/Applications/Jamf Pro/Jamf"

Example of removing debug directory:

rmdir "/Applications/Jamf Pro/Jamf"

Then index a package in Jamf Admin and you will see something like the following in the log output:

'/private/var/folders/ft/jtkcdd4r8xgcty0059_sts980000gq/T/AppTranslocation/4EEEA080-140C-4DD2-896D-A0A3506B3D5D/d/Jamf' 'index' '-path' '/Volumes/[JDS SHARE NAME]/Packages/' '-package' '[PACKAGE NAME].pkg' '-saveTo' '/private/tmp/indexes/'


Uninstalling Contents with Jamf Pro

In our testing, using a Jamf Pro policy or Jamf Remote to uninstall didn’t work properly due to its handling of links and installer packages fully indexed wouldn’t uninstall properly.

Build a Test Installation Package

To test Jamf Admin indexing we can build a test installation package with the following command:

$> mkdir -p test_package/Payload/directory
$> echo "test" > test_package/Payload/directory/test
$> ln test_package/Payload/directory/test test_package/Payload/directory/test_hard_link 
$> ln -s test_package/Payload/directory/test test_package/Payload/directory/test_symlink 
$> pkgbuild --root test_package/Payload --identifier --version 1.0 test_package.pkg

Expand Contents of Test Installation Package

Next, expand the contents of the test installation package to output the PackageInfo contents:

$> pkgutil --expand test_package.pkg example
$> cat example/PackageInfo
<?xml version="1.0" encoding="utf-8"?>
<pkg-info overwrite-permissions="true" relocatable="false" identifier="" postinstall-action="none" version="1.0" format-version="2" generator-version="InstallCmds-681 (18G103)" auth="root">
    <payload numberOfFiles="5" installKBytes="2"/>

Upload & Index Test Installer Package

Upload & index this test installation package to your Jamf Pro instance using Jamf Admin and install it on a Mac client system.

For example, here are the Jamf Pro contents for our test installer package:

This is what you should see on your client that the installer package was installed…

Uninstall Installer Package Contents
Next, you can use the jamf binary command-line tool to uninstall the test installer package:

First, you want to get the package ID in Jamf Pro by going here:

Settings > Computer Management > Packages > [PACKAGE NAME].pkg

Then get the address of the URL, should look something like the following:


You want to look for the id=[PACKAGE ID NUMBER] and get the package ID.

Then you want to run the following command to uninstall the installer package contents with the jamf  command-line tool:

sudo jamf uninstall -id [PACKAGE ID NUMBER] -verbose

For example…

# sudo jamf uninstall -id 598 -verbose
 verbose: Parsing uninstall information...
Getting package details from https://your.jss.domain:8443/...
 verbose: Creating uninstall BOM...
	Looking for Applications...
	Deleting files...
 verbose: 		Deleting file /directory/test...
 verbose: 		Deleting file /directory/test_hard_link...
Uninstalled .

You will notice that the uninstall process left the directory and symlink contents from the install…


We ran into the same issue using Jamf Remote to uninstall, which appears to use the jamf command-line tool anyway.

So, long-story-short, you currently can’t rely on Jamf Pro built-in functionality to properly remove installer package contents and must use other methods like custom scripts that handle different types of installer package configurations like applications that live entirely within /Applications folder or sub-folder only; trusting & running a vendor-supplied uninstaller applications, binaries or scripts; deleting installer package contents based on its installer receipts, or a custom uninstaller script. Depending on installer receipts to completely remove contents means that all file system modifications are contained in the installer package payload and not via pre or post-install scripts. Many vendors use and depend on pre or post-install scripts that are unnecessary and could be done via the standard installer payload methods and be tracked with the installer receipts.

In the end, its too bad Jamf Pro customers need to reverse engineer its process and provide documentation when ideally it should be coming from Jamf. Hopefully, this will change someday.

Automation Framework – Coming Soon…

We are working on a framework that will implement Jamf Admin functionality, Jamf Pro policy, patch and external patch for complete automation that can granularly be configured for your environment and individual needs. If you only want Jamf Admin feature(s) you can implement just those features without implementing policy, path and external patch. You can pick and choose which feature you want and don’t want to use based on your own environment needs and customize the feature based on your own configuration needs. We hope to have a publically digestible code available in the next month or two.

Check out our GitHub Repository for its future release and other projects.

No Comments

Leave a Reply