AirDrop Forensics 2

✨ Welcome in AirDrop Round 2! ✨

After I published the AirDrop Forensics post my dear friend Mikey got in touch to let me know about other ways of obtaining AirDrop artefacts. Those are looking into the quarantine database and the com.apple.quarantine extended attribute attached to downloaded files, including those transferred using AirDrop. As it turned out, it is possible to link entries in that  database to actual files in the file system by the value of a specific extended attribute of AirDropped and downloaded files. To make that process easier, I’ve written a Python tool – more on later though, let’s start from the beginning.

Extended attributes

What are extended attributes? They are key value pairs holding additional metadata about a file. One of the extended attributes is com.apple.quarantine, which is added to files downloaded from the Internet or AirDropped (coming from a foreign source) for enhanced security. This is the attribute holding flags which may lead to you seeing a popup like so:

Screenshot 2020-06-27 at 21.53.49

Extended attributes are not exclusive to macOS, other operating systems have them too, and – as you can imagine – their functionality is not limited to detecting downloaded and AirDropped files. I, however, am going to focus solely on the com.apple.quarantine attribute, but for a full overview please head on over to Mikey’s write up and Tuxera’s post as those are brilliant resources on the topic. I’d actually recommend you read Mikey’s post first before diving into this; I’m only glossing over things he explains in detail.

Let’s dive right in with a TL;DR:

Extended attributes can be viewed, edited and deleted using the xattr command. It’s easy to spot files which contain extended attributes by running ls -la:

Kingas-MacBook-Pro:files kingakieczkowska$ ls -la
total 10992
drwxr-xr-x    6 kingakieczkowska  staff           192 14 Jun 18:12 .
drwx------+ 142 kingakieczkowska  staff          4544 16 Jun 16:17 ..
-rw-r--r--@   1 kingakieczkowska  staff        328217 12 May 11:02 BIAŁKA.pptx
-rw-r--r--@   1 kingakieczkowska  staff       1033732  9 May 23:58 Collage_Fotor1.jpg
-rw-r--r--@   1 kingakieczkowska  access_bpf  2298741 24 May 18:27 IMG_0631.JPG
-rw-r--r--@   1 kingakieczkowska  access_bpf  1954141 27 May 16:37 IMG_6944.jpg

Every file with an @ at the end of the permissions will have extended attributes. We can check them by running the xattr command against any given file (full demo in Mikey’s post). We’re interested in what the com.apple.quarantine attribute looks like, so let’s view it for IMG_0631.JPG. We can do that by adding a -p (print) flag to the xattr command and specifying the attribute to be printed like so:

Kingas-MacBook-Pro:files kingakieczkowska$ xattr -p com.apple.quarantine IMG_0631.JPG 
0081;5ecaae8a;sharingd;29B870EA-BB4A-41EB-B014-F722B33D1C26

The output we get can be decoded as follows:

flags;downloaded / received timestamp in hex;agent;unique file identifier

The flags in the beginning are Gatekeeper scores (via here and here), determining what kind of security checks must be performed when the user tries to open the file. The timestamp in hex is pretty self explanatory, although it might be confusing how to turn it into a human-readable format – for this I recommend using an online tool such as this one, and if you’re interested in a programmatic solution bear with me to see in my script how it can be done in Python. The next value is the agent from which the file originates; that’s sharingd for AirDrop and for browsers it usually uses their names like so:

Kingas-MacBook-Pro:Downloads kingakieczkowska$ xattr -p com.apple.quarantine Spotify-0.8.5.dmg 
0081;5ef7b1c6;Chrome;47E57525-8A80-43BC-8193-DB61878C1C75

Kingas-MacBook-Pro:Downloads kingakieczkowska$ xattr -p com.apple.quarantine braveimg.jpg 
0081;5ef780e5;Brave;35CBF646-4602-4789-95F9-487DE097BE69

The unique file identifier happens to also be logged in the quarantine database in the LSQuarantineEventIdenfier​ field and is the value which will allow us to link the database entry to a specific file.

Quarantine database

The database is located in ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2. It only contains one table, LSQuarantineEvents, which consists of the following fields:

Screenshot 2020-06-18 at 21.21.07

From the perspective of investigating AirDrop transfers, we are interested in the following fields:

    • LSQuarantineEventIdentifier – which is equivalent to the value revealed by the xattr command
    • LSQuarantineTimeStamp – the date the file was received / downloaded
    • LSQuarantineSenderName – the name associated with the Apple ID of the sending device
    • LSQuarantineAgentName – which we need to filter out AirDropped files from other quarantined files; if a file has been AirDropped, the agent name will be sharingd

If we’re interested in investigating browser downloads, the below fields come in handy as well:

    • LSQuarantineOriginURLString– the website containing the link to the downloaded file
    • LSQuarantineDataURLString– the actual link where the file resides
    • LSQuarantineAgentName – if a file has been downloaded using a browser, usually the browsers name will be displayed here.

The differences between OriginURLString and DataURLString are portrayed by these two examples of downloading files from Gmail and Blogspot:

File photo.JPG
            Path: /Users/kingakieczkowska/Desktop/photo.JPG
            Browser name: Chrome
            Time downloaded: 2019-10-16 23:54:40
            Origin URL: https://mail.google.com/mail/u/0/
            Data URL: https://mail-attachment.googleusercontent.com/attachment/u/0/...
File tlo.JPG
            Path: /Users/kingakieczkowska/Desktop/tlo.JPG
            Browser name: Chrome
            Time downloaded: 2020-04-24 17:54:30
            Origin URL: http://simpleintricacy.blogspot.com/search?updated-max=2012-12-18T13:37:00-08:00&max-results=3&start=33&by-date=false
            Data URL: http://1.bp.blogspot.com/-eTPfRtUK2HY/UKlcoL_23zI/AAAAAAAACo8/DWvPRx7ecuM/s1600/DSC_0883.JPG

The same information can also be retrieved using the mdls command I talked about in my previous post. Running mdls on the file tlo.jpg yields a long list of metadata including the lines:

kMDItemWhereFroms                  = (
    "http://1.bp.blogspot.com/-eTPfRtUK2HY/UKlcoL_23zI/AAAAAAAACo8/DWvPRx7ecuM/s1600/DSC_0883.JPG",
    "http://simpleintricacy.blogspot.com/search?updated-max=2012-12-18T13:37:00-08:00&max-results=3&start=33&by-date=false"
)

However, it is incredibly easy to get rid of this metadata. A simple command presented below will delete this information immediately.

xattr -d com.apple.metadata:kMDItemWhereFroms tlo.jpg

That’s why when automating the extended attribute retrieval I decided to go with the contents of the quarantine database, which does not (at least not immediately) delete entries or specific fields’ contents for files which metadata has been manually removed. So while after running the above command I would not be able to see the kMDItemWhereFroms metadata using mdls anymore, the information is still be available in the database.

So, how can we put the intel we get from xattr together with what we found in the quarantine database?

You guessed it – with Python!

Collating extended attributes info with Python

Screenshot 2020-06-27 at 23.40.53
I get excited about emoji in docs, hence the screenshot here. More here.

The script, setup and usage instructions and examples are all available on the project’s Github. Here I will just provide the usage instructions and go into more detail on how it works. If you want the code and usage examples, go to Github.

Usage

python3 xattribs.py /path/to/directory/ [--db /path/to/db] [--json]

Use the --db flag to specify the QuarantineEvents database location, if different than the default (/Users/<current logged in user name>/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2).
Use --json to print output in JSON format, for instance to pipe output to another command or tool. By default the output will be presented in a human-friendly way, like so:

Kingas-MacBook-Pro:Desktop kinga$ python3 xattribs.py /Users/kinga/Downloads/files

Files in /Users/kinga/Downloads/files without the com.apple.quarantine attribute: ['.DS_Store']

    💨💨💨 AirDropped files: 

File IMG_6944.jpg
            Path: /Users/kinga/Downloads/files/IMG_6944.jpg
            Time received: 2020-05-27 16:37:51
            Sender name: Kinga Kieczkowska

File IMG_0631.JPG
            Path: /Users/kinga/Downloads/files/IMG_0631.JPG
            Time received: 2020-05-24 18:27:38
            Sender name: Brad Pitt

    🔥🔥🔥 Downloaded files: 

File BIAŁKA.pptx
            Path: /Users/kinga/Downloads/files/BIAŁKA.pptx
            Browser name: Chrome
            Time downloaded: 2020-05-12 11:02:25
            Origin URL: https://l.facebook.com/
            Data URL: https://cdn.fbsbx.com/v/....

How does it work?

First of all, the script parses positional arguments (including the path to the folder with files to be inspected) and flags.

Then the function get_files returns two dictionaries: airdropped_files and downloaded_files. It goes through all files in the specified directory and chooses those which were AirDropped or downloaded via a browser by detecting and then inspecting their com.apple.quarantine extended attribute. Subsequently, it saves some basic metadata about these files in a dictionary which will then be appended to airdropped_files and downloaded_files. Both the airdropped_files and downloaded_files dictionaries are of the the same structure at this point:

{
  "<file1 UID>": 
  {
    "file_name": "", 
    "file_path": "", 
    "download_time": "", 
  },

  "<file2 UID>": 
  {
    "file_name": "", 
    "file_path": "", 
    "download_time": "", 
  },
  ...
}

Next a connection to the database is made and the following query is executed:

SELECT LSQuarantineEventIdentifier as EventID,
datetime(LSQuarantineTimeStamp + strftime('%s','2001-01-01'), 'unixepoch') as Timestamp,
LSQuarantineSenderName as SenderName,
LSQuarantineOriginURLString as OriginURL,
LSQuarantineDataURLString as DataURL,
LSQuarantineAgentName as Agent 
FROM LSQuarantineEvent 
WHERE LSQuarantineAgentName IN ('sharingd', 'Chrome', 'Safari', 'Opera', 'Brave', 'Firefox');

The query simply pulls the information from following fields: LSQuarantineEventIdentifier, LSQuarantineTimeStamp, LSQuarantineSenderName, LSQuarantineOriginURLString, LSQuarantineDataURLString, LSQuarantineAgentName from the LSQuarantineEvent table in the database. I give each of the field names as XYZ aliases for clarity. It only pulls rows which also satisfy the requirement of the LSAgentName being sharingd, Chrome, Safari, Opera, Firefox or Brave.

The query’s results then are iterated over and matched against the dictionary of AirDropped files (airdropped_files) and downloaded files (downloaded_files) by comparing the LSQuarantineEventIdentifier to the file UID we have extracted using xattr.

If there is a match for an AirDropped file, an additional key (sender_name) value pair is added to file_list so it becomes the following format:

{
  "<file UID>": 
   {
    "file_name": "", 
    "file_path": "", 
    "download_time": "",
    "sender_name": ""
   },
  ...
}

If there is a match for a downloaded file, additional keys of origin_url, data_url and browser_name are added and populated with data creating a dictionary like so:

{
  "<file UID>": 
   {
     "file_name": "", 
     "file_path": "", 
     "download_time": "", 
     "origin_url": "",
     "data_url": "",
     "browser_name": ""
   },
  ...
}

Next, if the --json flag is present, the output is created using the json.dumps() function on the two dictionaries – airdropped_files and downloaded_files and encapsulating it in yet another dictionary:

{
  "aidropped_files":
  {
    "<file UID>": 
     { 
       "file_name": "", 
       "file_path": "", 
       "download_time": "", 
       "sender_name": "" 
     },
    ...
  }, 
  "downloaded_files":
   {
     "<file UID>": 
      {
        "file_name": "", 
        "file_path": "", 
        "download_time": "", 
        "origin_url": "",
        "data_url": "",
        "browser_name": ""
       },
     ...
   }
}

This is so that it is straightforward to pipe the output of this script to other tools or commands.

If the --json flag is not used, the pretty_print() function is called for both of the dictionaries. pretty_print provides an easily readable layout of the output, like we saw at the beginning of this section.

🔮🔮🔮

That’s it for this one, I hope you learned something new just like I did. Feel free to fork my project and add to it, I’d love to see what further work could be done. See you in the next one!

Sources

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s