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

Попробуйте