你怎麼知道它生效了?
用其他任何同類工具,你都無從得知。應用程式啟動了,顯示出某個日期,而你只能假定一切正常。可它也許透過一條沒有被攔截的途徑讀取時鐘——到那時,「測試通過」就毫無意義。
沒有症狀的失敗
漏掉一個通道的時間偏移工具既不會當機,也不會警告。應用程式只是透過那個沒人掛鉤的函式讀到了真實日期,然後像任何一個普通的下午那樣照常執行。測試變綠了,錯誤就這樣上線了。
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