mtgsig 1.2簽名逆向:補(bǔ)環(huán)境原理與實(shí)操指南)
1. 拆解 mtgsig 1.2它到底在防什么外賣平臺的請求簽名機(jī)制本質(zhì)上是一道“防偽水印”。mtgsig 是美團(tuán)系產(chǎn)品在客戶端與網(wǎng)關(guān)之間傳遞的一組動態(tài)簽名參數(shù)1.2 版本相比早期版本最大的變化在于把校驗(yàn)邏輯從“看參數(shù)對不對”升級成了“看環(huán)境像不像真的”。這句話聽起來有點(diǎn)繞我換個(gè)說法以前的簽名像是門衛(wèi)查身份證證件號碼對得上就放行現(xiàn)在的簽名像是門衛(wèi)不僅查證件還要觀察你的走路姿勢、口音、穿著打扮綜合判斷你是不是“本人”。這套機(jī)制的核心目標(biāo)有三個(gè)第一確認(rèn)請求來自真實(shí)的瀏覽器環(huán)境而非腳本工具第二確認(rèn)請求參數(shù)在傳輸過程中沒有被篡改第三確認(rèn)請求的時(shí)間窗口在合理范圍內(nèi)防止重放攻擊。三個(gè)目標(biāo)疊加在一起就構(gòu)成了 mtgsig 1.2 的基本防線。從技術(shù)棧來看mtgsig 1.2 的簽名生成鏈路涉及 JavaScript 環(huán)境檢測、瀏覽器 API 調(diào)用、加密算法組合、時(shí)間戳與隨機(jī)數(shù)拼接等多個(gè)環(huán)節(jié)。任何一個(gè)環(huán)節(jié)的環(huán)境特征不對生成的簽名就會被判定為無效。這也是為什么很多人在做逆向時(shí)會發(fā)現(xiàn)明明算法邏輯理清楚了參數(shù)也拼對了但簽名就是過不了——問題往往出在“環(huán)境”這一層。適合閱讀這篇內(nèi)容的人我大致分三類一是做數(shù)據(jù)采集和接口分析的技術(shù)人員需要理解簽名機(jī)制的工作原理二是做前端安全測試的工程師需要評估客戶端防護(hù)的強(qiáng)度三是對 JavaScript 逆向工程感興趣的學(xué)習(xí)者想通過一個(gè)真實(shí)案例把補(bǔ)環(huán)境、原型鏈、加密還原這些知識點(diǎn)串起來。不管你屬于哪一類接下來的內(nèi)容都會從“為什么”講到“怎么做”盡量把每個(gè)環(huán)節(jié)的決策邏輯說清楚。提示本文討論的是客戶端簽名機(jī)制的技術(shù)原理與分析方法所有操作應(yīng)在合法合規(guī)的前提下進(jìn)行僅用于技術(shù)學(xué)習(xí)與安全研究。2. 簽名生成的整體鏈路與設(shè)計(jì)思路2.1 從請求發(fā)出到簽名附帶的完整流程要理解 mtgsig 1.2 的簽名生成先得把整個(gè)請求生命周期捋一遍。當(dāng)用戶在 App 或網(wǎng)頁端觸發(fā)一個(gè)需要簽名的請求時(shí)大致會經(jīng)歷以下幾個(gè)階段第一階段是參數(shù)收集??蛻舳藭颜埱蟮?URL 路徑、查詢參數(shù)、請求體內(nèi)容、時(shí)間戳、設(shè)備標(biāo)識等信息匯總到一個(gè)對象里。這個(gè)對象就是后續(xù)簽名的“原材料”。第二階段是環(huán)境檢測。代碼會調(diào)用一系列瀏覽器 API 來采集當(dāng)前運(yùn)行環(huán)境的特征值包括但不限于 navigator 對象的部分屬性、screen 分辨率、時(shí)區(qū)偏移、Canvas 指紋、WebGL 渲染信息等。這些特征值會被混入簽名計(jì)算中作為“環(huán)境鹽值”。第三階段是簽名計(jì)算。把前兩個(gè)階段收集到的數(shù)據(jù)按照特定順序拼接經(jīng)過哈希和加密運(yùn)算最終生成 mtgsig 字符串。這個(gè)字符串通常包含版本號、時(shí)間戳、隨機(jī)數(shù)和簽名摘要幾個(gè)部分。第四階段是簽名附帶。生成的 mtgsig 會被放到請求頭或請求參數(shù)中隨請求一起發(fā)送到服務(wù)端。服務(wù)端用同樣的邏輯驗(yàn)算一遍比對結(jié)果。整個(gè)鏈路的設(shè)計(jì)思路可以用一句話概括讓簽名與運(yùn)行環(huán)境強(qiáng)綁定使得脫離真實(shí)環(huán)境的簽名生成變得極其困難。這也是為什么“補(bǔ)環(huán)境”成了逆向 mtgsig 的核心工作。2.2 為什么選擇“環(huán)境綁定”作為核心防護(hù)策略傳統(tǒng)的簽名機(jī)制通常只依賴請求參數(shù)和密鑰只要密鑰泄露或者算法被還原簽名就可以被隨意生成。mtgsig 1.2 顯然吸取了這個(gè)教訓(xùn)把環(huán)境特征作為簽名的一個(gè)必要輸入。這個(gè)策略的高明之處在于環(huán)境特征值不是固定的它隨著運(yùn)行環(huán)境的變化而變化。你在 Chrome 瀏覽器里跑和在 Node.js 里跑navigator.userAgent 不一樣screen 對象不存在Canvas 指紋也完全不同。服務(wù)端只要發(fā)現(xiàn)簽名中隱含的環(huán)境特征與預(yù)期不符就可以直接拒絕請求。從防護(hù)成本來看這種設(shè)計(jì)讓逆向者的工作量成倍增加。你不僅要還原加密算法還要模擬出一整套以假亂真的瀏覽器環(huán)境。而瀏覽器環(huán)境的 API 數(shù)量龐大、相互關(guān)聯(lián)任何一個(gè)細(xì)節(jié)的缺失都可能導(dǎo)致簽名校驗(yàn)失敗。2.3 1.2 版本相比早期版本的關(guān)鍵變化根據(jù)實(shí)際分析的經(jīng)驗(yàn)mtgsig 1.2 相比 1.0 和 1.1 版本主要有以下幾個(gè)值得注意的變化環(huán)境檢測維度增加早期版本可能只檢查 navigator.userAgent 和 screen 尺寸1.2 版本加入了更多原型鏈層面的檢測比如檢查某個(gè)方法是否被重寫、某個(gè)屬性的 getter 是否原生。簽名算法微調(diào)哈希的輸入順序和拼接方式有調(diào)整鹽值的插入位置也變了。這意味著直接套用舊版本的算法邏輯會得到錯(cuò)誤結(jié)果。時(shí)間窗口收緊簽名有效期從原來的較長窗口縮短到更短的時(shí)間范圍對時(shí)間同步的要求更高。隨機(jī)數(shù)參與方式變化隨機(jī)數(shù)不再只是簡單拼接而是參與了某一輪哈希的中間計(jì)算。這些變化疊加在一起使得 1.2 版本的逆向難度明顯高于早期版本。但萬變不離其宗核心思路仍然是“還原算法 補(bǔ)全環(huán)境”。3. 補(bǔ)環(huán)境的核心原理與實(shí)操要點(diǎn)3.1 什么是補(bǔ)環(huán)境為什么它是逆向的關(guān)鍵補(bǔ)環(huán)境說白了就是在一個(gè)非瀏覽器環(huán)境比如 Node.js里手動構(gòu)造出一套讓目標(biāo)代碼“以為自己在瀏覽器里運(yùn)行”的假象。目標(biāo)代碼會調(diào)用 window、document、navigator、location 等全局對象如果這些對象不存在或者屬性不對代碼就會報(bào)錯(cuò)或者生成錯(cuò)誤的簽名。我打個(gè)比方目標(biāo)代碼就像一個(gè)習(xí)慣了在特定辦公室里工作的員工你把他搬到另一個(gè)房間他會因?yàn)檎也坏轿募?、打印機(jī)、飲水機(jī)而無法正常工作。補(bǔ)環(huán)境就是在這個(gè)新房間里擺上仿制的文件柜、打印機(jī)和飲水機(jī)讓他感覺和原來一樣。補(bǔ)環(huán)境的關(guān)鍵難點(diǎn)在于你不僅要讓對象存在還要讓對象的屬性值、方法行為、原型鏈結(jié)構(gòu)都盡可能接近真實(shí)瀏覽器。服務(wù)端如果檢測到某個(gè)屬性的描述符不對比如應(yīng)該是一個(gè) getter 卻變成了普通屬性就可能判定環(huán)境異常。3.2 原型鏈補(bǔ)環(huán)境讓檢測代碼“看不出破綻”原型鏈?zhǔn)?JavaScript 中一個(gè)非常核心的概念也是 mtgsig 1.2 環(huán)境檢測的重點(diǎn)區(qū)域。目標(biāo)代碼可能會通過Object.getOwnPropertyDescriptor、Object.getPrototypeOf、Function.prototype.toString等方法來檢測某個(gè)對象或方法是否被篡改。舉個(gè)例子真實(shí)瀏覽器中navigator.userAgent的 getter 函數(shù)用Function.prototype.toString打印出來會顯示function get userAgent() { [native code] }。如果你在 Node.js 里直接用Object.defineProperty定義一個(gè)普通函數(shù)打印出來就會暴露[native code]缺失的問題。解決這個(gè)問題的常見做法是用 Proxy 或者重寫Function.prototype.toString來偽造原生函數(shù)的字符串表示。具體操作時(shí)可以維護(hù)一個(gè)“原生函數(shù)映射表”當(dāng)檢測代碼調(diào)用 toString 時(shí)返回預(yù)先準(zhǔn)備好的原生代碼字符串。// 偽造原生函數(shù) toString 的簡化示例 const nativeToString Function.prototype.toString; const fakeNativeMap new WeakMap(); function markAsNative(fn, name) { fakeNativeMap.set(fn, function ${name}() { [native code] }); } Function.prototype.toString new Proxy(nativeToString, { apply(target, thisArg, args) { if (fakeNativeMap.has(thisArg)) { return fakeNativeMap.get(thisArg); } return Reflect.apply(target, thisArg, args); } });這段代碼的思路是用一個(gè) WeakMap 記錄哪些函數(shù)需要偽裝成原生函數(shù)然后在 toString 被調(diào)用時(shí)優(yōu)先返回偽裝字符串。實(shí)際使用中還需要處理更多邊界情況比如箭頭函數(shù)、async 函數(shù)、getter/setter 等。注意重寫Function.prototype.toString本身也可能被檢測。有些檢測代碼會先保存原始的 toString 引用然后對比重寫后的行為是否一致。所以更穩(wěn)妥的做法是盡量少改全局原型而是針對具體對象做精細(xì)化處理。3.3 iv8 補(bǔ)環(huán)境方案的實(shí)際應(yīng)用iv8 是一類基于 V8 引擎的 JavaScript 運(yùn)行時(shí)環(huán)境常被用于執(zhí)行目標(biāo)代碼并觀察其行為。相比直接在 Node.js 里補(bǔ)環(huán)境iv8 方案的優(yōu)勢在于它更接近瀏覽器的底層實(shí)現(xiàn)某些在 Node.js 里難以模擬的行為比如特定的垃圾回收時(shí)機(jī)、特定的錯(cuò)誤堆棧格式在 iv8 里可能更自然。使用 iv8 補(bǔ)環(huán)境的一般流程是把目標(biāo) JavaScript 代碼加載到 iv8 環(huán)境中執(zhí)行。觀察代碼在哪些地方報(bào)錯(cuò)記錄缺失的全局對象或?qū)傩浴T?iv8 的全局作用域中注入這些對象和屬性。重復(fù)執(zhí)行直到代碼不再報(bào)錯(cuò)并能輸出簽名結(jié)果。對比生成的簽名與真實(shí)瀏覽器中的簽名驗(yàn)證補(bǔ)環(huán)境的完整性。iv8 方案的一個(gè)實(shí)際好處是它可以直接運(yùn)行經(jīng)過混淆的代碼而不需要先做反混淆。這對于分析 mtgsig 這種混淆程度較高的代碼來說能節(jié)省大量時(shí)間。不過 iv8 也不是萬能的。有些檢測會針對 V8 的特定版本特征進(jìn)行識別如果 iv8 使用的 V8 版本與目標(biāo)瀏覽器不一致仍然可能被檢測出來。這時(shí)候就需要結(jié)合其他手段比如在真實(shí)瀏覽器中通過調(diào)試工具直接觀察簽名生成過程。3.4 補(bǔ)環(huán)境中的常見陷阱與規(guī)避方法在實(shí)際補(bǔ)環(huán)境的過程中我踩過不少坑這里挑幾個(gè)典型的說說陷阱一只補(bǔ)屬性不補(bǔ)行為。很多人補(bǔ)環(huán)境時(shí)只關(guān)注navigator.userAgent的值對不對卻忽略了navigator對象上還有其他屬性和方法。檢測代碼可能調(diào)用navigator.plugins、navigator.languages、navigator.hardwareConcurrency等任何一個(gè)缺失都可能觸發(fā)異常。陷阱二原型鏈斷裂。在 Node.js 里手動創(chuàng)建的對象其原型鏈往往與瀏覽器中的不一致。比如document.createElement(canvas)返回的對象在瀏覽器中有一長串原型鏈而在 Node.js 里如果只是簡單模擬原型鏈可能只有一兩層。檢測代碼通過Object.getPrototypeOf逐層往上查很容易發(fā)現(xiàn)異常。陷阱三屬性描述符不匹配。瀏覽器中的很多屬性是只讀的 getter如果你用普通屬性賦值的方式模擬Object.getOwnPropertyDescriptor返回的writable、configurable、enumerable等標(biāo)志位就會不對。陷阱四時(shí)間與隨機(jī)數(shù)行為異常。Date.now()和Math.random()在瀏覽器和 Node.js 中的行為基本一致但如果檢測代碼對時(shí)間精度或隨機(jī)數(shù)分布有特定要求簡單的模擬可能不夠。規(guī)避這些陷阱的方法核心就一條盡量用真實(shí)瀏覽器做對照。在 Chrome 里執(zhí)行同樣的檢測代碼把結(jié)果打印出來然后在 Node.js 里逐項(xiàng)比對缺什么補(bǔ)什么哪里不對改哪里。4. 簽名生成的完整實(shí)操流程4.1 定位簽名生成入口的幾種方法要還原簽名算法第一步是找到簽名生成的入口函數(shù)。對于 mtgsig 1.2 這種經(jīng)過混淆的代碼定位入口有幾種常用方法方法一搜索關(guān)鍵字。在混淆后的代碼中搜索mtgsig、sign、encrypt等字符串往往能找到簽名相關(guān)的函數(shù)。即使字符串被編碼了也可以搜索編碼后的形式。方法二Hook 網(wǎng)絡(luò)請求。在瀏覽器中重寫XMLHttpRequest.prototype.setRequestHeader或fetch打印出所有請求頭找到 mtgsig 出現(xiàn)的位置然后通過調(diào)用棧回溯到簽名生成函數(shù)。方法三Hook 加密函數(shù)。重寫JSON.stringify、encodeURIComponent、btoa等常用函數(shù)觀察哪些數(shù)據(jù)流經(jīng)這些函數(shù)逐步縮小范圍。方法四AST 分析。把混淆代碼解析成抽象語法樹通過模式匹配找到可疑的函數(shù)結(jié)構(gòu)比如包含大量位運(yùn)算、字符串拼接、數(shù)組操作的函數(shù)。實(shí)際操作中通常是幾種方法結(jié)合使用。先用 Hook 網(wǎng)絡(luò)請求找到簽名出現(xiàn)的時(shí)機(jī)再用調(diào)用棧回溯定位到具體函數(shù)最后用 AST 分析理解函數(shù)邏輯。4.2 關(guān)鍵參數(shù)的提取與拼接順序還原定位到簽名函數(shù)后下一步是理解它接收哪些參數(shù)、如何拼接、經(jīng)過哪些運(yùn)算。以一個(gè)典型的 mtgsig 生成邏輯為例大致流程如下// 偽代碼示意非真實(shí)實(shí)現(xiàn) function generateMtgsig(params) { // 1. 收集環(huán)境特征 const env collectEnv(); // 包含 userAgent、screen、timezone 等 // 2. 拼接原始字符串 const raw [ params.url, params.body, params.timestamp, env.userAgent, env.screen, env.timezone, params.nonce ].join(|); // 3. 第一輪哈希 const hash1 md5(raw SALT_1); // 4. 第二輪哈希 const hash2 sha256(hash1 SALT_2 params.timestamp); // 5. 組裝最終簽名 const mtgsig 1.2|${params.timestamp}|${params.nonce}|${hash2}; return mtgsig; }真實(shí)代碼當(dāng)然比這個(gè)復(fù)雜得多但核心結(jié)構(gòu)類似。關(guān)鍵是要搞清楚哪些參數(shù)參與了拼接、拼接的順序是什么、用了哪些哈希算法、鹽值是什么、時(shí)間戳和隨機(jī)數(shù)如何參與。在還原拼接順序時(shí)一個(gè)實(shí)用的技巧是構(gòu)造差異化的輸入觀察輸出的變化。比如改變 URL 中的一個(gè)字符看簽名結(jié)果是否變化、變化了多少位。如果簽名結(jié)果完全變了說明 URL 參與了哈希如果沒變說明 URL 可能不參與或者參與了但不影響最終結(jié)果。4.3 加密算法的識別與還原mtgsig 1.2 中使用的加密算法通常包括 MD5、SHA-1、SHA-256 等常見哈希算法也可能包含自定義的位運(yùn)算混淆。識別算法的常用方法有特征值比對用已知輸入計(jì)算各種哈希算法的輸出與目標(biāo)代碼的輸出比對。比如輸入空字符串MD5 的結(jié)果是d41d8cd98f00b204e9800998ecf8427eSHA-256 的結(jié)果是e3b0c44298fc1c149afbf4c8996fb924...。代碼特征搜索MD5 的實(shí)現(xiàn)通常包含特定的常量數(shù)組比如0x67452301、0xefcdab89等。在代碼中搜索這些常量可以快速定位哈希算法。動態(tài)調(diào)試在關(guān)鍵函數(shù)處下斷點(diǎn)觀察輸入輸出逐步理解算法邏輯。如果遇到自定義的加密算法就需要逐行分析代碼邏輯理解每一步的運(yùn)算意圖。這時(shí)候把混淆代碼還原成可讀性較好的形式就很重要了。常用的工具包括 AST 還原、控制流平坦化還原、字符串解密等。4.4 時(shí)間戳與隨機(jī)數(shù)的處理策略時(shí)間戳和隨機(jī)數(shù)是簽名中常見的動態(tài)元素它們的處理策略直接影響簽名的有效性。時(shí)間戳方面mtgsig 1.2 通常會校驗(yàn)簽名的時(shí)間窗口。如果客戶端時(shí)間與服務(wù)端時(shí)間偏差過大簽名會被拒絕。因此在生成簽名時(shí)需要使用準(zhǔn)確的時(shí)間戳。如果運(yùn)行環(huán)境的系統(tǒng)時(shí)間不準(zhǔn)可以通過網(wǎng)絡(luò)時(shí)間協(xié)議同步或者在簽名生成前先請求一次服務(wù)端時(shí)間。隨機(jī)數(shù)方面mtgsig 1.2 中的隨機(jī)數(shù)通常用于防止重放攻擊。每次請求的隨機(jī)數(shù)不同簽名結(jié)果也不同。在補(bǔ)環(huán)境時(shí)需要確保隨機(jī)數(shù)的生成方式與真實(shí)環(huán)境一致。如果目標(biāo)代碼使用了Math.random()直接調(diào)用即可如果使用了crypto.getRandomValues()則需要模擬這個(gè) API。提示有些簽名機(jī)制會記錄已使用的隨機(jī)數(shù)如果發(fā)現(xiàn)重復(fù)會判定為重放攻擊。因此在批量生成簽名時(shí)要確保每次的隨機(jī)數(shù)都不相同。4.5 完整簽名生成代碼的組裝與驗(yàn)證把前面幾個(gè)環(huán)節(jié)的成果組裝起來就得到了一個(gè)完整的簽名生成器。組裝過程中需要注意以下幾點(diǎn)參數(shù)順序拼接順序必須與目標(biāo)代碼完全一致差一個(gè)字符都會導(dǎo)致簽名錯(cuò)誤。編碼方式字符串的編碼方式UTF-8、GBK 等要一致特殊字符的轉(zhuǎn)義規(guī)則也要一致。數(shù)值格式時(shí)間戳是秒級還是毫秒級、隨機(jī)數(shù)是整數(shù)還是浮點(diǎn)數(shù)都要與目標(biāo)代碼一致。鹽值處理鹽值是硬編碼還是動態(tài)生成是明文還是編碼后的都要搞清楚。驗(yàn)證簽名生成器是否正確最直接的方法是對比法在真實(shí)瀏覽器中觸發(fā)一次請求記錄下請求參數(shù)和生成的 mtgsig然后在補(bǔ)環(huán)境里用相同的參數(shù)生成簽名比對兩者是否一致。如果一致說明還原成功如果不一致就需要逐項(xiàng)排查差異。5. 常見問題與排查技巧實(shí)錄5.1 簽名校驗(yàn)失敗的典型原因速查表問題現(xiàn)象可能原因排查方法簽名格式正確但校驗(yàn)失敗環(huán)境特征值不對對比瀏覽器與補(bǔ)環(huán)境的 navigator、screen 等對象簽名長度不對哈希算法或拼接順序錯(cuò)誤檢查哈希輸出長度比對拼接字符串簽名偶爾成功偶爾失敗時(shí)間戳偏差或隨機(jī)數(shù)問題檢查時(shí)間同步確認(rèn)隨機(jī)數(shù)生成方式代碼執(zhí)行報(bào)錯(cuò)缺失全局對象或?qū)傩愿鶕?jù)報(bào)錯(cuò)信息逐項(xiàng)補(bǔ)全簽名結(jié)果與瀏覽器不一致鹽值或加密參數(shù)錯(cuò)誤動態(tài)調(diào)試觀察中間計(jì)算結(jié)果請求被拒絕但簽名看似正確請求頭或參數(shù)不完整檢查所有必需的請求頭和參數(shù)5.2 環(huán)境檢測繞過中的疑難雜癥有些環(huán)境檢測非常隱蔽不是簡單的屬性缺失而是行為層面的差異。比如檢測一函數(shù)調(diào)用棧。目標(biāo)代碼可能通過new Error().stack獲取調(diào)用棧檢查調(diào)用來源是否合理。在補(bǔ)環(huán)境中調(diào)用棧的格式和內(nèi)容與瀏覽器不同可能被識別。檢測二性能計(jì)時(shí)。目標(biāo)代碼可能用performance.now()測量某段代碼的執(zhí)行時(shí)間如果執(zhí)行時(shí)間過短或過長可能被判定為異常環(huán)境。檢測三Canvas 指紋。目標(biāo)代碼可能繪制一個(gè) Canvas 圖形然后讀取像素?cái)?shù)據(jù)作為指紋。在 Node.js 中模擬 Canvas 行為非常復(fù)雜通常需要借助canvas庫或者直接返回預(yù)設(shè)的指紋值。檢測四WebGL 指紋。類似 Canvas 指紋WebGL 指紋通過渲染特定的 3D 圖形并讀取參數(shù)來生成。模擬難度更高通常需要專門的庫或者直接偽造返回值。面對這些疑難檢測我的經(jīng)驗(yàn)是不要試圖完美模擬而是找到檢測的薄弱點(diǎn)。有些檢測只在特定條件下觸發(fā)如果能繞過觸發(fā)條件就不需要模擬。比如某個(gè)檢測只在首次請求時(shí)執(zhí)行那么后續(xù)請求就可以跳過。5.3 補(bǔ)環(huán)境代碼的調(diào)試與優(yōu)化經(jīng)驗(yàn)補(bǔ)環(huán)境代碼本身也需要調(diào)試和優(yōu)化。以下是我總結(jié)的幾條經(jīng)驗(yàn)日志要詳細(xì)在補(bǔ)環(huán)境的每個(gè)關(guān)鍵節(jié)點(diǎn)打印日志記錄哪些屬性被訪問、哪些方法被調(diào)用。這樣當(dāng)簽名失敗時(shí)可以快速定位到問題環(huán)節(jié)。對照要全面在真實(shí)瀏覽器中執(zhí)行同樣的代碼把關(guān)鍵對象的屬性列表、屬性描述符、原型鏈結(jié)構(gòu)都打印出來與補(bǔ)環(huán)境逐一比對。修改要最小盡量只補(bǔ)缺失的部分不要大范圍重寫全局對象。修改越多引入新問題的風(fēng)險(xiǎn)越大。版本要匹配目標(biāo)代碼可能針對特定瀏覽器版本做了適配補(bǔ)環(huán)境時(shí)也要注意版本一致性。比如 Chrome 120 和 Chrome 90 的某些 API 行為可能不同。測試要反復(fù)每次修改補(bǔ)環(huán)境代碼后都要重新生成簽名并驗(yàn)證。不要積累多個(gè)修改后再測試否則出問題時(shí)難以定位是哪個(gè)修改導(dǎo)致的。5.4 提升簽名生成穩(wěn)定性的實(shí)用技巧簽名生成的穩(wěn)定性直接影響后續(xù)操作的效率。以下幾個(gè)技巧可以幫助提升穩(wěn)定性技巧一緩存環(huán)境特征值。環(huán)境特征值在短時(shí)間內(nèi)不會變化可以在首次采集后緩存起來后續(xù)生成簽名時(shí)直接使用避免重復(fù)采集導(dǎo)致的不一致。技巧二時(shí)間戳校準(zhǔn)。在生成簽名前先獲取一次服務(wù)端時(shí)間計(jì)算本地時(shí)間與服務(wù)器時(shí)間的偏差后續(xù)生成簽名時(shí)用校準(zhǔn)后的時(shí)間戳。技巧三隨機(jī)數(shù)池。預(yù)先準(zhǔn)備一批隨機(jī)數(shù)生成簽名時(shí)從池中取用避免隨機(jī)數(shù)生成方式被檢測。技巧四簽名結(jié)果校驗(yàn)。在生成簽名后用本地的校驗(yàn)邏輯先驗(yàn)證一遍確保格式和長度正確再發(fā)送請求。技巧五失敗重試機(jī)制。如果簽名校驗(yàn)失敗不要立即放棄可以嘗試用不同的環(huán)境特征值或不同的隨機(jī)數(shù)重新生成有時(shí)候失敗是偶發(fā)的。6. 從逆向工程視角看客戶端安全設(shè)計(jì)6.1 mtgsig 1.2 防護(hù)思路的可取之處站在安全設(shè)計(jì)的角度mtgsig 1.2 有幾個(gè)值得肯定的地方。首先它把環(huán)境特征作為簽名的必要輸入這使得單純的算法還原不足以生成有效簽名必須同時(shí)解決環(huán)境模擬問題。其次它采用了多層哈希和鹽值增加了算法還原的難度。再次它引入了時(shí)間窗口和隨機(jī)數(shù)機(jī)制有效防止了重放攻擊。這些設(shè)計(jì)思路并不是美團(tuán)獨(dú)有的很多大型互聯(lián)網(wǎng)公司都在采用類似的方案。區(qū)別在于實(shí)現(xiàn)的細(xì)節(jié)和檢測的嚴(yán)格程度。mtgsig 1.2 在環(huán)境檢測的深度上做得比較到位尤其是原型鏈層面的檢測讓很多粗制濫造的補(bǔ)環(huán)境方案直接失效。6.2 逆向與防護(hù)的持續(xù)博弈逆向工程和客戶端安全防護(hù)之間本質(zhì)上是一場持續(xù)博弈。防護(hù)方不斷加檢測維度、加混淆強(qiáng)度、加算法復(fù)雜度逆向方不斷找新的繞過方法、優(yōu)化補(bǔ)環(huán)境方案、提升自動化程度。從趨勢來看未來的客戶端簽名機(jī)制可能會更多地依賴硬件特征如設(shè)備指紋、更多地使用 WebAssembly 來隱藏核心邏輯、更多地結(jié)合行為分析來判斷請求的合法性。這意味著逆向的難度會繼續(xù)增加但同時(shí)也意味著對逆向工程師的技術(shù)要求會更高。對于從事這個(gè)領(lǐng)域的人來說重要的不是掌握某一個(gè)具體簽名的破解方法而是理解這類機(jī)制的通用原理和分析思路。mtgsig 1.2 只是一個(gè)案例掌握了分析它的方法面對其他類似的簽名機(jī)制時(shí)也能快速上手。6.3 技術(shù)學(xué)習(xí)的邊界與合規(guī)意識最后說幾句實(shí)在話。逆向工程技術(shù)本身是中性的它可以用于安全研究、漏洞挖掘、協(xié)議分析等正當(dāng)用途也可能被濫用。在實(shí)際工作中我始終堅(jiān)持幾條原則只分析自己有權(quán)分析的系統(tǒng)只在自己可控的環(huán)境里做實(shí)驗(yàn)不將技術(shù)用于非法獲取數(shù)據(jù)或破壞服務(wù)。技術(shù)學(xué)習(xí)的目的應(yīng)該是提升自己的能力而不是繞過規(guī)則獲取不當(dāng)利益。mtgsig 1.2 的逆向分析對于理解現(xiàn)代 Web 安全機(jī)制、提升 JavaScript 技能、掌握補(bǔ)環(huán)境方法論都有很大幫助。把這些知識用在正道上才是長久之計(jì)。我在實(shí)際分析過程中最大的體會是補(bǔ)環(huán)境不是簡單地堆砌屬性而是要理解檢測代碼的意圖。每補(bǔ)一個(gè)屬性都要問自己“為什么需要這個(gè)屬性”“檢測代碼會怎么用它”“真實(shí)瀏覽器里它是什么行為”。把這三個(gè)問題想清楚了補(bǔ)環(huán)境的效率和成功率都會大幅提升。另外保持耐心也很重要有時(shí)候一個(gè)看似無關(guān)緊要的屬性缺失就會導(dǎo)致整個(gè)簽名校驗(yàn)失敗排查起來需要逐項(xiàng)比對、反復(fù)驗(yàn)證。