Hiding Compiled AppleScripts
Following a recent blog post covering the increasingly common use of compiled AppleScripts in malware, I wanted to explore methods to further hide malicious scripts and reduce the chance of detection.
Resource Forks⌗
Typically when compiling AppleScript or JXA with osacompile the resulting compiled script is output to an .scpt file, which can then be executed with osascript or opened in the Script Editor.app by double clicking. Adding the -x argument to osacompile results in an “execute-only” script, which cannot be edited in the Script Editor and can make for a more painful experience when trying to reverse-engineer the payload.
Looking at the man page for osacompile we also see a -r flag.
-r type:id Place the resulting script in the resource fork of the output file, in the specified resource.
Scripts compiled using this flag are injected into the com.apple.ResourceFork extended attribute (aka the resource fork) of the file supplied via the -o argument. This stores the compiled script in the extended attributes of an arbitrary file and allows the file to function as normal while still enabling the script to be executed with osascript.
Inspecting the contents of the file using cat, strings, or xxd will only show the contents of the target file and not data stored in the extended attributes, making it possible to hide a script in a benign looking file while also evading common methods of file inspection. Additionally, due to the compiled script being a binary format, using xattr -p com.apple.ResourceFork to print the contents of the extended attribute will yield nothing (you also need to include the -x argument).
Hashing the file does not include the extended attributes and so submitting the hash, or the file itself, to a sandbox such as Virus Total will only yield results for the benign file and not the embedded script.
Extended attributes are a feature of the Apple File System, and as such copying the file to another file system will not persist any extended attributes unless the file is first archived as a .zip, .dmg, or any other archive format where extended attributes are retained.
While not directly related to AppleScripts, SentinelOne touched on the subject of malware hiding in resource forks, though this technique differs slightly in that the Mach-O payload was extracted from the resource fork and written to disk.
Hidden Extensions⌗
As pointed out by @pberba, it is not uncommon for AppleScript payloads to use a combination of multiple extensions and character encoding in an attempt to appear benign and fit a given pretext, e.g. file.pdf.scpt. However, .scpt extensions are shown by default, though this can be overridden by setting the correct bits in the com.apple.FinderInfo extended attribute of the file. This makes it possible to toggle extension hiding on a per-file level, visually turning file.pdf.scpt into file.pdf.
An notable caveat is that this only works if Finder is not configured to show all file extensions (the default). This can be checked by inspecting the value of defaults read NSGlobalDomain AppleShowAllExtensions which defaults to 0.
The Eclectic Light Company has an excellent post covering this attribute in more detail.
Detection⌗
You may be wondering how you can detect all of this - fortunately Apple has us covered here with the ES_EVENT_TYPE_NOTIFY_SETEXTATTR event which is triggered when extended attributes are written. This includes when a file is unpacked from an archive such as a .zip. By monitoring for events where the extattr target is com.apple.ResourceFork we can identify instances where files containing data within a resource fork are being written to disk as seen in the below snippet.
"es_event_type" : "ES_EVENT_TYPE_NOTIFY_SETEXTATTR",
"event" : {
"setextattr" : {
"extattr" : "com.apple.ResourceFork",
"target" : {
"name" : "sample.pdf",
"path" : "/private/var/folders/sw/hx25g2l523s3lkj5phf691b00000gn/T/com.apple.desktopservices.ArchiveService/TemporaryItems/NSIRD_ArchiveService_EQNF8a/sample.pdf",
"path_truncated" : false,
"stat" : {
"id" : "993949F1-7E0D-45BA-AD9B-0DBBE92FB9C6",
"st_atimespec" : "2025-11-13T09:59:24.000000000Z",
"st_birthtimespec" : "2025-11-13T09:54:03.000000000Z",
"st_blksize" : 4096,
"st_blocks" : 40,
"st_ctimespec" : "2025-11-13T23:15:23.612641832Z",
"st_dev" : 16777229,
"st_flags" : 0,
"st_gen" : 0,
"st_gid" : 20,
"st_ino" : 520180,
"st_mode" : 33188,
"st_mtimespec" : "2025-11-13T09:54:03.000000000Z",
"st_nlink" : 1,
"st_rdev" : 0,
"st_size" : 18810,
"st_uid" : 501
}
}
}
},
"executable" : {
"name" : "ArchiveService",
"path" : "/System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/XPCServices/ArchiveService.xpc/Contents/MacOS/ArchiveService",
"path_truncated" : false,
"stat" : {
"id" : "28092F8E-6105-424D-A58A-C9ACF84492BC",
"st_atimespec" : "2025-09-09T07:15:49.000000000Z",
"st_birthtimespec" : "2025-09-09T07:15:49.000000000Z",
"st_blksize" : 4096,
"st_blocks" : 480,
"st_ctimespec" : "2025-09-09T07:15:49.000000000Z",
"st_dev" : 16777229,
"st_flags" : 524320,
"st_gen" : 0,
"st_gid" : 0,
"st_ino" : 1152921500312276159,
"st_mode" : 33261,
"st_mtimespec" : "2025-09-09T07:15:49.000000000Z",
"st_nlink" : 1,
"st_rdev" : 0,
"st_size" : 589088,
"st_uid" : 0
}
}
Note that several processes do legitimately write to the com.apple.ResourceFork extended attribute, and so simply catching all instances this event will likely result in a large number of false positives. Files written with data in com.apple.FinderInfo should be detectable in a similar manner.
Another detection avenue exists in the ES_EVENT_TYPE_NOTIFY_OPEN events which clearly show events targeting <file path>/..namedfork/rsrc. While it isn’t uncommon for this event to occur, combining this with the executable path /usr/bin/osascript should provide a strong indicator for AppleScripts being executed from within the resource fork of a file.
"es_event_type" : "ES_EVENT_TYPE_NOTIFY_OPEN",
"event" : {
"open" : {
"fflag" : 1,
"file" : {
"name" : "rsrc",
"path" : "/Users/admin/Downloads/sample.pdf/..namedfork/rsrc",
"path_truncated" : false,
"stat" : {
"id" : "D05EDA35-AAE9-4150-B76E-EBF8CC338E24",
"st_atimespec" : "2025-11-17T00:29:35.523013691Z",
"st_birthtimespec" : "2025-11-12T03:40:29.920987000Z",
"st_blksize" : 4096,
"st_blocks" : 8,
"st_ctimespec" : "2025-11-14T06:26:46.744530225Z",
"st_dev" : 16777226,
"st_flags" : 0,
"st_gen" : 0,
"st_gid" : 20,
"st_ino" : 490525,
"st_mode" : 33188,
"st_mtimespec" : "2025-11-14T06:26:46.744302234Z",
"st_nlink" : 1,
"st_rdev" : 0,
"st_size" : 940,
"st_uid" : 501
}
}
}
},
"executable" : {
"name" : "osascript",
"path" : "/usr/bin/osascript",
"path_truncated" : false,
"stat" : {
"id" : "633A2ED5-518F-49F3-AFDA-27E9E1415E6D",
"st_atimespec" : "2025-09-09T07:15:49.000000000Z",
"st_birthtimespec" : "2025-09-09T07:15:49.000000000Z",
"st_blksize" : 4096,
"st_blocks" : 72,
"st_ctimespec" : "2025-09-09T07:15:49.000000000Z",
"st_dev" : 16777226,
"st_flags" : 524320,
"st_gen" : 0,
"st_gid" : 0,
"st_ino" : 1152921500312555223,
"st_mode" : 33261,
"st_mtimespec" : "2025-09-09T07:15:49.000000000Z",
"st_nlink" : 1,
"st_rdev" : 0,
"st_size" : 175744,
"st_uid" : 0
}
},
Why Use This Technique?⌗
Although it can be detected, few macOS EDR solutions currently capture ESF events related to extended attributes and file reads. That makes this approach a relatively stealthy way to drop payloads without triggering signature-based detections and further complicates incident response. It is also an effective vector for placing second-stage payloads, such as apfell, on disk while remaining undetected.