Are You Docking Kidding Me?

.LNK persistence on macOS?


An area of recent interest for me is persistence on macOS. Specifically, I have been looking at files that low-level users can modify that may affect user interaction. An area of frequent interaction for end-users is the Dock.

Through my research, I noticed a plist that controls the visible representation of the Apple Dock applications. Nothing groundbreaking here as this plist is modified frequently by the end-user through the graphical user interface (GUI). Upon inspection of the values within the plist, I wondered if I could change the values to replace a valid application with a malicious application that will run our code.

The culmination of this research is DockPersist.js, which I have included in the PersistentJXA project. My implementation will replace either Safari or Chrome with our malicious app. I focused on Safari and Chrome as there is a high likelihood that they will be present in a user’s Dock. However, please note that the concept applies to any application. Once the end-user clicks on the Safari or Chrome icon, it will run our malicious application. This persistence method is similar to the Link (.LNK) file persistence on Windows as the Dock icons serve as shortcuts to the actual applications.

This persistence method does require uploading our malicious application to the target. I prefer using the upload function within a Mythic agent to save our application on the target.

After modifying the plist, you can reload the Dock immediately; however, it results in a brief screen flicker to the end-user. Alternatively, you can wait until a reboot for our fake application to appear in the Dock because the modified plist persists through a reboot.



Again, this persistence method requires a malicious app to be uploaded to the target, but there are various methods that can bypass Gatekeeper protections and allow us to upload the malicious app to the target. You can:

  • zip the application bundle, use the upload command in a Mythic agent (Apfell or Poseidon), then unzip to destination
  • zip the application bundle, host somewhere, use curl to pull down to the target, then unzip to destination
  • zip the application bundle, base64 encode, base64 decode to save on target, then unzip to destination

As a Proof of Concept (PoC), I simply created an application in Automator. The PoC application opens Safari in an attempt not to alert the end-user; then, it runs our Apfell payload.

JXA within PoC application to open Safari and execute Apfell payload

To not stand out to the end-user, I replaced the default Automator icon with the Safari compass. Of course, Xcode can be leverage to create more elaborate applications.

PoC application (named Safari) with Safari icon

Next, I zipped the application bundle and uploaded it to the target. After unzipping to /Users/Shared/, we can focus on invoking the persistence method with the prerequisites fulfilled.

Note: Due to the binary format of the plist, the automated implementation requires that the fake application be named “Google Chrome” or “Safari” and be located in /Users/Shared/. The Safari64 and Chrome64 variables can be modified to change this location.

Invoke Persistence

Import the script into the Apfell agent.

Imported script into Apfell Agent within Mythic

Call the DockPersist function. The function accepts three arguments: Application Name (Safari or Google Chrome, Bundle ID, and option to reload Dock immediately).

Note: The Bundle ID is within the Info.plist and can easily be obtained with the following: /usr/libexec/PlistBuddy -c 'Print CFBundleIdentifier' ~/FakeApp/

Call the DockPersist function in Apfell Agent specifying Safari, our bundle ID, and the option to reload Dock


A great tool to quickly capture events on a single host is Crescendo. Crescendo is a real-time event viewer for macOS. An excellent feature of Crescendo is that it leverages Apple’s Endpoint Security Framework (ESF). ESF monitors system events for potentially malicious activity and is an API that is part of the broader System Extensions Framework. For those coming from the Windows side, a crude comparison is a limited Event Tracing for Windows (ETW) for macOS.

Using Crescendo, we can easily view file and process events created by the persistence execution.

For the ESF nerds, the following ESF events map to Crescendo:

Note: Although Crescendo does not currently capture events such as ES_EVENT_TYPE_NOTIFY_MMAP, ES_EVENT_TYPE_NOTIFY_WRITE, and
ES_EVENT_TYPE_NOTIFY_EXEC it captures sufficient events for this persistence method. For additional events coverage, I highly recommend using Xorrior’s Appmon.

The following items focus on the persistence method’s execution as the actual malicious application artifacts will vary depending on what the attacker developed.

First, plutil converts the Dock plist to Extensible Markup Language (XML). The XML format is easier to manipulate.

plutil converting current to xml format

Additionally, a process creation is logged for the temp9876 file creation as well.

DockPersist.js creates a randomly named file under /private/tmp/. The script modifies the XML version of the plist and saves it to this random file name. In this instance, temp0wsn4p contains the malicious plist in XML format, so we overwrite this file with the binary format version required for correctly loading in the Dock.

plutil converting the modified plist back to binary format

Next, DockPersist.js removes the existing plist at ~/Library/Preferences/

Deleting the current

ESF captures saving the new malicious plist in binary format to ~/Library/Preferences/

Saving the modified

Lastly, since we specified reloading the Dock in the function call, killall is invoked.

Reloading the Dock

These events serve as a starting point for building detections. The critical binaries utilized are plutil and killall. Additionally, the file creation, deletion, and modification events can be leveraged for detections as well. Furthermore, before the persistence method itself, additional detections can be developed surrounding the various ways of uploading the malicious application to the target.

Normal Execution

The question you may be wondering is now that we know how ESF captures the known malicious behavior, how does ESF represent a normal execution?

As part of normal operations, cfprefsd (Core Foundation Preferences Daemon) will trigger file::rename events (file overwrite) on the These events are also triggered when a user manually makes changes to the Dock through the GUI.

Normal modification of

Evasion Attempts

An adversary could perform the plist modification tasks off-target and upload the modified plist to the dock plist location to reduce the number of potential indicators. However, this will still result in the file::rename event, which won’t be using the cfprefsd process we identified in standard execution. Modifications of the plist outside of the cfprefsd process may be a good starting point for identifying malicious behavior.

Simple overwrite of through Apfell agent

Visual indicators

The PoC application’s execution results in two instances of Safari appearing in the Dock.

Dock with both the malicious Safari app and the valid one

The first Safari is the malicious app, which is in the persistent-apps section of the plist, and the second is the actual Safari, which appears in the recent-apps section of the plist.

Miscellaneous Osascript Indicator

Some interesting items I noticed after digging through ESF logs were writes to a SQLite database. If an adversary is leveraging osascript, it may be beneficial to note that osascript has a cache database at ~/Library/Caches/

Upon inspection of this database using DB Browser for SQLite, I noticed that the cfurl_cache_response table contains our Mythic server’s IP address and a short log of the GET requests used for Command and Control (C2) communications in Mythic. This cache provides a valuable resource for forensic investigation.

SQLite DB in DB Browser showing evidence of C2 comms

The sqlite3 command-line tool can also view these entries.

SQLite DB using sqlite command line tool showing evidence of C2 comms


The purpose of this post was to display a persistence method similar to .LNK files on Windows. More importantly, I hope that the persistence indicators shown can help those developing detections for this technique. If you come across any additional indicators that this persistence method creates that are unmentioned above, please let me know.

References / Resources:

Are You Docking Kidding Me? was originally published in Posts By SpecterOps Team Members on Medium, where people are continuing the conversation by highlighting and responding to this story.

Article Link: