工程趣聞 2026 年 9 月 8 日

2026-09-08 — Stuxnet 原始碼重建、NEC V20 微碼解密與 Linux Jump Label 自我改寫機器碼術

primary=https://github.com/Sadpainy/Stuxnet primary=https://martypc.blogspot.com/2026/09/decoding-nec-v20-microcode.html primary=https://walac.github.io/jumplabels/

重建歷史上最著名的網路武器:GitHub 上的 Stuxnet 原始碼複刻計畫

GitHub · 2026-09-08

開發者 Sadpainy 在 GitHub 上發布了一個名為 Stuxnet 的專案,嘗試把 2010 年那隻震驚全球的工業控制系統蠕蟲的原始碼重建出來。這並非外洩的原始檔案,而是作者根據十多年來資安社群累積的分析報告反向拼裝而成的版本。作者在 README 中明確將專案定位為學術研究與惡意軟體分析教材,並非可部署的攻擊工具。

背景:一隻改寫規則的蠕蟲

Stuxnet 在 2010 年被發現時,首度證實了針對工業控制系統(ICS)的國家級網路武器確實存在。它鎖定伊朗納坦茲(Natanz)濃縮鈾工廠使用的西門子 SIMATIC WinCC、Step 7 軟體與 S7-300/400 系列 PLC。過去十餘年,Symantec、Kaspersky Lab、ESET 等資安廠商,以及獨立研究者 Amr Thabet、Christian Roggia 陸續發表深入的逆向工程報告,這個重建專案正是站在這些既有研究成果之上。

核心內容:模組化拆解回原始邏輯

專案將 Stuxnet 拆成數個對應原始樣本的模組。載入與投放階段由 winsta.exe 與暫存檔 ~WTR4141.tmp 負責初次感染,另一個暫存檔 ~WTR4132.tmp 則利用 Win32k.sys 的漏洞完成提權。真正的攻擊邏輯藏在 s7otbxdx.dll 與 s7aaapix.dll 這兩個 hook 函式庫裡,它們攔截並竄改 Step 7 與 PLC 之間的通訊。

核心酬載 s7plcmain 實作了俗稱「變頻竄改」(frequency tampering)的攻擊邏輯,鎖定驅動離心機的變頻馬達下手;而 mrxcls.sysmrxnet.sys 兩支驅動程式則透過 SSDT hooking 在核心層隱藏檔案與程序。傳播管道涵蓋 USB 隨身碟(利用 LNK 捷徑漏洞)、網路芳鄰共享與 P2P,這也是當年 Stuxnet 能夠跳過 air-gapped 網路傳播的關鍵。整套程式碼以 C 語言為主,鎖定 Windows XP/7,建置環境需要 Visual Studio 2019/2022 搭配 Windows Driver Kit 7600。

影響範圍:忠實度與定位的界線

作者坦言這是從「反編譯後的二進位檔」重新整理成可讀原始碼的結果,並不等於當年攻擊者手上那份原始檔案,而是保留了原始攻擊邏輯與傳播路徑的復刻版本。README 附上了明確的法律免責聲明,強調專案「不是可部署的惡意軟體」,使用者須自行承擔合規責任。對資安研究者而言,這類重建讓十多年前僅存在於分析報告文字敘述中的攻擊鏈,重新變成一份可以逐行閱讀、編譯、對照學習的教材。

原始來源:github.com/Sadpainy/Stuxnet


解碼 NEC V20 微碼:用神經網路讀懂一顆四十年前晶片的心跳

Adventures in PC Emulation · 2026-09-03

部落格 Adventures in PC Emulation 的作者 GloriousCow,同時也是 8088/V20 模擬器 MartyPC 的開發者,在 2026 年 9 月 3 日發布長文,記錄自己如何把 NEC V20 處理器內部的微碼(microcode)完整解碼出來。這顆晶片是 Intel 8088 的 second source 版本,也是許多相容機種偏好的升級選項。文章詳細拆解了從晶片照片到可讀微碼表的整個逆向工程過程。

背景:一直被模擬成「穿著馬甲的 8088」

NEC V20 二進位相容於 8088,同時疊加了 80186 指令集、NEC 自家 0Fh-prefixed 的擴充指令,還內建一套 8080 模擬模式。MartyPC 過去只能把 V20 當成「穿著 V20 外衣的 8088」來模擬,因為沒有人真正弄清楚它微碼層的行為差異。要做到 cycle-accurate 等級的模擬,就必須先把微碼本身讀出來。

核心內容:從晶片照片到卷積神經網路

研究者 InfoSecDJ 先用顯微攝影技術,把一顆 Sharp 代工的 V20 die 拍成解析度 70478×80672、共 5.6 gigapixel 的超高解析度合成照片。微碼 ROM 區塊在照片中只佔 258×116 像素,理論上藏有 29,928 個位元。初期用閾值法直接判讀位元全部失敗,作者改為替每個位元位置切出 42×42 像素的小圖,先手動標記逾一千個樣本,再用 PyTorch 訓練卷積神經網路(CNN),最終達到 99% 以上信心度的自動判讀,並持續用模稜兩可的樣本回頭重訓模型。

解碼結果顯示 ROM 總共 29,928 位元,除以 29 位元的微碼字長,得到 1,032 個微碼字——比預期的 1,024 多出 8 個。多出來的字數揭露了一個關鍵事實:V20 與 16 位元的 V30 共用同一片微碼光罩,靠金屬層上切斷一條線路來切換成對應的 CPU 型號。解碼 PLA 有 257 條啟動線服務這 1,032 個微碼字,指令入口以 4 為單位定址,搭配 4:1 的欄位多工器;Group Decode ROM 則會依 opcode 送出情境訊號,概念上類似 8088 那組 15 訊號的 GDR,但欄位更多。

影響範圍:法庭文件補不齊的空白,靠社群填上

當年 NEC 與 Intel 的訴訟留下的法庭文件,只提供了不完整的微碼字格式圖,實際格式中還有先前從未被記錄的 F、W、E 欄位。作者與 Vintage Computer Federation 論壇的 dreNorterR(先前已解出 80186 微碼)合作,像解填字遊戲一樣,靠比對指令行為反覆驗證每個欄位的意義,並確認來源欄位第一組數值依序對應 ES、CS、SS、DS 等區段暫存器,再接 AX、CX、DX、BX、SP、BP、SI、DI 通用暫存器。有了微碼層級的精確資料,cycle-exact 等級的 V20 模擬終於變得可行,所有解碼素材與微碼對照表都已公開在 GitHub 的 x86_microcode repo 中。

ROM  : 29,928 bits  ÷ 29-bit word = 1,032 words (expected 1,024)
GDR  : 257 activation lines → 1,032 microcode words
Word : F | W | E | src(reg) | dst(reg) | ... (29 bits total)

原始來源:martypc.blogspot.com


核心程式設計師都該懂的 Jump Label:讓 Linux 用自我改寫機器碼換來近乎零成本的開關

walac.github.io · 2026-09-07

開發者 Wander Lairson Costa 發布長文《What every kernel programmer should know about Jump Labels》,以 kernel v7.2-rc7 樹為基準,逐層拆解 Linux 核心裡 jump label(又稱 static key)機制如何在執行期直接改寫機器碼。文章把這項技巧形容為「自我修改代碼,執行在從未被設計成預期這種事的 CPU 上」。全文示範了它如何讓大量功能開關與 tracepoint 幾乎不佔用熱路徑的效能成本。

背景:一個 if 判斷式的代價

一般的 if (feature_enabled) 判斷式,執行時需要從記憶體載入旗標、與零比較、再交給分支預測器猜測結果。現代 CPU 的分支預測已經很準,真正甩不掉的成本是那次記憶體載入本身,尤其是旗標所在的快取線可能根本不在 L1 裡。Jump label 的做法是乾脆把整個條件判斷從程式碼裡拿掉,換成一段可以在關閉時是 NOP、開啟時是跳轉指令的區塊。

核心內容:三段式、跨核心同步的貼補協定

切換一個 jump label 時,核心會執行一套以 IPI 跨處理器同步的三階段貼補協定:先在目標位址寫入單一位元組的 INT30xCC),這個寫入對所有 CPU 而言是原子可見的;接著在 INT3 擋住執行的狀態下,改寫剩餘的指令位元組;最後才把 INT3 換回最終指令的第一個位元組。這個順序徹底避免了「指令被撕裂讀取」的風險,也就是其他核心同時讀到新舊位元組混雜的半成品指令。

x86 上實際會用到的編碼包括 2 位元組 NOP(66 90)、5 位元組 NOP(0f 1f 44 00 00)、相對 8 位元跳轉 JMP rel8eb xx),以及相對 32 位元跳轉 JMP rel32e9 xx xx xx xx)。由於核心的 .text 段是唯讀映射,實際貼補是靠 arch/x86/kernel/alternative.c 裡的 __text_poke(),透過一份僅該 CPU 可見、可寫的私有位址空間 text_poke_mm 對應到同一實體頁,藉此避免昂貴的 TLB shootdown。每個貼補點都對應 include/linux/jump_label.h 定義的 struct jump_entry,裡面用三個自身相對位移分別記錄可貼補指令、跳轉目標與對應 static_key 的位置。

影響範圍:切換很貴,但熱路徑幾乎免費

開發者透過 DEFINE_STATIC_KEY_FALSE()DEFINE_STATIC_KEY_TRUE() 宣告一個 key,在熱路徑用 static_branch_likely()static_branch_unlikely() 判斷,真正要切換狀態則呼叫 static_branch_enable()disable(),這組 API 還支援 refcount 式的 _inc()_dec() 語意,讓多個子系統共用同一把 key 時不會互相踩踏。設計上刻意把成本壓在「切換」而非「執行」這一側:切換一次要跑完整套跨 CPU 同步貼補協定,代價不小;但熱路徑上 CPU 只需要解碼一條 NOP,幾乎不耗成本。啟用 HAVE_JUMP_LABEL_HACK(x86_64 預設開啟)後,編譯器直接產生真正的 jmp,再由 objtool 把預設關閉的貼補點轉換成大小相符的 NOP,讓能省則省,優先用 2 位元組版本而非固定 5 位元組,藉此節省 instruction cache 空間。

2-byte NOP  : 66 90
5-byte NOP  : 0f 1f 44 00 00
JMP rel8    : eb xx
JMP rel32   : e9 xx xx xx xx

貼補步驟: 寫入 INT3(0xCC) → 改寫其餘位元組 → INT3 換回正式指令

原始來源:walac.github.io/jumplabels


End of article
0
Would love your thoughts, please comment.x
()
x