|
18 | 18 | - [SSH Scanning (T1046)](#ssh-scanning-t1046) |
19 | 19 | - [Network Service Scanning (T1046)](#network-service-scanning-t1046) |
20 | 20 | - [System Profiler (T1082)](#system-profiler-t1082) |
| 21 | + - [Unified Log Harvesting (T1552.001)](#unified-log-harvesting-t1552001) |
21 | 22 | - [Persistence (T1543.001)](#persistence-t1543001) |
22 | 23 | - [Extended Attributes (T1564.004)](#extended-attributes-t1564004) |
23 | 24 | - [LaunchAgent Backdoors (T1543.001)](#launchagent-backdoors-t1543001) |
@@ -334,6 +335,56 @@ Hardware: |
334 | 335 | Activation Lock Status: Disabled |
335 | 336 | ``` |
336 | 337 |
|
| 338 | +### Unified Log Harvesting (T1552.001) |
| 339 | +
|
| 340 | +`log` is a zsh builtin — use `/usr/bin/log`. `MATCHES` is fully anchored, so wrap regexes with `.*...*`. The `log` process logs itself; exclude with `NOT process == "log"` (avoid `!=`, zsh history-expands it). |
| 341 | +
|
| 342 | +Sudo command history with PWD, target user, and full command path: |
| 343 | +
|
| 344 | +```sh |
| 345 | +/usr/bin/log show --last 30d --info --debug --predicate 'process == "sudo" AND eventMessage CONTAINS "COMMAND="' --style compact |
| 346 | +``` |
| 347 | +
|
| 348 | +SSH client activity timeline (destinations are redacted, timestamps are not): |
| 349 | +
|
| 350 | +```sh |
| 351 | +/usr/bin/log show --last 7d --predicate 'process == "ssh" OR process == "sshd"' --style compact |
| 352 | +``` |
| 353 | +
|
| 354 | +TCC entitlements the user has already approved (ideal for piggy-back persistence): |
| 355 | +
|
| 356 | +```sh |
| 357 | +/usr/bin/log show --last 30d --predicate 'process == "tccd" AND eventMessage CONTAINS "REPLY"' --style compact |
| 358 | +``` |
| 359 | +
|
| 360 | +Emails seen in logs, with distnoted/Maps-tile noise filtered: |
| 361 | +
|
| 362 | +```sh |
| 363 | +/usr/bin/log show --last 30d --info --debug --predicate 'eventMessage CONTAINS "@" AND NOT process == "distnoted" AND NOT process == "log"' 2>/dev/null | grep -oE '\b[A-Za-z0-9._%+-]{3,}@[A-Za-z0-9][A-Za-z0-9-]{1,}\.[A-Za-z]{2,6}\b' | grep -viE '@[0-9]x|\.png$|\.styl$|\.icon|\.peer$' | sort -u |
| 364 | +``` |
| 365 | +
|
| 366 | +Filesystem sweep for Bearer tokens, OAuth fields, API keys, and private key blocks in Electron/dev app logs (where secrets actually live on macOS): |
| 367 | +
|
| 368 | +```sh |
| 369 | +grep -rIlEi '(bearer [A-Za-z0-9._-]{20,}|"(access|id|refresh)_token"|"client_secret"|(api|secret)[_-]?key["'"'"': =]+[A-Za-z0-9]{16,}|-----BEGIN (RSA|EC|OPENSSH| )?PRIVATE KEY-----)' ~/Library/Logs "$HOME/Library/Application Support" 2>/dev/null |
| 370 | +``` |
| 371 | +
|
| 372 | +Filesystem sweep for known token prefixes (AWS, GitHub, GitLab, Slack, OpenAI/Stripe, npm, JWT): |
| 373 | +
|
| 374 | +```sh |
| 375 | +grep -rIoE '(AKIA|ASIA)[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{36,}|glpat-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|sk-[A-Za-z0-9]{32,}|npm_[A-Za-z0-9]{36}|eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}' ~/Library/Logs "$HOME/Library/Application Support" 2>/dev/null | sort -u |
| 376 | +``` |
| 377 | +
|
| 378 | +Collect the unified log archive for offline triage with a real secret scanner: |
| 379 | +
|
| 380 | +```sh |
| 381 | +/usr/bin/log collect --last 30d --output /tmp/unified.logarchive |
| 382 | +/usr/bin/log show --archive /tmp/unified.logarchive --info --debug --style ndjson > /tmp/unified.ndjson |
| 383 | +trufflehog filesystem /tmp/unified.ndjson |
| 384 | +``` |
| 385 | +
|
| 386 | +Noisy processes to exclude from any keyword query: `log`, `bluetoothd`, `distnoted`, `airportd`, `launchd`, `mDNSResponder`, `symptomsd`, `tccd`. |
| 387 | +
|
337 | 388 | ## Persistence (T1543.001) |
338 | 389 |
|
339 | 390 | ### Extended Attributes (T1564.004) |
|
0 commit comments