✨ 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$ ls fw wy zz Kingas-MacBook-Pro:folders kingakieczkowska$ ls -la fw/* total 24 drwxr-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_Store drwxr-xr-x@ 15 kingakieczkowska staff 480 18 May 19:34 0 drwx------@ 161 kingakieczkowska staff 5152 15 May 21:12 C drwx------@ 116 kingakieczkowska staff 3712 19 May 19:34 T Kingas-MacBook-Pro:folders kingakieczkowska$ ls -la wy/* total 0 drwxr-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 0 drwx------ 43 testaccount staff 1376 17 May 13:05 C drwx------ 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 0 drwxr-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_n0000000000000 drwxr-xr-x@ 5 _appleevents _appleevents 160 30 Sep 2019 zyxvpxvq6csfxvn_n000006w00001q drwxr-xr-x@ 5 _mdnsresponder _mdnsresponder 160 30 Sep 2019 zyxvpxvq6csfxvn_n0000084000021 drwxr-xr-x@ 5 _windowserver _windowserver 160 30 Sep 2019 zyxvpxvq6csfxvn_n00000b000002r drwxr-xr-x@ 5 _spotlight _spotlight 160 30 Sep 2019 zyxvpxvq6csfxvn_n00000b400002s drwxr-xr-x@ 5 _securityagent _securityagent 160 14 Oct 2019 zyxvpxvq6csfxvn_n00000bh00002w drwxr-xr-x@ 5 _atsserver _atsserver 160 30 Sep 2019 zyxvpxvq6csfxvn_n00000c4000031 drwxr-xr-x@ 5 _softwareupdate _softwareupdate 160 14 Oct 2019 zyxvpxvq6csfxvn_n00000s0000068 drwxr-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_n00000th00006m drwxr-xr-x@ 5 _assetcache _assetcache 160 30 Sep 2019 zyxvpxvq6csfxvn_n00000xc00007b drwxr-xr-x@ 5 _nsurlsessiond _nsurlsessiond 160 30 Sep 2019 zyxvpxvq6csfxvn_n00000y800007k drwxr-xr-x@ 5 _mbsetupuser _mbsetupuser 160 14 Oct 2019 zyxvpxvq6csfxvn_n00000z000007r drwxr-xr-x@ 5 _captiveagent _captiveagent 160 14 Oct 2019 zyxvpxvq6csfxvn_n0000108000082 drwxr-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 com.apple.notificationcenter
.
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/com.apple.notificationcenter ./1nrrqb613j10lycdqc56bl480000gn/0/com.apple.notificationcenter ├── 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_center Kingas-MacBook-Pro:fw kingakieczkowska$ sudo cp -r ./1nrrqb613j10lycdqc56bl480000gn/0/com.apple.notificationcenter/db2 ~/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
.
app
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.
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:
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.
record
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:
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
).
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:
SELECT app.identifier as app_name, datetime(record.delivered_date + strftime('%s','2001-01-01'), 'unixepoch') as delivered_date, record.data 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:
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:
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: try: 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'): os.makedirs('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: try: 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") image.write(image_data) print("Image saved: carved_images/img{}.png.".format(i)) i = i + 1 except Exception as error: print(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.
Results
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.
iChat
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;
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.
- the countries I visited – because Three sent me welcome messages whenever I crossed borders;
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.

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:

The notification appeared like this:

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

Signal
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.555695 Body: Most recent: Definitely not Title: 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.

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’).
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.
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.
Disclaimer
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!
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?
LikeLike
I think so, yes! You can control which applications use the Notification Center in System Preferences.
LikeLike