Chrono Mock

Skąd wiesz, że zadziałało?

Przy każdym innym narzędziu tego typu - nie wiesz. Aplikacja startuje, pokazuje jakąś datę, a ty zakładasz. Tyle że może czytać zegar drogą, której nic nie przechwyciło - i wtedy „test przeszedł" nie znaczy zupełnie nic.

Porażka, która nie ma objawów

Narzędzie do przesuwania czasu, które pominęło kanał, nie wywala się i nie ostrzega. Aplikacja po prostu czyta prawdziwą datę tą jedną funkcją, której nikt nie podpiął, i zachowuje się dokładnie tak, jak zachowałaby się w zwykłe popołudnie. Test świeci na zielono. Błąd trafia do klienta.

RunAsDate pisze o tym uczciwie we własnej dokumentacji - że „nie wpłynie na aplikacje, które biorą bieżącą datę i godzinę z innego źródła" - i zostawia cię z ustaleniem, czy twoja jest jedną z nich. To narzędzie traktuje to samo zdanie jako coś do zmierzenia.

Co sesja naprawdę raportuje

Prawdziwe wyjście, z uruchomienia narzędzia na jednej z własnych sond tego projektu - takiej, która celowo otwiera połączenie sieciowe:

$ 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.3s, fake elapsed 3.3s
  covered channels (substituted, with call counts):
            - pid 38828: NtQuerySystemTime (3 calls)
            ... i każdy pozostały kanał, łącznie z nigdy niewywołanymi
  observed channels (hooked but left real):
            - pid 38828: NtCreateUserProcess (0 calls)
            - pid 38828: 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)

Dwa ostatnie bloki są sednem. Nic tu nie zawiodło - werdykt brzmi works - a raport i tak sam z siebie mówi, że ta aplikacja rozmawiała z siecią, zanim skończyła, więc lokalny hook może nie być całą historią. To zdanie jest różnicą między testem, któremu można ufać, a takim, któremu nie można.

Cztery werdykty i kod wyjścia do każdego

Werdykt powstaje z wnętrza działającego procesu, zanim zaczniesz pracować, i jest zarazem kodem wyjścia narzędzia, więc potok może się na nim rozgałęzić.

WerdyktKodZnaczenie
działa0każdy proces, który czytał czas, widział zegar sesji
działa częściowo10część kanałów objęta, część nie - nieobjęte są wypisane
nie działa11podmiana nie dotarła do kluczowych kanałów
nieokreślony4nie dało się ustalić pokrycia - uczciwe „nie wiem"

Gdy werdykt brzmi „nie działa", proces jest natychmiast zatrzymywany, zanim zdążysz z nim cokolwiek zrobić. Werdykt nie może wyprzedzić uruchomienia, bo jedynym źródłem prawdy jest odczyt z wnętrza działającego procesu. Gwarantowane jest to, że poznasz prawdę, zanim zaczniesz pracować - a nie to, że skazana sesja nigdy nie wystartuje. Można to nadpisać i lecieć dalej, a sesja jest wtedy oznaczona jako nierzetelna wszędzie, gdzie się pojawia, łącznie z eksportowanym dowodem.

Istnieją jeszcze trzy kody, które nie są werdyktami: 1 dla błędu użycia, 2, gdy nie udało się uruchomić celu albo wstrzyknąć, i 12, gdy cel zniknął w sposób sugerujący aplikację jednoinstancyjną, która oddała robotę już działającej kopii.

Objęte, obserwowane, nieobjęte

Kanały dzielą się na trzy kubełki, a najciekawszy jest środkowy.

  • Objęte - podpięte i podmienione. Aplikacja zapytała i dostała zegar sesji. Licznik mówi, ile razy.
  • Obserwowane - podpięte, liczone i celowo zostawione mówiące prawdę. Licznik wysokiej rozdzielczości jest tu domyślnie, bo jego skalowanie destabilizowało renderowanie. Timery multimedialne są tu, bo ich skalowanie przesunęłoby timing audio i MIDI. Połączenia sieciowe i jedna ścieżka tworzenia procesu są tu, bo są dowodem, a nie zegarem.
  • Nieobjęte - kanał, który aplikacja czytała, a podmiana do niego nie dotarła. To właśnie zamienia werdykt w działa częściowo i jest wypisywane z nazwy, a nie zwijane do podsumowania.

Zero wywołań też jest informacją: kanał był podpięty, a aplikacja nigdy nie zapytała. Dlatego raport je wypisuje, zamiast chować.

Czego audyt nie widzi

Raport jest uczciwy co do pokrycia, co znaczy, że musi być uczciwy co do własnych granic. Te źródła są czytane bez żadnego wywołania funkcji do przechwycenia albo w ogóle poza procesem:

  • bezpośrednie odczyty współdzielonej strony danych trybu użytkownika - wartość leży w pamięci, więc nie ma czego podpiąć. Rzadkie w zwykłych aplikacjach, częste w kodzie licencyjnym i antytamperowym
  • bezpośrednie wywołania systemowe omijające standardowe eksporty - ten sam wzorzec
  • czas pobierany poza procesem, przez WMI, pozaprocesowy serwer COM albo RPC
  • czas z sieci - nagłówek HTTP Date, NTP, odpowiedź API albo własny zegar serwera bazy danych
  • czas ze sterowników jądra

Przypadek sieciowy jest tym jednym, który audyt potrafi przynajmniej zauważyć, i stąd ostrzeżenie w raporcie powyżej. Dla reszty werdykt works znaczy, że kanały, które to narzędzie widzi, zostały objęte - a nie że żadna inna droga nie istnieje.

W potoku

--json wypisuje te same fakty w formie maszynowej, --report zapisuje wersję dla człowieka do pliku, a sesja z werdyktem innym niż works prowadzi ten plik banerem o nierzetelnym dowodzie, zamiast chować go na dole.

chrono run "C:\apps\test.exe" --at 2027-12-31T23:59:59 --json --report evidence.txt

Wypróbuj