Click File, App Opens
› reversing os x's launch services, to understand 'document handlers'
08/23/2016
On Friday (8/19th), noted OS X malware analyst Thomas Reed (@thomasareed) of MalwareBytes posted a writeup detailing a new piece of OS X malware: 'Mac File Opener.' I uploaded a sample to here (password: infect3d).
As 'Whats Your Sign' shows, the malware is signed with an Apple Developer ID belonging to 'Techyutils Software Private Limited.' More signed OS X malware... yay :/
Packaged in an 'Advanced Mac Cleaner' installer, Thomas noted that this malware is a fairly standard 'run-of-the-mill' adware, save for way it attempts to gain execution:
"Even more intriguing, this app didn't have any apparent mechanism for being launched. It hadn't been added to my login items. There wasn't a new launch agent or daemon designed to load it. It simply seemed to be sitting there, doing nothing."
Digging deeper, he discovered that this malicious application (by means of its Info.plist file) registers itself as the 'document handler' for a myriad of file types. In his words:
"Essentially, what this app had done is set itself up as an app that can open most files that are at all likely to be on the typical user's system. Worse, if there is no other app to open a specific file, this app would be the default. It turns out that this is exactly what that app wants, and it takes full advantage of that fact."
Since this mechanism requires a user launch an application that previously didn't have a default 'document handler' and matches one that 'Mac File Opener' registered for, this is somewhat of a 'unreliable' persistence mechanism. However, the upside to this method is that it will 'bypass' tools such as BlockBlock that monitor for true persistence mechanisms (i.e. ones that require no user interaction, instead are automatically executed each time the system is rebooted or the user logs in).
Regardless I was intrigued by this novel approach, so decided to perform a technical deep dive into the concept "document handlers," how an application can register as a 'document handler' for a file type, and what goes on behind the scenes to make this all work.
This blog is summary of my (hopefully accurate!) investigate findings into all of this. We'll describe
- how an application can register to be a 'document handler'
- how the OS processes such an app, to automatically register it as a handler
- at runtime, how such a handler is resolved, then invoked
[part 0x1] Application: "I'll handle that!"
The concept of 'document handlers' is simple. In short, applications can register with the OS to handle various document or file types. Then, when the user double-clicks such a file to open it, the OS will automatically launch the registered application. This is why opening an HTML file launches the default browser, while double-clicking on a .psd file opens Photoshop.
As Thomas pointed out in his writeup, "One of the things that file Info.plist does is allow the developer to identify what file types the app is capable of opening, using an array of data given the key 'CFBundleDocumentTypes' that defines all the file types."
This is well documented the Apple document "Registering the File Types Your App Supports"
Let's create a simple application to test this, and register the application to handle files of type .bar. The goal is to have the OS automatically launch our application when the user attempts to open a file such as foo.bar.
//app's entry point
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
//dbg msg
NSLog(@"App Launched\n path: %@", [[[NSProcessInfo processInfo] arguments] firstObject]);
return;
}
As described in Apple's documentation, to register as 'handler' for a certain document type, one can simply create a 'Document types' ('CFBundleDocumentTypes') array in the Info.plist file:
This array should contain dictionaries for each document/file type you'd like the application to handle. Each dictionary effectively tells the OS, "aloha OS, if nobody else has registered for this type of file, when the user double-clicks such a file, launch me to handle it" ...or more formally Apple notes:
"CFBundleDocumentTypes (Array - iOS, OS X) contains an array of dictionaries that associate one or more document types with your app. Each dictionary is called a type-definition dictionary and contains keys used to define the document"
Most of the keys in the dictionary are fairly self-explanatory. For example, the 'CFBundleTypeExtensions' key specifies the file type to resister for (e.g. .bar). Some however require more detail. For example, the 'Handler' key:
"Determines how Launch Services ranks this app among the apps that declare themselves editors or viewers of files of this type. The possible values are: Owner (this app is the creator of files of this type), Alternate (this app is a secondary viewer of files of this type), None (this app must never be used to open files of this type, but it accepts drops of files of this type), Default (default; this app doesn't accept drops of files of this type). Launch Services uses the value of LSHandlerRank to determine the app to use to open files of this type. The order of precedence is: Owner, Alternate, None."
However, testing showed that claiming to be the 'Owner' for a common file type (e.g. .png), would not usurp the existing, registered handler (on my box, Preview.app).
Once the test application is compiled, if then we create a file of type .bar and attempt to open said file, the application is automatically opened! Success :)
$ touch ~/Desktop/foo.bar
Since we didn't have to manually tell the OS what file type(s) our application could handle (e.g. .bar files), obviously there is a lot going on under the hood! Time to get down and dirty :)
[part 0x2] OS: "I'll automatically register your 'document handlers'"
Let's try to figure out how the OS automatically registers an application as a document handler. We'll use the aforementioned 'Mac File Opener' malware (mahalo for sharing Thomas!), as its (ab)uses this technique in an attempt to gain execution.
We know that an application specifies what document types it supports via its Info.plist file. Thus is seemed reasonable to watch file I/O events to see who, or what processed this file once the malware appeared. The assumption here was that some system component would automatically parse the malware's Info.plist file, and then register it for the 200+ document types it specified.
# fs_usage -w -f filesystem | grep Info.plist
open /Users/user/Desktop/Mac File Opener.app/Contents/Info.plist lsd.16457
fstat64 F=4 lsd.16457
read F=4 B=0x18a97 lsd.16457
As the following shows, once the malware was downloaded, a process named 'lsd' opened and read (i.e. parsed) its Info.plist file.
As described in Apple's "Launch Services Keys" document this OS component is part of Launch Services and "provides support for launching apps and matching document types to apps" bingo!
Looking at a process listing, there are two instance of the lsd process:
$ ps aux | grep -i lsd
patrickw 281 ... /usr/libexec/lsd
root 202 ... /usr/libexec/lsd runAsRoot
The system (root) instance is started via a launch daemon (/System/Library/LaunchDaemons/com.apple.lsd.plist), while the user instance is started via a launch agent (/System/Library/LaunchAgents/com.apple.lsd.plist). Both they register various Mach services (e.g. 'com.apple.lsd.open') with the Mach bootstrap sub-system:
$ less /System/Library/LaunchAgents/com.apple.lsd.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.apple.lsd</string>
<key>MachServices</key>
<dict>
<key>com.apple.lsd.advertisingidentifiers</key>
<true/>
<key>com.apple.lsd.icons</key>
<true/>
<key>com.apple.lsd.mapdb</key>
<true/>
<key>com.apple.lsd.modifydb</key>
<true/>
<key>com.apple.lsd.open</key>
<true/>
<key>com.apple.lsd.openurl</key>
<true/>
<key>com.apple.lsd.plugin</key>
<true/>
<key>com.apple.lsd.xpc</key>
<true/>
</dict>
...
</dict>
</plist>
Disassembling lsd shows that all logic appears to be implemented in the CoreServices framework:
//_LSServerMain (IDA) disassembly
__LSServerMain proc near
jmp cs:__imp___LSServerMain
__LSServerMain endp
//imports
__LSServerMain /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices
However examining this framework reveals that the LS* related APIs are not implemented in CoreServices, but rather in LaunchServices framework.
$ otool -l /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices
Load command 18
cmd LC_REEXPORT_DYLIB
name /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/ LaunchServices.framework/Versions/A/LaunchServices
...
Remember, we're looking for logic that registers the malware and its 'document handlers' with the system. Thus it seems reasonable to start by examining functions or methods containing *register* or *registration*... such as _LSCreateRegistrationData and registerBundleRecord.
Attaching to the user instance of lsd with lldb and setting a breakpoint on the _LSCreateRegistrationData method proved promising. Once this breakpoint was set, opening any file that the malware contained a 'document handler' entry for (in its Info.plist), caused the debugger to break:
* thread #5: LaunchServices_LSCreateRegistrationData
stop reason = breakpoint 1.1
frame 0: LaunchServices_LSCreateRegistrationData
frame 1: LaunchServices-[LSDXPCServer registerItemInfo:withConnection:Action:]
frame 2: LaunchServices-[LSServerDelegate dispatchMessage:withConnection:]
frame 3: libdispatch.dylib_dispatch_call_block_and_release
Looking at the stack trace, it was easy to see that lsd had received an XPC message to 'process' the malware by parsing its Info.plist file and register its 'document handlers.' This was confirmed by examining the values in various registers, which show the path to the malicious application:
[lldb] po $rdx
file:///.file/id=6571367.3096512/
[lldb] po NSStringFromClass((id)[$rdx class])
NSURL
[lldb] po [$rdx fileSystemRepresentation]
0x00007fe661840400
x/s 0x00007fe661862c00
0x7fe661862c00: "/Users/user/Desktop/Mac File Opener.app"
Stepping thru the code in the _LSCreateRegistrationData method, showed it invoking a method named _LSCopyBundleInfoDictionary (note the error logic that if a this method fails, "Missing property list"):
__LSCopyBundleInfoDictionary proc near
mov esi, 1
mov rdi, r13
mov qword ptr [rbp+var_F8], r13
call __LSCopyBundleInfoDictionary
mov r14, rax
test r14, r14
jnz short loc_B2175
lea rax, _gLogRegistrationErrors
cmp byte ptr [rax], 0
jz short loc_B213D
lea rsi, cfstr_MissingPropert ; "Missing property list"
mov rdi, r15
call __LSRegistrationWarning
As its name suggests, the _LSCopyBundleInfoDictionary method copies (or extracts) an application's Info.plist, including the 'document handlers' ('CFBundleDocumentTypes'):
[lldb] po $rax
{
...
CFBundleDevelopmentRegion = en;
CFBundleDocumentTypes = (
{
CFBundleTypeExtensions = (
7z
);
CFBundleTypeName = DocumentType;
CFBundleTypeRole = Viewer;
LSHandlerRank = Alternate;
NSDocumentClass = Document;
},
....
Continuing on, the LSBundleRecordBuilder::registerBundleRecord method is called:
[lldb] bt
* thread #8: LaunchServices`LSBundleRecordBuilder::registerBundleRecord
stop reason = instruction step over
frame 0: LaunchServices LSBundleRecordBuilder::registerBundleRecord(LSDatabase*, ...)
frame 1: LaunchServices _LSServerItemInfoRegistration
frame 2: LaunchServices _LSServer_RegisterItemInfo
frame 3: LaunchServices -[LSDXPCServer registerItemWithRegistrationInfo:alias:
diskImageAlias:containerAlias:plist:installationDict:extraItems:withReply:]
frame 4: LaunchServices -[LSDXPCServer registerItemInfo:withConnection:Action:]_block_invoke
Examining register values, such as $RSI reveal a variable, of type 'LSDatabase' that contains a path to what appears to be a persistent database (more on this in a minute):
[lldb] po $rsi
<LSDatabase 0x7fe66183e600>{ path = '/var/folders/np/85lyz_4545d5lz8wvy04xvlm0000gn/0//com.apple.LaunchServices-134501.csstore' }
The LSBundleRecordBuilder::registerBundleRecord method then invokes the _LSBundleAdd method which appears to extract various information from the malicious application bundle, such as its bundle id, display name, and path to its executable:
[lldb] po $rsi
com.pcvark.Mac-File-Opener
...
[lldb] po $rsi
Mac File Opener
...
[lldb] po $rsi
Contents/MacOS/Mac File Opener
Following this, the LSBundleRecordBuilder::registerBundleRecord method invokes a method named _LSRegisterDocumentTypes. Not hard to guess what this method does! In short it appears to iterate over each item in the malware's Info.plist 'CFBundleDocumentTypes' array (extracted via the LSCopyBundleInfoDictionary method) and registers it with the 'LSDatabase'.
[lldb] po $rdi
{
...
CFBundleDevelopmentRegion = en;
CFBundleDocumentTypes = (
{
CFBundleTypeExtensions = (
7z
);
CFBundleTypeName = DocumentType;
CFBundleTypeRole = Viewer;
LSHandlerRank = Alternate;
NSDocumentClass = Document;
},
....
Specifically it appears that helper functions (such as _LSBindingListCreate() _LSClaimAdd() _LSBindingListActivate()) are called by the registerBundleRecord method to interface with the database in order to insert pertinent information about the malicious application and its 'document handlers':
LSBundleRecordBuilder::registerBundleRecord(LSDatabase *, int *, unsigned char *) proc near
lea rsi, [rbp+var_1030]
lea rdx, [rbp+var_1064]
call __LSBindingListCreate
...
lea rsi, [rbp+var_1060]
lea rdx, [rbp+var_1034]
call __LSClaimAdd
...
mov rdi, rbx
call __LSBindingListActivate
It this point I felt like I had decent understanding of how an application and its 'document handlers' are registered automatically with the OS. To summarize:
- An application (malware) is saved to the file system
- This triggers an XPC message sent to the launch services daemon; lsd
- The lsd process parses the application, extracting then adding information about the application and its 'document handlers' to a persistent database.
As was previously shown, debugging lsd revealed the path (on my VM) to a persistent database:
/var/folders/np/85lyz_4545d5lz8wvy04xvlm0000gn/0//com.apple.LaunchServices-134501.csstore
Turns out this 'launch services database' can easily be parsed via the lsregister tool (found in /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/). When invoked with the -dump flag, the tool will kindly dump the database, displaying all applications that specify 'document handlers', which were automatically registered (by lsd). For example, we can see the malicious application, 'Mac File Opener' is present, along with the documents (file types) it registered for (e.g. .7z, etc):
$ lsregister -dump
...
Container mount state: mounted
bundle id: 2592
Mach-O UUIDs: 88225C07-0FDC-3875-A3B4-C5328E509B9E, 20A99135-975D-3A7B-A8DD-B7DF2CE428D0
path: /Users/user/Desktop/Mac File Opener.app
name: Mac File Opener
identifier: com.pcvark.Mac-File-Opener (0x80025f61)
executable: Contents/MacOS/Mac File Opener
--------------------------------------------------------
claim id: 31508
name: DocumentType
rank: Alternate
roles: Viewer
flags: doc-type
bindings: .7z
...
Now let's look at how this information is used at runtime. That is to say, when a user double-clicks a file, how does the system interface with the launch services database to extract and launch the correct (if any) handler application.
[part 0x3] OS: "I'll launch the 'correct' application to handle that!"
During some previous research (which resulted in a few Gatekeeper bypasses!) I briefly dove into how OS X launches an application:
As the following image shows, when a user double-clicks a file or application, various methods within the launch services framework are invoked.
Attaching to Finder.app and setting breakpoints on these functions proved to be quite insightful - starting with the LaunchServices LSOpenURLsWithRole method.
Setting a breakpoint on the LaunchServices LSOpenURLsWithRole method shows that this method is invoked with the path of the item the user is attempting to open. Here; a .7z file (recall the 'Mac File Opener' advertised a 'document handler' for this document type):
[lldb] po $rdi
<__NSArrayM 0x7fd3b3f02d40>(
file:///Users/user/Desktop/out.7z
)
Stepping thru the various LSOpen*/LSLaunch* methods eventually call into the _LSBundleCopyOrCheckNode method:
[lldb] bt
frame 0: LaunchServices _LSBundleCopyOrCheckNode
frame 1: LaunchServices _LSEvaluateClaimArray(LSBindingState*, ...)
frame 2: LaunchServices _LSGetBinding(LSBindingState*)
frame 3: LaunchServices _LSGetBindingStateForNode(LSBindingState*, FSNode*)
frame 4: LaunchServices _LSGetBindingForNode
frame 5: LaunchServices _LSOpenStuffCallLocal
frame 6: LaunchServices _LSOpenStuff
This method, by means of a call to the _LSBundleCopyOrCheckNode_block_invoke block, appears to look up what application (i.e. the malware) that was previously registered as the 'document' handler for the file the user is opening:
[lldb] b ___LSBundleCopyOrCheckNode_block_invoke
[lldb] x/gx $rdx
0x70000007f1f8: 0x0000000000000000
call _CFDictionaryGetValueIfPresent
[lldb] x/gx $rdx
0x700000115c48: 0x00007fd3b4a9c520
[lldb] po 0x00007fd3b4a9c520
<FSNode 0x7fd3b4a9c520> { flags = 0x00000020, path = '/Users/user/Desktop/Mac File Opener.app' }
Once launch services has resolved which (if any) application that was registered as the 'document handler' in the launch services database, it launches this application via a call to the spawn_via_launchd() function:
[lldb] bt
* thread #14: libxpc.dylib`_spawn_via_launchd
stop reason = breakpoint 11.1
frame 0: libxpc.dylib`_spawn_via_launchd
frame 1: LaunchServices`LaunchApplicationWithSpawnViaLaunchD(LSSessionID, ...)
...
frame 4: LaunchServices`_LSOpenItemsWithHandler_CFDictionaryApplier(void const*, ...)
...
frame 9: LaunchServices`_LSOpenStuff + 126
The function definition for spawn_via_launchd() can be found online
#define spawn_via_launchd(a, b, c) _spawn_via_launchd(a, b, c, 2)
pid_t _spawn_via_launchd(
const char *label,
const char *const *argv,
const struct spawn_via_launchd_attr *spawn_attrs,
int struct_version);
Dumping its arguments confirms that yes, the system has indeed resolved the malware as the 'document handler' for the .7z file the user was attempting to open.
[lldb] x/s $rdi
0x700000115680: "[0x0-0x3f03f].com.pcvark.Mac-File-Opener"
[lldb] p *(char**)$rsi
(char *) $12 = 0x00007fd3b3e35280 "/Users/user/Desktop/Mac File Opener.app/Contents/MacOS/Mac File Opener"]
In other words, opening any .7z file (or any other file the malware is a 'document handler' for), will cause the system to automatically execute the malware. And know you know exactly how :)
Conclusion
The most interesting aspect of the 'Mac File Opener' malware is certainly the way it attempts to be launched. By acting as a 'document handler' for over 200 file types - whenever a user launches one such file (assuming that no other application is already registered as the 'document handler'), the malware will be automatically executed by the OS. In this blog, we dug into how this registration process automatically occurs, and how, at runtime the system resolves the correct application to launch.
I'm on the fence whether KnockKnock should display all registered document handlers (currently it mostly just shows 'pure' persistence mechanisms - that is, ones that don't require user interactions). Thoughts?