Як зрозуміти, що підміна спрацювала?
З будь-яким іншим інструментом такого роду — ніяк. Застосунок запускається, показує якусь дату, і ви припускаєте. Але він може читати годинник шляхом, який ніщо не перехопило, — і тоді «тест пройдено» не означає взагалі нічого.
Збій, що не має симптомів
Інструмент зсуву часу, що пропустив канал, не падає й не попереджає. Застосунок просто читає справжню дату через ту єдину функцію, яку ніхто не перехопив, і поводиться так само, як у будь-який звичайний день. Тест зеленіє. Помилка йде в реліз.
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)
Останні два блоки — головне. Тут нічого не провалилося — вердикт працює — а звіт усе одно сам, без запиту, повідомляє, що цей застосунок до завершення звертався до мережі, тож локального хука може бути недостатньо. Це речення — різниця між тестом, якому можна довіряти, і тестом, якому не можна.
Чотири вердикти й код завершення для кожного
Вердикт виноситься зсередини процесу, що працює, до того як ви почнете працювати, і це власний код завершення інструмента, щоб конвеєр міг за ним розгалужуватися.
| Вердикт | Код | Значення |
|---|---|---|
| працює | 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