moddux commited on
Commit
7ea74aa
·
verified ·
1 Parent(s): b27c811

Upload streamlit_app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. streamlit_app.py +888 -0
streamlit_app.py ADDED
@@ -0,0 +1,888 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ PROJECT: I.O.S. D.F.I.R. // ENHANCEMENT SKELETON
4
+ Streamlit Application for iOS DFIR Documentation
5
+ """
6
+
7
+ import streamlit as st
8
+ from datetime import datetime, timedelta, timezone
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional, Tuple
11
+ from dataclasses import dataclass, field
12
+ import hashlib
13
+ import json
14
+ import logging
15
+
16
+ # Page configuration
17
+ st.set_page_config(
18
+ page_title="DFIR iOS Enhancement",
19
+ page_icon="🔍",
20
+ layout="wide",
21
+ initial_sidebar_state="collapsed"
22
+ )
23
+
24
+ # Custom CSS for cyberpunk aesthetic
25
+ CUSTOM_CSS = """
26
+ <style>
27
+ /* Import Fonts */
28
+ @import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=Share+Tech+Mono&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap');
29
+
30
+ /* CSS Variables */
31
+ :root {
32
+ --bg-dark: #050608;
33
+ --bg-panel: #0e1016;
34
+ --bg-card: #14161f;
35
+ --border-subtle: #2a2f3d;
36
+ --text-main: #e0e6ed;
37
+ --text-muted: #94a3b8;
38
+ --cyan: #00f0ff;
39
+ --green: #00ff9d;
40
+ --orange: #ff9d00;
41
+ --gold: #ffd700;
42
+ --purple: #bd00ff;
43
+ --red: #ff0055;
44
+ }
45
+
46
+ /* Main background */
47
+ .stApp {
48
+ background-color: var(--bg-dark);
49
+ background-image:
50
+ linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
51
+ linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px);
52
+ background-size: 40px 40px;
53
+ }
54
+
55
+ /* Hide default Streamlit elements */
56
+ #MainMenu {visibility: hidden;}
57
+ footer {visibility: hidden;}
58
+ header {visibility: hidden;}
59
+
60
+ /* Typography */
61
+ h1, h2, h3, h4, h5, h6 {
62
+ font-family: 'Rajdhani', sans-serif;
63
+ text-transform: uppercase;
64
+ letter-spacing: 1px;
65
+ color: var(--text-main);
66
+ }
67
+
68
+ .mono {
69
+ font-family: 'Share Tech Mono', monospace;
70
+ }
71
+
72
+ .meta {
73
+ font-family: 'Space Mono', monospace;
74
+ font-size: 0.85rem;
75
+ }
76
+
77
+ /* Cards */
78
+ .card {
79
+ background: var(--bg-card);
80
+ border: 1px solid var(--border-subtle);
81
+ padding: 1.5rem;
82
+ border-radius: 4px;
83
+ transition: transform 0.2s, border-color 0.2s;
84
+ }
85
+
86
+ .card:hover {
87
+ border-color: rgba(255, 255, 255, 0.1);
88
+ }
89
+
90
+ .card-cyan { border-left: 4px solid var(--cyan); }
91
+ .card-green { border-left: 4px solid var(--green); }
92
+ .card-orange { border-left: 4px solid var(--orange); }
93
+ .card-gold { border-left: 4px solid var(--gold); }
94
+ .card-purple { border-left: 4px solid var(--purple); }
95
+ .card-red { border-left: 4px solid var(--red); }
96
+
97
+ /* Badges */
98
+ .badge {
99
+ display: inline-block;
100
+ font-family: 'Share Tech Mono', monospace;
101
+ font-size: 0.7rem;
102
+ padding: 2px 8px;
103
+ border: 1px solid var(--border-subtle);
104
+ border-radius: 2px;
105
+ text-transform: uppercase;
106
+ margin-right: 0.5rem;
107
+ }
108
+
109
+ .badge-cyan { border-color: var(--cyan); color: var(--cyan); }
110
+ .badge-gold { border-color: var(--gold); color: var(--gold); }
111
+ .badge-green { border-color: var(--green); color: var(--green); }
112
+ .badge-red { border-color: var(--red); color: var(--red); }
113
+
114
+ /* Code blocks */
115
+ .code-block {
116
+ background: #000;
117
+ border: 1px solid var(--border-subtle);
118
+ padding: 1rem;
119
+ font-family: 'Space Mono', monospace;
120
+ font-size: 0.85rem;
121
+ border-radius: 4px;
122
+ overflow-x: auto;
123
+ color: #d4d4d4;
124
+ }
125
+
126
+ /* Navigation buttons */
127
+ .nav-button {
128
+ background: transparent;
129
+ border: none;
130
+ color: var(--text-muted);
131
+ font-family: 'Share Tech Mono', monospace;
132
+ font-size: 0.9rem;
133
+ padding: 0.8rem 1.2rem;
134
+ cursor: pointer;
135
+ text-transform: uppercase;
136
+ transition: all 0.2s ease;
137
+ border-bottom: 2px solid transparent;
138
+ }
139
+
140
+ .nav-button:hover {
141
+ color: var(--text-main);
142
+ background: rgba(255, 255, 255, 0.03);
143
+ }
144
+
145
+ .nav-button.active {
146
+ color: var(--cyan);
147
+ border-bottom-color: var(--cyan);
148
+ }
149
+
150
+ /* Signals */
151
+ .signal {
152
+ display: inline-block;
153
+ font-family: 'Share Tech Mono', monospace;
154
+ font-size: 0.65rem;
155
+ padding: 1px 4px;
156
+ margin-right: 4px;
157
+ border-radius: 2px;
158
+ }
159
+
160
+ .sig-ref { color: var(--cyan); border: 1px solid var(--cyan); }
161
+ .sig-block { color: var(--red); border: 1px solid var(--red); }
162
+ .sig-done { color: var(--green); border: 1px solid var(--green); }
163
+ .sig-gate { color: var(--gold); border: 1px solid var(--gold); }
164
+
165
+ /* Timeline */
166
+ .timeline-item {
167
+ padding: 1rem;
168
+ border-left: 2px solid var(--border-subtle);
169
+ margin-left: 1rem;
170
+ position: relative;
171
+ }
172
+
173
+ .timeline-item::before {
174
+ content: '';
175
+ position: absolute;
176
+ left: -6px;
177
+ top: 1.5rem;
178
+ width: 10px;
179
+ height: 10px;
180
+ border-radius: 50%;
181
+ background: var(--cyan);
182
+ }
183
+
184
+ .timeline-item.done::before { background: var(--green); }
185
+ .timeline-item.active::before { background: var(--gold); }
186
+ .timeline-item.next::before { background: var(--text-muted); }
187
+
188
+ /* Role cards */
189
+ .role-card {
190
+ overflow: hidden;
191
+ position: relative;
192
+ }
193
+
194
+ .role-watermark {
195
+ position: absolute;
196
+ bottom: -10px;
197
+ right: -10px;
198
+ font-size: 5rem;
199
+ font-weight: 700;
200
+ opacity: 0.05;
201
+ font-family: 'Share Tech Mono', monospace;
202
+ line-height: 1;
203
+ pointer-events: none;
204
+ }
205
+
206
+ .role-tag {
207
+ font-family: 'Space Mono', monospace;
208
+ font-size: 0.7rem;
209
+ color: var(--cyan);
210
+ margin-bottom: 0.5rem;
211
+ display: block;
212
+ }
213
+
214
+ /* Lists */
215
+ .role-list {
216
+ list-style: none;
217
+ margin-top: 1rem;
218
+ padding-left: 0;
219
+ }
220
+
221
+ .role-list li {
222
+ font-size: 0.9rem;
223
+ margin-bottom: 0.4rem;
224
+ padding-left: 1rem;
225
+ position: relative;
226
+ }
227
+
228
+ .role-list li::before {
229
+ content: '>';
230
+ position: absolute;
231
+ left: 0;
232
+ color: var(--text-muted);
233
+ }
234
+
235
+ /* Header styling */
236
+ .header-container {
237
+ padding: 2rem 0;
238
+ border-bottom: 1px solid var(--border-subtle);
239
+ margin-bottom: 2rem;
240
+ }
241
+
242
+ .header-title {
243
+ font-size: 3rem;
244
+ font-weight: 700;
245
+ line-height: 1;
246
+ margin-bottom: 0.5rem;
247
+ }
248
+
249
+ .header-title span {
250
+ color: var(--cyan);
251
+ }
252
+
253
+ .header-subtitle {
254
+ font-size: 1.2rem;
255
+ color: var(--text-muted);
256
+ max-width: 800px;
257
+ }
258
+
259
+ /* Diagram boxes */
260
+ .diagram-box {
261
+ display: flex;
262
+ align-items: center;
263
+ justify-content: center;
264
+ gap: 1rem;
265
+ padding: 2rem;
266
+ background: rgba(0, 0, 0, 0.2);
267
+ border: 1px dashed var(--border-subtle);
268
+ border-radius: 4px;
269
+ flex-wrap: wrap;
270
+ }
271
+
272
+ .diag-node {
273
+ border: 1px solid var(--cyan);
274
+ background: rgba(0, 240, 255, 0.1);
275
+ padding: 0.5rem 1rem;
276
+ font-family: 'Share Tech Mono', monospace;
277
+ font-size: 0.8rem;
278
+ border-radius: 2px;
279
+ }
280
+
281
+ .diag-arrow {
282
+ color: var(--cyan);
283
+ font-size: 1.2rem;
284
+ }
285
+
286
+ /* Anycoder link */
287
+ .anycoder-link {
288
+ font-family: 'Space Mono', monospace;
289
+ font-size: 0.7rem;
290
+ color: var(--text-muted);
291
+ text-decoration: none;
292
+ border: 1px solid var(--border-subtle);
293
+ padding: 2px 6px;
294
+ border-radius: 4px;
295
+ transition: color 0.2s, border-color 0.2s;
296
+ }
297
+
298
+ .anycoder-link:hover {
299
+ color: var(--cyan);
300
+ border-color: var(--cyan);
301
+ }
302
+
303
+ /* Section headers */
304
+ .section-header {
305
+ margin-bottom: 2rem;
306
+ border-left: 4px solid var(--cyan);
307
+ padding-left: 1rem;
308
+ }
309
+
310
+ .section-title {
311
+ font-size: 2rem;
312
+ color: var(--text-main);
313
+ }
314
+
315
+ .section-desc {
316
+ font-family: 'Space Mono', monospace;
317
+ color: var(--text-muted);
318
+ font-size: 0.9rem;
319
+ }
320
+
321
+ /* Column styling */
322
+ .st-column {
323
+ padding: 0.5rem;
324
+ }
325
+
326
+ /* Text colors */
327
+ .text-cyan { color: var(--cyan); }
328
+ .text-green { color: var(--green); }
329
+ .text-orange { color: var(--orange); }
330
+ .text-gold { color: var(--gold); }
331
+ .text-purple { color: var(--purple); }
332
+ .text-red { color: var(--red); }
333
+ </style>
334
+ """
335
+
336
+ # Inject custom CSS
337
+ st.markdown(CUSTOM_CSS, unsafe_allow_html=True)
338
+
339
+ # Initialize session state for navigation
340
+ if 'current_section' not in st.session_state:
341
+ st.session_state.current_section = 'overview'
342
+
343
+ # Header
344
+ def render_header():
345
+ st.markdown("""
346
+ <div class="header-container">
347
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
348
+ <span class="meta">SYS_REF: DFIR_IOS_V1.0</span>
349
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
350
+ </div>
351
+ <div>
352
+ <h1 class="header-title">Project <span>DFIR</span> // iOS ENHANCEMENT</h1>
353
+ <p class="header-subtitle">Structured SQLite-backed iOS backup extractor pipeline. Artifact hashing, logging, and extensible parser modules.</p>
354
+ <div style="margin-top: 1rem;">
355
+ <span class="badge badge-cyan">STATUS: SKELETON</span>
356
+ <span class="badge badge-gold">LANG: PYTHON3</span>
357
+ <span class="badge badge-green">LOGGING: ENABLED</span>
358
+ <span class="badge">HASHING: SHA256</span>
359
+ </div>
360
+ </div>
361
+ </div>
362
+ """, unsafe_allow_html=True)
363
+
364
+ # Navigation
365
+ def render_navigation():
366
+ sections = [
367
+ ('overview', 'Manifesto'),
368
+ ('modules', 'Modules'),
369
+ ('pipeline', 'Pipeline'),
370
+ ('schema', 'Data Structs'),
371
+ ('source', 'Source Code')
372
+ ]
373
+
374
+ cols = st.columns(len(sections))
375
+ for i, (key, label) in enumerate(sections):
376
+ with cols[i]:
377
+ active_class = 'active' if st.session_state.current_section == key else ''
378
+ if st.button(
379
+ label,
380
+ key=f"nav_{key}",
381
+ use_container_width=True,
382
+ type="secondary" if st.session_state.current_section != key else "primary"
383
+ ):
384
+ st.session_state.current_section = key
385
+ st.rerun()
386
+
387
+ # Section: Overview
388
+ def render_overview():
389
+ st.markdown("""
390
+ <div class="section-header">
391
+ <h2 class="section-title">System Manifesto</h2>
392
+ <span class="section-desc">:: EXEC_SUMMARY :: Purpose and operational goals of the DFIR skeleton.</span>
393
+ </div>
394
+ """, unsafe_allow_html=True)
395
+
396
+ col1, col2 = st.columns(2)
397
+
398
+ with col1:
399
+ st.markdown("""
400
+ <div class="card card-cyan">
401
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
402
+ <span class="mono" style="font-size: 0.85rem; text-transform: uppercase;">Primary Objective</span>
403
+ <span class="signal sig-gate">CORE</span>
404
+ </div>
405
+ <p>Upgrade simple SQLite-backed iOS backup extractors into a structured DFIR pipeline. Preserve analyst visibility, logging, and artifact hashing.</p>
406
+ </div>
407
+ """, unsafe_allow_html=True)
408
+
409
+ with col2:
410
+ st.markdown("""
411
+ <div class="card card-gold">
412
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
413
+ <span class="mono" style="font-size: 0.85rem; text-transform: uppercase;">Current Status</span>
414
+ <span class="signal sig-ref">DEV</span>
415
+ </div>
416
+ <p>This file is intentionally verbose. Several modules are scaffolded as placeholders (Stubs) for future parser implementation.</p>
417
+ </div>
418
+ """, unsafe_allow_html=True)
419
+
420
+ st.markdown("<div style='margin-top: 2rem;'></div>", unsafe_allow_html=True)
421
+
422
+ st.markdown("""
423
+ <div class="card card-purple">
424
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
425
+ <span class="mono" style="font-size: 0.85rem; text-transform: uppercase;">Operational Flow</span>
426
+ <span class="signal sig-ref">REF</span>
427
+ </div>
428
+ </div>
429
+ """, unsafe_allow_html=True)
430
+
431
+ st.markdown("""
432
+ <div class="diagram-box">
433
+ <div class="diag-node">CLI_ARGS</div>
434
+ <div class="diag-arrow">→</div>
435
+ <div class="diag-node" style="border-color: var(--purple);">MANIFEST_DB</div>
436
+ <div class="diag-arrow">→</div>
437
+ <div class="diag-node" style="border-color: var(--green);">PARSER_MODULES</div>
438
+ <div class="diag-arrow">→</div>
439
+ <div class="diag-node">TIMELINE_CSV</div>
440
+ </div>
441
+ """, unsafe_allow_html=True)
442
+
443
+ # Section: Modules
444
+ def render_modules():
445
+ st.markdown("""
446
+ <div class="section-header">
447
+ <h2 class="section-title">Parser Modules</h2>
448
+ <span class="section-desc">:: AGENTS :: Specialized extraction subroutines.</span>
449
+ </div>
450
+ """, unsafe_allow_html=True)
451
+
452
+ col1, col2, col3, col4 = st.columns(4)
453
+
454
+ with col1:
455
+ st.markdown("""
456
+ <div class="card card-cyan role-card">
457
+ <div class="role-watermark">CORE</div>
458
+ <span class="role-tag">CONTACTS</span>
459
+ <h3 style="font-size: 1.4rem; margin-bottom: 0.2rem;">AddressBook Parser</h3>
460
+ <div class="mono" style="color: var(--text-muted); font-size: 0.8rem; margin-bottom: 1rem;">export_contacts()</div>
461
+ <ul class="role-list">
462
+ <li>Modern & Legacy Schema</li>
463
+ <li>Phone Normalization</li>
464
+ <li>CSV/JSON Export</li>
465
+ </ul>
466
+ </div>
467
+ """, unsafe_allow_html=True)
468
+
469
+ with col2:
470
+ st.markdown("""
471
+ <div class="card card-green role-card">
472
+ <div class="role-watermark">CORE</div>
473
+ <span class="role-tag">MESSAGING</span>
474
+ <h3 style="font-size: 1.4rem; margin-bottom: 0.2rem;">SMS / iMessage</h3>
475
+ <div class="mono" style="color: var(--text-muted); font-size: 0.8rem; margin-bottom: 1rem;">extract_sms()</div>
476
+ <ul class="role-list">
477
+ <li>Handle Resolution</li>
478
+ <li>AttributedBody Stub</li>
479
+ <li>Timeline Event Gen</li>
480
+ </ul>
481
+ </div>
482
+ """, unsafe_allow_html=True)
483
+
484
+ with col3:
485
+ st.markdown("""
486
+ <div class="card card-orange role-card">
487
+ <div class="role-watermark">GEO</div>
488
+ <span class="role-tag">LOCATION</span>
489
+ <h3 style="font-size: 1.4rem; margin-bottom: 0.2rem;">LocationD / Cache</h3>
490
+ <div class="mono" style="color: var(--text-muted); font-size: 0.8rem; margin-bottom: 1rem;">extract_locations()</div>
491
+ <ul class="role-list">
492
+ <li>RTCL Location MO</li>
493
+ <li>Visit MO Parsing</li>
494
+ <li>Cell Tower Logs</li>
495
+ </ul>
496
+ </div>
497
+ """, unsafe_allow_html=True)
498
+
499
+ with col4:
500
+ st.markdown("""
501
+ <div class="card card-red role-card">
502
+ <div class="role-watermark">STUB</div>
503
+ <span class="role-tag">FUTURE</span>
504
+ <h3 style="font-size: 1.4rem; margin-bottom: 0.2rem;">Safari / Notes</h3>
505
+ <div class="mono" style="color: var(--text-muted); font-size: 0.8rem; margin-bottom: 1rem;">extract_safari_stub()</div>
506
+ <ul class="role-list">
507
+ <li>History / Downloads</li>
508
+ <li>Rich Notes</li>
509
+ <li>App Containers</li>
510
+ </ul>
511
+ </div>
512
+ """, unsafe_allow_html=True)
513
+
514
+ # Section: Pipeline
515
+ def render_pipeline():
516
+ st.markdown("""
517
+ <div class="section-header">
518
+ <h2 class="section-title">Execution Pipeline</h2>
519
+ <span class="section-desc">:: LOGIC :: The `run()` orchestration sequence.</span>
520
+ </div>
521
+ """, unsafe_allow_html=True)
522
+
523
+ st.markdown("""
524
+ <div class="timeline-item done">
525
+ <div class="meta" style="margin-bottom: 0.5rem;">STEP 1</div>
526
+ <h3 style="margin-bottom: 0.5rem;">Initialization</h3>
527
+ <div class="card" style="margin-top: 0.5rem; border: none; background: rgba(255,255,255,0.02); padding: 1rem;">
528
+ <span class="signal sig-done">DONE</span> Setup Logging. Validate Backup Root. Parse CLI Arguments.
529
+ </div>
530
+ </div>
531
+
532
+ <div class="timeline-item active">
533
+ <div class="meta" style="margin-bottom: 0.5rem;">STEP 2</div>
534
+ <h3 style="margin-bottom: 0.5rem;">Manifest Indexing</h3>
535
+ <div class="card" style="margin-top: 0.5rem; border: none; background: rgba(255,255,255,0.02); padding: 1rem;">
536
+ <span class="signal sig-gate">ACTIVE</span> Load `Manifest.db`. Search for `sms.db`, `AddressBook`, and Location artifacts.
537
+ </div>
538
+ </div>
539
+
540
+ <div class="timeline-item next">
541
+ <div class="meta" style="margin-bottom: 0.5rem;">STEP 3</div>
542
+ <h3 style="margin-bottom: 0.5rem;">Extraction & Export</h3>
543
+ <div class="card" style="margin-top: 0.5rem; border: none; background: rgba(255,255,255,0.02); padding: 1rem;">
544
+ <span class="signal sig-ref">NEXT</span> Run Parser Modules. Generate CSV/JSON. Build Unified Timeline.
545
+ </div>
546
+ </div>
547
+ """, unsafe_allow_html=True)
548
+
549
+ # Section: Schema
550
+ def render_schema():
551
+ st.markdown("""
552
+ <div class="section-header">
553
+ <h2 class="section-title">Data Structures</h2>
554
+ <span class="section-desc">:: STRUCT :: Python Dataclasses used for typing.</span>
555
+ </div>
556
+ """, unsafe_allow_html=True)
557
+
558
+ st.markdown("""
559
+ <div class="card card-cyan">
560
+ <div class="mono" style="font-size: 0.85rem; text-transform: uppercase; margin-bottom: 1rem;">Config & Metadata</div>
561
+ <div class="code-block" style="max-height: 400px; overflow-y: auto;">
562
+ <span class="text-purple">@dataclass</span>
563
+ <span class="text-gold">class</span> <span class="text-cyan">CaseMetadata</span>:
564
+ case_name: <span class="text-cyan">str</span> = <span class="text-orange">"UNSPECIFIED_CASE"</span>
565
+ examiner: <span class="text-cyan">str</span> = <span class="text-orange">"UNSPECIFIED_EXAMINER"</span>
566
+ evidence_id: <span class="text-cyan">str</span> = <span class="text-orange">"UNSPECIFIED_EVIDENCE"</span>
567
+
568
+ <span class="text-purple">@dataclass</span>
569
+ <span class="text-gold">class</span> <span class="text-cyan">AppConfig</span>:
570
+ backup_root: <span class="text-cyan">Path</span>
571
+ manifest_db: <span class="text-cyan">Path</span>
572
+ output_root: <span class="text-cyan">Path</span>
573
+ query_terms: <span class="text-cyan">List</span>[<span class="text-cyan">str</span>] = field(default_factory=<span class="text-cyan">list</span>)
574
+ verbose: <span class="text-cyan">bool</span> = <span class="text-gold">False</span>
575
+ hash_exports: <span class="text-cyan">bool</span> = <span class="text-gold">True</span>
576
+ copy_raw_files: <span class="text-cyan">bool</span> = <span class="text-gold">True</span>
577
+ export_csv: <span class="text-cyan">bool</span> = <span class="text-gold">True</span>
578
+ export_jsonl: <span class="text-cyan">bool</span> = <span class="text-gold">True</span>
579
+ </div>
580
+ </div>
581
+ """, unsafe_allow_html=True)
582
+
583
+ st.markdown("<div style='margin-top: 1.5rem;'></div>", unsafe_allow_html=True)
584
+
585
+ st.markdown("""
586
+ <div class="card card-purple">
587
+ <div class="mono" style="font-size: 0.85rem; text-transform: uppercase; margin-bottom: 1rem;">Timeline Event Model</div>
588
+ <div class="code-block" style="max-height: 300px; overflow-y: auto;">
589
+ <span class="text-purple">@dataclass</span>
590
+ <span class="text-gold">class</span> <span class="text-cyan">TimelineEvent</span>:
591
+ timestamp: <span class="text-cyan">Optional</span>[<span class="text-cyan">str</span>]
592
+ artifact_type: <span class="text-cyan">str</span>
593
+ source_file: <span class="text-cyan">str</span>
594
+ summary: <span class="text-cyan">str</span>
595
+ attributes: <span class="text-cyan">Dict</span>[<span class="text-cyan">str</span>, <span class="text-cyan">Any</span>] = field(default_factory=<span class="text-cyan">dict</span>)
596
+ </div>
597
+ </div>
598
+ """, unsafe_allow_html=True)
599
+
600
+ # Section: Source Code
601
+ def render_source():
602
+ st.markdown("""
603
+ <div class="section-header">
604
+ <h2 class="section-title">Source Implementation</h2>
605
+ <span class="section-desc">:: CODE :: The complete Python script logic.</span>
606
+ </div>
607
+ """, unsafe_allow_html=True)
608
+
609
+ source_code = '''#!/usr/bin/env python3
610
+ """ DFIR iOS Backup Enhancement Skeleton """
611
+
612
+ from __future__ import annotations
613
+ import argparse, csv, dataclasses, hashlib, json, logging, os, shutil, sqlite3, sys, traceback
614
+ from dataclasses import dataclass, field
615
+ from datetime import datetime, timedelta, timezone
616
+ from pathlib import Path
617
+ from typing import Any, Dict, Iterable, List, Optional, Tuple
618
+
619
+ APPLE_EPOCH = datetime(2001, 1, 1, tzinfo=timezone.utc)
620
+
621
+ # ============================================================
622
+ # CONFIG / DATA MODELS
623
+ # ============================================================
624
+
625
+ @dataclass
626
+ class CaseMetadata:
627
+ case_name: str = "UNSPECIFIED_CASE"
628
+ examiner: str = "UNSPECIFIED_EXAMINER"
629
+ evidence_id: str = "UNSPECIFIED_EVIDENCE"
630
+ notes: str = ""
631
+
632
+ @dataclass
633
+ class AppConfig:
634
+ backup_root: Path
635
+ manifest_db: Path
636
+ output_root: Path
637
+ query_terms: List[str] = field(default_factory=list)
638
+ verbose: bool = False
639
+ hash_exports: bool = True
640
+ copy_raw_files: bool = True
641
+ export_csv: bool = True
642
+ export_jsonl: bool = True
643
+ export_kml: bool = False
644
+ export_geojson: bool = False
645
+ case: CaseMetadata = field(default_factory=CaseMetadata)
646
+
647
+ @dataclass
648
+ class LocatedFile:
649
+ file_id: str
650
+ relative_path: str
651
+ domain: Optional[str]
652
+ source_path: Path
653
+
654
+ @dataclass
655
+ class TimelineEvent:
656
+ timestamp: Optional[str]
657
+ artifact_type: str
658
+ source_file: str
659
+ summary: str
660
+ attributes: Dict[str, Any] = field(default_factory=dict)
661
+
662
+ # ============================================================
663
+ # LOGGING
664
+ # ============================================================
665
+
666
+ def setup_logging(output_root: Path, verbose: bool = False) -> logging.Logger:
667
+ output_root.mkdir(parents=True, exist_ok=True)
668
+ log_path = output_root / "dfir_run.log"
669
+ logger = logging.getLogger("dfir_ios")
670
+ logger.setLevel(logging.DEBUG if verbose else logging.INFO)
671
+ logger.handlers.clear()
672
+ fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
673
+ fh = logging.FileHandler(log_path, encoding="utf-8")
674
+ fh.setLevel(logging.DEBUG)
675
+ fh.setFormatter(fmt)
676
+ logger.addHandler(fh)
677
+ sh = logging.StreamHandler(sys.stdout)
678
+ sh.setLevel(logging.DEBUG if verbose else logging.INFO)
679
+ sh.setFormatter(fmt)
680
+ logger.addHandler(sh)
681
+ logger.debug("Logging initialized")
682
+ return logger
683
+
684
+ # ============================================================
685
+ # UTILS
686
+ # ============================================================
687
+
688
+ def apple_time_to_datetime(ts: Any) -> Optional[datetime]:
689
+ if ts is None:
690
+ return None
691
+ try:
692
+ ts = float(ts)
693
+ if ts > 1e12: ts = ts / 1e9
694
+ return APPLE_EPOCH + timedelta(seconds=ts)
695
+ except Exception:
696
+ return None
697
+
698
+ def normalize_phone(phone: Optional[str]) -> Optional[str]:
699
+ if not phone: return phone
700
+ value = str(phone).replace("+1", "").replace(" ", "").replace("-", "").replace("(", "").replace(")", "")
701
+ return value.strip() or None
702
+
703
+ def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str:
704
+ h = hashlib.sha256()
705
+ with path.open("rb") as f:
706
+ while True:
707
+ chunk = f.read(chunk_size)
708
+ if not chunk: break
709
+ h.update(chunk)
710
+ return h.hexdigest()
711
+
712
+ # ============================================================
713
+ # MAIN ORCHESTRATION
714
+ # ============================================================
715
+
716
+ def run(cfg: AppConfig) -> int:
717
+ logger = setup_logging(cfg.output_root, cfg.verbose)
718
+ logger.info("Starting DFIR iOS backup extraction")
719
+ logger.info(f"Case: {cfg.case.case_name}")
720
+ logger.info(f"Examiner: {cfg.case.examiner}")
721
+ logger.info(f"Evidence ID: {cfg.case.evidence_id}")
722
+
723
+ # Validate backup root
724
+ if not cfg.backup_root.exists():
725
+ logger.error(f"Backup root does not exist: {cfg.backup_root}")
726
+ return 1
727
+
728
+ # Load manifest database
729
+ if not cfg.manifest_db.exists():
730
+ logger.error(f"Manifest database does not exist: {cfg.manifest_db}")
731
+ return 1
732
+
733
+ logger.info("Manifest database loaded successfully")
734
+
735
+ # Create output directories
736
+ cfg.output_root.mkdir(parents=True, exist_ok=True)
737
+ (cfg.output_root / "exports").mkdir(exist_ok=True)
738
+ (cfg.output_root / "raw").mkdir(exist_ok=True)
739
+ (cfg.output_root / "timeline").mkdir(exist_ok=True)
740
+
741
+ logger.info("Output directories created")
742
+
743
+ # Run parser modules
744
+ timeline_events: List[TimelineEvent] = []
745
+
746
+ # Export contacts
747
+ try:
748
+ contacts_count = export_contacts(cfg, logger)
749
+ timeline_events.append(TimelineEvent(
750
+ timestamp=datetime.now(timezone.utc).isoformat(),
751
+ artifact_type="CONTACTS",
752
+ source_file="AddressBook.sqlitedb",
753
+ summary=f"Exported {contacts_count} contacts"
754
+ ))
755
+ except Exception as e:
756
+ logger.error(f"Contact export failed: {e}")
757
+
758
+ # Export SMS/iMessage
759
+ try:
760
+ sms_count = extract_sms(cfg, logger)
761
+ timeline_events.append(TimelineEvent(
762
+ timestamp=datetime.now(timezone.utc).isoformat(),
763
+ artifact_type="MESSAGES",
764
+ source_file="sms.db",
765
+ summary=f"Exported {sms_count} messages"
766
+ ))
767
+ except Exception as e:
768
+ logger.error(f"SMS export failed: {e}")
769
+
770
+ # Export locations
771
+ try:
772
+ location_count = extract_locations(cfg, logger)
773
+ timeline_events.append(TimelineEvent(
774
+ timestamp=datetime.now(timezone.utc).isoformat(),
775
+ artifact_type="LOCATIONS",
776
+ source_file="LocationD/Cache.sqlite",
777
+ summary=f"Exported {location_count} location points"
778
+ ))
779
+ except Exception as e:
780
+ logger.error(f"Location export failed: {e}")
781
+
782
+ # Generate timeline
783
+ if cfg.export_csv:
784
+ generate_timeline_csv(timeline_events, cfg.output_root / "timeline" / "unified_timeline.csv")
785
+ logger.info("Timeline CSV generated")
786
+
787
+ if cfg.export_jsonl:
788
+ generate_timeline_jsonl(timeline_events, cfg.output_root / "timeline" / "unified_timeline.jsonl")
789
+ logger.info("Timeline JSONL generated")
790
+
791
+ logger.info("DFIR iOS extraction complete")
792
+ return 0
793
+
794
+ def export_contacts(cfg: AppConfig, logger: logging.Logger) -> int:
795
+ """Export contacts from AddressBook database"""
796
+ logger.info("Exporting contacts...")
797
+ # Implementation stub
798
+ return 0
799
+
800
+ def extract_sms(cfg: AppConfig, logger: logging.Logger) -> int:
801
+ """Extract SMS/iMessage data"""
802
+ logger.info("Extracting SMS/iMessage data...")
803
+ # Implementation stub
804
+ return 0
805
+
806
+ def extract_locations(cfg: AppConfig, logger: logging.Logger) -> int:
807
+ """Extract location data from LocationD"""
808
+ logger.info("Extracting location data...")
809
+ # Implementation stub
810
+ return 0
811
+
812
+ def generate_timeline_csv(events: List[TimelineEvent], output_path: Path):
813
+ """Generate unified timeline CSV"""
814
+ with output_path.open("w", newline="", encoding="utf-8") as f:
815
+ writer = csv.writer(f)
816
+ writer.writerow(["timestamp", "artifact_type", "source_file", "summary", "attributes"])
817
+ for event in events:
818
+ writer.writerow([
819
+ event.timestamp,
820
+ event.artifact_type,
821
+ event.source_file,
822
+ event.summary,
823
+ json.dumps(event.attributes)
824
+ ])
825
+
826
+ def generate_timeline_jsonl(events: List[TimelineEvent], output_path: Path):
827
+ """Generate unified timeline JSONL"""
828
+ with output_path.open("w", encoding="utf-8") as f:
829
+ for event in events:
830
+ f.write(json.dumps(dataclasses.asdict(event)) + "\\n")
831
+
832
+ if __name__ == "__main__":
833
+ parser = argparse.ArgumentParser(description="DFIR iOS Backup Enhancement")
834
+ parser.add_argument("--backup-root", type=Path, required=True, help="Path to iOS backup")
835
+ parser.add_argument("--output-root", type=Path, required=True, help="Output directory")
836
+ parser.add_argument("--case-name", type=str, default="UNSPECIFIED_CASE")
837
+ parser.add_argument("--examiner", type=str, default="UNSPECIFIED_EXAMINER")
838
+ parser.add_argument("--evidence-id", type=str, default="UNSPECIFIED_EVIDENCE")
839
+ parser.add_argument("--verbose", "-v", action="store_true")
840
+ args = parser.parse_args()
841
+
842
+ manifest_db = args.backup_root / "Manifest.db"
843
+
844
+ cfg = AppConfig(
845
+ backup_root=args.backup_root,
846
+ manifest_db=manifest_db,
847
+ output_root=args.output_root,
848
+ verbose=args.verbose,
849
+ case=CaseMetadata(
850
+ case_name=args.case_name,
851
+ examiner=args.examiner,
852
+ evidence_id=args.evidence_id
853
+ )
854
+ )
855
+
856
+ sys.exit(run(cfg))'''
857
+
858
+ st.markdown("""
859
+ <div class="card card-gold">
860
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
861
+ <span class="mono" style="font-size: 0.85rem; text-transform: uppercase;">dfir_ios_enhancement.py</span>
862
+ <span class="signal sig-done">READY</span>
863
+ </div>
864
+ </div>
865
+ """, unsafe_allow_html=True)
866
+
867
+ st.code(source_code, language="python")
868
+
869
+ # Main app
870
+ def main():
871
+ render_header()
872
+ render_navigation()
873
+
874
+ st.markdown("<div style='margin: 2rem 0;'></div>", unsafe_allow_html=True)
875
+
876
+ if st.session_state.current_section == 'overview':
877
+ render_overview()
878
+ elif st.session_state.current_section == 'modules':
879
+ render_modules()
880
+ elif st.session_state.current_section == 'pipeline':
881
+ render_pipeline()
882
+ elif st.session_state.current_section == 'schema':
883
+ render_schema()
884
+ elif st.session_state.current_section == 'source':
885
+ render_source()
886
+
887
+ if __name__ == "__main__":
888
+ main()