工程趣聞 2026 年 9 月 7 日

2026-09-07 — Anubis 花一年把 WebAssembly 做進反爬蟲挑戰、工程師把 Python 直譯器塞進 1024 bytes、GNU Health 創辦人手動裝出一台加密 NetBSD 11

primary=https://anubis.techaro.lol/blog/2026/anubis-wasm/ primary=https://austinhenley.com/blog/python1024.html primary=https://github.com/AZHenley/python1024 primary=https://meanmicio.org/2026/09/06/netbsd-11-from-scratch/

一年、五代 PR、一個編譯器 bug:Anubis 把 WebAssembly 塞進反爬蟲挑戰的過程

Techaro(Anubis Blog) · 2026-09-06

反爬蟲系統 Anubis 開發者暨 Techaro CEO Xe Iaso 在部落格中描述,團隊耗時一整年、數百個 commit、五代 pull request,才把以 WebAssembly 為核心的工作量證明(proof of work)挑戰做進下一版 Anubis,過程中還意外揪出生涯第一個編譯器臭蟲。

手機和爬蟲的 CPU 拔河

Anubis 長期面對一個矛盾:調低難度能避免手機發燙,同一個難度卻也得讓爬蟲夠痛。新版本讓管理者直接在規則裡指定挑戰演算法:

thresholds:
  - name: moderate-suspicion
    expression:
      all:
        - weight >= 10
        - weight < 20
    action: CHALLENGE
    challenge:
      algorithm: argon2id
      difficulty: 6

改用 memory-hard 的 argon2id 取代原本只吃 CPU 的雜湊函式,讓「拿 CUDA 硬寫 solver」這條路基本死透。

同一份二進位檔,兩端執行

Anubis 原本的 fast 挑戰要在瀏覽器端寫一份 JavaScript、伺服器端寫一份 Go,兩邊邏輯得手動同步。這次 Xe Iaso 讓用戶端和伺服器共用同一份 WebAssembly 二進位檔,概念上像任天堂紅白機的 CIC 防拷晶片,兩端必須鎖步。他選 Rust 以 no-stdwasm32-unknown-unknown 為編譯目標,理由很單純:Rust 產出的 WASM 體積最小、跑得也最快。

手刻 ABI 與雙份 SIMD build

理想做法是用 WebAssembly Component Model 描述介面,但當時 Go 端的 host binding 工具 gravity 還不支援在 host/guest 間傳遞 struct 或 byte 陣列,只好手刻 ABI:一塊 4096 bytes 資料緩衝區(卡在一個記憶體分頁大小)、約 32 bytes 的結果與驗證緩衝區,搭配 anubis_workanubis_verify 兩個進入點。WASM 的 SIMD 擴充能加速雜湊,但 CanIUse 認定 SIMD 到 Chrome 91 才算基準支援,Anubis 卻得撐到 Chrome 75,於是維護兩份 build——含 SIMD 與不含——靠 wasm-feature-detect 探測後決定載入哪一份。

生涯第一個編譯器 bug

為了讓停用 WebAssembly 的瀏覽器(如 iOS 鎖定模式、GrapheneOS 的 Vanadium)也能跑,他用 binaryen 專案的 wasm2js 把 WASM 反編譯回 JS 當備援。CI 在 Ubuntu 上的 wasm2js 解析 Rust 產生的 tail call 擴充會失敗,Fedora 版本卻正常。他手動 bisect 多個版本,鎖定 binaryen v128,卻發現每次重編,輸出的 WASM 都神秘飄移約 29 bytes:

- try_table (catch_all_ref 8)   ;; 1f 40 01 03 08
+ try_table (catch_all_ref 4)   ;; 1f 40 01 03 04

setarch --addr-no-randomize 關掉 ASLR 後結果變得穩定,證實問題出在 LLVM 依機器指標順序走訪例外處理區塊,而不是自己的程式碼寫錯——這是他生涯第一次真的踩到編譯器本身的 bug,後來在新版 wasi-sdk 中修掉。

用 Kubernetes 養瀏覽器動物園

為測試跨版本相容性,他寫了工具 chromesweep,能同時開出多版本 Chrome 打向測試中的 Anubis,並把瀏覽器版本清單開源在 TecharoHQ/gubal。舊版 Chrome 被視為資安風險,每個 Chrome pod 包進 Kata containers 的 microVM,再用 NetworkPolicy 限制它只能連到受測實例。最後一坑是 Chrome 75 編譯 WASM 會噴 CompileError,原因是 rustup 下載的預編譯標準庫用了 reference types,即使程式鎖定 MVP 特性也一樣;他改用 wasm-opt 搭配一串旗標把不相容特性硬剝掉。這套機制目前關閉隨 Anubis v1.28.0(代號 Wuk Lamat)出貨,預計在 v1.29.0 打開預設值。

原始來源:Anubis Blog(Techaro)TecharoHQ/gubal


1024 bytes 的 C 語言,如何生出一個「看起來像」Python 的直譯器

Austin Z. Henley · 2026-09-06

微軟工程師 Austin Z. Henley 在部落格中分享他用純 C 語言、在 1024 bytes 原始碼裡塞進一個能跑 FizzBuzz 的「類 Python」直譯器,不用巨集也不依賴外部函式庫。完整過程與最終 golf 版原始碼公開在 GitHub 的 AZHenley/python1024

先求能動,再求夠小

Henley 一開始挑戰 512 bytes,寫出運算式 1+2、賦值 x=1+2*3、條件式 if x>y: z=3 後就已經爆表——只做出一台計算機,還沒有半點 Python 的樣子。他因此改弦易轍,先列出「看起來像 Python」的元素清單(def、冒號、縮排、if 不用括號),把目標放寬到 1024 bytes,採先求能動、再求夠小的順序。

沒有 AST,邊解析邊執行

真正的 CPython 會先斷詞、蓋語法樹、跑最佳化才產生 bytecode 執行,這個直譯器完全跳過中間表示,靠遞迴下降解析器邊讀原始碼邊算值。所有狀態塞進幾個全域變數:

char src[999];   /* 整份程式(去掉多數空白) */
int  vars[256];  /* 符號表 */
int  pos, ch, line_start;

whilefor x in range(y) 迴圈會記住條件運算式在原始碼裡的位置,每輪執行完就跳回去重新解析同一段字元;函式呼叫同理,呼叫時記住呼叫端位置、跳到函式本體、執行完再跳回來,完全靠 C 自身的呼叫堆疊處理遞迴。變數名稱被限制成單一小寫字母,可以直接當 vars[] 的索引查表,省掉整張符號表的字串比對。

golf 招數:把可讀版壓成三分之一

可讀版本原本超過 4800 bytes,Henley 從 Stack Overflow 的《Tips for golfing in C》學來一串只有 GNU C89 吃得下的技巧:單字母變數與函式名稱、用全域變數當暫存(全域變數預設歸零)、C89 允許隱式 int 宣告與回傳值、用函式參數當堆疊上保留的暫存空間、以 ASCII 數值取代字元字面值,再加上三元運算子、逗號運算子與位元運算取代邏輯運算。原本的加法解析函式:

int parse_sum(void){
  int value=parse_term();
  while(ch=='+'||ch=='-'){...}
  return value;
}

最後被壓成一行 e(){for(z=t();c-43u<3;)y=44-c,z+=y*t();return z;}。整份程式砍完剩下的功能包括:單字母整數變數與賦值、+ - * % 運算子(含優先序)、< > <= >= == 比較(每個運算式限一個)、整數真值判斷、if/elsewhilefor x in range(y)(皆支援 else 區塊)、無參數的函式定義與遞迴呼叫、縮排區塊,以及只印字串或整數運算式的 print()沒有任何錯誤處理——直譯器假設輸入的語法完全正確。Henley 表示如果只求跑出 FizzBuzz,他認為還能再壓到 800 bytes 以下。

原始來源:Austin Z. Henley BlogAZHenley/python1024


GNU Health 創辦人手動裝一台 NetBSD 11:MBR、disklabel 與磁碟加密全記錄

meanmicio.org(Luis Falcon) · 2026-09-06

GNU Health 專案創辦人 Luis Falcon 在部落格 meanmicio.org 記錄他捨棄標準安裝程式 sysinst,在一台 2012 年出廠的 HP Pavilion 筆電上手動從零安裝 NetBSD 11.0,並在過程中設定磁碟層級加密,目的是藉此摸熟系統內部運作。

從 MBR 而非 GPT 切起

這台筆電只有傳統 BIOS,得走 MBR 分割而非現代 UEFI 系統慣用的 GPT。Falcon 先用 dd if=NetBSD-11.0-amd64-install.img of=/dev/<usb_device> bs=2m 把安裝映像寫進 USB 開機碟,開機後按 CTRL+C 跳出 sysinst 進入 shell,改用 wsconsctl -w encoding=es 設定西班牙鍵盤、以 dhcpcd -d -n re0 取得網路,再建帳號並用 service sshd onestart 開 SSH,讓後續步驟能遠端操作與截圖記錄。

MBR 之上疊 disklabel,再疊 CGD 加密

NetBSD 的磁碟分割是兩層結構:MBR(用 fdisk 管理,最多 4 個 slice)之上,再用 disklabel 切出實際分割區。Falcon 把約 931 GB 的硬碟 wd0 劃成一個 sysid 169、大小 950 GB 的可開機 NetBSD slice,再用 disklabel 切出 a(50 GB,4.2BSD,放不加密的根目錄)和 e(900 GB,型別設為 cgd)。e 分割區交給 Cryptographic Disk Driver(CGD):cgdconfig -g -V disklabel -o /etc/cgd/wd0e adiantum 產生一份用 adiantum 演算法、pkcs5_pbkdf2/sha1 搭配 292728 次疊代的金鑰參數檔,再以 cgdconfig -V re-enter cgd0 /dev/wd0e 建立加密裝置 cgd0cgd0 之上又跑一次 disklabel,切出 /home(1.6 TB)、swap(20 GB)、/var(100 GB)、/usr(60 GB)四個分割區,全部用 newfs 建成 FFS 檔案系統。

裝進去、chroot、開機

分割完成後,依序用 tar -xzpf 解開 kern-GENERIC.tar.xzbase.tar.xzetc.tar.xz 到掛在 /mnt/target 的根目錄,寫入 /etc/cgd/cgd.conf(內容只有一行 cgd0 /dev/wd0e)並在 /etc/rc.conf 加上 cgd=YES,確保開機時會先解鎖加密磁碟才掛載檔案系統。用 sh MAKEDEV all 補齊裝置節點、掛好 kernfs/procfs/ptyfs/tmpfs 後,chroot /mnt/target su - 進入新系統改根密碼、寫好 /etc/fstab,重開機就直接落地在新裝的系統上。Falcon 表示這篇不是要取代官方安裝程式,而是為了搞懂磁碟標籤與 CGD 加密的底層機制,並讓一台 2012 年的舊筆電繼續發揮價值。

原始來源:meanmicio.orgNetBSD Guide


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