逆向程式該重寫還是容忍瀏覽器橋接?落地策略的三層階梯

最近更新: 2026-07-30 11:05:25

逆向工程的前半段,無論是機械式拆解、辨識演算法家族,還是差分驗證,最後都會得出同一個結論:「我知道它算了什麼。」但這個結論本身不能直接上線。邏輯仍綁在原始執行環境裡,你還得決定它最終要成為可放進 CI 的純函式,還是一個需要人持續照看的外部程序。這一步選錯,前面省下的時間,日後都會連本帶利地在維運階段還回去。

四階段總覽只用一句話帶過這件事:「第 4 階段,依傳輸層逐級降級。」本文把它展開來談。核心原則只有一句:每往下一層,依賴範圍、故障型態與部署成本都會跳升一個數量級,因此預設策略應該是盡力往上爭取。

三種落地方式,順序不能顛倒

逆向邏輯的落地形式只有三層,由上而下排列。這個順序應寫進團隊規範,而不是每次看「哪個最快跑起來」再臨時決定:

  1. 原生重寫。用目標語言重寫,完全脫離原始執行環境,只依賴標準函式庫。前提是已正確辨識演算法家族;一旦指紋辨識步驟通過,90% 的內容可直接取自公開實作,剩下的差異點另外處理,最後落成純函式。

  2. 以區域 JS 引擎執行最小片段。有些邏輯短期內要完全純化,成本實在太高;這時保留原始 JS 中的一小段,在本機 Node/V8 上執行必要的數十行程式,而不是把整個頁面都搬進來。

  3. 被動式瀏覽器橋接。有一類狀態只存在於真實且已登入的頁面執行環境中,例如執行期下發的簽名、綁定 session 的動態識別碼。靜態重建無法重現,現階段只能從瀏覽器內讀取。這只能是暫時方案,必須在介面說明中明確標記,絕不能作為預設部署方式。

成本差距不是線性的

這個順序之所以固定,是因為三層方案的成本並非逐步增加;每往下一層,成本都是前一層的一個數量級。

層級

依賴範圍

故障型態

可進 CI?

原生重寫

標準函式庫,零外部程序

輸出不一致,一個 assert 就能定位

可以,因為它是純函式

區域 JS 引擎

額外多一個 Node 執行環境;V8 context 非執行緒安全,並行需加鎖

引擎版本差異、片段依賴的全域變數缺失

勉強可以,但必須安裝引擎

被動式瀏覽器橋接

真實 Chrome + 擴充功能 + 人工維護的 session + 本機 loopback 通道

頁面未開啟、session 過期、結構變動、分頁被關閉

不行,必須有真人在線

第一層出問題,單元測試就能抓到;第三層失敗的原因可能是「使用者今天把那個分頁關掉了」。本來能做成純函式的事,一旦降到第三層,就等於每次呼叫都綁上一個人。可供參考的是:在辨識出演算法家族後,一套經過混淆的簽名 SDK,通常可在 600 行以內落成獨立實作,只依賴內建的 crypto,運行於第一層。你原本以為只能走瀏覽器橋接的東西,往往只是某個尚未被完整辨識的演算法。

被動橋接的邊界:只取快照,絕不主動操作

被動橋接會失控,通常只有一個原因:它開始「幫忙」了,例如自動重新整理、自動登入、自動等待頁面載入。每多一個「自動」,它就從轉送器更靠近爬蟲一步。因此邊界必須畫得極窄。以下這組限制,都是踩過坑後留下來的:

每次只做一次快照式探測,不得修改頁面。針對已開啟的頁面,各讀取一次 cookies、session 狀態與頁面執行環境,且只讀一次。不要建立新分頁、不要重新整理、不要導頁、不要聚焦,也不要輪詢等待。頁面不存在就是不存在,不要替使用者打開它。

缺什麼就立刻回傳明確錯誤。沒有符合條件的分頁,回傳 tab_unavailable;頁面存在但尚未登入,回傳 not_logged_in;已登入但執行環境尚未就緒,回傳 runtime_unavailable。這三種錯誤碼各自對應一種真實狀態與一個後續動作:等待頁面、登入,或切換目標。不要丟出一句模糊的「失敗了」,讓呼叫端自行猜測。

敏感狀態不得離開瀏覽器。擴充功能不申請 cookies / webRequest 權限;它只查詢已開啟且符合條件的分頁,在該頁面 context 內完成請求,回傳前再清除欄位。cookies 與頁面簽名狀態全程不會離開 Chrome,而通道預設連往本機 loopback 位址。橋接傳遞的是結果,不是憑證。

為何只有幾個平台,卻需要 15 個 scope

被動橋接轉送的,是經白名單限制的請求:路徑、參數與 referer 都受到 adapter 約束。最容易讓人意外的是 scope 的切分粒度:明明只有少數幾個平台,卻有合計 15 個 scope,因為scope 是依「頁面 context」切分,而非依「平台」切分。TikTok 的 Creative Center、Top Ads、Creator platform、influencer library 與 Ads Manager,就是五個彼此獨立的 scope——五份獨立的 session 狀態、五個獨立的頁面執行環境。登入廣告後台,不代表你能取得 Creator platform 的 runtime。若每個平台只切一份,第一次遇到「已登入子站 A,卻無法服務子站 B」時,就得全部重來。小紅書主站、等同 App 的路徑,以及創作者市集,同樣需要分成三個 scope。

排程粒度也跟著確定:鎖只加在平台家族層級。同一家族內的請求,例如抖音家族,必須序列執行,因為它們共用同一個真實分頁;同時往同一個頁面 context 發送請求,會彼此踩到。不同家族,例如抖音與小紅書,則可平行處理,因為它們是兩個無關的分頁。同一家族內還要加上最小請求間隔。粒度太粗,原本能平行的工作被迫串行;粒度太細,共用分頁的請求又會衝突。平台家族正是「共用同一頁面 runtime」最自然的邊界。

登入交接:唯一允許的人為介入

被動橋接不操作頁面,但 session 終究會過期。解法是把人為介入收斂為一次明確、單次觸發的操作:互動式指令進入交接流程後,程式透過作業系統開啟對應的業務頁面,等待你親手登入並讓頁面完成準備,接著重放原始請求。整個過程中,擴充功能不點按鈕、不填表單、不匯出 cookies;登入必須由你在真實瀏覽器中完成,程式只會在你結束後接回原本的請求。

關鍵限制是:絕對不能隱式觸發。非互動式指令,例如 CI 或排程工作,絕不能跳出瀏覽器;它只需明確回傳 session 錯誤,交由上層決策。交接流程也必須避免誤判:登入成功後,runtime 最多只能再等待一小段時間,實作中為 20 秒,之後就必須得出結果。若業務頁面其實已重新導向至缺少目標介面 context 的帳號頁面,應立即結束當前階段,而不是把一個確定「到不了」的情況誤認為「還在載入」,傻等下去。允許降級的流程會將這個階段標記為 unavailable,然後繼續執行,而不是讓整個流程直接失敗。

階段狀態必須說清楚:完成、刻意跳過,還是需要 session

上述設計背後有一個共同前提:任何階段都不能只回傳「成功」或「失敗」。在編排流程裡,每個階段的結果有六種型態:completed(完成)、empty(已執行但沒有資料)、ready(已備妥,等待提交)、skipped(依規則刻意跳過)、unavailable(目前無法使用,通常代表需要 session)、blocked(前置條件不成立)。

「刻意跳過」、「需要 session」與「確實沒有資料」,是三種完全不同的訊號。若只回傳一個不透明結果,你無法判斷空結果究竟是「本來就該是空的」,還是「session 失效卻沒人發現」;這種流程根本無法維運。將階段狀態建模為有限 enum,編排層無論是腳本或模型,就能據此決定該降級、重新驗證,或中止流程。這正是被動橋接三種錯誤碼的同一套原則,只是提升到了整個流程層級。

讓模型判斷能力該落在哪一層

模型只在這裡登場,而且角色必須克制:它不替你完成純化,而是協助判斷該不該純化,以及要純化到什麼程度。這是架構判斷,不是破解簽名。核心問題只有一個:它依賴的狀態能否靜態重建,還是只能在執行期取得?接著再衡量純化成本與變動頻率。不同步驟對模型的要求不同:

步驟

所需能力

選擇

model id

閱讀整個模組,掌握依賴範圍

長 context,能一次讀懂呼叫圖

Kimi K3

kimi-k3

從正反兩面論證層級選擇,反駁「先讓它能跑」

推理能力強,會為多花一天純化提出論據

Claude Opus 5

claude-opus-5

批次初步篩選數十到數百項能力

成本低、可高併發

Claude Sonnet 5

claude-sonnet-5

降級後進行歸因分析

中等推理能力,能依失敗日誌說明原因

GPT-5.6 Sol

gpt-5.6-sol

其中最重要的是第二項。決定落點層級時,最常見的錯誤,是模型順著你「先讓它跑起來」的口氣,給出「瀏覽器橋接最簡單」的答案——它只是在附和你,沒有替你計算長期成本。推理能力強的層級會提出反駁:「這段只是標準雜湊加上一個常數擾動,值得花一天做成純函式,不該放到橋接層。」不必相信我,自己測試即可:挑 3 段邏輯,至少 1 段是你已知正確落點的對照組;把相同的「給出落點、提出論證,並反駁過早放進瀏覽器橋接」提示,分別交給 claude-opus-5gpt-5.6-sol。只看一件事:它會不會努力把你往上推一層,還是懶得思考就預設丟到第三層。

真正卡人的,是切換模型的成本

四個層級、來自三家供應商,意味著三套 SDK、三種驗證機制、三種錯誤格式。為了在不同步驟切換模型而重寫 client 並不划算,因此多數人最後都用同一個模型做完所有事,偏偏在最需要推理能力的落點判斷上,使用了只會附和的模型。

AIReiter 把這一層攤平:一組 key、一個相容 OpenAI 的介面,四個層級都在後面;要切換時,只需修改請求 body 裡的 model 欄位。

# Placement argument: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<placement prompt + the reversed logic fragment + dependency list>"}]
  }'

# Bulk first-pass triage: change one field
#   "model": "claude-sonnet-5"
# Degradation attribution:
#   "model": "gpt-5.6-sol"

已經使用 OpenAI SDK?把 base_url 指向 https://aireiter.com/api/v1 即可。使用 Anthropic SDK?以相同 key 呼叫 POST /api/v1/messages。價格方面,Claude 模型為定價 7 折,GPT 模型則為半價。這套工作流的成本主要集中在兩處:數十到數百項能力的批次初步篩選(Sonnet,呼叫量高),以及讀取整個模組以掌握依賴範圍時的單次長 context 輸入(Kimi,每次呼叫的 token 較多)。批次篩選使用 Claude 模型,因此折扣正好落在呼叫最密集的步驟;推理層的落點論證同樣使用 Claude 模型,享有 7 折。Kimi K3 的長 context 層級也可透過同一組 key 使用。

  • 取得 API key

  • 免註冊試用——先手動丟幾段邏輯進去,觀察兩個模型會附和你的判斷,還是會反駁「這東西該不該放進瀏覽器橋接」。

結語

逆向邏輯的落地不是單純技術問題,而是成本問題。原生重寫 > 區域 JS 引擎 > 被動式瀏覽器橋接,這條三層階梯不能倒過來,因為每降一層,就是以純函式換取一個帶有外部依賴、人為介入與即時分頁需求的程序。被動橋接不是禁區,但它只能是邊界嚴格的暫時元件:只取一次快照、絕不主動操作、頁面缺失立即回傳明確錯誤、敏感狀態不離開瀏覽器、登入只透過明確交接、階段狀態永遠可辨識。守住這些原則,它就是可靠的過渡方案;少了任何一項,它就會變成沒人敢維護的黑盒子。模型協助你判斷能力應落在哪一層,並抵抗「先讓它能跑」的慣性;至於每一次重寫是否正確,裁判應是差分測試,不是模型。