Building forensic timelines from sysdiagnose artifacts. This guide covers techniques for reconstructing user activity and system events.
| Source | Data | Time Resolution |
|---|---|---|
| Unified logs | System events | Milliseconds |
| TCC.db | Permission changes | Seconds |
| PowerLog | Battery, usage | Seconds |
| WiFi CSVs | Network joins | Seconds |
| Crash reports | Crashes | Seconds |
| ps.txt | Process state | Snapshot only |
Seconds since 1970-01-01 00:00:00 UTC.
# Convert in SQLite
datetime(timestamp, 'unixepoch', 'localtime')
# Convert in bash
date -r 1732000000Seconds since 2001-01-01 00:00:00 UTC.
# Convert in SQLite
datetime(timestamp + 978307200, 'unixepoch', 'localtime')Human-readable format in logs.
2025-11-22 19:30:00.123456-0500
# Get archive time from filename
# sysdiagnose_2025.11.22_19-36-23-0500_*
# Captured at: 2025-11-22 19:36:23 EST
# Typically want 24-48 hours before capture
START="2025-11-21 00:00:00"
END="2025-11-22 19:36:23"log show --archive system_logs.logarchive \
--start "$START" --end "$END" \
--style json > /tmp/logs.jsonsqlite3 logs/Accessibility/TCC.db "
SELECT
'TCC' as source,
datetime(last_modified, 'unixepoch', 'localtime') as time,
service || ' -> ' || client as event,
auth_value as detail
FROM access
ORDER BY last_modified
" > /tmp/tcc_events.csvsqlite3 logs/powerlogs/*.PLSQL "
SELECT
'Battery' as source,
datetime(timestamp, 'unixepoch', 'localtime') as time,
'Level: ' || CAST(Level * 100 AS INTEGER) || '%' as event,
IsCharging as detail
FROM PLBatteryAgent_EventBackward_Battery
ORDER BY timestamp
" > /tmp/battery_events.csv# Extract CSV
tar -xzf WiFi/Entity_*_Join.csv.tgz -C /tmp/
# Format events
awk -F',' 'NR>1 {
cmd = "date -r " $1 " \"+%Y-%m-%d %H:%M:%S\""
cmd | getline time
close(cmd)
print "WiFi," time ",Join: " $2 ","
}' /tmp/Join.csv > /tmp/wifi_events.csv# Combine all sources
cat /tmp/tcc_events.csv /tmp/battery_events.csv /tmp/wifi_events.csv | \
sort -t',' -k2 > /tmp/timeline.csvsqlite3 logs/powerlogs/*.PLSQL "
SELECT
datetime(timestamp, 'unixepoch', 'localtime') as time,
BundleID,
ScreenOnTime as foreground_seconds
FROM PLAppTimeService_Aggregate_AppRunTime
WHERE ScreenOnTime > 60
ORDER BY timestamp
"log show --archive system_logs.logarchive \
--predicate 'process == "locationd" AND eventMessage CONTAINS "location"' \
--style json | \
grep -o '"timestamp" *: *"[^"]*"' | \
cut -d'"' -f4 | sortsqlite3 logs/powerlogs/*.PLSQL "
SELECT
datetime(timestamp, 'unixepoch', 'localtime') as time,
CASE State WHEN 0 THEN 'Screen OFF' ELSE 'Screen ON' END as event
FROM PLDisplayAgent_EventForward_Display
ORDER BY timestamp
"log show --archive system_logs.logarchive \
--predicate '
process == "identityservicesd" OR
process == "imagent" OR
process == "callservicesd"
' \
--style json | \
grep -E '"timestamp"|"eventMessage"' | \
head -200- Find interesting time: Identify a key event
- Window query: Get events ±5 minutes around it
- Multi-source: Query all sources for that window
# Found crash at 19:29:43
# Query logs around that time
log show --archive system_logs.logarchive \
--start "2025-11-22 19:25:00" \
--end "2025-11-22 19:35:00" \
--predicate 'messageType == error OR messageType == fault' \
--style jsonTrack causation across events:
- App launch → TCC prompt → Permission grant → Feature use
- WiFi join → Location update → App activity
- Push notification → App launch → Network activity
Find periods of no activity:
sqlite3 logs/powerlogs/*.PLSQL "
SELECT
datetime(timestamp, 'unixepoch', 'localtime') as time,
CAST((timestamp - LAG(timestamp) OVER (ORDER BY timestamp)) / 60 AS INTEGER) as gap_minutes
FROM PLBatteryAgent_EventBackward_Battery
WHERE gap_minutes > 30
ORDER BY timestamp
"#!/bin/bash
# generate_timeline.sh
ARCHIVE="$1"
OUTPUT="timeline.csv"
echo "timestamp,source,event,detail" > "$OUTPUT"
# TCC events
sqlite3 "$ARCHIVE/logs/Accessibility/TCC.db" "
SELECT last_modified, 'TCC', service || ' for ' || client,
CASE auth_value WHEN 2 THEN 'allowed' ELSE 'denied' END
FROM access" >> "$OUTPUT"
# Battery events
sqlite3 "$ARCHIVE/logs/powerlogs/"*.PLSQL "
SELECT timestamp, 'Battery',
'Level: ' || CAST(Level * 100 AS INTEGER) || '%',
CASE IsCharging WHEN 1 THEN 'charging' ELSE '' END
FROM PLBatteryAgent_EventBackward_Battery" >> "$OUTPUT"
# Sort by timestamp
sort -t',' -k1 -n "$OUTPUT" -o "$OUTPUT"
echo "Timeline written to $OUTPUT"#!/usr/bin/env python3
# view_timeline.py
import sqlite3
import sys
from datetime import datetime
def format_time(ts):
return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
def main(archive_path):
# TCC events
tcc_db = f"{archive_path}/logs/Accessibility/TCC.db"
conn = sqlite3.connect(tcc_db)
cursor = conn.execute("""
SELECT last_modified, service, client, auth_value
FROM access ORDER BY last_modified
""")
for row in cursor:
ts, service, client, auth = row
status = "ALLOWED" if auth == 2 else "DENIED"
print(f"{format_time(ts)} [TCC] {service} -> {client}: {status}")
conn.close()
if __name__ == "__main__":
main(sys.argv[1])# Simple timeline bars
sqlite3 logs/powerlogs/*.PLSQL "
SELECT
strftime('%H', datetime(timestamp, 'unixepoch', 'localtime')) as hour,
COUNT(*) as events
FROM PLBatteryAgent_EventBackward_Battery
GROUP BY hour
" | while read line; do
hour=$(echo "$line" | cut -d'|' -f1)
count=$(echo "$line" | cut -d'|' -f2)
bar=$(printf '%*s' "$((count/10))" | tr ' ' '#')
echo "$hour: $bar ($count)"
done# CSV for Excel/Sheets
sqlite3 -header -csv logs/powerlogs/*.PLSQL "
SELECT
datetime(timestamp, 'unixepoch') as time,
Level,
IsCharging
FROM PLBatteryAgent_EventBackward_Battery
" > battery_timeline.csv
# JSON for web tools
sqlite3 -json logs/powerlogs/*.PLSQL "..." > timeline.json19:00 - Screen ON
19:00 - App launch (Messages)
19:01 - iMessage activity
19:05 - App switch (Safari)
19:10 - Location request
19:15 - Screen OFF
19:30 - App launch (Camera app)
19:30 - TCC prompt (Camera)
19:30 - User grants permission
19:31 - Camera active
19:35 - Photo saved
08:00 - WiFi disconnect (home)
08:05 - Cellular connect
08:30 - WiFi connect (office)
08:31 - Location update
- Start with battery - Battery events are reliable time anchors
- Use screen state - Screen ON/OFF brackets user interaction
- Correlate crashes - Crash timestamps often reveal issues
- Watch for gaps - Missing data may indicate device off or issues
- Timezone awareness - Convert everything to consistent timezone
- delta-comparison.md - Comparing archives
- common-queries.md - Log queries
- ../power/powerlog.md - PowerLog data
- ../privacy/tcc.md - TCC events