Analiza knowledgeC.db w macOS
Narzędzia
- mac-apt (więcej plików niz tylko knowledgeC.db)
- timeliner (https://github.com/mholt/timeliner)
- calendars (priority: 5)
- icloud - co konkretnie?
- iPhone (priority: 5)
- messages (priority: 0)
Userful resources
- KnowledgeC Complete(ish)
- Knowledge is Power II – A Day in the Life of My iPhone using knowledgeC.db
- KnowledgeC Database Forensics: A Comprehensive Guide
- Accessing / Exporting Apple's Screen Time Data
Wprowadzenie
KnowledgeC.db to baza danych używana przez system macOS do przechowywania informacji o aktywności użytkownika, użyciu aplikacji, oraz innych danych związanych z zachowaniem systemu. Ta baza danych jest częścią mechanizmu, który wspiera funkcje takie jak Siri Suggestions, Screen Time i inne elementy inteligentnego zachowania systemu.
Lokalizacja bazy danych
KnowledgeC.db znajduje się w:
~/Library/Application Support/Knowledge/knowledgeC.dbDostęp do tej bazy danych wymaga uprawnień administratora.
Schemat bazy danych
Główne tabele
Z_4EVENT, columns:
- Z_4CUSTOMMETADATA
- Z_11EVENT
puste w knowledgeC.db firmowy macbook
Z_METADATA, columns:
- Z_VERSION = 1,
- Z_UUID,
- Z_PLIST (jakiś hash)
jeden wiersz
Z_MODELCACHE
- Z_CONTENT (zahashowane tak jak Z_PLIST w Z_METADATA)
jeden wiersz
Z_PRIMARYKEY
- Z_ENT - prawdopodobnie numer encji, od 1 do 16
- Z_NAME - nazwa encji / tabeli(?) - nazwy pokrywają się z nazwami tabel w knowledgeC.db
- Z_SUPER - wartości 0 albo 9
- Z_MAX - rózne wartości od 0 do 18k, count wierszy?
16 wierszy, każdy dla innej encji
ZADDITIONCHANGESET
- Z_PK
- Z_ENT
- Z_OPT
- ZSEQUENCENUMBER
- ZVERSION
- ZENDDATE
- ZSTARTDATE
- ZCKFOREIGNKEY
- ZCKRECORDID
- ZDEVICEIDENTIFIER
- ZCHANGESET (UUID)
- ZCKRECORDSYSTEMFIELDS
pusta tabela w knowledgeC.db firmowy macbook
ZCONTEXTUALCHANGEREGISTRATION
pusta tabela w knowledgeC.db firmowy macbook
ZCONTEXTUALKEYPATH
pusta tabela w knowledgeC.db firmowy macbook
ZCUSTOMMETADATA
pusta tabela w knowledgeC.db firmowy macbook
ZDELETIONCHANGESET
pusta tabela w knowledgeC.db firmowy macbook
ZHISTOGRAM
- Z_PK (1, 2, 3)
- Z_ENT = 6
- Z_OPT = 1
- ZSTREAMTYPECODE = 5907650307545087287
- ZENDDATE
- ZSTARTDATE
- ZCUSTOMIDENTIFIER = _DKDeviceActivityStandingQuery-7-15
- ZDEVICEIDENTIFIER
- ZIDENTIFIER
- ZSTREAMNAME
3 wiersze, kazdy inny ZIDENTIFIER, ten sam ZSTREAMNAME "/activity/level"
ZHISTOGRAMVALUE
- Z_PK (1 - 50)
- Z_ENT = 7
- Z_OPT = 1
- ZINTEGERVALUE
- ZHISTOGRAM (1, 2, 3)
- ZCOUNT (floating number)
- ZSTRINGVALUE (null)
50 wierszy, każdy inny Z_PK, ZHISTOGRAM to 1, 2 lub 3
ZKEYVALUE
- Z_PK (1 - 4)
- Z_ENT = 8
- Z_OPT = (1, 22, 70)
- ZDOMAIN
- ZKEY
- ZVALUE (zahashowane dane binarne)
ZSYNCPEER
pusta tabela w knowledgeC.db firmowy macbook
ZOBJECT - Główna tabela zawierająca informacje o aktywności użytkownika
- Z_PK
- Z_ENT = 11
- Z_OPT = (1, 2)
- ZUUIDHASH
- ZEVENT = null
- ZSOURCE (null, 1, 2, 3)
- ZCATEGORYTYPE = null
- ZINTEGERVALUE = null
- ZCOMPATIBILITYVERSION = 0
- ZENDDAYOFWEEK = 1-7
- ZENDSECONDOFDAY
- ZHASCUSTOMMETADATA = 0
- ZHASSTRUCTUREDMETADATA = (0, 1)
- ZSECONDSFROMGMT
- ZSHOULDSYNC = 0
- ZSTARTDAYOFWEEK = 1-7
- ZSTARTSECONDOFDAY
- ZVALUECLASS = (1, 2)
- ZVALUEINTEGER
- ZVALUETYPECODE
- ZSTRUCTUREDMETADATA
- ZVALUE = null
- Z9_VALUE = null
- ZIDENTIFIERTYPE = null
- ZQUANTITYTYPE = null
- ZCREATIONDATE (np. 769108440.012471)
- ZLOCALCREATIONDATE (np. 769108440.012471)
- ZCONFIDENCE = 1
- ZSTARTDATE (np. 769108413)
- ZENDDATE (np. 769108413)
- ZVALUEDOUBLE
- ZUUID - jakieś UUID, np. 1B2D3E4F-5A6B-7C8D-9E0F-1A2B3C4D5E6F
- ZSTREAMNAME = "app_usage" (lub inne, np. "/activity/level")
- ZVALUESTRING - jakieś UUID, np. 1B2D3E4F-5A6B-7C8D-9E0F-1A2B3C4D5E6F
- ZSTRING = null
- ZMETADATA (UUID) = null
ZSTRUCTUREDMETADATA - Metadane związane z aktywnościami
- Z_PK
- Z_ENT
- Z_OPT
- Z_DKBLUETOOTHMETADATAKEY_BATTERYLEVELHEADPHONELEFT
- Z_DKBLUETOOTHMETADATAKEY_BATTERYLEVELHEADPHONERIGHT
- Z_DKBLUETOOTHMETADATAKEY__DEVICETYPE
- Z_DKBLUETOOTHMETADATAKEY__ISAPPLEAUDIODEVICE
- Z_DKBLUETOOTHMETADATAKEY__ISUSERWEARING
- Z_DKBLUETOOTHMETADATAKEY__PRODUCTID
- Z_DKDIGITALHEALTHMETADATAKEY__ISUSAGETRUSTED
- Z_DKDIGITALHEALTHMETADATAKEY__USAGETYPE
- Z_DKBLUETOOTHMETADATAKEY__ADDRESS
- Z_DKBLUETOOTHMETADATAKEY__NAME
- Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN
- Z_DKNOTIFICATIONUSAGEMETADATAKEY__BUNDLEID
- Z_DKNOTIFICATIONUSAGEMETADATAKEY__IDENTIFIER
- Z_DKTOMBSTONEMETADATAKEY__EVENTSTREAMNAME
- ZMETADATAHASH
- Z_DKAPPMEDIAUSAGEMETADATAKEY__URL
- Z_DKAPPMEDIAUSAGEMETADATAKEY__MEDIAURL
- Z_DKDIGITALHEALTHMETADATAKEY__WEBPAGEURL
ZSOURCE - Źródła danych
- Z_PK
- Z_ENT = 14
- Z_OPT = 1
- ZUSERID = null
- ZBUNDLEID = com.apple.assistantd
- ZDEVICEID = null
- ZGROUPID = null
- ZINTENTID = null
- ZITEMID = null
- ZSOURCEID = null
Entities
| ID | Entity Name |
|---|---|
| 1 | AdditionChangeSet |
| 2 | ContextualChangeRegistration |
| 3 | ContextualKeyPath |
| 4 | CustomMetadata |
| 5 | DeletionChangeSet |
| 6 | Histogram |
| 7 | HistogramValue |
| 8 | KeyValue |
| 9 | Object |
| 10 | Category |
| 11 | Event |
| 12 | Identifier |
| 13 | Quantity |
| 14 | Source |
| 15 | StructuredMetadata |
| 16 | SyncPeer |
Relacje między obiektami
Główne relacje między tabelami:
- ZOBJECTS zawiera klucze obce do innych tabel, takie jak:
- ZSOURCE (źródło danych)
- ZSTRUCTUREDMETADATA (powiązane metadane)
- ZATTRIBUTION (przypisanie do aplikacji)
Kwerendy SQL do analizy
Przykładowe kwerendy SQL do eksploracji bazy danych knowledgeC.db:
-- Wyświetlenie aktywności aplikacji
SELECT
datetime(ZOBJECT.ZSTARTDATE + 978307200, 'UNIXEPOCH', 'LOCALTIME') as "Start",
datetime(ZOBJECT.ZENDDATE + 978307200, 'UNIXEPOCH', 'LOCALTIME') as "End",
ZOBJECT.ZVALUESTRING as "App Name",
(ZOBJECT.ZENDDATE - ZOBJECT.ZSTARTDATE) as "Usage in Seconds"
FROM ZOBJECT
ORDER BY "Start" DESC;
-- 1. PODSTAWOWA ANALIZA AKTYWNOŚCI APLIKACJI
-- Wyświetla aktywność aplikacji posortowaną czasowo
SELECT
datetime(ZOBJECT.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') as start_time,
datetime(ZOBJECT.ZENDDATE + 978307200, 'unixepoch', 'localtime') as end_time,
date(ZOBJECT.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') as activity_date,
strftime('%H:%M:%S', datetime(ZOBJECT.ZSTARTDATE + 978307200, 'unixepoch', 'localtime')) as start_hour,
ZOBJECT.ZVALUESTRING as app_identifier,
ROUND((ZOBJECT.ZENDDATE - ZOBJECT.ZSTARTDATE), 2) as duration_seconds,
ROUND((ZOBJECT.ZENDDATE - ZOBJECT.ZSTARTDATE) / 60.0, 2) as duration_minutes,
ZOBJECT.ZSTREAMNAME as stream_type,
CASE
WHEN ZOBJECT.ZSTREAMNAME = '/app/usage' THEN 'Aplikacja'
WHEN ZOBJECT.ZSTREAMNAME = '/safari/history' THEN 'Strona web'
WHEN ZOBJECT.ZSTREAMNAME = '/activity/level' THEN 'Aktywność fizyczna'
ELSE ZOBJECT.ZSTREAMNAME
END as activity_type
FROM ZOBJECT
WHERE ZOBJECT.ZSTREAMNAME IN ('/app/usage', '/safari/history', '/app/webUsage')
AND ZOBJECT.ZSTARTDATE IS NOT NULL
AND ZOBJECT.ZENDDATE IS NOT NULL
AND date(ZOBJECT.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') = date('now', 'localtime') -- dzisiaj
ORDER BY ZOBJECT.ZSTARTDATE DESC;
-- 2. TIMELINE Z METADANYMI (STRONY INTERNETOWE)
-- Łączy z tabelą metadanych aby uzyskać URL-e stron
SELECT
datetime(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') as start_time,
datetime(obj.ZENDDATE + 978307200, 'unixepoch', 'localtime') as end_time,
date(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') as activity_date,
obj.ZVALUESTRING as app_bundle,
meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN as web_domain,
meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBPAGEURL as web_url,
ROUND((obj.ZENDDATE - obj.ZSTARTDATE) / 60.0, 2) as duration_minutes,
'Strona internetowa' as activity_type
FROM ZOBJECT obj
LEFT JOIN ZSTRUCTUREDMETADATA meta ON obj.ZSTRUCTUREDMETADATA = meta.Z_PK
WHERE obj.ZSTREAMNAME LIKE '%web%'
AND obj.ZSTARTDATE IS NOT NULL
AND obj.ZENDDATE IS NOT NULL
AND date(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') = date('now', 'localtime')
ORDER BY obj.ZSTARTDATE DESC;
-- 3. KOMPLETNY DZIENNY TIMELINE (APLIKACJE + STRONY)
-- Łączy wszystkie typy aktywności w jeden timeline
WITH timeline_data AS (
SELECT
datetime(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') as start_time,
datetime(obj.ZENDDATE + 978307200, 'unixepoch', 'localtime') as end_time,
date(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') as activity_date,
strftime('%H:%M', datetime(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime')) as start_hour,
obj.ZSTARTDATE + 978307200 as unix_start,
obj.ZENDDATE + 978307200 as unix_end,
CASE
WHEN meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN IS NOT NULL
THEN meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN
ELSE obj.ZVALUESTRING
END as activity_name,
COALESCE(meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBPAGEURL, '') as web_url,
ROUND((obj.ZENDDATE - obj.ZSTARTDATE) / 60.0, 2) as duration_minutes,
CASE
WHEN obj.ZSTREAMNAME LIKE '%web%' OR meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN IS NOT NULL
THEN 'Strona internetowa'
WHEN obj.ZSTREAMNAME = '/app/usage' THEN 'Aplikacja'
ELSE 'Inne'
END as activity_type,
obj.ZSTREAMNAME as stream_name
FROM ZOBJECT obj
LEFT JOIN ZSTRUCTUREDMETADATA meta ON obj.ZSTRUCTUREDMETADATA = meta.Z_PK
WHERE obj.ZSTREAMNAME IN ('/app/usage', '/safari/history', '/app/webUsage', '/app/inFocus')
AND obj.ZSTARTDATE IS NOT NULL
AND obj.ZENDDATE IS NOT NULL
AND (obj.ZENDDATE - obj.ZSTARTDATE) > 5 -- tylko sesje dłuższe niż 5 sekund
AND date(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') = date('now', 'localtime')
)
SELECT
start_time,
end_time,
activity_date,
start_hour,
activity_name,
web_url,
duration_minutes,
activity_type,
stream_name,
-- Obliczenie przerw między aktywnościami
LAG(end_time) OVER (ORDER BY unix_start) as previous_end,
CASE
WHEN LAG(unix_end) OVER (ORDER BY unix_start) IS NOT NULL
THEN ROUND((unix_start - LAG(unix_end) OVER (ORDER BY unix_start)) / 60.0, 1)
ELSE 0
END as gap_minutes
FROM timeline_data
ORDER BY unix_start;
-- 4. STATYSTYKI DZIENNE - PODSUMOWANIE AKTYWNOŚCI
-- Agreguje czas spędzony w aplikacjach i na stronach
SELECT
activity_type,
COUNT(*) as sessions_count,
ROUND(SUM(duration_minutes), 2) as total_minutes,
ROUND(SUM(duration_minutes) / 60.0, 2) as total_hours,
ROUND(AVG(duration_minutes), 2) as avg_session_minutes,
MIN(start_time) as first_activity,
MAX(end_time) as last_activity
FROM (
SELECT
datetime(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') as start_time,
datetime(obj.ZENDDATE + 978307200, 'unixepoch', 'localtime') as end_time,
ROUND((obj.ZENDDATE - obj.ZSTARTDATE) / 60.0, 2) as duration_minutes,
CASE
WHEN obj.ZSTREAMNAME LIKE '%web%' OR meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN IS NOT NULL
THEN 'Strony internetowe'
WHEN obj.ZSTREAMNAME = '/app/usage' THEN 'Aplikacje'
ELSE 'Inne'
END as activity_type
FROM ZOBJECT obj
LEFT JOIN ZSTRUCTUREDMETADATA meta ON obj.ZSTRUCTUREDMETADATA = meta.Z_PK
WHERE obj.ZSTREAMNAME IN ('/app/usage', '/safari/history', '/app/webUsage', '/app/inFocus')
AND obj.ZSTARTDATE IS NOT NULL
AND obj.ZENDDATE IS NOT NULL
AND (obj.ZENDDATE - obj.ZSTARTDATE) > 5
AND date(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') = date('now', 'localtime')
) as daily_stats
GROUP BY activity_type
ORDER BY total_minutes DESC;
-- 5. TOP APLIKACJE I STRONY DZISIAJ
-- Pokazuje najczęściej używane aplikacje i strony
SELECT
activity_name,
activity_type,
COUNT(*) as sessions,
ROUND(SUM(duration_minutes), 2) as total_minutes,
ROUND(AVG(duration_minutes), 2) as avg_session_minutes,
MIN(start_time) as first_use,
MAX(end_time) as last_use
FROM (
SELECT
datetime(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') as start_time,
datetime(obj.ZENDDATE + 978307200, 'unixepoch', 'localtime') as end_time,
CASE
WHEN meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN IS NOT NULL
THEN meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN
ELSE obj.ZVALUESTRING
END as activity_name,
ROUND((obj.ZENDDATE - obj.ZSTARTDATE) / 60.0, 2) as duration_minutes,
CASE
WHEN obj.ZSTREAMNAME LIKE '%web%' OR meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN IS NOT NULL
THEN 'Strona'
WHEN obj.ZSTREAMNAME = '/app/usage' THEN 'Aplikacja'
ELSE 'Inne'
END as activity_type
FROM ZOBJECT obj
LEFT JOIN ZSTRUCTUREDMETADATA meta ON obj.ZSTRUCTUREDMETADATA = meta.Z_PK
WHERE obj.ZSTREAMNAME IN ('/app/usage', '/safari/history', '/app/webUsage', '/app/inFocus')
AND obj.ZSTARTDATE IS NOT NULL
AND obj.ZENDDATE IS NOT NULL
AND (obj.ZENDDATE - obj.ZSTARTDATE) > 5
AND date(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') = date('now', 'localtime')
) as app_stats
WHERE activity_name IS NOT NULL
GROUP BY activity_name, activity_type
HAVING total_minutes > 1 -- tylko aktywności dłuższe niż 1 minuta
ORDER BY total_minutes DESC
LIMIT 20;
-- 6. TIMELINE GODZINOWY - AKTYWNOŚĆ W CIĄGU DNIA
-- Pokazuje rozkład aktywności w poszczególnych godzinach
SELECT
strftime('%H:00', datetime(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime')) as hour_slot,
COUNT(*) as activities_count,
ROUND(SUM((obj.ZENDDATE - obj.ZSTARTDATE) / 60.0), 2) as total_minutes,
COUNT(DISTINCT
CASE
WHEN meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN IS NOT NULL
THEN meta.Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN
ELSE obj.ZVALUESTRING
END
) as unique_apps_sites
FROM ZOBJECT obj
LEFT JOIN ZSTRUCTUREDMETADATA meta ON obj.ZSTRUCTUREDMETADATA = meta.Z_PK
WHERE obj.ZSTREAMNAME IN ('/app/usage', '/safari/history', '/app/webUsage', '/app/inFocus')
AND obj.ZSTARTDATE IS NOT NULL
AND obj.ZENDDATE IS NOT NULL
AND (obj.ZENDDATE - obj.ZSTARTDATE) > 5
AND date(obj.ZSTARTDATE + 978307200, 'unixepoch', 'localtime') = date('now', 'localtime')
GROUP BY hour_slot
ORDER BY hour_slot;Dalsze kroki analizy
- Zbadanie pełnego schematu bazy danych (wszystkich tabel i ich pól)
- Identyfikacja typów danych przechowywanych w każdej tabeli
- Analiza relacji między tabelami przy użyciu kluczy obcych
- Rozpoznanie formatów danych używanych do przechowywania różnych typów informacji
- Eksperymentowanie z kwerendami SQL dla wydobycia konkretnych informacji
Narzędzia do analizy
- SQLite Browser - graficzny interfejs do przeglądania baz danych SQLite
- Terminal z poleceniem
sqlite3- bezpośredni dostęp przez linię komend - Python z biblioteką sqlite3 - programistyczna analiza i wydobycie danych
Uwagi
- Baza knowledgeC.db używa epoki czasu macOS (różnica 978307200 sekund od epoki Unixa)
- Niektóre pola zawierają dane binarne, które mogą wymagać specjalnego dekodowania
- Pełna analiza może wymagać odniesienia do dokumentacji Apple Developer lub inżynierii wstecznej