770 lines
23 KiB
Markdown
770 lines
23 KiB
Markdown
---
|
|
title: "SQL in der digitalen Forensik: Von SQLite-Datenbanken zur Timeline-Analyse"
|
|
description: "Umfassender Leitfaden für SQL-basierte Forensik-Analysen: SQLite-Datenbanken untersuchen, Timeline-Rekonstruktion durchführen, mobile App-Daten analysieren und komplexe Korrelationen aufdecken."
|
|
author: "Claude 4 Sonnett (Prompt: Mario Stöckl)"
|
|
last_updated: 2025-08-10
|
|
difficulty: intermediate
|
|
categories: ["analysis", "configuration", "case-study"]
|
|
tags: ["sqlite-viewer", "correlation-engine", "mobile-app-data", "browser-history", "data-extraction", "timeline-queries", "join-operations", "aggregate-analysis", "wal-analysis", "python-integration"]
|
|
tool_name: "SQL"
|
|
related_tools: ["DB Browser for SQLite", "Autopsy", "Cellebrite UFED"]
|
|
published: true
|
|
---
|
|
|
|
# SQL in der digitalen Forensik: Von SQLite-Datenbanken zur Timeline-Analyse
|
|
|
|
SQL (Structured Query Language) ist eine der mächtigsten und unterschätztesten Fähigkeiten in der modernen digitalen Forensik. Während viele Ermittler auf GUI-basierte Tools setzen, ermöglicht SQL direkten Zugriff auf Rohdaten und komplexe Analysen, die mit herkömmlichen Tools unmöglich wären.
|
|
|
|
## Warum SQL in der Forensik unverzichtbar ist
|
|
|
|
### SQLite dominiert die mobile Forensik
|
|
- **WhatsApp-Chats**: Nachrichten, Metadaten, gelöschte Inhalte
|
|
- **Browser-History**: Zeitstempel, Besuchshäufigkeit, Suchverläufe
|
|
- **App-Daten**: Standortdaten, Nutzerverhalten, Cache-Inhalte
|
|
- **System-Logs**: Verbindungsprotokoll, Fehleraufzeichnungen
|
|
|
|
### Vorteile gegenüber GUI-Tools
|
|
- **Flexibilität**: Komplexe Abfragen jenseits vordefinierter Filter
|
|
- **Performance**: Direkte Datenbankzugriffe ohne Interface-Overhead
|
|
- **Automatisierung**: Skript-basierte Analysen für wiederkehrende Aufgaben
|
|
- **Tiefe**: Zugriff auf Metadaten und versteckte Tabellenstrukturen
|
|
|
|
## Grundlagen: SQLite-Struktur verstehen
|
|
|
|
### Datenbank-Anatomie in der Forensik
|
|
|
|
```sql
|
|
-- Tabellen einer WhatsApp-Datenbank analysieren
|
|
.tables
|
|
|
|
-- Tabellenstruktur untersuchen
|
|
.schema messages
|
|
|
|
-- Beispiel-Output:
|
|
CREATE TABLE messages (
|
|
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
key_remote_jid TEXT,
|
|
key_from_me INTEGER,
|
|
key_id TEXT,
|
|
status INTEGER,
|
|
needs_push INTEGER,
|
|
data TEXT,
|
|
timestamp INTEGER,
|
|
media_url TEXT,
|
|
media_mime_type TEXT,
|
|
media_wa_type INTEGER,
|
|
media_size INTEGER,
|
|
latitude REAL,
|
|
longitude REAL
|
|
);
|
|
```
|
|
|
|
### SQLite-spezifische Forensik-Herausforderungen
|
|
|
|
**WAL-Mode (Write-Ahead Logging)**:
|
|
```sql
|
|
-- WAL-Datei auf nicht-committete Transaktionen prüfen
|
|
PRAGMA journal_mode;
|
|
|
|
-- Temporäre Daten in WAL-Datei finden
|
|
-- (Erfordert spezielle Tools wie sqlitewalreader)
|
|
```
|
|
|
|
**Gelöschte Records**:
|
|
```sql
|
|
-- Freespace-Analyse für gelöschte Daten
|
|
-- Hinweis: Erfordert spezialisierte Recovery-Tools
|
|
```
|
|
|
|
## Timeline-Rekonstruktion: Der Forensik-Klassiker
|
|
|
|
### Grundlegende Timeline-Abfrage
|
|
|
|
```sql
|
|
-- Chronologische Ereignisübersicht erstellen
|
|
SELECT
|
|
datetime(timestamp/1000, 'unixepoch', 'localtime') as ereignis_zeit,
|
|
CASE
|
|
WHEN key_from_me = 1 THEN 'Ausgehend'
|
|
ELSE 'Eingehend'
|
|
END as richtung,
|
|
key_remote_jid as kontakt,
|
|
substr(data, 1, 50) || '...' as nachricht_preview
|
|
FROM messages
|
|
WHERE timestamp > 0
|
|
ORDER BY timestamp DESC
|
|
LIMIT 100;
|
|
```
|
|
|
|
### Erweiterte Timeline mit Kontextinformationen
|
|
|
|
```sql
|
|
-- Timeline mit Geolocation und Media-Daten
|
|
SELECT
|
|
datetime(m.timestamp/1000, 'unixepoch', 'localtime') as zeitstempel,
|
|
c.display_name as kontakt_name,
|
|
CASE
|
|
WHEN m.key_from_me = 1 THEN '→ Gesendet'
|
|
ELSE '← Empfangen'
|
|
END as richtung,
|
|
CASE
|
|
WHEN m.media_wa_type IS NOT NULL THEN 'Media: ' || m.media_mime_type
|
|
ELSE 'Text'
|
|
END as nachricht_typ,
|
|
CASE
|
|
WHEN m.latitude IS NOT NULL THEN
|
|
'Standort: ' || ROUND(m.latitude, 6) || ', ' || ROUND(m.longitude, 6)
|
|
ELSE substr(m.data, 1, 100)
|
|
END as inhalt
|
|
FROM messages m
|
|
LEFT JOIN wa_contacts c ON m.key_remote_jid = c.jid
|
|
WHERE m.timestamp BETWEEN
|
|
strftime('%s', '2024-01-01') * 1000 AND
|
|
strftime('%s', '2024-01-31') * 1000
|
|
ORDER BY m.timestamp;
|
|
```
|
|
|
|
## Kommunikations-Analyse: Soziale Netzwerke aufdecken
|
|
|
|
### Häufigste Kontakte identifizieren
|
|
|
|
```sql
|
|
-- Top-Kommunikationspartner nach Nachrichtenvolumen
|
|
SELECT
|
|
c.display_name,
|
|
m.key_remote_jid,
|
|
COUNT(*) as nachrichten_gesamt,
|
|
SUM(CASE WHEN m.key_from_me = 1 THEN 1 ELSE 0 END) as gesendet,
|
|
SUM(CASE WHEN m.key_from_me = 0 THEN 1 ELSE 0 END) as empfangen,
|
|
MIN(datetime(m.timestamp/1000, 'unixepoch', 'localtime')) as erster_kontakt,
|
|
MAX(datetime(m.timestamp/1000, 'unixepoch', 'localtime')) as letzter_kontakt
|
|
FROM messages m
|
|
LEFT JOIN wa_contacts c ON m.key_remote_jid = c.jid
|
|
GROUP BY m.key_remote_jid
|
|
HAVING nachrichten_gesamt > 10
|
|
ORDER BY nachrichten_gesamt DESC;
|
|
```
|
|
|
|
### Kommunikationsmuster-Analyse
|
|
|
|
```sql
|
|
-- Tägliche Aktivitätsmuster
|
|
SELECT
|
|
strftime('%H', timestamp/1000, 'unixepoch', 'localtime') as stunde,
|
|
COUNT(*) as nachrichten_anzahl,
|
|
AVG(length(data)) as durchschnittliche_laenge
|
|
FROM messages
|
|
WHERE timestamp > 0 AND data IS NOT NULL
|
|
GROUP BY stunde
|
|
ORDER BY stunde;
|
|
```
|
|
|
|
```sql
|
|
-- Verdächtige Aktivitätsspitzen identifizieren
|
|
WITH hourly_stats AS (
|
|
SELECT
|
|
date(timestamp/1000, 'unixepoch', 'localtime') as tag,
|
|
strftime('%H', timestamp/1000, 'unixepoch', 'localtime') as stunde,
|
|
COUNT(*) as nachrichten_pro_stunde
|
|
FROM messages
|
|
WHERE timestamp > 0
|
|
GROUP BY tag, stunde
|
|
),
|
|
avg_per_hour AS (
|
|
SELECT stunde, AVG(nachrichten_pro_stunde) as durchschnitt
|
|
FROM hourly_stats
|
|
GROUP BY stunde
|
|
)
|
|
SELECT
|
|
h.tag,
|
|
h.stunde,
|
|
h.nachrichten_pro_stunde,
|
|
a.durchschnitt,
|
|
ROUND((h.nachrichten_pro_stunde - a.durchschnitt) / a.durchschnitt * 100, 2) as abweichung_prozent
|
|
FROM hourly_stats h
|
|
JOIN avg_per_hour a ON h.stunde = a.stunde
|
|
WHERE h.nachrichten_pro_stunde > a.durchschnitt * 2
|
|
ORDER BY abweichung_prozent DESC;
|
|
```
|
|
|
|
## Browser-Forensik: Digitale Spuren verfolgen
|
|
|
|
### Chrome/Chromium History-Analyse
|
|
|
|
```sql
|
|
-- Browser-History mit Besuchshäufigkeit
|
|
SELECT
|
|
url,
|
|
title,
|
|
visit_count,
|
|
datetime(last_visit_time/1000000-11644473600, 'unixepoch', 'localtime') as letzter_besuch,
|
|
CASE
|
|
WHEN typed_count > 0 THEN 'Direkt eingegeben'
|
|
ELSE 'Über Link/Verlauf'
|
|
END as zugriff_art
|
|
FROM urls
|
|
WHERE last_visit_time > 0
|
|
ORDER BY last_visit_time DESC
|
|
LIMIT 100;
|
|
```
|
|
|
|
### Such-Verlauf analysieren
|
|
|
|
```sql
|
|
-- Google-Suchen aus Browser-History extrahieren
|
|
SELECT
|
|
datetime(last_visit_time/1000000-11644473600, 'unixepoch', 'localtime') as suchzeit,
|
|
CASE
|
|
WHEN url LIKE '%google.com/search%' THEN
|
|
replace(substr(url, instr(url, 'q=') + 2,
|
|
case when instr(substr(url, instr(url, 'q=') + 2), '&') > 0
|
|
then instr(substr(url, instr(url, 'q=') + 2), '&') - 1
|
|
else length(url) end), '+', ' ')
|
|
ELSE 'Andere Suchmaschine'
|
|
END as suchbegriff,
|
|
url
|
|
FROM urls
|
|
WHERE url LIKE '%search%' OR url LIKE '%q=%'
|
|
ORDER BY last_visit_time DESC;
|
|
```
|
|
|
|
## Anomalie-Erkennung mit SQL
|
|
|
|
### Ungewöhnliche Datei-Zugriffe identifizieren
|
|
|
|
```sql
|
|
-- Dateizugriffe außerhalb der Arbeitszeiten
|
|
WITH file_access AS (
|
|
SELECT
|
|
datetime(timestamp, 'unixepoch', 'localtime') as zugriffszeit,
|
|
strftime('%H', timestamp, 'unixepoch', 'localtime') as stunde,
|
|
strftime('%w', timestamp, 'unixepoch', 'localtime') as wochentag,
|
|
file_path,
|
|
action_type
|
|
FROM file_access_logs
|
|
)
|
|
SELECT *
|
|
FROM file_access
|
|
WHERE (
|
|
stunde < '08' OR stunde > '18' OR -- Außerhalb 8-18 Uhr
|
|
wochentag IN ('0', '6') -- Wochenende
|
|
) AND action_type IN ('read', 'write', 'delete')
|
|
ORDER BY zugriffszeit DESC;
|
|
```
|
|
|
|
### Datenexfiltration-Indikatoren
|
|
|
|
```sql
|
|
-- Große Dateiübertragungen in kurzen Zeiträumen
|
|
SELECT
|
|
datetime(transfer_start, 'unixepoch', 'localtime') as start_zeit,
|
|
SUM(file_size) as gesamt_bytes,
|
|
COUNT(*) as anzahl_dateien,
|
|
destination_ip,
|
|
GROUP_CONCAT(DISTINCT file_extension) as dateitypen
|
|
FROM network_transfers
|
|
WHERE transfer_start BETWEEN
|
|
strftime('%s', 'now', '-7 days') AND strftime('%s', 'now')
|
|
GROUP BY
|
|
date(transfer_start, 'unixepoch', 'localtime'),
|
|
strftime('%H', transfer_start, 'unixepoch', 'localtime'),
|
|
destination_ip
|
|
HAVING gesamt_bytes > 100000000 -- > 100MB
|
|
ORDER BY gesamt_bytes DESC;
|
|
```
|
|
|
|
## Erweiterte Techniken: Window Functions und CTEs
|
|
|
|
### Sliding Window-Analyse für Ereigniskorrelation
|
|
|
|
```sql
|
|
-- Ereignisse in 5-Minuten-Fenstern korrelieren
|
|
WITH event_windows AS (
|
|
SELECT
|
|
datetime(timestamp, 'unixepoch', 'localtime') as ereigniszeit,
|
|
event_type,
|
|
user_id,
|
|
LAG(timestamp, 1) OVER (PARTITION BY user_id ORDER BY timestamp) as prev_timestamp,
|
|
LEAD(timestamp, 1) OVER (PARTITION BY user_id ORDER BY timestamp) as next_timestamp
|
|
FROM security_events
|
|
ORDER BY timestamp
|
|
)
|
|
SELECT
|
|
ereigniszeit,
|
|
event_type,
|
|
user_id,
|
|
CASE
|
|
WHEN (timestamp - prev_timestamp) < 300 THEN 'Schnelle Aufeinanderfolge'
|
|
WHEN (next_timestamp - timestamp) < 300 THEN 'Vor schnellem Event'
|
|
ELSE 'Isoliert'
|
|
END as ereignis_kontext
|
|
FROM event_windows;
|
|
```
|
|
|
|
### Temporäre Anomalie-Scores
|
|
|
|
```sql
|
|
-- Anomalie-Score basierend auf Abweichung vom Normalverhalten
|
|
WITH user_baseline AS (
|
|
SELECT
|
|
user_id,
|
|
AVG(daily_logins) as avg_logins,
|
|
STDEV(daily_logins) as stddev_logins
|
|
FROM (
|
|
SELECT
|
|
user_id,
|
|
date(login_time, 'unixepoch', 'localtime') as login_date,
|
|
COUNT(*) as daily_logins
|
|
FROM user_logins
|
|
WHERE login_time > strftime('%s', 'now', '-30 days')
|
|
GROUP BY user_id, login_date
|
|
)
|
|
GROUP BY user_id
|
|
HAVING COUNT(*) > 7 -- Mindestens 7 Tage Daten
|
|
),
|
|
current_behavior AS (
|
|
SELECT
|
|
user_id,
|
|
date(login_time, 'unixepoch', 'localtime') as login_date,
|
|
COUNT(*) as daily_logins
|
|
FROM user_logins
|
|
WHERE login_time > strftime('%s', 'now', '-7 days')
|
|
GROUP BY user_id, login_date
|
|
)
|
|
SELECT
|
|
c.user_id,
|
|
c.login_date,
|
|
c.daily_logins,
|
|
b.avg_logins,
|
|
ROUND(ABS(c.daily_logins - b.avg_logins) / b.stddev_logins, 2) as anomalie_score
|
|
FROM current_behavior c
|
|
JOIN user_baseline b ON c.user_id = b.user_id
|
|
WHERE anomalie_score > 2.0 -- Mehr als 2 Standardabweichungen
|
|
ORDER BY anomalie_score DESC;
|
|
```
|
|
|
|
## Python-Integration für Automatisierung
|
|
|
|
### SQLite-Forensik mit Python
|
|
|
|
```python
|
|
import sqlite3
|
|
import pandas as pd
|
|
from datetime import datetime
|
|
import matplotlib.pyplot as plt
|
|
|
|
class ForensicSQLAnalyzer:
|
|
def __init__(self, db_path):
|
|
self.conn = sqlite3.connect(db_path)
|
|
self.conn.row_factory = sqlite3.Row
|
|
|
|
def extract_timeline(self, start_date=None, end_date=None):
|
|
"""Timeline-Extraktion mit Datumsfilterung"""
|
|
query = """
|
|
SELECT
|
|
datetime(timestamp/1000, 'unixepoch', 'localtime') as timestamp,
|
|
event_type,
|
|
details,
|
|
user_context
|
|
FROM events
|
|
WHERE 1=1
|
|
"""
|
|
|
|
params = []
|
|
if start_date:
|
|
query += " AND timestamp >= ?"
|
|
params.append(int(start_date.timestamp() * 1000))
|
|
if end_date:
|
|
query += " AND timestamp <= ?"
|
|
params.append(int(end_date.timestamp() * 1000))
|
|
|
|
query += " ORDER BY timestamp"
|
|
|
|
return pd.read_sql_query(query, self.conn, params=params)
|
|
|
|
def communication_analysis(self):
|
|
"""Kommunikationsmuster analysieren"""
|
|
query = """
|
|
SELECT
|
|
contact_id,
|
|
COUNT(*) as message_count,
|
|
AVG(message_length) as avg_length,
|
|
MIN(timestamp) as first_contact,
|
|
MAX(timestamp) as last_contact
|
|
FROM messages
|
|
GROUP BY contact_id
|
|
HAVING message_count > 5
|
|
ORDER BY message_count DESC
|
|
"""
|
|
|
|
return pd.read_sql_query(query, self.conn)
|
|
|
|
def detect_anomalies(self, threshold=2.0):
|
|
"""Statistische Anomalie-Erkennung"""
|
|
query = """
|
|
WITH daily_stats AS (
|
|
SELECT
|
|
date(timestamp, 'unixepoch', 'localtime') as day,
|
|
COUNT(*) as daily_events
|
|
FROM events
|
|
GROUP BY day
|
|
),
|
|
stats AS (
|
|
SELECT
|
|
AVG(daily_events) as mean_events,
|
|
STDEV(daily_events) as stddev_events
|
|
FROM daily_stats
|
|
)
|
|
SELECT
|
|
d.day,
|
|
d.daily_events,
|
|
s.mean_events,
|
|
ABS(d.daily_events - s.mean_events) / s.stddev_events as z_score
|
|
FROM daily_stats d, stats s
|
|
WHERE z_score > ?
|
|
ORDER BY z_score DESC
|
|
"""
|
|
|
|
return pd.read_sql_query(query, self.conn, params=[threshold])
|
|
|
|
def export_findings(self, filename):
|
|
"""Ermittlungsergebnisse exportieren"""
|
|
timeline = self.extract_timeline()
|
|
comms = self.communication_analysis()
|
|
anomalies = self.detect_anomalies()
|
|
|
|
with pd.ExcelWriter(filename) as writer:
|
|
timeline.to_excel(writer, sheet_name='Timeline', index=False)
|
|
comms.to_excel(writer, sheet_name='Communications', index=False)
|
|
anomalies.to_excel(writer, sheet_name='Anomalies', index=False)
|
|
|
|
# Verwendung
|
|
analyzer = ForensicSQLAnalyzer('/path/to/evidence.db')
|
|
findings = analyzer.export_findings('investigation_findings.xlsx')
|
|
```
|
|
|
|
## Häufige Fallstricke und Best Practices
|
|
|
|
### Datenintegrität sicherstellen
|
|
|
|
```sql
|
|
-- Konsistenz-Checks vor Analyse
|
|
SELECT
|
|
'Null Timestamps' as issue_type,
|
|
COUNT(*) as count
|
|
FROM messages
|
|
WHERE timestamp IS NULL OR timestamp = 0
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
'Missing Contact Info' as issue_type,
|
|
COUNT(*) as count
|
|
FROM messages m
|
|
LEFT JOIN wa_contacts c ON m.key_remote_jid = c.jid
|
|
WHERE c.jid IS NULL;
|
|
```
|
|
|
|
### Performance-Optimierung
|
|
|
|
```sql
|
|
-- Index für häufige Abfragen erstellen
|
|
CREATE INDEX IF NOT EXISTS idx_messages_timestamp
|
|
ON messages(timestamp);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_contact_timestamp
|
|
ON messages(key_remote_jid, timestamp);
|
|
|
|
-- Query-Performance analysieren
|
|
EXPLAIN QUERY PLAN
|
|
SELECT * FROM messages
|
|
WHERE timestamp BETWEEN ? AND ?
|
|
ORDER BY timestamp;
|
|
```
|
|
|
|
### Forensische Dokumentation
|
|
|
|
```sql
|
|
-- Metadaten für Gerichtsverwertbarkeit dokumentieren
|
|
SELECT
|
|
'Database Schema Version' as info_type,
|
|
user_version as value
|
|
FROM pragma_user_version
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
'Last Modified',
|
|
datetime(mtime, 'unixepoch', 'localtime')
|
|
FROM pragma_file_control;
|
|
```
|
|
|
|
## Spezialisierte Forensik-Szenarien
|
|
|
|
### Mobile App-Forensik: Instagram-Datenbank
|
|
|
|
```sql
|
|
-- Instagram-Nachrichten mit Medien-Metadaten
|
|
SELECT
|
|
datetime(m.timestamp/1000, 'unixepoch', 'localtime') as nachricht_zeit,
|
|
u.username as absender,
|
|
CASE
|
|
WHEN m.item_type = 1 THEN 'Text: ' || m.text
|
|
WHEN m.item_type = 2 THEN 'Bild: ' || mi.media_url
|
|
WHEN m.item_type = 3 THEN 'Video: ' || mi.media_url
|
|
ELSE 'Anderer Typ: ' || m.item_type
|
|
END as inhalt,
|
|
m.thread_key as chat_id
|
|
FROM direct_messages m
|
|
LEFT JOIN users u ON m.user_id = u.pk
|
|
LEFT JOIN media_items mi ON m.media_id = mi.id
|
|
WHERE m.timestamp > 0
|
|
ORDER BY m.timestamp DESC;
|
|
```
|
|
|
|
### Incident Response: Systemprotokoll-Korrelation
|
|
|
|
```sql
|
|
-- Korrelation zwischen Login-Events und Netzwerk-Aktivität
|
|
WITH suspicious_logins AS (
|
|
SELECT
|
|
login_time,
|
|
user_id,
|
|
source_ip,
|
|
login_time + 3600 as investigation_window -- 1 Stunde nach Login
|
|
FROM login_events
|
|
WHERE source_ip NOT LIKE '192.168.%' -- Externe IPs
|
|
AND login_time > strftime('%s', 'now', '-7 days')
|
|
),
|
|
network_activity AS (
|
|
SELECT
|
|
connection_time,
|
|
source_ip,
|
|
destination_ip,
|
|
bytes_transferred,
|
|
protocol
|
|
FROM network_connections
|
|
)
|
|
SELECT
|
|
datetime(sl.login_time, 'unixepoch', 'localtime') as verdaechtiger_login,
|
|
sl.user_id,
|
|
sl.source_ip as login_ip,
|
|
COUNT(na.connection_time) as netzwerk_aktivitaeten,
|
|
SUM(na.bytes_transferred) as gesamt_daten_bytes,
|
|
GROUP_CONCAT(DISTINCT na.destination_ip) as ziel_ips
|
|
FROM suspicious_logins sl
|
|
LEFT JOIN network_activity na ON
|
|
na.connection_time BETWEEN sl.login_time AND sl.investigation_window
|
|
AND na.source_ip = sl.source_ip
|
|
GROUP BY sl.login_time, sl.user_id, sl.source_ip
|
|
HAVING netzwerk_aktivitaeten > 0
|
|
ORDER BY gesamt_daten_bytes DESC;
|
|
```
|
|
|
|
## Erweiterte WAL-Analyse und Recovery
|
|
|
|
### WAL-Datei Untersuchung
|
|
|
|
```sql
|
|
-- WAL-Mode Status prüfen
|
|
PRAGMA journal_mode;
|
|
PRAGMA wal_checkpoint;
|
|
|
|
-- Uncommitted transactions in WAL identifizieren
|
|
-- Hinweis: Erfordert spezielle Tools oder Hex-Editor
|
|
-- Zeigt Konzept für manuelle Analyse
|
|
|
|
SELECT
|
|
name,
|
|
rootpage,
|
|
sql
|
|
FROM sqlite_master
|
|
WHERE type = 'table'
|
|
ORDER BY name;
|
|
```
|
|
|
|
### Gelöschte Daten-Recovery
|
|
|
|
```python
|
|
# Python-Script für erweiterte SQLite-Recovery
|
|
import sqlite3
|
|
import struct
|
|
import os
|
|
|
|
class SQLiteForensics:
|
|
def __init__(self, db_path):
|
|
self.db_path = db_path
|
|
self.page_size = self.get_page_size()
|
|
|
|
def get_page_size(self):
|
|
"""SQLite Page-Size ermitteln"""
|
|
with open(self.db_path, 'rb') as f:
|
|
f.seek(16) # Page size offset
|
|
return struct.unpack('>H', f.read(2))[0]
|
|
|
|
def analyze_freespace(self):
|
|
"""Freespace auf gelöschte Records analysieren"""
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
# Freespace-Informationen sammeln
|
|
cursor.execute("PRAGMA freelist_count;")
|
|
free_pages = cursor.fetchone()[0]
|
|
|
|
cursor.execute("PRAGMA page_count;")
|
|
total_pages = cursor.fetchone()[0]
|
|
|
|
recovery_potential = {
|
|
'total_pages': total_pages,
|
|
'free_pages': free_pages,
|
|
'recovery_potential': f"{(free_pages/total_pages)*100:.2f}%"
|
|
}
|
|
|
|
conn.close()
|
|
return recovery_potential
|
|
|
|
def extract_unallocated(self):
|
|
"""Unallocated Space für Recovery extrahieren"""
|
|
# Vereinfachtes Beispiel - echte Implementation erfordert
|
|
# detaillierte SQLite-Interna-Kenntnisse
|
|
unallocated_data = []
|
|
|
|
with open(self.db_path, 'rb') as f:
|
|
file_size = os.path.getsize(self.db_path)
|
|
pages = file_size // self.page_size
|
|
|
|
for page_num in range(1, pages + 1):
|
|
f.seek((page_num - 1) * self.page_size)
|
|
page_data = f.read(self.page_size)
|
|
|
|
# Suche nach Text-Patterns in Freespace
|
|
# (Vereinfacht - echte Recovery ist komplexer)
|
|
if b'WhatsApp' in page_data or b'@' in page_data:
|
|
unallocated_data.append({
|
|
'page': page_num,
|
|
'potential_data': page_data[:100] # Erste 100 Bytes
|
|
})
|
|
|
|
return unallocated_data
|
|
|
|
# Verwendung für Recovery-Assessment
|
|
forensics = SQLiteForensics('/path/to/damaged.db')
|
|
recovery_info = forensics.analyze_freespace()
|
|
print(f"Recovery-Potenzial: {recovery_info['recovery_potential']}")
|
|
```
|
|
|
|
## Compliance und Rechtssicherheit
|
|
|
|
### Audit-Trail erstellen
|
|
|
|
```sql
|
|
-- Forensische Dokumentation aller durchgeführten Abfragen
|
|
CREATE TABLE IF NOT EXISTS forensic_audit_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
investigator TEXT,
|
|
query_type TEXT,
|
|
sql_query TEXT,
|
|
affected_rows INTEGER,
|
|
case_number TEXT,
|
|
notes TEXT
|
|
);
|
|
|
|
-- Beispiel-Eintrag
|
|
INSERT INTO forensic_audit_log
|
|
(investigator, query_type, sql_query, affected_rows, case_number, notes)
|
|
VALUES
|
|
('Max Mustermann', 'TIMELINE_EXTRACTION',
|
|
'SELECT * FROM messages WHERE timestamp BETWEEN ? AND ?',
|
|
1247, 'CASE-2024-001',
|
|
'Timeline-Extraktion für Zeitraum 01.01.2024 - 31.01.2024');
|
|
```
|
|
|
|
### Hash-Verifikation implementieren
|
|
|
|
```python
|
|
import hashlib
|
|
import sqlite3
|
|
|
|
def verify_database_integrity(db_path, expected_hash=None):
|
|
"""Datenbank-Integrität durch Hash-Verifikation prüfen"""
|
|
|
|
# SHA-256 Hash der Datenbankdatei
|
|
sha256_hash = hashlib.sha256()
|
|
with open(db_path, "rb") as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
sha256_hash.update(chunk)
|
|
|
|
current_hash = sha256_hash.hexdigest()
|
|
|
|
# Zusätzlich: Struktureller Integritäts-Check
|
|
conn = sqlite3.connect(db_path)
|
|
cursor = conn.cursor()
|
|
|
|
try:
|
|
cursor.execute("PRAGMA integrity_check;")
|
|
integrity_result = cursor.fetchall()
|
|
is_structurally_intact = integrity_result == [('ok',)]
|
|
except Exception as e:
|
|
is_structurally_intact = False
|
|
integrity_result = [f"Error: {str(e)}"]
|
|
finally:
|
|
conn.close()
|
|
|
|
return {
|
|
'file_hash': current_hash,
|
|
'hash_matches': current_hash == expected_hash if expected_hash else None,
|
|
'structurally_intact': is_structurally_intact,
|
|
'integrity_details': integrity_result,
|
|
'verified_at': datetime.now().isoformat()
|
|
}
|
|
|
|
# Chain of Custody dokumentieren
|
|
def log_database_access(db_path, investigator, purpose):
|
|
"""Datenbankzugriff für Chain of Custody protokollieren"""
|
|
verification = verify_database_integrity(db_path)
|
|
|
|
log_entry = {
|
|
'timestamp': datetime.now().isoformat(),
|
|
'investigator': investigator,
|
|
'database_path': db_path,
|
|
'access_purpose': purpose,
|
|
'pre_access_hash': verification['file_hash'],
|
|
'database_integrity': verification['structurally_intact']
|
|
}
|
|
|
|
# Log in separater Audit-Datei speichern
|
|
with open('forensic_access_log.json', 'a') as log_file:
|
|
json.dump(log_entry, log_file)
|
|
log_file.write('\n')
|
|
|
|
return log_entry
|
|
```
|
|
|
|
## Fazit und Weiterführende Ressourcen
|
|
|
|
SQL in der digitalen Forensik ist mehr als nur Datenbankabfragen - es ist ein mächtiges Werkzeug für:
|
|
|
|
- **Timeline-Rekonstruktion** mit präziser zeitlicher Korrelation
|
|
- **Kommunikationsanalyse** für soziale Netzwerk-Aufklärung
|
|
- **Anomalie-Erkennung** durch statistische Analyse
|
|
- **Automatisierung** wiederkehrender Untersuchungsschritte
|
|
- **Tiefe Datenextraktion** jenseits GUI-Limitationen
|
|
|
|
### Nächste Schritte
|
|
|
|
1. **Praktische Übung**: Beginnen Sie mit einfachen WhatsApp-Datenbank-Analysen
|
|
2. **Tool-Integration**: Kombinieren Sie SQL mit Python für erweiterte Analysen
|
|
3. **Spezialisierung**: Vertiefen Sie mobile-spezifische oder Browser-Forensik
|
|
4. **Automation**: Entwickeln Sie wiederverwendbare SQL-Scripts für häufige Szenarien
|
|
5. **Rechtssicherheit**: Implementieren Sie Audit-Trails und Hash-Verifikation
|
|
|
|
### Empfohlene Tools
|
|
|
|
- **DB Browser for SQLite**: GUI für interaktive Exploration
|
|
- **SQLiteStudio**: Erweiterte SQLite-Verwaltung
|
|
- **Python sqlite3**: Programmbasierte Automatisierung
|
|
- **Autopsy**: Integration in forensische Workflows
|
|
- **Cellebrite UFED**: Mobile Forensik mit SQL-Export
|
|
|
|
Die Kombination aus SQL-Kenntnissen und forensischem Verständnis macht moderne Ermittler zu hocheffizienten Datenanalytikern. In einer Welt zunehmender Datenmengen wird diese Fähigkeit zum entscheidenden Wettbewerbsvorteil. |