Chrono Mock
МоваУкраїнська

Як зрозуміти, що підміна спрацювала?

З будь-яким іншим інструментом такого роду — ніяк. Застосунок запускається, показує якусь дату, і ви припускаєте. Але він може читати годинник шляхом, який ніщо не перехопило, — і тоді «тест пройдено» не означає взагалі нічого.

Збій, що не має симптомів

Інструмент зсуву часу, що пропустив канал, не падає й не попереджає. Застосунок просто читає справжню дату через ту єдину функцію, яку ніхто не перехопив, і поводиться так само, як у будь-який звичайний день. Тест зеленіє. Помилка йде в реліз.

RunAsDate чесний щодо цього у власній документації: він каже, що «не вплине на застосунки, які беруть поточні дату й час з іншого джерела», — а потім залишає вас самих з’ясовувати, чи не з таких ваш. Цей інструмент ставиться до того самого речення як до того, що треба виміряти.

Що сесія насправді повідомляє

Справжній вивід від запуску інструмента на одному з власних тестових зондів проєкту — такому, що навмисно відкриває мережеве з’єднання:

$ 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)

Останні два блоки — головне. Тут нічого не провалилося — вердикт працює — а звіт усе одно сам, без запиту, повідомляє, що цей застосунок до завершення звертався до мережі, тож локального хука може бути недостатньо. Це речення — різниця між тестом, якому можна довіряти, і тестом, якому не можна.

Екран результату Chrono Mock з відкритим розділом What the application read: таблиця зі стовпцями Function, Read from і Reads. NtCreateUserProcess позначено як таку, що читається зі справжнього годинника за задумом, а connect — як таку, що читається зі справжнього годинника за задумом і перехоплена пізно, тоді як решта рядків — GetSystemTimeAsFileTime, NtQuerySystemTime, GetTimeZoneInformation та інші — читають із підробленого годинника, у кожного свій лічильник. Під таблицею два попередження.
Та сама відповідь у вікні. Кожна функція, до якої міг звернутися застосунок, звідки він насправді читав і скільки разів — зокрема й ті, що навмисно залишені на справжньому годиннику: вони названі, а не тихо зараховані як успіх.

Чотири вердикти й код завершення для кожного

Вердикт виноситься зсередини процесу, що працює, до того як ви почнете працювати, і це власний код завершення інструмента, щоб конвеєр міг за ним розгалужуватися.

ВердиктКодЗначення
працює0кожен процес, що читав час, бачив годинник сесії
працює частково10частина каналів покрита, частина ні — непокриті перелічено
не працює11підміна не дійшла до ключових каналів
не визначено4покриття встановити не вдалося — чесне «не знаю»

Коли вердикт — «не працює», процес зупиняється негайно, перш ніж ви зможете з ним взаємодіяти. Вердикт не може передувати запуску, бо єдине джерело істини — замір, зроблений зсередини процесу, що працює. Гарантується, що ви дізнаєтеся правду до початку роботи, — а не те, що приречений запуск ніколи не відбудеться. Ви можете перевизначити це й продовжити, і тоді сесію позначено як ненадійну скрізь, де вона з’являється, зокрема в будь-яких експортованих вами доказах.

Інші коди кажуть, що зламався сам запуск, а не підміна - помилка використання, ціль, яку не вдалося запустити, перерваний запуск, інша сесія, що вже працює - і жоден із них не є вердиктом. Повна таблиця є в довіднику з командного рядка.

Покриті, спостережувані, непокриті

Канали поділяються на три корзини, і середня — найцікавіша.

  • Покриті — перехоплені й підмінені. Застосунок спитав і отримав годинник сесії. Лічильник викликів каже, скільки разів.
  • Спостережувані - перехоплені, пораховані й навмисно залишені казати правду. Тут очікування об’єктів, бо скорочений тайм-аут міг би зобразити спливання часу на справжньому дескрипторі, і мультимедійний таймер, бо його масштабування зсунуло б таймінг звуку та MIDI - обидва відстежуються, коли сесія масштабує вісь минулого часу. Мережеві підключення та один шлях створення процесів тут у кожній сесії, бо це докази, а не годинники. Лічильника високої роздільності в цій групі немає: він лишається незмінним, доки ви не попросите про нього через --scale-qpc, і тоді він покритий.
  • Непокриті — канал, який застосунок читав і до якого підміна не дійшла. Саме це перетворює вердикт на працює частково, і він перелічується за назвою, а не згортається в загальне зведення.

Нульовий лічильник — теж інформація: канал було перехоплено, а застосунок так і не спитав. Тому звіт перелічує їх, а не ховає.

Чого аудит не бачить

Звіт чесний щодо покриття, а отже, і щодо власних меж. Ось що читається без будь-якого виклику функції, який можна перехопити, або взагалі поза процесом:

  • пряме читання спільної сторінки даних користувацького режиму — значення лежить у пам’яті, тож перехоплювати нічого. Рідко в звичайних застосунках, часто в коді ліцензування й захисту від втручання
  • прямі системні виклики повз стандартні експорти — той самий шаблон
  • час, отриманий поза процесом, через WMI, позапроцесний сервер COM або RPC
  • час із мережі — заголовок HTTP Date, NTP, відповідь API або власний годинник сервера бази даних
  • час від драйверів ядра

Випадок мережі — той, який аудит може принаймні помітити, тому й існує попередження у звіті вище. Для решти вердикт працює означає, що канали, які цей інструмент бачить, були покриті, — а не що інших шляхів немає.

У конвеєрі

--json видає ті самі факти в машинному вигляді, --report записує версію для людей у файл, а сесія, вердикт якої — будь-що, крім працює, відкриває цей файл банером про ненадійні докази, а не ховає застереження наприкінці.

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

Спробуйте