node.js中PC端微信小程序包解密的處理思路
原來(lái)發(fā)布在掘金,搬過(guò)來(lái)好了。
微信小程序在PC端是加密存儲(chǔ)的,如果直接打開是看不到什么有用的信息的,需要經(jīng)過(guò)解密才可以看到包內(nèi)具體的內(nèi)容。本文使用nodejs實(shí)現(xiàn)解密算法,主要涉及到crypto, commander, chalk三個(gè)包的使用。
小程序的源碼在哪里
PC端打開過(guò)的小程序會(huì)被緩存到本地微信文件的默認(rèn)保存位置,可以通過(guò)微信PC端=>更多=>設(shè)置查看:
進(jìn)入默認(rèn)保存位置下的/WeChat Files/WeChat Files/Applet文件夾,可以看到該目錄下有一系列前綴為wx的文件(文件名其實(shí)是小程序的appid),這些就是我們打開過(guò)的小程序啦:
進(jìn)入其中某個(gè)小程序的文件夾,我們可以看到一個(gè)名字為一串?dāng)?shù)字的文件夾。點(diǎn)進(jìn)這個(gè)文件夾, 就可以看到一個(gè)__APP__.wxapkg文件,也就是小程序?qū)?yīng)的代碼啦:
然而,當(dāng)我們打開這個(gè)文件之后卻發(fā)現(xiàn)是這樣的:
WTF 這能看出來(lái)個(gè)??。很明顯,這個(gè)文件是經(jīng)過(guò)加密的,需要解密才能看到我們想看到的東西。
PC端小程序是怎么被加密的
這里參考了一位大佬用Go語(yǔ)言寫的PC端wxapkg解密代碼。整理一下的話,加密流程是這樣的:
首先將明文代碼在第1024字節(jié)處一分為二,前半部分使用CBC模式的AES加密,后半部分則直接進(jìn)行異或。最后,將加密后的兩節(jié)拼接起來(lái),并在最前邊寫入固定的字符串:"V1MMWX"。
所以,我們打開__APP__.wxapkg文件看到的就是加密后的代碼,如果想還原回去的話,需要從后往前逐步推回去。
解密思路
預(yù)處理
我們使用node.js去寫一個(gè)解碼的程序。根據(jù)上邊加密的流程,我們首先讀取加密文件,把前6個(gè)字節(jié)的固定字符串去除。由于AES加密和異或前后數(shù)據(jù)的位數(shù)是相同的,我們可以據(jù)此獲取到加密后的頭部1024字節(jié)和加密后的尾部部分:
const fs = require('fs').promises; ... const buf = await fs.readFile(pkgsrc); // 讀取原始Buffer const bufHead = buf.slice(6, 1024 + 6); const bufTail = buf.slice(1024 + 6);
加密后的頭部部分
為了得到這1024個(gè)字節(jié)的明文,我們需要知道AES加密的初始向量iv,以及一個(gè)32位的密鑰。已知16字節(jié)的初始向量iv是字符串:“the iv: 16 bytes”,我們接下來(lái)需要計(jì)算出這個(gè)由pbkdf2算法導(dǎo)出的32位的密鑰。
pbkdf2(Password-Based Key Derivation Function)是一個(gè)用來(lái)生成密鑰的函數(shù),它使用一個(gè)偽隨機(jī)函數(shù),將原文密碼和salt作為輸入,通過(guò)不斷的迭代得到密鑰。在crypto庫(kù)中,pbkdf2函數(shù)是這樣的:
const crypto = require('crypto'); ... crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)
其中參數(shù)分別是:原文密碼、鹽值、迭代次數(shù)、密鑰長(zhǎng)度、散列算法、回調(diào)函數(shù)。已知salt是"saltiest",原文密碼為微信小程序的id(也就是wx開頭的那個(gè)文件夾名),迭代次數(shù)為1000,散列算法為sha1。因此,我們可以寫出計(jì)算密鑰的代碼:
crypto.pbkdf2(wxid, salt, 1000, 32, 'sha1', (err, dk) => { if (err) { // 錯(cuò)誤 } // dk即為計(jì)算得到的密鑰 })
密鑰和初始向量iv都有了之后,我們可以開始對(duì)密文進(jìn)行解密了。AES加密算法是一種非對(duì)稱加密算法,它的密鑰分成公開的公鑰和只有自己知道的私鑰,任何人都可以使用公鑰進(jìn)行加密,但是只有持有私鑰的人解密得到明文。
小程序使用的加密算法是CBC(Cipher Block Chaining, 密碼分組鏈接)模式的AES,也就是它在加密的時(shí)候,首先把明文進(jìn)行分塊,然后將每一塊與前一塊加密后的密文進(jìn)行異或,再使用公鑰進(jìn)行加密,得到每一塊的密文。對(duì)于第一塊明文,由于它不存在前一塊明文,因此它會(huì)與初始向量iv進(jìn)行異或,再進(jìn)行公鑰加密。在實(shí)現(xiàn)的時(shí)候,我們只需要調(diào)用crypto提供的解密函數(shù)就可以啦。
我們知道,AES算法根據(jù)密鑰長(zhǎng)度的不同有AES128, AES192和AES256。回顧上邊,我們的密鑰是32字節(jié),也就是256位的,因此顯然我們應(yīng)該使用的是AES256。綜上,我們可以寫出來(lái)解密的代碼:
const decipher = crypto.createDecipheriv('aes-256-cbc', dk, iv); const originalHead = Buffer.alloc(1024, decipher.update(bufHead));
其中originalHead就是我們要的前1024字節(jié)的明文啦。我們可以打印出來(lái)看看:
嗯…… 有那么點(diǎn)意思了。
加密后的尾部部分
這一部分就很簡(jiǎn)單啦。由于異或運(yùn)算是具有自反性的,因此只需要簡(jiǎn)單的判斷一下小程序id的位數(shù)獲得異或的xorKey,再把它與密文進(jìn)行異或,就可以得到原文了:
const xorKey = wxid.length < 2 ? 0x66 : wxid.charCodeAt(wxid.length - 2); const tail = []; for(let i = 0; i < bufTail.length; ++i){ tail.push(xorKey ^ bufTail[i]); } const originalTail = Buffer.from(tail);
將頭部部分的明文與尾部部分的明文進(jìn)行拼接,再以二進(jìn)制形式寫入文件,就可以得到最終的明文啦。
再漂亮點(diǎn)
根據(jù)上邊的描述,我們可以把我們整個(gè)的解密過(guò)程封裝成一個(gè)黑盒子:
commander
我們可以使用commander庫(kù)讓程序直接從命令行讀取小程序的id和密文包。commander是一個(gè)nodejs命令行界面的解決方案,可以很方便的定義自己的cli命令。比如說(shuō)對(duì)于下面這一串代碼:
const program = require('commander'); ... program .command('decry <wxid> <src> [dst]') .description('解碼PC端微信小程序包') .action((wxid, src, dst) => { wxmd(wxid, src, dst); }) program.version('1.0.0') .usage("decry <wxid> <src> [dst]") .parse(process.argv);
我定義了一個(gè)"decry <wxid> <src> [dst]"的命令,其中尖括號(hào)代表必選參數(shù),方括號(hào)代表可選參數(shù)。description內(nèi)是關(guān)于這個(gè)命令的描述文本,action則是執(zhí)行這段命令。在控制臺(tái)使用node執(zhí)行代碼之后,可以看到如下界面:
于是我們就可以根據(jù)提示,輸入?yún)?shù)進(jìn)行解密啦。commander.js的中文文檔在這里。
chalk
為了讓我們的控制臺(tái)多一抹顏色,我們可以使用chalk.js來(lái)美化輸出。chalk的基本用法也比較簡(jiǎn)單:
const chalk = require('chalk'); ... console.log(chalk.green('綠了'))
這樣我們就可以在黑白的控制臺(tái)上填上一抹綠色,替大熊貓實(shí)現(xiàn)夢(mèng)想:
除此之外,我們還可以使用es6的字符串標(biāo)簽?zāi)0甯奖愕氖褂胏halk。具體的參考chalk官方文檔吧。
源代碼
代碼發(fā)布到github和gitee啦,可以給大家參考一下下~
github點(diǎn)這里,?gitee點(diǎn)這里
到此這篇關(guān)于node.js中PC端微信小程序包解密的文章就介紹到這了,更多相關(guān)nodejs微信小程序解密內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
NodeJs的優(yōu)勢(shì)和適合開發(fā)的程序
做頁(yè)游或webqq這樣的應(yīng)用nodejs有優(yōu)勢(shì),但如果做微博、豆瓣、facebook這樣的社交網(wǎng)絡(luò),nodejs還有優(yōu)勢(shì)嗎?另外不知道大家是什么原因選擇的nodejs?是因?yàn)閼?yīng)用需求還是對(duì)javascript這門語(yǔ)言的喜歡?2016-08-08express如何解決ajax跨域訪問(wèn)session失效問(wèn)題詳解
這篇文章主要給大家介紹了關(guān)于express如何解決ajax跨域訪問(wèn)session失效問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06淺談使用nodejs搭建web服務(wù)器的過(guò)程
這篇文章主要介紹了淺談使用nodejs搭建web服務(wù)器的過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07用node-webkit把web應(yīng)用打包成桌面應(yīng)用(windows環(huán)境)
這篇文章主要介紹了windows環(huán)境下用node-webkit把web應(yīng)用打包成桌面應(yīng)用的教程,需要的朋友可以參考下2018-02-02Node.js連接數(shù)據(jù)庫(kù)實(shí)現(xiàn)過(guò)程詳解
這篇文章主要為大家介紹了Node.js連接數(shù)據(jù)庫(kù)實(shí)現(xiàn)過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12express express-session的使用小結(jié)
這篇文章主要介紹了express express-session的使用小結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12