MacOS Notification Center Forensics

✨ Welcome to the Notification Center rundown! ✨

Today we are going to look at the Notification Center database and find out what forensic information can be dug out of there.

First of all, what is the Notification Center? It is a feature which manages notifications from various applications such as web browsers, email clients (eg. macOS’s native Mail), desktop chat applications (eg. Signal, iChat). As many other applications it keeps user, temporary and cache files in the /var/folders directory which we can analyse for useful intelligence.

The /var/folders directory contains further directories, each named with two random characters. Within each, you will find further directories containing the user (0), cache (C) and temporary (T) files.  If you’re investigating this on a machine with multiple user accounts, you will be able to see other user’s folders and it can be difficult to immediately tell which one belongs to your account. You can check that by examining who is the owner of each respective directory:

Kingas-MacBook-Pro:folders kingakieczkowska$ lsfw wy zzKingas-MacBook-Pro:folders kingakieczkowska$ ls -la fw/*total 24drwxr-xr-x@   6 kingakieczkowska  staff   192 18 Apr 22:53 .drwxr-xr-x@   3 root              wheel    96 14 Oct  2019 ..-rw-r--r--@   1 kingakieczkowska  staff  8196 18 Apr 22:54 .DS_Storedrwxr-xr-x@  15 kingakieczkowska  staff   480 18 May 19:34 0drwx------@ 161 kingakieczkowska  staff  5152 15 May 21:12 Cdrwx------@ 116 kingakieczkowska  staff  3712 19 May 19:34 TKingas-MacBook-Pro:folders kingakieczkowska$ ls -la wy/*
total 0drwxr-xr-x@  5 testaccount  staff   160 17 May 13:01 .
drwxr-xr-x@  3 root         wheel    96 17 May 13:01 ..drwxr-xr-x@ 12 testaccount  staff   384 17 May 13:03 0drwx------  43 testaccount  staff  1376 17 May 13:05 Cdrwx------  52 testaccount  staff  1664 17 May 13:05 T

There will also be a directory which branches out into a much more complicated structure, and these are used by various system accounts. This can also be verified by checking the ownership of files and directories within. In my case that was the case with the zz folder:

Kingas-MacBook-Pro:folders kingakieczkowska$ ls -la zz
total 0drwxr-xr-x@ 18 root             wheel            576 14 Oct  2019 .drwxr-xr-x   5 root             wheel            160 17 May 13:01 ..
drwxr-xr-x@  6 root             wheel            192 30 Sep  2019 zyxvpxvq6csfxvn_n0000000000000drwxr-xr-x@  5 _appleevents     _appleevents     160 30 Sep  2019 zyxvpxvq6csfxvn_n000006w00001qdrwxr-xr-x@  5 _mdnsresponder   _mdnsresponder   160 30 Sep  2019 zyxvpxvq6csfxvn_n0000084000021drwxr-xr-x@  5 _windowserver    _windowserver    160 30 Sep  2019 zyxvpxvq6csfxvn_n00000b000002r
drwxr-xr-x@  5 _spotlight       _spotlight       160 30 Sep  2019 zyxvpxvq6csfxvn_n00000b400002sdrwxr-xr-x@  5 _securityagent   _securityagent   160 14 Oct  2019 zyxvpxvq6csfxvn_n00000bh00002wdrwxr-xr-x@  5 _atsserver       _atsserver       160 30 Sep  2019 zyxvpxvq6csfxvn_n00000c4000031drwxr-xr-x@  5 _softwareupdate  _softwareupdate  160 14 Oct  2019 zyxvpxvq6csfxvn_n00000s0000068drwxr-xr-x@  5 _coreaudiod      _coreaudiod      160 30 Sep  2019 zyxvpxvq6csfxvn_n00000s800006_drwxr-xr-x@  5 _locationd       _locationd       160 14 Oct  2019 zyxvpxvq6csfxvn_n00000sm00006d
drwxr-xr-x@  5 _cvmsroot        _cvms            160 30 Sep  2019 zyxvpxvq6csfxvn_n00000th00006mdrwxr-xr-x@  5 _assetcache      _assetcache      160 30 Sep  2019 zyxvpxvq6csfxvn_n00000xc00007bdrwxr-xr-x@  5 _nsurlsessiond   _nsurlsessiond   160 30 Sep  2019 zyxvpxvq6csfxvn_n00000y800007kdrwxr-xr-x@  5 _mbsetupuser     _mbsetupuser     160 14 Oct  2019 zyxvpxvq6csfxvn_n00000z000007rdrwxr-xr-x@  5 _captiveagent    _captiveagent    160 14 Oct  2019 zyxvpxvq6csfxvn_n0000108000082drwxr-xr-x@  5 _timed           _timed           160 30 Sep  2019 zyxvpxvq6csfxvn_n000011800008_

We won’t be looking into those today.

Another, probably simpler method is to run the command getconf DARWIN_USER_DIR as it will return the path to the user directory containing the Notification Center files we are after. This command makes use of the confstr function to return the values of some system variables – you can dive deeper into this starting here.

Kingas-MacBook-Pro:folders kingakieczkowska$ getconf DARWIN_USER_DIR/var/folders/fw/1nrrqb613j10lycdqc56bl480000gn/0/

When you locate that directory you can then navigate straight to

Let’s take a look at what’s inside. I’m using the tree command, which does not come natively with macOS – if you use brew you can install it with brew install tree. I am using it as it provides a clearly formatted output which helps to understand complex directory structures. This isn’t really the case here, but it’s still a good tool to know.

Kingas-MacBook-Pro:fw kingakieczkowska$ tree ./1nrrqb613j10lycdqc56bl480000gn/0/├── attachments
└── db2
    ├── db
    ├── db-shm
    └── db-wal

2 directories, 3 files

We are interested in the db file found in the db2 directory. The db-shm and db-wal files are temporary SQLite files, make sure to copy them together with the database which will use them. As a general rule, I don’t want to perform any operations on a database that’s currently in use, so I’m going to copy it over to a folder on my Desktop first. Then I need to change permissions on the copied files and we’re good to start digging.

Kingas-MacBook-Pro:fw kingakieczkowska$ mkdir ~/Desktop/notification_centerKingas-MacBook-Pro:fw kingakieczkowska$ sudo cp -r ./1nrrqb613j10lycdqc56bl480000gn/0/ ~/Desktop/notification_center/Kingas-MacBook-Pro:fw kingakieczkowska$ sudo chmod -R 755 ~/Desktop/notification_center/

I use the DB Browser for SQLite to view SQLite databases on my Mac and all the database screenshots from now on are of that software. In the pic below you can find the list of all tables within the database. The tables we are most interested in are app and record.

Screenshot 2020-05-13 at 21.06.52


The app table provides us with names of the applications which use Notification Center as well as their IDs – if you want to avoid SQL JOINs you might want to remember the ones denoting the applications which are most interesting to you as other tables use these values to identify the applications.

Screenshot 2020-05-13 at 21.03.03

We can immediately spot a number of records mentioning system apps which we might less interested in than the apps used for communication or web browsers. Using a simple SQL query allows us to filter out of all the system entries:

Screenshot 2020-05-13 at 21.03.40

After executing this query the more interesting applications are much better visible. If you’d like to learn about formulating SQL queries head on over to W3School’s SQL Tutorial.


In the app table we discussed above each row referenced an application. In the record table, each entry represents a notification in the Notification Center. It holds a number of interesting types of metadata about each notification including:

    • the ID of the application the notification originates from;
    • the date the notification got delivered;
    • whether or not the notification got presented to the user;
    • whether or not the notification was snoozed.

And last but not least – the record table also includes the actual content of the notification stored in a field called data.

You might not be interested in all of them at once and would prefer to filter out the noise. It would also be handy to be able to see the name of the application together with the content of it. To achieve that we can use this SQL query:

Screenshot 2020-05-18 at 22.09.49

In case you’re willing to learn more about how different types of  SQL join work, go here.

The above command outputs a clearer output of the application name (identifier), the date when the notification got delivered and the notification content (data).

Screenshot 2020-05-18 at 22.11.46

The date is unfortunately in an Apple-specific format called NSDate. It’s a representation of time elapsed since 00:00:00 UTC on 1 January 2001 so we need to do some processing to transform the NSDate into a more human-friendly (and readable) form. While we’re changing the SQL query, I’m also going to rename the identifier field to app_name and data field to notification_content for greater clarity. This is the new query:

  app.identifier as app_name, 
  datetime(record.delivered_date + strftime('%s','2001-01-01'), 'unixepoch') as delivered_date, as notification_content
FROM app 
INNER JOIN record ON app.app_id=record.app_id;

And that gives us a much clearer and more elegant output such as:

Screenshot 2020-05-18 at 22.21.16

Obviously though the most interesting information is the actual content of the notification, and with all the queries to the database it is still not clearly presented to us. The notification content is stored as binary data and is generally quite tricky to read in the generic database viewer:

Screenshot 2020-05-18 at 22.12.02

Fortunately, someone has been there before and automated this, and that someone is Patrick Wardle – an absolute legend of macOS security research, in the unlikely case you’ve never heard of him before. He investigated the Notification Center files looking specifically for saved Signal messages and goes into detail on the binary plist data type the notification data is stored in. Patrick also included a blueprint for a Python script to process those Notification Center files:

from biplist import *

conn = sqlite3.connect(path2db)
conn.row_factory = sqlite3.Row
cursor = conn.execute("SELECT data from record");
for row in cursor:
    plist = readPlistFromString(row[0])
    print plist


After spending some time learning about and experimenting with the binary plist structure and testing the functionalities of the Python biplist library, I built upon Patrick’s script to format and filter the output more to my liking as well as to add a functionality to extract images from a notification if one happens to contain it.

from biplist import *
import sqlite3
import json
import datetime

# if the dir carved_images does not exist,
# create it
if not os.path.exists('carved_images'):

# path to the database files
path2db = ""
conn = sqlite3.connect(path2db)
conn.row_factory = sqlite3.Row
cursor = conn.execute("SELECT data from record");
i = 1

for row in cursor:
        plist = readPlistFromString(row[0])

        # turning Apple date to human ;)
        unixTS = 978307200
        date = datetime.datetime.fromtimestamp(unixTS + plist["date"])
        date = str(date)

        print("\n## App #{}: {} on {}".format(i, plist["app"], date))

        print("Body: ", plist["req"]["body"])
        if "titl" in plist["req"]:
            print("Title:", plist["req"]["titl"])

        if "atta" in plist["req"].keys():
            print("     ----> Attachment detected.")
            image_data = plist["req"]["atta"][0]["data"]
            image = open("carved_images/img{}.png".format(i), "wb")
            print("Image saved: carved_images/img{}.png.".format(i))

        i = i + 1
    except Exception as error:

(Feel free to change App to anything else in the print statement above; only after producing all the screenshots have I realised using Notification would be much more intuitive).

Let’s test run the script on the notification database extracted from my system.


Calendar & Notes

For this experiment I made a note in the macOS’s native Notes app  and shared it with another person who made an edit. The notification was logged with the title of the note in the notification body.
Calendar reminders showed the set location of the event in the body field and the name of the event as the title.

Screenshot 2020-05-19 at 16.36.41


I have found many interesting pieces of information in the iChat records. As I have enabled the functionality to see all my SMS on my laptop, a plethora of confidential information was visible in the Notification Center database. In most cases it is circumstantial data such as:

    • the countries I visited – because Three sent me welcome messages whenever I crossed borders;

      Screenshot 2020-05-19 at 14.16.18
      Yes, I went to Italy days before the COVID started.
    • OTPs for various websites and services, which might not be of use when you get to them but still provide intelligence on what apps or services I’m using;
    • messages from couriers and online stores about pending deliveries.

Additionally, photos sent as iChat (iMessage) messages got saved in the notification data as well, and they were of satisfactory quality allowing to easily recognise what the photo depicts.

Screenshot 2020-05-19 at 16.00.08
Me Slavic squatting in Vienna to complete the graffiti so it makes my name. Lesson learnt: do not send embarrassing photos via iChat or they will persist in places you never suspected.

I find it really fascinating that the pictures present in the notifications are saved in such good quality. It has not been my experience when studying eg. QuickLook thumbnail caches, where unless you set up your GUI to show giant icons, the extracted thumbnails were all of small size and low resolution by default.


Another app leaking lots of interesting information is Mail. Just like with iChat, the actual level of ‘leakiness’ depends on the kind of things you use your email for. I have set up my Twitter account to have email notifications sent to me every time I get a Direct Message. I wanted to test how much of the text I send will be saved as a notification so I used another account I manage to DM myself the lyrics to Taylor Swift’s ‘Bad Blood’ (I’m still not sure why. I don’t even like that song that much. It was very late, I was very tired and that was the first song that came to mind).
The Twitter message in its entirety looked like this:

Screenshot 2020-05-19 at 14.55.11
190 words of Taylor’s ‘Bad Blood’ sent as a Twitter DM.

The notification appeared like this:

Screenshot 2020-05-19 at 14.53.58
I can see some words. Not more than 10-20 though.

However, the corresponding notification record content includes the full 190 words of the DM.

Screenshot 2020-05-19 at 14.59.24
190 words in the notification content



OK. To start with let’s acknowledge that Signal has bigger problems than some of its messages being stored in the Notification Center database, as it is literally keeping its encryption key in plaintext in a JSON file on your machine. However, it still has a place in our Notification Center story as the messages get stored in that database.

In my experiments it didn’t always save the name of the sender of the message for which a notification was created, but the attachment detected within the notification’s body turned out to be the profile picture of the sender.

## App #47: org.whispersystems.signal-desktop on 2020-05-19 13:51:30.555695Body:  Most recent: Definitely notTitle: 3 New Messages
     ----> Attachment detected.Image saved: carved_images/img47.png.

If the profile picture is not available, it saves an icon with the initials of the contact.

Screenshot 2020-05-19 at 14.33.12
the profile photos extracted from Signal notifications are the profile photos of the senders

This is obviously not conclusive proof of the sender’s identity – as anyone can set anything to be their profile photo – but it can give you some idea or maybe help when correlated with other evidence you have.

Browser notifications

Another application to be careful about are the web browsers. Below you can see an example of a message I got on WhatsApp while being logged into Whatsapp Web on Safari. In the body we can see the name of the contact and the content of the message (“Hey it’s WhatsApp”) after a colon. The Title field here holds the name of the group chat (ridiculously enough I have a group chat with a Gordon which is named just ‘Gordon’).

Screenshot 2020-05-19 at 16.46.09

Interestingly, these results may differ between different web browsers. For comparison, Google Chrome will display the profile photo / group chat photo along with the message and therefore that photo will be stored along with the other message data.

Screenshot 2020-05-19 at 16.54.42

You will see similar entries if you enable Desktop notifications for other social media platforms in your browser such as Facebook or Twitter, as well as any other websites which offer to send you notifications.


I hope I got you at least a bit excited about the possibilities of good forensic info to be found in the Notification Center database. I have not by any means exhausted all the possibilities – the more applications you use, the more interesting data you will be able to pull out. For instance, a quick test on a different machine found it is possible to pinpoint timestamps of the start and end of a VPN connection based on the notification contents created by the VPN software. However, it is important to remember this is all circumstantial – I have not tested or conclusively verified the following:

    • What are the exact circumstances for a notification to be added to the database (are we sure all notifications ever created and presented make their way to that database?)
    • How long do notifications stay in the database?
    • What is the manner in which the notifications are removed from the database (is it when the database reaches a specific size, or when they ‘time out’, or something else?)

Some of these questions might seem easy to answer, but experience shows the answers to such issues are often counter-intuitive. For instance, while establishing the lifecycle of thumbnails in macOS thumbnail caches, I found that once the cache size hit 500MB it deleted all it contents to 0MB, which is not at all a behaviour I would have had anticipated. So, to conclude – there are many things that would need checking before all of this could be taken as Serious Scientific Knowledge™ so please use & share accordingly.


Thank you for reading! I hope you enjoyed it and if you have any feedback or comments, please let me know. See you in the next one!


3 thoughts on “MacOS Notification Center Forensics”

  1. If I disable notifications, on macOS or iOS or whereever, nothing should be written in the notification log.
    Is that naive assumption of mine correct?


Leave a Reply

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

You are commenting using your 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