《隱形兵器 III:代碼的宿命》| Autonomous Vulnerability Discovery, Memory Safety & Adversarial Self-Play | IDRI

《隱形兵器》

紅隊三部曲・下篇:代碼的宿命

——當機器學會尋找漏洞,人類留下的錯誤開始反過來尋找人類


01|05:42

凌晨 05:42。

紅隊基地。

整座機房沒有一盞燈是為了照亮人。

冷白色的指示燈一排排閃爍。

GPU node 的風扇維持在高轉速。

液冷系統持續吞吐熱量。

空氣裡有一種很奇怪的味道。

金屬。

灰塵。

塑膠。

還有電子設備長時間滿載後,那種近乎沒有溫度的焦躁。


林祈安站在玻璃牆前。

她看著牆另一邊的機櫃。

「全部上線?」

程遠回答:

「全部。」

「Fuzzer?」

「上線。」

「Symbolic engine?」

「上線。」

「Crash triage?」

「上線。」

「Model-based scheduler?」

「已經開始跑。」


Richard坐在後面。

沒有說話。


周野把最後一個 terminal 關掉。

「現在這東西。」

他說。

「已經不是工具了。」

林祈安回頭。

「那是什麼?」

周野看著機房。

「是一個不會累的工程師。」


Richard終於笑了。

「不。」

他說。

「工程師至少知道什麼時候該回家。」


02|Self-Play

第二代攻防平台正式啟動。

它不再依賴人類一次次提出假設。

而是讓兩組模型互相產生問題。


Attack Policy
     ↓
Candidate State
     ↓
Simulation
     ↓
Observation
     ↓
Defensive Response
     ↓
Reward / Penalty
     ↓
Policy Update
     ↺

紅隊不再單純問:

「哪裡有漏洞?」

而是:

「在目前防禦策略下,哪一類狀態最值得探索?」

藍隊也不再單純問:

「怎麼修?」

而是:

「哪一種修補會迫使攻擊策略改變?」


這已經不是傳統的:

Red vs Blue

而是:

Policy vs Policy。


林祈安看著第一輪結果。

「開始收斂。」

Richard問:

「太快?」

「有點。」

「為什麼?」

她指向螢幕。

「雙方策略開始互相適應。」


周野淡淡說:

「兩個人下棋。」

林祈安搖頭。

「不是。」

「兩個模型。」


Richard補了一句:

「兩個不知道自己為什麼下棋的模型。」


03|速度

第一輪:

17.4 ms

第二輪:

9.8 ms

第三輪:

3.2 ms

然後:

1.1 ms


程遠看著 dashboard。

「還能快。」

Richard問:

「為什麼要快?」

「因為越快,探索空間越大。」

「然後?」

「找到更多 candidate。」

「然後?」

程遠停住。

Richard看著他。

「最後得到更多你不知道自己為什麼相信的東西。」


程遠沒有回答。


林祈安卻在另一邊看著數據。

她知道Richard擔心什麼。

速度會讓模型產生一個危險幻覺:

只要計算量足夠大,就能消除未知。


但她也知道。

這個幻覺很難阻止。

因為數字真的很好看。


04|Blue Side

顧青禾也看到同樣的變化。

ASTER-X 的 telemetry pipeline 開始出現前所未有的壓力。

不是流量突然暴增。

而是:

行為變化速度。


過去。

攻擊者可能一天調整一次策略。

現在:

幾秒。

甚至更短。


模型剛適應一個 behavior。

下一輪 behavior 已經變了。


工程師問:

「我們提高 retraining frequency?」

顧青禾搖頭。

「不要。」

「為什麼?」

「如果我們讓模型跟著每一次變化跑。」

「它會開始學習噪音。」


她看著那條快速震盪的曲線。


「我們現在遇到的問題不是 drift。」

「是 velocity。」


05|Velocity

她把兩個詞寫在白板上。

Drift

下面:

Velocity

「Drift 是方向。」

她說。

「Velocity 是變化速度。」

「當速度高到超過 observation window。」

她停了一下。

「你甚至還沒看懂上一個狀態,下一個狀態已經死了。」


這是第三篇真正的戰場。


不是:

誰比較準。

而是:

誰能在更短時間裡完成一次完整的 observe → decide → act → adapt。


06|Fuzzing Engine

紅隊的漏洞研究引擎開始工作。

它沒有一個漂亮的名字。

只有:

FUSE

Fuzzing and Unified Search Engine。


它可以從:

  • program state
  • execution trace
  • crash signature
  • memory state
  • input grammar
  • historical vulnerability pattern

建立候選搜尋空間。


然後反覆探索。


第一次。

沒有結果。

第二次。

沒有結果。

第一萬次。

沒有結果。

第一億次。


Interesting State Detected


程遠站起來。

「找到了?」

周野走過來。

「還不知道。」


他看著 crash report。

不是典型 crash。

沒有立即的 segmentation fault。

只有:

state inconsistency。


周野的表情第一次變了。


07|Rust

周野的代號:

Rust

這個名字其實不是因為他喜歡 Rust 語言。

而是因為他曾經說:

「所有系統最後都會生鏽。」


他看著 execution trace。

一次。

兩次。

十次。


「不是一般 memory corruption。」

林祈安問:

「那是什麼?」

「生命週期問題。」

「Memory lifetime?」

「嗯。」


他把幾個 state 疊在一起。

Allocate
   ↓
Use
   ↓
Release
   ↓
Unexpected Reference

林祈安看著它。

「UAF?」

周野沒有立即回答。


他又跑了一次。


同樣的 state。

同樣的錯誤。


他才說:

「可能。」


Richard站在後面。

「可能就夠了。」

周野回頭。

「不。」

「為什麼?」

「因為 memory safety 不能靠直覺。」


Richard點頭。

「很好。」


08|Three Years Ago

另一條時間線。

三年前。

凌晨 02:43。

那個年輕工程師還坐在電腦前。


他正在修一個完全不同的問題。

Pipeline。

不是 security。

不是 exploit。

甚至不是 production incident。

只是:

某個舊元件的相容性。


他看到一個 cleanup path。

知道它很醜。

知道它不理想。

但 deadline 在後面。


他寫了一個 workaround。

註解:

temporary


然後 commit。


Approved.


沒有人知道。

這個 commit 從此活了三年。


09|現在

周野重新看著那個 crash state。

「這東西很老。」

Richard問:

「多久?」

「不知道。」

「估。」

「三年以上。」


房間安靜。


林祈安看向Richard。

她忽然想起第二篇。

legacy-ingest

temporary workaround


「就是它?」

Richard沒有回答。


周野卻說:

「不是同一個 bug。」

他停了一下。

「但可能是同一種人留下的東西。」


10|人類的漏洞

林祈安皺眉。

「什麼意思?」

周野回答:

「不是 coding error。」

「是 decision error。」


他指著那段歷史。

「當時的人知道這裡不漂亮。」

「但風險不夠高。」

「所以 postponed。」


Richard接著說:

「然後下一個人看到。」

「以為前一個人已經處理。」

周野點頭。

「再下一個人。」

「看到 test 都是綠的。」


最後:

沒有人真的擁有它。


Richard說:

「這就是 legacy 最危險的地方。」

「它沒有主人。」


11|藍隊的模型也找到了

顧青禾收到同一份 anomaly。

但她看到的是另一種證據。


Memory telemetry。

Process behavior。

Crash frequency。

Restart pattern。

Runtime mitigation state。


模型給出:

Risk: High

但:

Exploitability: Uncertain


顧青禾沒有直接下結論。

她問:

「原始 crash state?」

工程師把資料送過來。


她看了很久。

「隔離。」


工程師問:

「要 patch?」

「先。」

「然後?」

「建立獨立 reproduction environment。」


她沒有追擊。

沒有封鎖所有相關服務。

也沒有立刻重建整個模型。


她只做一件事:

把未知從 production 裡拿出去。


12|紅隊的誘惑

紅隊看著 candidate。

程遠說:

「可以深入。」

Richard問:

「為什麼?」

「因為它可能是突破點。」

周野說:

「可能。」


林祈安卻突然問:

「如果它是真的呢?」

所有人看她。

「如果這個漏洞不是模型生成的。」

「而是真的存在。」

「那我們現在找到的不是 candidate。」

她停了一下。

「是現實。」


Richard看著她。

「繼續。」


13|The Machine

自動化搜尋引擎開始加速。


10⁶

10⁷

10⁸


候選 state 不斷被淘汰。


Noise

Duplicate

Non-reproducible

Low relevance


最後只剩少數。


其中一個:

Persistent State Anomaly


周野看了十秒。

「這個。」

林祈安問:

「確認?」

「還沒有。」

「那為什麼?」

周野回答:

「因為它很像一個人寫出來的 bug。」


Richard看向他。

「這是什麼標準?」

周野笑了一下。

「經驗。」


14|Nash

雙方模型開始高速迭代。

紅隊:

Attack Policy A

藍隊:

Defense Policy B

紅隊:

A'

藍隊:

B'


每次更新。

收益差距縮小。


ΔU → 0


林祈安看著曲線。

「開始收斂。」

Richard問:

「到哪?」

「接近 equilibrium。」


周野問:

「Nash?」

「近似。」


Richard沉默。


如果兩個模型都能預測對方。

如果兩邊都可以快速適應。

如果每一次策略變更,都被對方立即修正。


最後:

沒有人能取得穩定優勢。


這就是模型世界裡的和平。


不是因為大家理解彼此。

而是:

每一個動作都已經被對方預期。


15|The Human Move

Richard突然問:

「它們知道這個嗎?」

林祈安看向他。

「什麼?」

「平衡。」

「知道。」

「那如果我們不按照平衡走?」

她愣了一下。


Richard說:

「AI 只會對它認為可能的行為做最佳化。」

「所以?」

「真正的異常,不一定是最好的策略。」


林祈安慢慢理解。

如果模型學會:

optimal attack

它也可能忽略:

stupid attack。


人類最擅長的東西之一。

就是做一件看起來完全不合理的事情。


16|那個舊漏洞

周野重新跑歷史 binary analysis。

不是尋找新漏洞。

而是:

確認舊漏洞。


結果出來。

三年前的版本。

某個 lifecycle path。

某個 reference。

某個 cleanup sequence。


不是漂亮的 zero-day。

不是全新發現。

不是 AI 自主生成的神蹟。


只是:

一個三年前留下來的 memory safety 問題。


周野把結果傳給Richard。

只有一句:

「是真的。」


Richard看著那行字。

沒有笑。


17|The Free

他走到白板。

寫下:

Object
   ↓
Reference
   ↓
Release
   ↓
Reference remains

然後把最後一行圈起來。


林祈安問:

「這就是最後一擊?」

Richard搖頭。

「不是。」

「那是什麼?」

「這是提醒。」


她不明白。

Richard說:

「我們一直以為最後一擊要由 AI 找到。」

「其實三年前就有人把它留下了。」


房間安靜。


18|顧青禾知道了

藍隊收到 patch validation。

修補後:

memory state 穩定。

runtime telemetry 正常。


但顧青禾沒有立即關掉 anomaly。

她問:

「誰寫的?」

工程師回答:

「三年前的元件團隊。」

「現在呢?」

「團隊已經解散。」


顧青禾閉上眼睛。


又是:

沒有主人。


她突然明白。

第一篇的 emergence。

第二篇的 provenance。

第三篇的 legacy。

原來一直是同一個問題。


不是漏洞。

而是:

責任的時間跨度。


19|最後的對話

紅隊。

林祈安問:

「如果 AI 可以找到所有漏洞。」

「我們還需要人嗎?」

Richard沒有立即回答。


他走到窗前。

城市已經開始亮。

早餐店開門。

公車開始行駛。

一個送貨員把箱子搬下車。

沒有人知道。

幾百公里外。

兩套機器剛剛完成了一場數十億次狀態探索。


Richard說:

「需要。」

「為什麼?」

「因為 AI 找得到漏洞。」

「然後?」

「但它不知道哪一個漏洞,是人類應該負責的。」


林祈安沉默。


Richard繼續:

「你可以讓模型理解 code。」

「可以讓它理解 exploitability。」

「甚至可以讓它理解對手。」


他轉身。

「但你很難讓它理解,為什麼三年前那個人會選擇先回家。」


20|顧青禾

顧青禾站在同一片晨光裡。

她手上拿著修補完成的報告。

最後一欄:

Root Cause

不是:

Memory Error

不是:

Unsafe Reference


她要求工程師重新寫。

最後報告變成:

「未被持續擁有的工程決策,經過時間累積後形成安全風險。」


她簽名。


然後把報告歸檔。

檔名:

legacy_decision_001


這一次。

她沒有把它分類成:

Incident

而是:

Institutional Memory。


21|Equilibrium

紅隊模型停止。

藍隊模型停止。


雙方的策略曲線最後重疊。


ΔU ≈ 0


林祈安看著螢幕。

「Nash equilibrium。」

周野說:

「看起來。」

Richard卻搖頭。

「不是。」


兩人看向他。


他指著另一條線。


Human Decision


那條線從頭到尾都沒有收斂。


Richard說:

「因為人不是模型。」


22|最後一個 Bug

故事回到三年前。

那個年輕工程師終於完成工作。

凌晨 03:12。

他關掉 terminal。

看著最後一個 commit。


temporary workaround


他猶豫了一下。

想改掉。

但手機響了。

家人問:

「你什麼時候回來?」

他看了一眼時間。

03:13。


他說:

「現在。」


然後關機。


那段 code 留了下來。


不是因為他笨。

不是因為他不負責。

甚至不是因為公司不重視安全。


只是因為:

那天晚上,他也是一個人。


23|The Last State

多年後。

沒有人知道他的名字。

沒有人記得那個 commit。

沒有人會在 incident report 裡寫:

「因為某個工程師凌晨三點想回家。」


但所有模型。

所有 fuzzers。

所有 detection engine。

所有 threat intelligence。

所有 automated defense。

最後都在處理:

那個選擇留下來的結果。


這就是代碼的宿命。


Code 不會記得作者。

Binary 不會理解 deadline。

Memory 不知道誰加班。

CPU 不知道誰想回家。


它們只會:

執行。


24|黎明

早上 06:18。

紅隊基地。

Richard關掉最後一台螢幕。

林祈安問:

「結束了?」

Richard看著窗外。

「這一輪。」

「下一輪呢?」

他笑了一下。


「下一輪永遠會有。」


周野背起包。

「所以我們到底贏了沒有?」

Richard想了一下。

「如果你需要一個勝負。」

「嗯。」

「那我們沒贏。」

「藍隊呢?」

「他們也沒有。」


程遠問:

「那我們做了什麼?」

Richard走出門。

只留下最後一句:

「我們只是證明,世界沒有被算完。」


《代碼的宿命》・完

《隱形兵器》紅隊三部曲・完


Story Background|終局背景

三部曲最初的戰場是:

Network。

然後進入:

Model。

最後抵達:

Code。

但真正的戰場,其實一路都沒有改變。

一直都是:

Trust。


三篇可以濃縮成:

第一篇
「我能不能讓你看不到我?」
        ↓
第二篇
「我能不能讓你相信錯誤的答案?」
        ↓
第三篇
「即使你看穿一切,
你能不能理解三年前為什麼有人留下這個 Bug?」

這才是整套三部曲真正的閉環。


Technical Architecture|終局技術架構

第三篇不再單純依賴某個 exploit。

整體架構是:

Historical Software
       ↓
Static / Dynamic Analysis
       ↓
Fuzzing
       ↓
State Exploration
       ↓
Crash / Anomaly Triage
       ↓
Exploitability Assessment
       ↓
Defensive Simulation
       ↓
Policy Adaptation
       ↺

攻防雙方則進入:

Red Policy
    ↕
Simulation
    ↕
Blue Policy
    ↕
Simulation
    ↺

最終:

ΔU ≈ 0

也就是雙方策略收益差距逐漸收斂。

但故事故意留下最後一條沒有收斂的變數:

Human Context

因為:

人類不是 stationary policy。

人會改變目標。

改變優先級。

改變價值。

改變心情。

甚至在凌晨三點決定:

「今天先回家。」

這些東西沒有辦法漂亮地塞進 reward function。


Technical Concepts|本篇核心技術

本篇涉及:

  • Automated Fuzzing
  • Coverage-Guided Exploration
  • State-Space Search
  • Symbolic Execution
  • Crash Triage
  • Memory Safety
  • Use-After-Free
  • Object Lifetime
  • Reference Lifetime
  • Runtime Mitigation
  • Dynamic Analysis
  • Static Analysis
  • Exploitability Assessment
  • Reinforcement Learning
  • Self-Play
  • Policy Optimization
  • Reward Shaping
  • Adversarial Adaptation
  • Nash Equilibrium
  • Model Convergence
  • Automated Patch Validation
  • Legacy Software Risk
  • Technical Debt
  • Security Debt
  • Provenance
  • Institutional Memory

其中最重要的一個不是 UAF。

是:

Exploitability ≠ Existence。

某個 memory safety issue 存在,不代表它一定能形成可靠攻擊。

反過來:

某個模型認為「沒有 exploit」,也不代表漏洞不存在。

因此紅隊與藍隊都必須區分:

Bug
 ↓
Reachability
 ↓
State Control
 ↓
Impact
 ↓
Exploitability

這比「找到一個 crash 就宣布 zero-day」嚴謹得多。

後者通常只是把 fuzzing report 穿上軍裝。


Character Arc|人物終局

Richard

第一篇:

經驗 > 自動化

第二篇:

理解模型

第三篇:

接受模型永遠無法完整理解人。

他最後沒有成為「最強紅隊」。

反而成為一個更成熟的人:

知道什麼可以做,與什麼值得做,是兩個不同問題。


林祈安

她的弧線最完整。

第一篇:

模型可以理解世界。

第二篇:

模型可能誤解世界。

第三篇:

模型可以探索世界,但不能替人決定世界的意義。

她最後沒有放棄 AI。

恰恰相反。

她變得更相信 AI。

只是:

不再崇拜 AI。


周野

他從頭到尾都相信:

machine state 才是真實。

最後卻發現:

machine state 的上游,

是:

Requirement
↓
Design
↓
Decision
↓
Code
↓
Binary
↓
Memory

所以他最後真正理解:

最低階的漏洞,有時候是最高階決策留下的影子。


顧青禾

她是三部曲裡最接近「成熟」的人。

她沒有擊敗紅隊。

也沒有讓模型完美。

她只是始終拒絕把:

Unknown

偽裝成:

Known。

這讓她在第三篇保住了系統。


程遠

他的變化最微妙。

第一篇:

「只要 automation 能做,就不需要人。」

第二篇:

「模型可以替我們做判斷。」

第三篇:

他第一次問:

「如果機器比我們更快,我們還有什麼用?」

Richard沒有回答。

因為這個問題沒有標準答案。


Final Foreshadowing|最後留下的東西

故事結束。

但世界沒有結束。

在最後一頁。

一台紅隊分析節點重新啟動。


沒有攻擊任務。

沒有 C2。

沒有 exploit。

只有一個新的 repository。

名稱:

legacy-review


第一個 commit:

human_context_required


另一座城市。

顧青禾的藍隊也建立了一個新的模型。

名字:

ASTER-Ω

第一個 rule:

Never collapse uncertainty without provenance.


林祈安看到這條 rule。

她笑了一下。

然後回覆:

「Good rule。」


幾秒後。

顧青禾收到回覆。

她看著那個帳號。

沒有名字。

只有:

Morrow


她沒有封鎖。

也沒有回報。

只回了一句:

「I know。」


Leo's Note|終章後記

這套三部曲寫到最後,我反而不太想談 0-Day。

因為 0-Day 很浪漫。

尤其對沒有真的維護過大型系統的人。

一講:

自動化漏洞挖掘。

Symbolic Execution。

AI Exploit Synthesis。

Microsecond Decision Loop。

整個畫面馬上有了。

GPU 在燒。

風扇在叫。

螢幕滿是紅色。

彷彿下一秒就能把 Pentagon 螢幕炸黑。

很電影。

也很爽。

但真正做工程的人知道。

系統最常死的地方,通常沒有那麼華麗。

可能是一個:

TODO

一個:

temporary

一個:

legacy

一個:

「明天再改。」

甚至只是:

「這次先這樣。」


這些東西不會出現在 AI 的 reward function 裡。

但它們會出現在 codebase 裡。

然後活三年。

五年。

十年。

最後某一天。

一個模型花幾十億次計算。

才發現:

原來人類早就把門打開了。


這就是我想留下的最後一句話。

不是:

AI 會取代駭客。

也不是:

AI 會取代防守者。

甚至不是:

AI 會超越人類。

這些都太廉價。

真正值得害怕的是:

當機器開始比人類更快地發現錯誤,而人類卻比機器更慢地承認「這個錯誤為什麼會存在」。

因為那一刻。

問題已經不在技術。

而在我們。


四十五歲以後,我對 Bug 的態度也變了。

年輕時看到 bug:

修掉。

當主管後看到 bug:

評估風險。

再老一點:

先問誰會因此受傷。

現在?

我大概會先看 commit history。

然後泡一杯咖啡。

如果發現最後是:

temporary workaround

我通常會笑。

不是因為好笑。

是因為我知道。

這個故事還沒完。

代碼不會記得我們。

但它會忠實地繼承我們所有沒有處理完的決定。

這大概就是:

**代碼的宿命。**