Chrono Mock
言語日本語

効いたかどうか、どうやって分かるのか

この種のほかのツールでは分かりません。アプリが起動して何かの日付を表示し、あなたは「効いているのだろう」と思い込みます。ところがアプリは、何もフックしていない経路から時計を読むことがあり、そうなると「テストに合格した」という結果にはまったく意味がありません。

症状のない失敗

チャネルを 1 つ取りこぼした時間ずらしツールは、クラッシュもしなければ警告もしません。アプリは、誰もフックしていない 1 つの関数を通じて本当の日付を読み、ごく普通の午後とまったく同じように動作します。テストは緑になり、バグはそのまま出荷されます。

RunAsDate はこの点について自身のドキュメントで正直であり、「現在の日付と時刻を別のソースから取得するアプリには影響しません」と述べています。そしてそのうえで、自分のアプリがそれに該当するかどうかは、利用者が突き止めるしかありません。このツールは、同じ一文を測定すべきものとして扱います。

セッションが実際に報告する内容

このプロジェクト自身のテスト用プローブの 1 つに対してツールを実行した、本物の出力です。このプローブは、意図的にネットワーク接続を開きます。

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

最後の 2 つのブロックが要点です。ここでは何も失敗していません。判定は有効です。それでもレポートは、尋ねられなくても、このアプリが終了する前にネットワークと通信したこと、つまりローカルのフックだけでは全体像にならないかもしれないことを伝えています。この一文が、信頼できるテストと信頼できないテストとの違いです。

Chrono Mock の結果画面で、What the application read が開いています。Function、Read from、Reads の列を持つ表で、NtCreateUserProcess は設計上実際の時計から読むもの、connect は設計上実際の時計から読み、フックが遅れたものとして示され、そのほかの行 - GetSystemTimeAsFileTime、NtQuerySystemTime、GetTimeZoneInformation など - は偽の時計から読み、それぞれに独自の回数が付いています。表の下には 2 つの警告があります。
同じ答えをウィンドウで見たところです。アプリが尋ねた可能性のあるすべての関数、実際にどこから読んだか、そして何回か。意図的に実際の時計のままにしてあるものも含まれ、それらは黙って合格に数えられず、名前を挙げて示されます。

4 つの判定と、それぞれの終了コード

判定は、作業を始める前に、実行中のプロセスの内側から下されます。これがそのままツールの終了コードなので、パイプラインはそれに応じて分岐できます。

判定終了コード意味
有効0時刻を読んだすべてのプロセスがセッションの時計を見た
一部有効10カバーできたチャネルとできなかったチャネルがある - カバーされなかったものは列挙される
無効11置き換えが主要なチャネルに届かなかった
判定不能4カバレッジを確定できなかった - 正直な「分かりません」

判定が「無効」の場合、プロセスはあなたが操作できるようになる前に直ちに停止されます。判定を起動より前に出すことはできません。唯一の真実の源は、実行中のプロセスの内側で取った読み取り値だからです。保証されるのは、作業を始める前に真実が分かることであって、失敗が確定している起動がそもそも起こらないことではありません。上書きして続行することもでき、その場合そのセッションは、エクスポートした証拠を含め、現れるあらゆる場所で信頼できないものとして印が付けられます。

ほかのコードは、置き換えではなく実行そのものがうまくいかなかったことを示します。使い方の誤り、起動できなかった対象、途中で打ち切られた実行、すでに動いている別のセッションなどで、どれも判定ではありません。全体の表はコマンドライン リファレンスにあります。

カバー済み、観測のみ、未カバー

チャネルは 3 つの区分に分かれ、真ん中の区分が興味深いものです。

  • カバー済み - フックされ、置き換えられたもの。アプリが尋ね、セッションの時計を得ました。呼び出し回数がその回数を示します。
  • 観測 - フックして回数を数え、あえて本当の値を返させるものです。オブジェクト待機がここに入ります。タイムアウトを縮めると本物のハンドルでタイムアウトを偽装しかねないからです。マルチメディア タイマーも入ります。スケールすると音声や MIDI のタイミングがずれるからです。この 2 つはセッションが経過時間の軸をスケールするときに監視します。ネットワーク接続と、プロセス作成の経路の 1 つは、時計ではなく証拠なので、すべてのセッションでここに入ります。高分解能カウンターはこのグループに入りません。--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

試してみる