)
簡(jiǎn)介wxappUnpacker 是一套面向微信小程序開發(fā)者與安全研究人員的反編譯工具包用于將小程序包還原為可讀的 WXML、WXSS、JavaScript 與 JSON 代碼并支持主包與分包處理便于分析業(yè)務(wù)邏輯、優(yōu)化加載性能及開展安全審計(jì)。壓縮包共 780 個(gè)文件約 1.97MB以 js、json、md、ts、map 為主包含工具源碼、依賴說明、類型聲明與調(diào)試映射另有 license、cmd/ps1 腳本及配置文件目錄結(jié)構(gòu)較完整便于在本地搭建解析環(huán)境并按模塊查閱。已有 269 人學(xué)習(xí)說明其在逆向分析與小程序機(jī)制研究場(chǎng)景中有一定參考價(jià)值。讀者可據(jù)此了解反編譯流程與關(guān)鍵文件構(gòu)成對(duì)照主包、分包結(jié)構(gòu)梳理頁面配置、樣式與交互實(shí)現(xiàn)為代碼復(fù)用、問題排查和漏洞檢測(cè)提供線索。使用時(shí)需注意僅限合法合規(guī)場(chǎng)景遵守微信開發(fā)者協(xié)議與相關(guān)授權(quán)要求。1. 第一次運(yùn)行 wxappUnpacker 處理小程序反編譯時(shí)我盯著控制臺(tái)那行Wrong Header!看了半小時(shí)包是我從自己的測(cè)試機(jī)里導(dǎo)出來的路徑?jīng)]寫錯(cuò)Node 也沒崩依賴都在。問題出在包體本身首字節(jié)不是0xBE說明它在我動(dòng)手之前就已經(jīng)被處理過一遍。這件事之后我養(yǎng)成一個(gè)習(xí)慣拿到任何一個(gè) wxapkg先花兩分鐘看頭部和索引再?zèng)Q定要不要上 wxappUnpacker。小程序反編譯這件事工具只占三成剩下七成在“你能不能判斷這次還原算成功”。這篇按我實(shí)際的操作順序走先講容器里到底裝了什么再走一遍從小程序包到可讀工程的完整鏈路然后把我踩過的五類失敗現(xiàn)象逐條對(duì)上原因和解法最后說還原完之后怎么讀代碼才有價(jià)值。新手可以照著命令跑通熟手可以看我在參數(shù)和校驗(yàn)點(diǎn)上的取舍。2. 容器與管線wxappUnpacker 處理的是哪幾層文件2.1 一個(gè) wxapkg 里裝的不是壓縮包是索引表加數(shù)據(jù)區(qū)很多人第一次解包會(huì)下意識(shí)去找解壓密碼其實(shí) wxapkg 根本沒有壓縮這一層。它是一個(gè)自定義二進(jìn)制容器結(jié)構(gòu)大致是一字節(jié)起始標(biāo)記0xBE接著三個(gè)四字節(jié)的長(zhǎng)度字段一字節(jié)分隔標(biāo)記0xED然后是文件數(shù)量跟著一張索引表每條記錄包含“文件名長(zhǎng)度 文件名 數(shù)據(jù)偏移 數(shù)據(jù)長(zhǎng)度”索引表之后才是所有文件內(nèi)容首尾相接的數(shù)據(jù)區(qū)。所有多字節(jié)整數(shù)都是大端序這點(diǎn)在你自己寫腳本讀的時(shí)候特別容易翻車按小端解析出來會(huì)得到一堆荒唐的數(shù)字。位置字段長(zhǎng)度說明0起始標(biāo)記1 字節(jié)固定0xBE被改過基本就是加密或二次封裝1保留字段4 字節(jié)大端整數(shù)不同構(gòu)建版本取值不一不用深究5索引區(qū)長(zhǎng)度4 字節(jié)索引表總字節(jié)數(shù)9數(shù)據(jù)區(qū)長(zhǎng)度4 字節(jié)所有文件內(nèi)容加起來的總長(zhǎng)13分隔標(biāo)記1 字節(jié)固定0xED14文件數(shù)量4 字節(jié)索引條目數(shù)18 起索引條目變長(zhǎng)文件名長(zhǎng)度 / 文件名 / 偏移 / 長(zhǎng)度真正的坑在數(shù)據(jù)區(qū)。解出來的東西離“源代碼工程”差得很遠(yuǎn)所有頁面的 JS 邏輯被define(pages/xxx/index.js, function(...){...})這樣的包裹串成一整個(gè)app-service.jsWXML 被壓成一行標(biāo)簽屬性引號(hào)被吞掉{{}}里的表達(dá)式被做過轉(zhuǎn)義WXSS 的import被內(nèi)聯(lián)展開rpx換算規(guī)則散落在各處。所以嚴(yán)格說這不是“反編譯”是“結(jié)構(gòu)還原”。我在評(píng)估一個(gè)包的時(shí)候從來不用“能不能還原出源碼”當(dāng)標(biāo)準(zhǔn)而是看“能不能還原到可讀、可搜索、可定位邏輯”的程度。還有一個(gè)常被忽略的點(diǎn)包里不只有代碼。圖片、字體、音頻這些二進(jìn)制資源是原樣存著的索引表里能拿到準(zhǔn)確的偏移和長(zhǎng)度所以資源還原的準(zhǔn)確率接近 100%出問題的一定是代碼那幾層。把這條記住后面排查的時(shí)候能省一半時(shí)間——資源對(duì)得上、代碼對(duì)不上問題一定出在解析器不在容器。2.2 四個(gè)腳本各管一層串起來才是一次完整還原wxappUnpacker 不是一個(gè)單體程序是入口加幾個(gè)分工明確的腳本。入口wuWxapkg.js只干容器層的事校驗(yàn)頭部、讀索引、按偏移把每個(gè)文件切出來寫盤然后根據(jù)文件類型決定要不要繼續(xù)往下交給別的腳本。真正的還原邏輯分散在wuJs.js拆app-service.js、wuWxml.js還原 WXML、wuWxss.js還原樣式、wuConfig.js處理app-config.json里。wuJs.js的做法值得單獨(dú)說。它用esprima把整個(gè)app-service.js解析成 AST然后在語法樹上遍歷找形如define(模塊名, 函數(shù)體)的調(diào)用節(jié)點(diǎn)把模塊名當(dāng)路徑、把函數(shù)體重新生成代碼寫出去。這個(gè)思路的成立前提是構(gòu)建產(chǎn)物還保留著可識(shí)別的define包裹。一旦構(gòu)建鏈路換成別的打包器、或者基礎(chǔ)庫版本變了把模塊表換了形式AST 里就找不到這些節(jié)點(diǎn)結(jié)果就是只得到一個(gè)幾十萬行的巨型 JS——這不是工具壞了是它的識(shí)別假設(shè)失效了。wuWxml.js更像一套規(guī)則集而不是編譯器。它做的是逆向壓縮動(dòng)作把被吞掉的屬性引號(hào)補(bǔ)回來、把自閉合標(biāo)簽補(bǔ)全、把被轉(zhuǎn)義的表達(dá)式還原成{{}}、把被壓成一行的結(jié)構(gòu)按縮進(jìn)展開。所以它對(duì)“標(biāo)準(zhǔn)寫法”效果很好碰上自定義組件、新語法、或者被內(nèi)聯(lián)成字符串模板的片段還原質(zhì)量會(huì)斷崖式下降。wuWxss.js的邏輯類似主要處理import展開后的還原和rpx相關(guān)的換算。理解這條管線之后很多報(bào)錯(cuò)的性質(zhì)就清楚了容器層報(bào)錯(cuò)頭部、長(zhǎng)度、索引是硬錯(cuò)誤必須解決解析層報(bào)錯(cuò)AST 遍歷失敗、正則匹配不上是軟錯(cuò)誤經(jīng)常是“部分還原”還能繼續(xù)用。我一般會(huì)先把容器層跑通拿到全部原始文件再?zèng)Q定要不要花時(shí)間調(diào)解析層——因?yàn)楣饪吭嘉募锏淖址阉骱芏鄷r(shí)候已經(jīng)夠定位問題了。2.3 三個(gè)開關(guān)決定你要不要返工入口腳本的參數(shù)不多但每一個(gè)都直接影響后面要不要重跑。-o表示只解容器、不做代碼還原跑完得到的是原始文件樹這個(gè)模式在我只想確認(rèn)包結(jié)構(gòu)和資源清單的時(shí)候特別省時(shí)間幾十秒出結(jié)果不會(huì)卡在 AST 解析上。-d用于處理子包。-s主包目錄指定主包解出來的路徑子包里的公共資源引用要靠它來定位。# 老版本解析依賴在新 Node 上容易拋錯(cuò)建議固定到 12/14 nvm use 14 npm install # esprima / escodegen / js-beautify / css 等解析依賴 # 只解容器不還原代碼先看目錄結(jié)構(gòu)和資源清單快 node wuWxapkg.js -o ./pkg/__APP__.wxapkg # 完整還原產(chǎn)物輸出到與包同名的目錄 node wuWxapkg.js ./pkg/__APP__.wxapkg # 子包必須帶 -s且主包要先跑完順序反了會(huì)找不到公共路徑 node wuWxapkg.js -s./__APP__ ./pkg/sub1.wxapkg-o這個(gè)開關(guān)我建議所有人都先用一遍。原因很實(shí)際完整還原在大包上可能跑幾分鐘如果包體本身有問題你會(huì)在幾分鐘后拿到一個(gè)半成品然后再回頭懷疑是解析器的問題。先用-o確認(rèn)容器層干凈把變量一個(gè)個(gè)排除掉比一次性跑完再猜要快得多。這個(gè)習(xí)慣類似調(diào)試時(shí)先打日志再下斷點(diǎn)看著笨實(shí)際最省時(shí)間。3. 復(fù)現(xiàn)鏈路從小程序包到可讀工程的四步實(shí)操3.1 先定位包體、校驗(yàn)頭部別急著上工具包體的位置和命名是有規(guī)律的。主包通常叫__APP__.wxapkg子包按分包名命名同一目錄下平鋪。我一般先把整個(gè)目錄拷出來按“主包 子包”分組然后對(duì)每個(gè)文件跑一遍頭部檢查。這一步的意義不在于“檢查能不能用”而在于“提前知道哪些包需要特殊處理”避免跑到一半才發(fā)現(xiàn)某個(gè)子包是加密的。import struct, sys, os def peek(path): with open(path, rb) as f: buf f.read() # 首字節(jié)不是 0xBE說明包體被處理過加密或二次封裝先別往下走 if not buf or buf[0] ! 0xBE: print(f{os.path.basename(path)}: 首字節(jié)異常跳過) return info1, idx_len, body_len struct.unpack(III, buf[1:13]) # 全程大端序 if buf[13] ! 0xED: print(f{os.path.basename(path)}: 分隔標(biāo)記異常索引區(qū)可能被替換) return count struct.unpack(I, buf[14:18])[0] print(f{os.path.basename(path)} 文件數(shù){count} 索引{idx_len} 數(shù)據(jù){body_len}) p 18 for _ in range(min(count, 10)): # 只打印前 10 條夠看結(jié)構(gòu)了 n struct.unpack(I, buf[p:p4])[0]; p 4 name buf[p:pn].decode(utf-8, replace); p n off, size struct.unpack(II, buf[p:p8]); p 8 print(f {name} off{off} size{size}) if __name__ __main__: for f in sys.argv[1:]: peek(f)這段腳本的關(guān)鍵在三個(gè)地方III里的是大端序?qū)懗蓵?huì)解析出完全對(duì)不上的數(shù)字偏移off是相對(duì)于數(shù)據(jù)區(qū)起點(diǎn)的不是相對(duì)文件頭實(shí)際讀取時(shí)要加上數(shù)據(jù)區(qū)起始位置文件名用utf-8加replace容錯(cuò)解碼因?yàn)閭€(gè)別包里的路徑是轉(zhuǎn)義過的。跑完這個(gè)你對(duì)包里有多少文件、有沒有異常條目、資源大概占多少心里就有數(shù)了。3.2 主包先跑、子包后跑順序錯(cuò)了后面全白干分包機(jī)制是這套流程里最容易出錯(cuò)的一環(huán)。主包里有公共組件、公共樣式、公共工具函數(shù)子包在引用這些資源時(shí)用的是相對(duì)主包根的路徑。wuWxapkg.js在處理子包時(shí)需要靠-s參數(shù)知道主包解到哪兒了才能把這些引用解析成真實(shí)文件。如果順序反了或者忘了帶-s你會(huì)得到一堆“找不到模塊”的報(bào)錯(cuò)然后誤以為是解析器不支持。我的操作順序固定成三步先跑主包并確認(rèn)產(chǎn)物目錄結(jié)構(gòu)正確再逐個(gè)跑子包每個(gè)都帶上-s主包產(chǎn)物目錄最后把子包的產(chǎn)物合并回主包目錄沖突的文件按“主包優(yōu)先、子包覆蓋非公共路徑”處理。第三步不是工具做的是手工或者腳本做的因?yàn)楹喜⒉呗匀Q于你要干什么——如果只是想搜索關(guān)鍵字合不合并無所謂如果要還原完整工程結(jié)構(gòu)就必須合。還有一個(gè)細(xì)節(jié)子包之間可能互相引用。這種情況下合并順序會(huì)影響結(jié)果我一般先按app.json里的分包聲明順序建一個(gè)依賴圖把葉子包先合公共包最后合。聽著麻煩但比事后對(duì)著幾十個(gè)“模塊未定義”試錯(cuò)要快。3.3 五個(gè)檢查點(diǎn)判斷這次還原算不算成功跑完不知道成不成是最浪費(fèi)時(shí)間的狀態(tài)。我給自己定了五個(gè)檢查點(diǎn)從粗到細(xì)依次過任何一個(gè)不通過就停下來先解決它不帶病往下走。檢查點(diǎn)怎么查不通過說明什么目錄結(jié)構(gòu)產(chǎn)物里是否有pages/、components/等目錄且與配置文件里的路徑一一對(duì)應(yīng)容器層解析有問題索引表讀錯(cuò)了JS 拆分app-service.js是否被拆成多個(gè)文件文件名是否就是模塊路徑define包裹沒被識(shí)別AST 遍歷失效WXML 結(jié)構(gòu)標(biāo)簽是否成對(duì)閉合{{}}是否成對(duì)屬性引號(hào)是否補(bǔ)全還原規(guī)則沒有覆蓋到這批寫法WXSS 引用import是否已經(jīng)展開成真實(shí)樣式內(nèi)容樣式層還原被跳過或順序錯(cuò)亂資源完整性抽幾張圖片對(duì)比字節(jié)數(shù)與原包索引記錄的長(zhǎng)度數(shù)據(jù)區(qū)偏移計(jì)算有偏差屬于嚴(yán)重問題第五個(gè)檢查點(diǎn)最容易被跳過但它最能說明問題。資源是原樣存儲(chǔ)的索引表里有準(zhǔn)確長(zhǎng)度如果產(chǎn)物里圖片字節(jié)數(shù)和索引記錄對(duì)不上那一定是“偏移計(jì)算”或者“數(shù)據(jù)區(qū)起點(diǎn)”錯(cuò)了這時(shí)候去調(diào) JS 解析器純屬浪費(fèi)時(shí)間。反過來如果資源全對(duì)但代碼不對(duì)問題就精確定位在解析層可以直接去看wuJs.js的遍歷邏輯有沒有匹配上。4. 排查清單五類常見故障的現(xiàn)象、原因和解法4.1 首字節(jié)不是0xBE入口直接報(bào)Wrong Header!現(xiàn)象腳本一啟動(dòng)就退出提示頭部校驗(yàn)失敗日志里連文件數(shù)量都沒打印出來。原因包體在你拿到之前已經(jīng)被處理過了常見于從某些環(huán)境導(dǎo)出的包頭部被替換或整體做了加密wuWxapkg.js的第一道校驗(yàn)就過不去。解決先用 3.1 的腳本確認(rèn)首字節(jié)到底是什么如果是固定偏移的替換可以定位到原始標(biāo)記的位置再看后面結(jié)構(gòu)是否完整如果整個(gè)數(shù)據(jù)區(qū)都被加密那容器層工具無能為力得先解決解密再談還原。我一般的判斷是頭部異常但數(shù)據(jù)區(qū)長(zhǎng)度字段合理說明只是頭被改連長(zhǎng)度字段都是亂的說明整包被重寫過。4.2 只得到一個(gè)幾十萬行的app-service.js沒有拆成單文件現(xiàn)象容器層跑通了文件都出來了但 JS 目錄里只有一個(gè)巨型文件其余頁面文件一個(gè)沒有。原因wuJs.js靠識(shí)別define(模塊名, 函數(shù)體)這種調(diào)用形式來拆分模塊構(gòu)建產(chǎn)物一旦換成別的打包器或者模塊表改成了數(shù)組映射AST 里就找不到這個(gè)節(jié)點(diǎn)。解決先在這個(gè)巨型文件里搜define(和webpackJsonp看它用的是哪種模塊形式如果是define檢查是不是被包在了一層eval或者字符串里這種要先剝一層如果是別家的模塊表就自己寫個(gè)腳本按模塊表里的名稱數(shù)組和函數(shù)數(shù)組配對(duì)還原思路一樣只是入口節(jié)點(diǎn)不同。4.3 WXML 還原出來一堆殘缺表達(dá)式{{}}對(duì)不上現(xiàn)象結(jié)構(gòu)大致在但屬性值變成半截表達(dá)式里的變量名被截?cái)嗫刂婆_(tái)還刷了一堆正則匹配失敗的警告。原因這批模板用了自定義組件、新語法或者被壓縮成了字符串拼接形式wuWxml.js的規(guī)則集覆蓋不到。解決先別急著改規(guī)則降低期望——大多數(shù)排查場(chǎng)景里能在這堆文本里做關(guān)鍵字搜索就夠了。如果確實(shí)需要結(jié)構(gòu)就針對(duì)具體文件手工還原幾個(gè)關(guān)鍵模板別想著一次性全救回來。我吃過這個(gè)虧花了兩天調(diào)正則最后發(fā)現(xiàn)需要的三個(gè)關(guān)鍵字段在原始文本里直接搜就有。4.4 子包報(bào)“找不到模塊”路徑解析成一堆../現(xiàn)象子包單獨(dú)跑能出文件但引用主包資源的語句全變成了失效路徑報(bào)錯(cuò)集中在公共組件和工具函數(shù)上。原因沒帶-s參數(shù)或者-s指向的目錄不是主包產(chǎn)物根目錄或者跑的順序反了——子包先跑主包目錄還不存在。解決嚴(yán)格按“主包先、子包后、每個(gè)子包都帶-s主包產(chǎn)物目錄”的順序重跑一遍如果還不行檢查-s的值是不是寫成了相對(duì)路徑且當(dāng)前工作目錄不對(duì)改成絕對(duì)路徑最穩(wěn)。這個(gè)錯(cuò)誤我犯過不止一次后來干脆在腳本里寫死絕對(duì)路徑。4.5 Node 版本太新裝依賴時(shí)就崩現(xiàn)象npm install報(bào)錯(cuò)或者跑起來拋SyntaxError: Unexpected token堆棧指向解析庫內(nèi)部。原因這類工具大多寫于 Node 10 到 12 的年代依賴的解析庫版本很老在新版本 Node 上語法或 API 對(duì)不上。解決用版本管理器切到 12 或 14重新裝依賴再跑。別試圖升級(jí)依賴版本來“修好”它解析庫的主版本一升esprima的 AST 節(jié)點(diǎn)結(jié)構(gòu)就變了wuJs.js里的遍歷邏輯會(huì)全部失效那才是真正的麻煩。我的做法是給這個(gè)工具單獨(dú)建一個(gè)環(huán)境目錄鎖死版本不跟其他項(xiàng)目混用。5. 讀懂還原產(chǎn)物把文件樹變成可評(píng)估的攻擊面5.1 先掃接口和密鑰再談邏輯還原出來的文件樹有幾百上千個(gè)文件從頭讀是不可能的。我的第一遍永遠(yuǎn)是關(guān)鍵字掃不看邏輯只看“面”。這一步的目標(biāo)不是找漏洞是畫出一張“這個(gè)包跟外界有多少條通路”的地圖。搜索關(guān)鍵詞想定位的東西后續(xù)動(dòng)作request、http、api網(wǎng)絡(luò)請(qǐng)求封裝與接口前綴匯總出接口清單看有沒有內(nèi)網(wǎng)地址、測(cè)試環(huán)境地址token、sign、encrypt鑒權(quán)與簽名算法判斷簽名是在客戶端算還是服務(wù)端算key、secret、appid硬編碼憑據(jù)單獨(dú)列出來評(píng)估是否可被直接復(fù)用upload、download文件流轉(zhuǎn)路徑看有沒有未校驗(yàn)的存儲(chǔ)路徑eval、Function(動(dòng)態(tài)執(zhí)行點(diǎn)這類點(diǎn)在客戶端腳本里通常是風(fēng)險(xiǎn)放大器掃完你會(huì)得到兩樣?xùn)|西一張接口表和一份“客戶端自己算出來的安全參數(shù)”列表。后者是重點(diǎn)。簽名算法放在客戶端意味著算法本身對(duì)攻擊者完全公開安全性只能靠密鑰而密鑰通常也在這個(gè)包里。這不是說一定有問題而是說這條鏈路的安全假設(shè)必須由服務(wù)端的二次校驗(yàn)來兜底還原產(chǎn)物恰好是驗(yàn)證“有沒有兜底”的最直接材料。5.2 客戶端校驗(yàn)只是體驗(yàn)層重點(diǎn)看服務(wù)端有沒有兜底還原之后最容易產(chǎn)生的錯(cuò)覺是“這里能改所以有漏洞”。實(shí)際上客戶端的所有判斷都可以被繞過這句話在業(yè)內(nèi)是常識(shí)但真正做評(píng)估的時(shí)候還是容易上頭。我的做法是把所有客戶端校驗(yàn)點(diǎn)列出來然后針對(duì)每一個(gè)去接口層找對(duì)應(yīng)的服務(wù)端校驗(yàn)證據(jù)。找不到證據(jù)的才寫成風(fēng)險(xiǎn)項(xiàng)。典型的幾類客戶端校驗(yàn)金額在提交前算好并做格式化服務(wù)端如果直接采信這個(gè)值就是問題權(quán)限判斷靠本地緩存里的角色字段服務(wù)端如果不重新查就是問題次數(shù)限制在本地做計(jì)數(shù)服務(wù)端如果不記賬就是問題。這三類在還原產(chǎn)物里都能找到痕跡——格式化函數(shù)、角色字段的讀取點(diǎn)、計(jì)數(shù)變量的自增位置。找到痕跡之后判斷依據(jù)不是代碼寫得好不好看而是“這個(gè)值最終有沒有被送到服務(wù)端并被服務(wù)端復(fù)核”這需要結(jié)合接口表一起看。我在寫這類評(píng)估記錄時(shí)有個(gè)固定格式現(xiàn)象哪個(gè)文件的哪一行做了什么、影響路徑這個(gè)值怎么流到接口、判定依據(jù)服務(wù)端有沒有對(duì)應(yīng)校驗(yàn)有就寫“已兜底”沒有就寫“待確認(rèn)”。這個(gè)格式看起來啰嗦但它能避免把“看起來不安全”寫成“確認(rèn)存在風(fēng)險(xiǎn)”后者是要負(fù)責(zé)的。5.3 把一次性動(dòng)作變成可重復(fù)流程單次還原的價(jià)值有限真正省時(shí)間的是把它變成流程。我現(xiàn)在的做法是按日期建目錄每次拿到新包就跑一遍固定腳本產(chǎn)物按版本歸檔然后用目錄對(duì)比看兩個(gè)版本之間改了哪些文件。這個(gè)動(dòng)作在跟蹤同一套業(yè)務(wù)的不同版本時(shí)特別有效——改動(dòng)點(diǎn)往往就是新功能或者剛修過的地方重點(diǎn)看那幾個(gè)文件比通讀整棵樹高效一個(gè)量級(jí)。#!/usr/bin/env bash set -e STAMP$(date %Y%m%d) OUT./dump/$STAMP mkdir -p $OUT # 主包先解產(chǎn)物目錄作為后續(xù)子包的 -s 基準(zhǔn) node wuWxapkg.js -o $OUT/__APP__ ./pkg/__APP__.wxapkg /dev/null node wuWxapkg.js $OUT/__APP__ ./pkg/__APP__.wxapkg # 子包按聲明順序逐個(gè)處理-s 必須是絕對(duì)路徑避免工作目錄漂移 for sub in ./pkg/sub*.wxapkg; do node wuWxapkg.js -s$(realpath $OUT/__APP__) $sub || echo skip: $sub done # 只保留業(yè)務(wù)代碼二進(jìn)制資源另存避免歸檔體積膨脹 find $OUT -type f \( -name *.png -o -name *.jpg -o -name *.ttf \) -delete echo done - $OUT腳本里|| echo skip這一句是刻意的子包處理失敗很常見但我更希望整批跑完再統(tǒng)一看哪些失敗而不是第一個(gè)失敗就中斷。歸檔時(shí)刪掉二進(jìn)制資源是為了控制體積文本產(chǎn)物才是后續(xù)對(duì)比和搜索的對(duì)象。這套流程跑順之后一個(gè)中等規(guī)模的包從拿到到可搜索大概十分鐘以內(nèi)。6. 一個(gè)具體技巧用目錄對(duì)比鎖定改動(dòng)比通讀整棵樹高效得多如果只讓我留一個(gè)技巧就是這個(gè)用diff -rq對(duì)比兩個(gè)版本的還原產(chǎn)物只看差異文件列表。# 只列差異文件名不打印內(nèi)容幾千個(gè)文件也能一眼看完 diff -rq ./dump/20240110/__APP__ ./dump/20240118/__APP__ | grep -v ^Only in這個(gè)命令在跟蹤同一套業(yè)務(wù)的迭代時(shí)幾乎是作弊。業(yè)務(wù)方每次發(fā)版改動(dòng)的文件通常只有幾十個(gè)剩下幾百個(gè)是原封不動(dòng)的。你把差異列表拉出來按目錄聚類一眼就能看出這次動(dòng)的是哪個(gè)模塊——如果集中在一個(gè)業(yè)務(wù)目錄那就是功能迭代如果散落在網(wǎng)絡(luò)層和工具目錄那就值得多看一眼因?yàn)檫@類改動(dòng)往往涉及鑒權(quán)、加密或者請(qǐng)求封裝的調(diào)整是整個(gè)包里最需要復(fù)核的部分。我最深的一次教訓(xùn)跟這個(gè)技巧正好相反。早期我做評(píng)估拿到還原產(chǎn)物就一頭扎進(jìn)app-service.js幾百個(gè)模塊挨個(gè)讀讀了整整兩天寫出來的記錄全是“某某函數(shù)實(shí)現(xiàn)了某某功能”一句有用的判斷都沒有。后來帶我的前輩問了一句“你知道這次發(fā)版改了什么嗎”我答不上來。他讓我先做版本對(duì)比結(jié)果改動(dòng)只集中在三個(gè)文件其中一個(gè)是簽名函數(shù)——把一段本地拼接改成了走服務(wù)端下發(fā)。這一處改動(dòng)直接推翻了前面兩天的很多結(jié)論。從那以后我每次做這類分析都強(qiáng)制走一遍“先對(duì)比、再定點(diǎn)、最后才通讀”的順序不跳步。前面那些準(zhǔn)備工作——頭部校驗(yàn)、-o先跑一遍、五個(gè)檢查點(diǎn)驗(yàn)收——看著繁瑣本質(zhì)上都是同一個(gè)道理先把變量控制住再談結(jié)論。這份 wxappUnpacker 的整理版我放在資源里了入口腳本、依賴清單還有我這些年攢下來的報(bào)錯(cuò)對(duì)照備注都在里面幾個(gè)典型失敗的現(xiàn)場(chǎng)處理方式也一并寫上去了拿去按第 3 章的順序跑一遍就能出結(jié)果。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取