database

Guest Post by @bizzybarney! A Peek Inside the PPSQLDatabase.db Personalization Portrait Database

The DFIR Twitter-sphere exploded this morning when @mattiaep mentioned /private/var/mobile/Library/PersonalizationPortrait/PPSQLDatabase.db. I’ve been doing some research work on this file and plan to present pieces of it during my talk at the upcoming SANS DFIR Summit. I reached out to @iamevltwin and asked if she would host a quick blog post and she graciously agreed - but I now owe her gin, and steak and cheese egg rolls. In all seriousness, a huge thank you to my good friend and mentor!

Check out the upcoming Summit agenda here.

The PersonalizationPortrait directory is native on iOS 13 and contains a few interesting files. One specifically, PPSQLDatabase.db, is loaded with data. From my research, the directory existed on iOS 12 but this database did not.  Some of the data is repetitive from other native locations, but there’s a lot of context that can be correlated from this database and pieces of this data might exist longer here than they do in other places. So what is the purpose of this database? Don’t forget as you poke at it, that it has ‘Personalization’ in its title. It resides in the native /Library directory, so it’s Apple. What are you up to Apple? 

My research on this is still ongoing, but one thing I know is Apple wants me to like my device and to feel comfortable while I’m constantly immersed in their operating system. When I open News I want to see certain things of interest. When I select a photo to send to a friend Apple gives me a short list of probable recipients. When I plug my car into CarPlay I appreciate when Apple weirdly predicts where I’m headed. Is this ‘Personalization’ just for Apple or are pieces of this filtering out to other parties for better advertisement tailoring? The Widgets screen to the left of my home screen on my iPhone is a dynamic representation of so many pieces of personal data. What’s next on my Calendar and where it’s occurring, which apps I recently used, News, Weather based on my location, where I last parked my car when using CarPlay, and ScreenTime totals I can’t hardly believe. 

I want to highlight a few pieces of the PPSQLDatabase.db file to show it’s potential, and pass along an appropriate amount of caution as well. This database is aggregating data from many sources and attribution must be done carefully.

The first table to peek into is loc_records. It is absolutely loaded with location data - mine had 1000 entries. The 1000 is curious, and I wonder if anyone else’s table is limited at 1000 or if mine just landed on that nice even number. @iamevltwin did a quick check and found 1000 as well on Mac and iOS, so it seems that is the limit for the table. In reviewing just the locations from this table without joining any other table data, I quickly recognize many of them as places I have definitely been with this device in my pocket. But…then there are those locations I definitely haven’t been, ever. So while having 1000 locations records to play with sounds like good fun, don’t forget to be diligent with attribution.

Most of these location records have a column named “clp_location” which is stored as BLOB data. At closer inspection the BLOB data is a binary plist (.bplist). 

Using plutil -p produces a more friendly output format to inspect, reminiscent of Cloud-V2.sqlite and other “Significant Location” files. But that doesn’t mean my device was at that place at that time, because I wasn’t in Las Vegas on June 2nd, 2020 which is what that timestamp converts to. I checked my photos based on the ‘Places’ feature, and I did take a photo at that location - but it was June 4th, 2019 at 5:35PM. Weird.

So why is a photo from a year ago popping up? Making a few joins and gathering a bit more data produces more context to this specific item. After hammering out a query to join it over to the ‘sources’ table, I am able to see that this entry is associated with ‘com.apple.mobileslideshow’ and more specifically ‘com.apple.proactive.PersonalizationPortrait.PhotosGraphDonation’. One word really jumps out there - ‘proactive.’ That makes me think Apple is doing something for me here, and without asking. I went to my Photos section and found a categorization of Las Vegas photos for June 2-7 in my ‘Months’ category, and the photo is in that album. As I scanned the other places in these ‘proactive’ listings I am also able to see photo groupings from a trip to Jamaica (Kingston) and to Huntington Beach, CA in their own categories. 

So you may be asking yourself at this point, “Why did I just read this? It’s a location in a photo.” Stick with me, and remember we are trying to figure out what this newly discovered file is doing and how far reaching it might be. 

A few lines down from the photos we have an address in New Jersey being attributed to com.apple.mobilemail. I tweaked the query to ‘localtime’ for the sake of presentation, but I received an email on May 31, 2020 at 11:17AM EDT regarding the June 2nd release of Cellebrite Physical Analyzer version 7.34. In the signature of that email was the US headquarters address for Cellebrite - 7 Campus Drive, Suite 210, Parsippany, NJ. 

This is just scratching the surface of a few items from one table with basic table joins made. Scanning other bundles represented here I can see some of my favorite places from Apple Maps, locations recently mentioned in iMessage conversations, and Calendar records of my standing 12:30PM Zoom meeting for Life has No Ctrl+Alt+Del. 

If you don’t have time to research but would like to hear more about it, tune in to my talk at the SANS DFIR Summit on July 16th! If you do, try out this query I used for this blog post to pry around at this one table from this database and let me know how it works on your data via Twitter @bizzybarney

select
 loc_records.id,
 	sources.bundle_id as "Bundle ID",
 	sources.group_id as "Group ID",
 	datetime(sources.seconds_from_1970, 'unixepoch') as "Source Time",
 	loc_records.cll_latitude_degrees || ", "|| loc_records.cll_longitude_degrees as "Coordinates",
 	loc_records.clp_name as "Name",
 	loc_records.clp_thoroughfare as "Road",
 	loc_records.clp_subThoroughfare as "Address #",
 	loc_records.clp_locality as "City",
 	loc_records.clp_subLocality as "Sub-locality",
 	loc_records.clp_administrativeArea as "Admin Area",
 	loc_records.clp_subAdministrativeArea as "Sub Admin Area",
 	loc_records.clp_postalCode as "Postal Code",
 	loc_records.clp_ISOcountryCode as "Country Code",
 	loc_records.clp_country as "Counrty",
 	hex (loc_records.clp_location) as "Location BLOB (hex)",
 	loc_records.extraction_os_build as "iOS Build Version",
 	loc_records.category as "Category",
 	loc_records.algorithm as "Algorithm",
 	loc_records.initial_score as "Initial Score",
 	CASE
 	WHEN loc_records.is_sync_eligible = 1 then "Yes"
	WHEN loc_records.is_sync_eligible = 0 then "No"
	end as "Sync Eligible"
from loc_records  
left join sources on loc_records.source_id=sources.id

Knowledge is Power II – A Day in the Life of My iPhone using knowledgeC.db

Discover & share this Phone GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.

iOS devices may potentially have more personal information and user patterns than their macOS counterparts. People tend to go about their daily lives with their mobile devices rarely being separated from them. In this post I will present to you a day in the life of my iPhoneX – Monday September 10, 2018.

My previous post on the knowledgeC.db database focused more on macOS devices versus iOS, with a few scattered iOS examples. This post will focus entirely on iOS analysis of this database. This database is located on physical dumps of devices in /private/var/mobile/Library/CoreDuet/Knowledge/knowledgeC.db. It is not captured by an iTunes backup.

The first query I would like to do execute for iOS analysis is a simple SQL query to show which “Stream Names” I have. These can provide an idea of what kind of data is potentially available.

SELECT
DISTINCT ZOBJECT.ZSTREAMNAME
FROM ZOBJECT
ORDER BY ZSTREAMNAME

This query outputted the following streams. Some of these I’ve already covered in my previous post. Reading through some of these it is likely you can guess what some of them may contain.

  • /app/activity

  • /app/inFocus

  • /app/install

  • /app/intents

  • /app/locationActivity

  • /audio/inputRoute

  • /audio/outputRoute

  • /bluetooth/isConnected

  • /carplay/isConnected

  • /device/batteryPercentage

  • /device/isLocked

  • /device/isPluggedIn

  • /display/isBacklit

  • /display/orientation

  • /inferred/motion

  • /media/nowPlaying

  • /portrait/entity

  • /portrait/topic

  • /safari/history

  • /search/feedback

  • /siri/ui

  • /user/isFirstBacklightOnAfterWakeup

  • /watch/nearby

  • /widgets/viewed

Application Usage

Let’s start with what apps did I use on that day. I may have hundreds of apps on my phone but in reality, I use only a fraction. I used the following query from my previous post to capture all the “/app/inFocus” entries. I’ve screenshotted the majority of my day to give you a good idea of what this data looks like. My apologies in advance for the lengthiness of this post – but hey, everyone loves pictures! This is just one day’s worth of data, imagine having the same data for up to four weeks! I will never complain about too much data, queries and analysis can help you digest this information.

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
CASE ZOBJECT.ZSTARTDAYOFWEEK 
    WHEN "1" THEN "Sunday"
    WHEN "2" THEN "Monday"
    WHEN "3" THEN "Tuesday"
    WHEN "4" THEN "Wednesday"
    WHEN "5" THEN "Thursday"
    WHEN "6" THEN "Friday"
    WHEN "7" THEN "Saturday"
END "DAY OF WEEK",
ZOBJECT.ZSECONDSFROMGMT/3600 AS "GMT OFFSET",
datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
(ZOBJECT.ZENDDATE-ZOBJECT.ZSTARTDATE) as "USAGE IN SECONDS",
ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUESTRING
FROM ZOBJECT
WHERE ZSTREAMNAME IS "/app/inFocus" 
ORDER BY "START"

I started my “day” for this post on 09/09/2018 at 22:04:58, you will notice that all timestamps are recorded in local time (Eastern) as per my SQL query. Note the column ‘GMT OFFSET’ where it shows what time zone this data was recorded in. The following queries may not show some of these columns due to screenshot readability.

Around 22:04 on 09/09/2018 I checked my email (com.apple.mobilemail), Messages (com.apple.MobileSMS), and Twitter (com.atebits.Tweetie2). In fact, I spend a TON of time checking Twitter, I’m always on Twitter.

Next at 22:38:34 I check my Orangetheory app (com.shufflecloud.orangetheoryfitness) to see what time I need to head to the gym in the morning. I opened the app a couple of times trying to convince myself to keep my reservation. I actually cancel my class, I’m too sore from the previous day to go - don’t you judge me! If I’m not going to the gym in the morning – I can sleep in! I access the Clock app (com.apple.mobiletimer) to change my morning alarm.

Around 04:25 on Monday I wake up and can’t sleep fall back asleep so I put in my fancy shmancy Bose Sleepbuds and use the app (com.bose.corporation.bosesleep) to provide me some dreamy white noise. I proceed to wake up a couple times to adjust the white noise type and to change my alarm again (definitely sleeping in, I require lots of sleep to function as a human.) I finally wake at 7:55am to turn off the Sleepbuds and to check Messages and Slack (com.tinyspeck.chatlyio).

At 07:58 in the morning, I turn my alarm off (was set to go off at 08:00) and proceed to check email, Twitter, Weather, and Messages.

While getting ready for work I check Messages, send a Bitmoji using the Bitmoji keyboard (com.bitstrips.imoji.BitmojiKeyboard) in Messages and again check weather (Hurricane Florence is making her way in!)

At 09:49 at work, my coworker asks how my SANS Fantasy Football (com.espn.fantasyFootball) team did. I check my scores - I got destroyed by Alissa’s team. This is going to be a rough season.

Check email again and my calendar.

At 10:42, a quick check of Facebook (com.facebook.Facebook) and check a setting in the iOS Settings (com.apple.Preferences) application.

I got a phone call (com.apple.InCallService) at 11:25 but was unable to pick up as I was eating lunch. I called them back at 11:56 using the Phone app (com.apple.mobilephone), it was a 10 minute call that ended at 12:06. Notice the artifacts of app usage when you leave an app and come back to it 40 minutes later. I was using the Phone app for all of 2 seconds before switching to Facebook to gaze at photos of Stacy’s super cute puppy, Piper.

In the afternoon, I check some Slack, screw around with Settings, email, and of course check Twitter.

Finally commuting home, I need some Apple Music (com.apple.Music) to listen too! I have my phone hooked up to my car with CarPlay and I’m getting directions using Apple Maps (something about that makes the com.ubercab.UberClient.intentsextension go nuts!). I also use Siri in my car to create a Note (com.apple.mobilenotes.IntentsExtension) just after 18:00. (Hands free of course!)

I get home and proceed to listen to Apple Music, check Slack, email, Twitter, etc. I also check to see if I have an Orangetheory class scheduled – sure do! I set my alarm for zero dark thirty, I’m not canceling this one. I also edit a Note and check my calendar before I settle in to write this post.

The ‘app/inFocus’ gives a good rundown of what apps the user uses and when, however it is missing quite a bit of detail that can provide an investigator more context to what exactly the user was doing during these events.

Application Activity

The streams for ‘app/activity’ provide more details on what exactly the app is doing. I used the following query for this data. NOTE: For the sake of getting a readable screenshot, I’ve commented out a few columns (--) in my query below. When running you will just want to uncomment those lines or customize as necessary.

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
--CASE ZOBJECT.ZSTARTDAYOFWEEK 
--  WHEN "1" THEN "Sunday"
--  WHEN "2" THEN "Monday"
--  WHEN "3" THEN "Tuesday"
--  WHEN "4" THEN "Wednesday"
--  WHEN "5" THEN "Thursday"
--  WHEN "6" THEN "Friday"
--  WHEN "7" THEN "Saturday"
--END "DAY OF WEEK",
--datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
--datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
--ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUESTRING,
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__ACTIVITYTYPE AS "ACTIVITY TYPE",  
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__TITLE as "TITLE", 
datetime(ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__EXPIRATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "EXPIRATION DATE",
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__ITEMRELATEDCONTENTURL as "CONTENT URL",
datetime(ZSTRUCTUREDMETADATA.ZCOM_APPLE_CALENDARUIKIT_USERACTIVITY_DATE+978307200,'UNIXEPOCH', 'LOCALTIME')  as "CALENDAR DATE",
datetime(ZSTRUCTUREDMETADATA.ZCOM_APPLE_CALENDARUIKIT_USERACTIVITY_ENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME')  as "CALENDAR END DATE"
FROM ZOBJECT
left join ZSTRUCTUREDMETADATA on ZOBJECT.ZSTRUCTUREDMETADATA = ZSTRUCTUREDMETADATA.Z_PK
left join ZSOURCE on ZOBJECT.ZSOURCE = ZSOURCE.Z_PK
WHERE ZSTREAMNAME is "/app/activity" 
ORDER BY "ENTRY CREATION"

Sunday evening, I checked my email around 22:04:58. What emails was I actually reading? My Inbox - reading a message that I had just been added from the waitlist (hence the canceling of it later! (#DFIRFIT? More like #DFIRSore!)

Discover & share this Cartoon GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.

Next, I changed my alarm to sleep in at 05:52 and to turn it off at 07:58. The activity type of com.apple.clock.alarm is a good way to tell what “screen” a certain app is using. For instance, I was not viewing the “World Clock”, “Stopwatch”, or “Timer” screens.

Around 17:54pm, I start to drive home. I used Maps (com.apple.Maps) through CarPlay to get directions. This is something that is nearly missed if you only look at /app/inFocus entries. The blurred section is my home address which is assigned to “Home” when I ask Siri to take me there.

When I got home at 18:42, I started browsing Twitter. Some apps yield more detailed information than others (other examples in my data include RedFin, Zappos, and Yelp). Each time I clicked though to a particular tweet (versus just aimlessly scrolling), it would record it as an activity type of “com.atebits.Tweetie2.spotlight”, complete with full URL to the tweet.

Piper is a good #DFIRpup.

Finally, I do a few more tasks before sitting down to write this. Set an alarm to get up, check a few days in my calendar and make sure I have the Apple Event in there – I need a new Apple Watch!

Application Intents

You can never have enough context when doing data analysis. We can use the “app/intents” entries for even more detail. I provided the query I used below with some items commented out for screenshot purposes.

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
--CASE ZOBJECT.ZSTARTDAYOFWEEK 
--  WHEN "1" THEN "Sunday"
--  WHEN "2" THEN "Monday"
--  WHEN "3" THEN "Tuesday"
--  WHEN "4" THEN "Wednesday"
--  WHEN "5" THEN "Thursday"
--  WHEN "6" THEN "Friday"
--  WHEN "7" THEN "Saturday"
--END "DAY OF WEEK",
datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
--ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUESTRING,
ZSTRUCTUREDMETADATA.Z_DKINTENTMETADATAKEY__INTENTCLASS as "INTENT CLASS", 
ZSTRUCTUREDMETADATA.Z_DKINTENTMETADATAKEY__INTENTVERB as "INTENT VERB", 
ZSTRUCTUREDMETADATA.Z_DKINTENTMETADATAKEY__SERIALIZEDINTERACTION as "SERIALIZED INTERACTION",
ZSOURCE.ZBUNDLEID,
ZSOURCE.ZGROUPID,
ZSOURCE.ZITEMID,
ZSOURCE.ZSOURCEID
FROM ZOBJECT
left join ZSTRUCTUREDMETADATA on ZOBJECT.ZSTRUCTUREDMETADATA = ZSTRUCTUREDMETADATA.Z_PK
left join ZSOURCE on ZOBJECT.ZSOURCE = ZSOURCE.Z_PK
WHERE ZSTREAMNAME is "/app/intents" 
ORDER BY "START"

Far more detail is provided in the “app/intents” entries. Messages sent, the “Serialized Interaction” provides message details (contact/message info) that can be correlated with the sms.db. If the Alarms were enabled or disabled, the “Serialized Interaction” BLOB provides the specific alarm GUID info if that is necessary to your investigation. Always look at BLOB data – you never know what you can find. This particular BLOB is an NSKeyedArchiver plist embedded into another NSKeyedArchiver plist – plist inception!

Note the two different Bundle IDs for the Sent Messages:

  • com.apple.MobileSMS – This one is used when I was actually interacting with the Messages application and typing my response.

  • com.apple.assistant_service – This one is used when Siri is “helping”. In my case I was using CarPlay to dictate my messages while on my commute. Siri (w/o CarPlay) looks similar.

Recall that I received a call (but was eating lunch) at 11:25. I called them back at 11:56. The intents are showing a “StartAudioCall” intent when I received the call (but didn’t take it) and when I ended the call at 12:06pm. Without testing, this can be misleading. Always test before making assumptions.

When driving home at 17:59, I created a Note and attempted to append text to that note. I can see the original note creation as well as attempted “Appends”. Sadly, Siri couldn’t understand what I wanted to do when I wanted to append the note, so she just read the title of all bajillion notes that I have (not super helpful Siri!). Some of the text in the “Serialized Interaction” show what Siri interpreted me saying.

When driving home, I asked Siri to take me “Home” and she populated the directions to my place in the Maps application. While you see an “EndNavigation” intent you may not necessarily see a “StartNavigation” intent. It depends on how you initiate the driving directions.

When I got home I also wanted listen to some NPR news through Apple Music app. This shows a Search and various “Selects”. Looking into these BLOBS you can see what I searched for and what I selected to listen to. (More on this in a bit.)

Finally, I append what I wanted to in the Note, manually this time (no thanks to Siri). I also update a calendar entry.

Device Status

The /device/* streams track the device’s status such as whether the device is plugged in, locked, and what the battery level is. This data is tracked in other databases as well (See my iOS of Sauron presentation), but I’ll never complain about data redundancy.

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
CASE ZOBJECT.ZSTARTDAYOFWEEK 
    WHEN "1" THEN "Sunday"
    WHEN "2" THEN "Monday"
    WHEN "3" THEN "Tuesday"
    WHEN "4" THEN "Wednesday"
    WHEN "5" THEN "Thursday"
    WHEN "6" THEN "Friday"
    WHEN "7" THEN "Saturday"
END "DAY OF WEEK",
datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
(ZOBJECT.ZENDDATE-ZOBJECT.ZSTARTDATE) as "USAGE IN SECONDS",
ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUEDOUBLE
FROM ZOBJECT
left join ZSTRUCTUREDMETADATA on ZOBJECT.ZSTRUCTUREDMETADATA = ZSTRUCTUREDMETADATA.Z_PK
left join ZSOURCE on ZOBJECT.ZSOURCE = ZSOURCE.Z_PK
WHERE ZSTREAMNAME like "/device/%"  
ORDER BY "START"

The example shows when the device is locked (isLocked=1), unlocked (isLocked=0), unplugged (isPluggedIn=0), and plugged in (isPluggedIn=1) in the ZVALUEDOUBLE column. You can also see the battery charging and discharging in line with the plugged-in status.

Extracting just the /device/isPluggedIn events I can distill when this device was plugged into something. Sunday evening, I plugged the device in before going to sleep and unplugged it at 07:55 in the morning. At 08:42 in the morning, I plugged it into my car for my morning commute and unplugged it at 9:15 when I got to work.

In the afternoon I plugged it into my car again at 15:34-16:06 to drive to a different site and then finally plugged it in from 17:47-18:15 for my commute home.

This output doesn’t provide any detail into what the device was plugged into, but other streams can provide some hints.

Audio & Media

This database keeps track of what is playing (depending on the app) and how. The SQL query below is a good “all in one” query to use for all the audio and media events. The query examples I provide in screenshots have been edited down to be viewable.

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
CASE ZOBJECT.ZSTARTDAYOFWEEK 
    WHEN "1" THEN "Sunday"
    WHEN "2" THEN "Monday"
    WHEN "3" THEN "Tuesday"
    WHEN "4" THEN "Wednesday"
    WHEN "5" THEN "Thursday"
    WHEN "6" THEN "Friday"
    WHEN "7" THEN "Saturday"
END "DAY OF WEEK",
datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
(ZOBJECT.ZENDDATE-ZOBJECT.ZSTARTDATE) as "USAGE IN SECONDS",
ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUESTRING,
ZSTRUCTUREDMETADATA.Z_DKAUDIOMETADATAKEY__IDENTIFIER as "AUDIO IDENTIFIER",
ZSTRUCTUREDMETADATA.Z_DKAUDIOMETADATAKEY__PORTNAME as "AUDIO PORT NAME",
ZSTRUCTUREDMETADATA.Z_DKAUDIOMETADATAKEY__PORTTYPE as "AUDIO PORT TYPE",
ZSTRUCTUREDMETADATA.Z_DKBLUETOOTHMETADATAKEY__ADDRESS as "BLUETOOTH ADDRESS",
ZSTRUCTUREDMETADATA.Z_DKBLUETOOTHMETADATAKEY__NAME as "BLUETOOTH NAME",
ZSTRUCTUREDMETADATA.Z_DKNOWPLAYINGMETADATAKEY__ALBUM as "NOW PLAYING ALBUM",
ZSTRUCTUREDMETADATA.Z_DKNOWPLAYINGMETADATAKEY__ARTIST as "NOW PLAYING ARTIST",
ZSTRUCTUREDMETADATA.Z_DKNOWPLAYINGMETADATAKEY__GENRE as "NOW PLAYING GENRE",
ZSTRUCTUREDMETADATA.Z_DKNOWPLAYINGMETADATAKEY__TITLE as "NOW PLAYING TITLE",
ZSTRUCTUREDMETADATA.Z_DKNOWPLAYINGMETADATAKEY__DURATION as "NOW PLAYING DURATION"
FROM ZOBJECT
left join ZSTRUCTUREDMETADATA on ZOBJECT.ZSTRUCTUREDMETADATA = ZSTRUCTUREDMETADATA.Z_PK
left join ZSOURCE on ZOBJECT.ZSOURCE = ZSOURCE.Z_PK
WHERE ZSTREAMNAME like "/audio%" or ZSTREAMNAME like "/bluetooth%" or ZSTREAMNAME like "/media%" 
ORDER BY "START"

Starting with audio output and input, where we can get an idea of what type of device this phone was plugged into and how the audio directed.

This is an example of my afternoon commute. I plugged my phone into my car and used CarPlay. You can see indications of CarPlay and the iPhone Microphone. You may see this audio input go back and forth between CarAudio and Microphone in instances where the user is using Siri to do dictation for SMS messages, Maps, Music, Notes, etc. Just after I got home I listed to audio on my iPhone using my AirPods, note the “/Bluetooth/isConnect” connection switch.

It is worth noting here that my Bluetooth Bose QC35s show up in the Bluetooth/Audio events, but my Bose Sleepbuds did not.

Now that we know how the audio is routed, what was I listening to at the time? Everything I listen to gets recorded in (embarrassing) detail. On my commute to work I decided to get my day started with the 90s Radio Station in Apple Music. You can determine if I listened to a song or skipped it by looking at the ‘Usage is Seconds’ column (Sorry Spice Girls, you’re just not my jam). Looking at the data you may think I’m a huge Cher fan for listening to Believe for 22758+ seconds but remember it will record time from last “usage”. When I plugged my phone back into my car and it started playing where it left off.

On my jaunt between work sites, I put a podcast on and listened to the whole thing (~29 minutes). The “Usage in Seconds” column makes it look like I skipped it if you go with the logic that I presented with the Apple Music songs. It appears not all audio apps will store the data the same. This is an important caveat and another reason to test!

After I was done with my podcast, I switched back to music – the 80’s Radio Station this time. After work, I felt like I needed to rock out, so I asked Siri to “Play Muse”. The “Loading…” title gets recorded and a custom Muse playlist started playing.

Installed Apps

As expected this one keeps track of installed apps! Nothing more, nothing less. It does not appear to keep track of app updates.

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
CASE ZOBJECT.ZSTARTDAYOFWEEK 
    WHEN "1" THEN "Sunday"
    WHEN "2" THEN "Monday"
    WHEN "3" THEN "Tuesday"
    WHEN "4" THEN "Wednesday"
    WHEN "5" THEN "Thursday"
    WHEN "6" THEN "Friday"
    WHEN "7" THEN "Saturday"
END "DAY OF WEEK",
datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
(ZOBJECT.ZENDDATE-ZOBJECT.ZSTARTDATE) as "USAGE IN SECONDS",
ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUESTRING
FROM ZOBJECT
left join ZSTRUCTUREDMETADATA on ZOBJECT.ZSTRUCTUREDMETADATA = ZSTRUCTUREDMETADATA.Z_PK
left join ZSOURCE on ZOBJECT.ZSOURCE = ZSOURCE.Z_PK
WHERE ZSTREAMNAME is "/app/install" 
ORDER BY "START"

Other Streams

A few other streams of interest:

  • /app/locationActivity – Contains location data, but not exactly what you think. In the examples I’ve seen on my own phone it was Redfin MLS locations – not where I was at a given moment.

  • /display/isBacklit – Was the display on or off?

    • 0 = no

    • 1 = yes

  • /display/orientation – How was the devices being viewed?

    • 0 = landscape

    • 1 = portrait

  • /safari/history – Safari history, same as macOS – see my previous article.

  • /watch/nearby – Determines if watch is within communication distance or not.

    • 0 = no

    • 1 = yes

  • /widgets/viewed – Swipe right to view widgets, it will show how many were “viewed” but not exactly which ones.

Now what?

Correlation! This database holds a serious amount of data and it can be easy to get tunnel vision. Think about correlating this data with the location data I’ve presented in other presentations and blog articles. Where was the user when they were looking at a specific app or browsing to a specific website? Were they driving distracted and watching YouTube when they shouldn’t have? If the user was using a specific app during a time of interest, go to that app’s data and look to see if it may contain data relevant to your investigation.

Discover & share this Cartoon GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.

Knowledge is Power! Using the macOS/iOS knowledgeC.db Database to Determine Precise User and Application Usage

Having access to precise and granular user and application usage can be extremely useful in a forensic investigation, some of which are listed here. I find that pattern-of-life data is some of the most useful information on a device - it really does tell the story about a user and their devices. I've done fairly extensive research on this previously on iOS (much of which can be used for macOS as well) but have yet to really dive into this database.

  • Application Usage
  • Application Activities
  • Safari Browser History
  • Device Power Status
  • Lock Status (iOS Only)
  • Battery Usage (iOS Only)
  • App Installations (iOS Only)
  • Audio Status (iOS Only)

We can use some of these records to help answer a myriad of investigative questions or just about any type of investigation.

  • What applications did a particular user use? How often do they use this application? How long are they using this application for?
  • What websites did they visit? Are they doing research and putting this information into another application?
  • Where did they ask for directions? Are they driving distracted?
  • How often do they check their email? What led them to click on a specific email and infect themselves?
  • How often are they chatting? Who are they chatting with?

The knowledgeC.db database can be found on macOS and iOS devices. On Mac systems there will be a system context database located in the /private/var/db/CoreDuet/Knowledge directory, while a user context database is located in the user’s ~/Library/Application Support/Knowledge/ directory. *Update 08/7/18: The data in this article is specific to macOS 10.13 and iOS 11. Other versions may contain the same data but the schemas/contents may be slightly different. A note about iOS 10 database is at the end of this article.

On iOS there is only one main knowledgeC.db database located in /private/var/mobile/Library/CoreDuet/Knowledge/ that appears to merge the contents of the user and system context databases found on macOS. (Note: Others may exist for other applications, they are not covered here but follow a similar database schema.) It is worth noting that this database only appears to be available on a physical acquisitions and/or jailbroken iOS devices. I have not seen it in iTunes-style backups. 

The database has many tables which have many columns. This article will only go over three of these that I have found to be particularly interesting. I encourage you to look at your own data to discover other items of investigative value. Timestamps in this database use the Mac Epoch time (01/01/2001 00:00:00 UTC).

ZOBJECT – Contains potentially thousands of usage entries for approximately 4 weeks. I will use this table as my primary table of analysis and add on other tables as needed throughout this article. Other tables that ZOBJECT entries may reference are located in these tables:

  • ZSOURCE – Source of the ZOBJECT entries
  • ZSTRUCTUREDMETADATA – Additional metadata associated with ZOBJECT entries

Taking a look at ZOBJECT, you’ll see that entries appear to have a “type” associated in the ZSTREAMNAME column. Using the following tiny SQLite query, I can see what “types” of entries I’m dealing with.

SELECT
DISTINCT ZOBJECT.ZSTREAMNAME
FROM ZOBJECT
ORDER BY ZSTREAMNAME

The output of this on the system context knowledgeC.db database on macOS shows the following “types”:

  • "/activity/level"
  • "/app/activity"
  •  "/app/inFocus"
  • "/app/intents"
  • "/device/isPluggedIn"
  • "/display/isBacklit"
  • "/safari/history"

An iOS example shows the following “types”:

  • "/app/activity"
  • "/app/inFocus"
  • "/app/install"
  •  "/app/intents"
  • "/audio/outputRoute"
  • "/device/batteryPercentage"
  • "/device/isLocked"
  • "/device/isPluggedIn"
  • "/display/isBacklit"
  • "/display/orientation"
  • "/inferred/motion"
  • "/media/nowPlaying"
  • "/portrait/entity"
  • "/safari/history"
  • "/search/feedback"
  • "/user/isFirstBacklightOnAfterWakeup"
  • "/widgets/viewed"

 Application Usage

Let’s start with just the “/app/inFocus”. This “type” is available for macOS and iOS and will show us what application is being used at a given time. I will use the following SQL query for this.

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
CASE ZOBJECT.ZSTARTDAYOFWEEK 
    WHEN "1" THEN "Sunday"
    WHEN "2" THEN "Monday"
    WHEN "3" THEN "Tuesday"
    WHEN "4" THEN "Wednesday"
    WHEN "5" THEN "Thursday"
    WHEN "6" THEN "Friday"
    WHEN "7" THEN "Saturday"
END "DAY OF WEEK",
ZOBJECT.ZSECONDSFROMGMT/3600 AS "GMT OFFSET",
datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
(ZOBJECT.ZENDDATE-ZOBJECT.ZSTARTDATE) as "USAGE IN SECONDS",
ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUESTRING
FROM ZOBJECT
WHERE ZSTREAMNAME IS "/app/inFocus" 
ORDER BY "START"

This query is only using data from the ZOBJECT table (no JOINS needed yet!) I’ve filtered out only entries that contain the “/app/inFocus” in the ZSTREAMNAME column using a WHERE statement. I sorted the output by the “START” timestamp using the ORDER BY statement.

For all timestamps in these queries, I have to add 978307200 to convert a Mac Epoch to a Unix Epoch value. SQLite has no idea what a Mac Epoch is – this is a handy conversion to use if you are doing any Mac/iOS forensic database analysis, you will see it very often. I’ve converted all timestamps to my local system time for sanity reasons (you can also choose to use UTC here if you are of the ‘UTC or GTFO’ persuasion). In my testing I have found the timestamps to be accurate.

I’ve extracted the following columns:

  • ZCREATIONDATE - When the entry was written to the database.
  • ZSTARTDAYOFWEEK – What day did this entry occur. I created a CASE statement here to make an analyst friendly output of the days of the week instead of numbers 1-7.
  • ZSECONDSFROMGMT – The GMT offset in seconds. I converted this to an GMT offset in hours by dividing by 3600. This is useful if you have a device that has travelled time zones in the last 4 weeks.
  • ZSTARTDATE and ZENDDATE – A start and end timestamp for the entry. The following “USAGE IN SECONDS” “column” is not a true database column. I’ve done the math between the START and END dates to quickly see usage times in seconds.
  • ZSTREAMNAME – The “type” of entry.
  • ZVALUESTRING – The bundle ID for the application.

The output example above shows a few items of interest.

  • No entries were recorded on Friday 08/03/2018. (I can in fact take a whole day off and not use my laptop – crazy right?!)
  • In these (very) few entries shown, I spend a decent amount of time using Google Chrome (com.google.Chrome).
  • The com.apple.loginwindow entry is tracking time between device logons. Since I didn’t log in on Friday, this “app usage” time is a quite long (~35 hours, divide by 3600 to get number of hours). During this time the laptop lid was closed and just sitting idle.
  • Other applications that I used during this period include:
    • Calendar (com.apple.iCal) – Very briefly, it was only a few seconds – perhaps I just switched to a desktop with the application open. I tend to flip between many desktops – each with a different app open on it.
    • Apple Mail (com.apple.mail)
    • Messages (com.apple.iChat)

Since some data can appear slightly different I will bounce between macOS and iOS examples. This example is from an iOS device.

The examples above is a tiny fraction of events that took place on my system. Once you take the entirety of the data available (4 weeks!) you can start to see patterns. 

  • When is the device in use?
  • What applications are used?
  • How applications used? How often, how long?

This data is great but there are of course caveats associated! This application usage is really only for GUI-based applications. If your users are always in the Terminal, you’ll see plenty of time in com.apple.Terminal. 

Another issue is user attribution. The first example came from the system level context database on macOS how can I associate this usage to a particular user? (More on this in a bit. The second example came from an iOS device. There is only one “user” for iOS devices.)

Application Activities

Using another stream “type” - “/app/activity”, I can add more context to what is happening on the device. Using the same query above, I’ll add a few more items. 

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
ZOBJECT.ZSECONDSFROMGMT/3600 AS "GMT OFFSET",
CASE ZOBJECT.ZSTARTDAYOFWEEK 
    WHEN "1" THEN "Sunday"
    WHEN "2" THEN "Monday"
    WHEN "3" THEN "Tuesday"
    WHEN "4" THEN "Wednesday"
    WHEN "5" THEN "Thursday"
    WHEN "6" THEN "Friday"
    WHEN "7" THEN "Saturday"
END "DAY OF WEEK",
datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
(ZOBJECT.ZENDDATE-ZOBJECT.ZSTARTDATE) as "USAGE IN SECONDS", 
ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUESTRING,
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__ACTIVITYTYPE AS "ACTIVITY TYPE",  
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__TITLE as "TITLE", 
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__USERACTIVITYREQUIREDSTRING as "ACTIVITY STRING",
datetime(ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__EXPIRATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "EXPIRATION DATE"
FROM ZOBJECT
left join ZSTRUCTUREDMETADATA on ZOBJECT.ZSTRUCTUREDMETADATA = ZSTRUCTUREDMETADATA.Z_PK
WHERE ZSTREAMNAME is "/app/activity" or ZSTREAMNAME is "/app/inFocus"
ORDER BY "START"

I performed a LEFT JOIN on ZSTRUCTUREDMETADATA as the activity entries contain some extra metadata info (this is where I can get more context.) From this new table I’m extracting the following:

  • Z_DKAPPLICATIONACTIVITYMETADATAKEY__ACTIVITYTYPE – What is the user viewing or doing with the application.
  • Z_DKAPPLICATIONACTIVITYMETADATAKEY__TITLE – Name of the item being viewed or edited.
  • Z_DKAPPLICATIONACTIVITYMETADATAKEY__USERACTIVITYREQUIREDSTRING – A combination of the previous two columns above. You may choose to use this one, the previous two, or all three!
  • Z_DKAPPLICATIONACTIVITYMETADATAKEY__EXPIRATIONDATE – An “expiration” timestamp. I’m still researching this one – it always seems to be a month into the future.

It is worth noting the ZSTRUCTUREDMETADATA table has over 100 columns – it is worth it to peruse what data your apps are populating in this table. I’m only extracting a fraction of the column data. Customize these SQL queries as needed for your data, each device, each user will be different.

On the macOS system context example I can now see application activities. On my macOS example I’ve only seen the Apple Mail and Notes applications populate this data. (Note: I’ve cut off the first few columns to make it easier to view for this article.)

  • What email mailbox or message am I viewing?
  • What Note am I editing?

The same query can be used on iOS. However, one line needs to be removed as that column does not exist.

ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__USERACTIVITYREQUIREDSTRING as "ACTIVITY STRING",

In my iOS example I found you can get more context to Apple Maps data as shown below. (I also saw Apple News populated.) Now you can see what directions I was searching!

Another stream “type” - /app/intents can provide more context to application activity.

SELECT
datetime(ZOBJECT.ZCREATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "ENTRY CREATION", 
CASE ZOBJECT.ZSTARTDAYOFWEEK 
    WHEN "1" THEN "Sunday"
    WHEN "2" THEN "Monday"
    WHEN "3" THEN "Tuesday"
    WHEN "4" THEN "Wednesday"
    WHEN "5" THEN "Thursday"
    WHEN "6" THEN "Friday"
    WHEN "7" THEN "Saturday"
END "DAY OF WEEK",
datetime(ZOBJECT.ZSTARTDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "START", 
datetime(ZOBJECT.ZENDDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "END", 
(ZOBJECT.ZENDDATE-ZOBJECT.ZSTARTDATE) as "USAGE IN SECONDS",
ZOBJECT.ZSTREAMNAME, 
ZOBJECT.ZVALUESTRING,
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__ACTIVITYTYPE AS "ACTIVITY TYPE",  
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__TITLE as "TITLE", 
ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__USERACTIVITYREQUIREDSTRING as "ACTIVITY STRING", 
datetime(ZSTRUCTUREDMETADATA.Z_DKAPPLICATIONACTIVITYMETADATAKEY__EXPIRATIONDATE+978307200,'UNIXEPOCH', 'LOCALTIME') as "EXPIRATION DATE",
ZSTRUCTUREDMETADATA.Z_DKINTENTMETADATAKEY__INTENTCLASS as "INTENT CLASS", 
ZSTRUCTUREDMETADATA.Z_DKINTENTMETADATAKEY__INTENTVERB as "INTENT VERB", 
ZSTRUCTUREDMETADATA.Z_DKINTENTMETADATAKEY__SERIALIZEDINTERACTION as "SERIALIZED INTERACTION",
ZSOURCE.ZBUNDLEID
FROM ZOBJECT
left join ZSTRUCTUREDMETADATA on ZOBJECT.ZSTRUCTUREDMETADATA = ZSTRUCTUREDMETADATA.Z_PK
left join ZSOURCE on ZOBJECT.ZSOURCE = ZSOURCE.Z_PK
WHERE ZSTREAMNAME is "/app/activity" or ZSTREAMNAME is "/app/inFocus" or ZSTREAMNAME is "/app/intents" 
ORDER BY "START"

Building on the same query as before, I’m now LEFT JOIN’ing another table ZSOURCE for the ZBUNDLEID column. I also added three more columns from the ZSTRUCTUREDMETADATA table.

  • Z_DKINTENTMETADATAKEY__INTENTCLASS – An activity type
  • Z_DKINTENTMETADATAKEY__INTENTVERB – Activity verb
  • Z_DKINTENTMETADATAKEY__SERIALIZEDINTERACTION – Binary plist with extra metadata information.

On my macOS system context example I have more context on when Messages are sent, or calls are synced. In my research, I have found that the messages sent timestamps are accurate however the call syncing is misleading. This isn’t likely when the calls are being made but when they are synced to the macOS system. A further look at the BLOB data can provide the appropriate timestamps. (Note: I’ve removed the /app/activity columns to make this data easier to view.)

The BLOB data can be very important but very easy to miss – it depends on how your SQLite viewer of choice shows it to you. I usually use SQLite Database Browser which will show the word “BLOB” for this type of data. I can double-click on this cell and review the data in various formats (Text, Binary, and Image). I use the Binary option for a quick review of embedded binary plists. I can extract this plist and view it in something a tad more analyst friendly like XCode or plutil.

Once extracted I can review the data. I used plutil in this example. It is a serialized NSKeyArchiver plist, with an embedded binary plist (see the hex at beginning 0x62706c69 73743030 = “bplist00”) because why the heck not, yay for binary plist inception! After extracting the hex (again) and saving it as another binary plist I can open it and view its metadata. 

It’s another NSKeyedArchiver plist! For more information on how to parse this type of plist see my article here. It will be worth it, this will provide user context (ie: who is chatting with whom, when, and using which accounts!).

What else do we get on iOS? How about Maps specific data! Was someone driving distracted using many apps? (FWIW I wasn’t, I was a passenger in a car.) The BLOB data for these entries contains geo location based metadata details.

…or some Spotlight interactions?

Safari History

For some reason Safari browsing activity is specifically tracked under its own “type” – /safari/history. Add ‘or ZSTREAMNAME is "/safari/history"’ to the end of the WHERE clause in the previous query to get this information. In this case, the ZVALUESTRING is not a bundle ID but a URL that was visited using Safari. This is another method you can use to attempt to tie a specific user account to application usage. Correlate this data to user Safari history and caches. Similar data will be shown on iOS however the bundle ID will be com.apple.mobilesafari.

Fantastic you might say! Now I have multiple places to look for Safari history entries. Oh, but wait – if the user clears their browsing history, these entries get removed too. Damn….and before you ask – Private Mode browsing doesn’t show up either.

User Correlation

If you’ve read this far, I’ve been focused on the knowledgeC.db database for iOS and the system level context database. Attributing this to specific users may be potentially difficult. On iOS there is really only one “user” but on macOS there could be many. You should also take a look at the user’s knowledgeC.db database to provide context.

It may be possible to correlate entries in each database to determine which user is doing what. Some of the data contains more specific information such as the email or phone number whom they are chatting with or the address searched for in the Maps application. 

This database may also have synced data from associated devices. The items that show a specific iOS version in the OSBUILD column and a GUID in the ZDEVICEID column came from my iPhone X. This includes third-party applications (not shown).

The stream “types” for the ZOBJECT table are /portrait/entity and
/portrait/topic. Items of type “portrait/entity” are fairly self-explanatory, however the “/portrait/topic” are unknown at this time. Each of these start with “Q” in the ZVALUESTRING column. 

The strings in the NAME column are extracted from the email, chat, webpage, app that is being used. Looking at my own data I see everything from names, addresses, contact information, to terms on webpages I’ve viewed, messages I’ve sent, or emails I’ve written.

Still Reading?

I’m impressed! There are many stream “types” that I didn’t get a chance to cover such as the device status and iOS specific entries. I hope to have a Part II out sometime soon!

*Update 08/7/18: The databases presented in this article are for iOS 11 and macOS 10.13. Thanks to a reader, I was informed that the iOS 10 database schema is a bit different but most of the data appears to be the same. the ZEVENT number needs to be paired with the primary key in the ZOBJECT table and the BLOB data in ZSTRUCTUREDMETADATA table is stored in the ZMETADATA column. Thanks to "PN" for the info!