Saturday, October 26, 2013

OS X XProtect

XProtect is Apple’s built-in AV system for OS X. I took at look at it on 10.8 (Mountain Lion). Virus definitions are stored in a simple format here:
/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/XProtect.plist
This is part of the definition for a variant of MacDefender:
           
                <key>Description</key>
                <string>OSX.MacDefender.B</string>
                <key>LaunchServices</key>
                <dict>
                        <key>LSItemContentType</key>
                        <string>com.apple.installer-package</string>
                </dict>
                <key>Matches</key>
                <array>
                        <dict>
                                <key>MatchFile</key>
                                <dict>
                                        <key>NSURLNameKey</key>
                                        <string>Info.plist</string>
                                </dict>
                                <key>MatchType</key>
                                <string>Match</string>
                                <key>Pattern</key>
                                <string>4946506B67466C6167417574686F72697A6174696F6E416374696F6E3C2F6B65793E0A093C737472696E673E4E6F417574686F72697A6174696F6E3C2F737472696E673E0A093C6B65793E4946506B67466C616744656661756C744C6F636174696F6E3C2F6B65793E0A093C737472696E673E2F4170706C69636174696F6E733C2F737472696E673E0A09</string>
                        </dict>
It contains a simple cleartext signature for a binary string in the Info.plist inside the MacDefender installer:
In [1]: a="4946506B67466C6167417574686F72697A6174696F6E416374696F6E3C2F6B65793E0A093C737472696E673E4E6F417574686F72697A6174696F6E3C2F737472696E673E0A093C6B65793E4946506B67466C616744656661756C744C6F636174696F6E3C2F6B65793E0A093C737472696E673E2F4170706C69636174696F6E733C2F737472696E673E0A09"

In [2]: a.decode('hex')
Out[2]:'IFPkgFlagAuthorizationAction\n\t<string>NoAuthorization</string>\n\t<key>IFPkgFlagDefaultLocation</key>\n\t<string>/Applications</string>\n\t'
The XProtect updater (/usr/libexec/XProtectUpdater) runs as a launch daemon (/System/Library/LaunchDaemons/com.apple.xprotectupdater.plist), set to run every 24 hours and at load time by default. XProtectUpdater works by downloading a signed version of the plist from apple.

It (I assume) verifies the signature, and checks the version number in the downloaded version:
 <key>meta</key>
 <dict>
  <key>Version</key>
  <integer>2024</integer>
  <key>PlugInBlacklist</key>
  <dict>
   <key>10</key>
   <dict>
    <key>com.macromedia.Flash Player.plugin</key>
    <dict>
     <key>MinimumPlugInBundleVersion</key>
     <string>11.3.300.271</string>
    </dict>
    <key>com.oracle.java.JavaAppletPlugin</key>
    <dict>
     <key>BlockedPlugInBundleVersions</key>
     <array>
      <string>1.7.06.24</string>
     </array>
    </dict>
   </dict>
  </dict>
 </dict>
against the one recorded in:
/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/XProtect.meta.plist
and updates the metadata and signature plists if the version on disk is older. An sqlite database seems to be used as part of the update process:
/var/root/Library/Caches/XProtectUpdater/Cache.db

Observing detection/cleaning behaviour


When I first started looking at XProtect Apple didn't have signatures for eicar, so seeing it in action was a little tricky, or required real malware. Now they do and it looks like this:
        <dict>
<key>Description</key>
<string>OSX.eicar.com.i</string>
<key>Matches</key>
<array>
<dict>
<key>Identity</key>
<data>
M5WFbOgfK3OC3ucmAveYtkLxQUA=
</data>
<key>MatchFile</key>
<dict>
<key>NSURLNameKey</key>
<string>eicar.com</string>
</dict>
<key>MatchType</key>
<string>Match</string>
</dict>
</array>
</dict>
Breaking that down:
  • Description will show up in the "will damage your computer" warning box as seen below.
  • Matches all criteria must match for alert to be shown
  • Identity a base64 encoded sha1 hash.
  • NSURLNameKey filename
  • MatchType whether to match the filetype. Values are MatchAny, or Match. In this case it relies purely on the extension part of the filename specified in NSURNNameKey (so eicar.com will match, but downloading the same file as eicar.txt won't, and downloading eicar.com and renaming it eicar.txt also will avoid the warning). This makes sense since com has no header and eicar is just a test AV file.
Here's a python snippet to show the identity string is the sha1 hash:
In [12]: identity = "M5WFbOgfK3OC3ucmAveYtkLxQUA="

In [13]: identity.decode("base64").encode("hex")
Out[13]: '3395856ce81f2b7382dee72602f798b642f14140'

In [14]: import hashlib

In [15]: hashlib.sha1(open("eicar.com").read()).hexdigest()
Out[15]: '3395856ce81f2b7382dee72602f798b642f14140'
The signature format also supports various other NSURL filesystem resource keys as seen in this sig:
                                <key>MatchFile</key>
<dict>
<key>NSURLFileSizeKey</key>
<integer>50000</integer>
<key>NSURLNameKey</key>
<string>FileAgent</string>
<key>NSURLTypeIdentifierKey</key>
<string>public.unix-executable</string>
</dict>
<key>MatchType</key>
<string>Match</string>
Lets look at a full signature, OpinionSpy is the simplest, here it is in full:
        <dict>
                <key>Description</key>
                <string>OSX.OpinionSpy</string>
                <key>LaunchServices</key>
                <dict>
                        <key>LSItemContentType</key>
                        <string>{(com.sun.java-archive|com.apple.application-bundle)}</string>
                </dict>
                <key>Matches</key>
                <array>
                        <dict>
                                <key>MatchFile</key>
                                <dict>
                                        <key>NSURLTypeIdentifierKey</key>
                                        <string>com.sun.java-archive</string>
                                </dict>
                                <key>MatchType</key>
                                <string>Match</string>
                                <key>Pattern</key>
                                <string>504B010214000A0000000800547D8B3B9B0231BC{4}502D0700250000000000{12}636F6D2F697A666F7267652F697A7061636B2F70616E656C732F706F696E7374616C6C6572</string>
                        </dict>
                </array>
        </dict>
Note that type match is specified by UTI, and we have a pattern match rather than just a hash match. Replacing {4} and {12} with 4 and 12 bytes of stuff (I chose A's), here is a one-liner that generates a file that matches the signature:
$ python -c "open('eicar.jar','w').write('504B010214000A0000000800547D8B3B9B0231BCAAAAAAAA502D0700250000000000AAAAAAAAAAAAAAAAAAAAAAAA636F6D2F697A666F7267652F697A7061636B2F70616E656C732F706F696E7374616C6C6572'.decode('hex'))"
Which gives you part of a jar file (this signature works against the jar archive itself, not the contents):
$ hexdump -C eicar.jar 
00000000  50 4b 01 02 14 00 0a 00  00 00 08 00 54 7d 8b 3b  |PK..........T}.;|
00000010  9b 02 31 bc aa aa aa aa  50 2d 07 00 25 00 00 00  |..1.....P-..%...|
00000020  00 00 aa aa aa aa aa aa  aa aa aa aa aa aa 63 6f  |..............co|
00000030  6d 2f 69 7a 66 6f 72 67  65 2f 69 7a 70 61 63 6b  |m/izforge/izpack|
00000040  2f 70 61 6e 65 6c 73 2f  70 6f 69 6e 73 74 61 6c  |/panels/poinstal|
00000050  6c 65 72                                          |ler|
00000053
If you double-click the file, you'll see something like this since the jar doesn't actually work:



XProtect doesn't detect it since the quarantine bit isn't present. Upload it somewhere and download it with a browser to get the quarantine bit applied. Double-click and you will see this warning:


If you leave 'report malware' ticked, regardless of whether you click OK or Cancel, the malware report is sent to Apple and you get a nice malware report in:
/Users/me/Library/Logs/DiagnosticReports/XProtect_2012-09-26-102829_my-MacBook-Pro.diag
which looks like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
        <dict>
                <key>LSQuarantineAgentBundleIdentifier</key>
                <string>com.google.Chrome</string>
                <key>LSQuarantineDataURL</key>
                <string>https://some.download.url</string>
                <key>LSQuarantineOriginURL</key>
                <string>https://some.referrer/url</string>
                <key>LSQuarantineTimeStamp</key>
                <date>2012-09-26T17:09:40Z</date>
                <key>UserAction</key>
                <string>trash</string>
                <key>XProtectSignatureName</key>
                <string>OSX.OpinionSpy</string>
        </dict>
</array>
</plist>
and similar information is written to system.log:
Sep 26 10:28:29 my-MacBook-Pro.local CoreServicesUIAgent[4961]: Adding LSProperties {
            LSQuarantineAgentBundleIdentifier = "com.google.Chrome";
            LSQuarantineAgentName = "Google Chrome";            
            LSQuarantineDataURL = "https://some.download.url";
            LSQuarantineEventIdentifier = "EEEEEEEE-DEAD-DEAD-DEAD-BEEFBEEFBEEF";
            LSQuarantineIsOwnedByCurrentUser = 1;
            LSQuarantineOriginURL = "https://some.referrer/url";
            LSQuarantineTimeStamp = "2012-09-26 17:09:40 +0000";
            LSQuarantineType = LSQuarantineTypeWebDownload;
        } to malware report for file://localhost/Users/me/Downloads/eicar.jar
Sep 26 10:28:29 my-MacBook-Pro.local CoreServicesUIAgent[4961]: Saved diag report for XProtect version ??? to /Users/me/Library/Logs/DiagnosticReports/XProtect_2012-09-26-102829_my-MacBook-Pro.diag
Sep 26 10:28:29 my-MacBook-Pro.local CoreServicesUIAgent[4961]: Successfully submitted CR malware report
Sep 26 10:28:29 my-MacBook-Pro com.apple.launchd.peruser.501[126] ([0x0-0x182182].com.apple.JarLauncher[4960]): Exited: Killed: 9
If you untick 'report malware', no report is written and nothing about the malware is logged, which is terrible for sysadmins who want to know what malware has been found on their machines :(

If you try running the same file (with quarantine) from the commandline, XProtect won't fire because it only applies to files launched through LaunchServices, similar to GateKeeper.
$ java -jar eicar.jar 
Invalid or corrupt jarfile eicar.jar

Modifying signatures


Modifying signatures is as simple as changing the plist:
/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/XProtect.plist
I tested by downloading eicar as above, checking it was detected, then modifying the signature in the XProtect.plist and re-launching: it wasn't detected. When I put the original signature back, it was detected once again.

I didn't test adding signatures, but since the plist seems to be unprotected from modification (i.e. the signature checked at download time isn't stored and checked when it is read from disk), and changes take effect immediately, it should be as simple as modifying the plist. After testing with XprotectUpdater, local modifications seem to persist as long as the version number is higher in the local version.

Summary


  • Signatures are in the clear. An attacker who has been signatured doesn't need to spend any time figuring out what is signatured and testing modifications, they can just release a new version that evades the signature. Not a big deal since this is usually fairly easy without access to the actual signature, and since XProtect only has a small number of infrequently updated signatures the attacker will likely have to adapt to beat 3rd-party AV long before this anyway.
  • Only covers files with the quarantine bit set that are run via LaunchServices. Malware delivered by binaries that haven't opted-in with LSFileQuarantineEnabled won't be detected, and malware that writes files outside the normal APIs (e.g. with a Java browser plugin exploit) probably also won't be detected. Execution via the commandine or POSIX APIs also won't be blocked. Apple is really only targeting the download and double click malware that has been common to-date.
  • XProtect relies on unsigned plists for storing signatures and for storing XProtectUpdater version information that controls updates.  However, while most mac users continue to run as admin malware doesn't need to target these, it can overwrite the updater itself: one variant of Flashback already did this, which is presumably why the MRT was written and distributed via software update.

Update for Mavericks (10.9)


Some things have changed in 10.9. XProtectUpdater seems to have been removed or subsumed into another part of the OS, possibly part of the regular software update. This seems like a good move considering the comment above about malware attacking it. If I find out more I'll write another post.

No comments: