Come fai a sapere che ha funzionato?
Con qualsiasi altro strumento di questo tipo, non lo sai. L'applicazione parte, mostra una data qualsiasi e tu dai per scontato che sia giusta. Ma può leggere l'orologio da una via che nulla ha intercettato - e allora «il test è passato» non significa niente.
Il fallimento che non ha sintomi
Uno strumento di spostamento del tempo che manca un canale non si blocca e non avvisa. L'applicazione legge semplicemente la data reale attraverso l'unica funzione che nessuno ha intercettato, e si comporta esattamente come si sarebbe comportata in un qualsiasi pomeriggio normale. Il test diventa verde. Il bug va in produzione.
RunAsDate è onesto su questo nella propria documentazione - dice che «non influirà sulle applicazioni che ricavano la data e l'ora correnti da un'altra fonte» - e poi ti lascia scoprire se la tua è una di quelle. Questo strumento tratta quella stessa frase come qualcosa da misurare.
Che cosa riporta davvero una sessione
Output reale, ottenuto eseguendo lo strumento contro una delle sonde di test di questo progetto - una che apre deliberatamente una connessione di rete:
$ chrono run "...\pnet-x64.exe" --at 2038-01-19T03:14:07 --report session.txt Chrono Mock - session report target: C:\ChronoMock\tools\probes\bin\pnet-x64.exe verdict: WORKS - time substitution took effect (processes: 1) every process that read time saw the session clock session: fake clock reached 2038-01-19T03:14:10 real elapsed 3.4s, fake elapsed 3.4s exited: the target closed itself with code 0 covered channels (substituted, with call counts, 4 calls in all): - pid 9152: GetSystemTimeAsFileTime (1 call) - pid 9152: NtQuerySystemTime (3 calls) ... every other channel, including the ones never called observed channels (hooked but left real): - pid 9152: NtCreateUserProcess (0 calls) - pid 9152: connect (1 call) warnings: - the target opened a network connection - it may read time from a server, which no local hook can cover (source.network_at_start) requested: 2038-01-19T03:14:07 (zone +02:00 (host default), mode flow)
Gli ultimi due blocchi sono il punto. Qui nulla è fallito - il verdetto è funziona - e il report dice comunque, senza che glielo si chieda, che questa applicazione ha parlato con la rete prima di finire, quindi un hook locale potrebbe non essere tutta la storia. Quella frase è la differenza tra un test di cui ti puoi fidare e uno di cui no.
Quattro verdetti, e un codice di uscita per ciascuno
Il verdetto si raggiunge dall'interno del processo in esecuzione, prima che tu inizi a lavorare, ed è il codice di uscita stesso dello strumento, così una pipeline può diramare su di esso.
| Verdetto | Uscita | Significato |
|---|---|---|
| funziona | 0 | ogni processo che ha letto l'ora ha visto l'orologio della sessione |
| funziona in parte | 10 | alcuni canali coperti, altri no - quelli non coperti sono elencati |
| non funziona | 11 | la sostituzione non ha raggiunto i canali chiave |
| indeterminato | 4 | non è stato possibile stabilire la copertura - un onesto «non lo so» |
Quando il verdetto è «non funziona», il processo viene fermato subito, prima che tu possa interagire con esso. Il verdetto non può precedere l'avvio, perché l'unica fonte di verità è una lettura presa dall'interno del processo in esecuzione. Ciò che è garantito è che scopri la verità prima di cominciare a lavorare - non che un avvio destinato a fallire non avvenga mai. Puoi forzare e proseguire, e la sessione viene allora contrassegnata come inaffidabile ovunque compaia, comprese le prove che esporti.
Altri codici dicono che è fallita l'esecuzione stessa e non la sostituzione - un errore d'uso, un target che non si è potuto avviare, un'esecuzione interrotta, un'altra sessione già in corso - e nessuno di essi è un verdetto. La tabella completa è nel riferimento della riga di comando.
Coperti, osservati, non coperti
I canali si dividono in tre gruppi, e quello di mezzo è il più interessante.
- Coperti - intercettati e sostituiti. L'applicazione ha chiesto e ha ottenuto l'orologio della sessione. Il conteggio delle chiamate dice quante volte.
- Osservati - intercettati, contati e lasciati di proposito a dire la verità. Qui ci sono le attese su oggetti, perché accorciarne il timeout potrebbe simulare una scadenza su un handle reale, e il timer multimediale, perché scalarlo sposterebbe i tempi di audio e MIDI - entrambi osservati quando la sessione scala l'asse del tempo trascorso. Le connessioni di rete e una via di creazione dei processi sono qui in ogni sessione, perché sono indizi, non orologi. Il contatore ad alta risoluzione non è in questo gruppo: resta intatto finché non lo chiedi con
--scale-qpc, e allora è coperto. - Non coperti - un canale che l'applicazione ha letto e che la sostituzione non ha raggiunto. È ciò che trasforma un verdetto in funziona in parte, ed è elencato per nome invece di essere riassunto.
Anche un conteggio pari a zero è un'informazione: il canale era intercettato e l'applicazione non ha mai chiesto. Per questo il report li elenca invece di nasconderli.
Che cosa l'audit non può vedere
Il report è onesto sulla copertura, il che significa essere onesti sui propri limiti. Queste letture avvengono senza alcuna chiamata di funzione da intercettare, oppure del tutto fuori dal processo:
- letture dirette della pagina dati condivisa in modalità utente - il valore è in memoria, quindi non c'è nulla da agganciare. Rare nelle applicazioni normali, comuni nel codice di licenza e anti-manomissione
- chiamate di sistema dirette che aggirano le esportazioni standard - stesso schema
- ora ottenuta fuori dal processo, tramite WMI, un server COM fuori processo o RPC
- ora dalla rete - un'intestazione HTTP
Date, NTP, la risposta di un'API o l'orologio stesso di un server di database - ora proveniente da driver del kernel
Il caso della rete è quello che l'audit può almeno notare, ed è per questo che esiste l'avviso nel report qui sopra. Per il resto, un verdetto funziona significa che i canali che questo strumento può vedere sono stati coperti - non che non esista nessun'altra via.
In una pipeline
--json emette gli stessi fatti in forma leggibile da una macchina, --report
scrive la versione per le persone in un file, e una sessione il cui verdetto sia qualsiasi cosa
diversa da funziona apre quel file con un avviso di prova inaffidabile invece di
seppellirlo in fondo.
chrono run "C:\apps\test.exe" --at 2027-12-31T23:59:59 --json --report evidence.txt