微信小程序逆向分析
WeChatAppEx.exe 版本:2.0.6609.4
以融智云考學(xué)生端為例。
網(wǎng)上已經(jīng)有關(guān)于微信小程序解密的非常優(yōu)秀的文章,本著學(xué)習(xí)的目的便不參考相關(guān)內(nèi)容。
筆者水平實在有限,如發(fā)現(xiàn)紕漏,還請讀者不吝賜教。
行為監(jiān)控
工具:火絨劍
首先看看打開一個小程序微信做了點什么,對微信進行火絨行為監(jiān)控。因為小程序最初在PC端運行,必然會相關(guān)文件在客戶機上釋放,所以我們主要關(guān)注微信的文件讀寫行為。
注意到這里有類似文件釋放的行為,在監(jiān)控上訪其實同樣有讀取此文件夾的行為,根據(jù)經(jīng)驗這其實就是一個簡單的讀取相關(guān)目錄,發(fā)現(xiàn)沒有相關(guān)程序邏輯文件后,主動請求服務(wù)器下載相關(guān)文件。
那我們的關(guān)注點來到\.wxapkg
,根據(jù)前人的經(jīng)驗,這就是小程序的主要邏輯所在的地方。
其中可以監(jiān)控到很多關(guān)于調(diào)用堆棧的信息,不過這些堆棧附近大概是文件釋放相關(guān)邏輯,這并不是我們關(guān)注的重點。
分析文件特征
我們用010Editor打開文件,任意一個二進制編輯器都可以。
可以看到明顯程序邏輯被加密那么我們關(guān)注點就來到了這個文件的解密操作。
wxapkg解密思路
那么有兩種思路
- 很經(jīng)典的思路,既然文件被加密,那么微信客戶端裝載此程序的時候必然要進行解密,那么必然要進行打開文件的操作,我們對CreateFile下斷點,應(yīng)該是可以找到打開文件的操作,記錄打開的句柄,同時微信也需要對文件進行讀取的操作。我們在ReadFile下條件斷點,當(dāng)傳入的的句柄是我們獲得的小程序文件的句柄時斷下,調(diào)用堆棧附近應(yīng)該就有相關(guān)解密的操作,這種操作可行性很大,但是相對比較麻煩,微信打開的文件很多,在CreateFile下斷點可能比較麻煩。
- 如果微信使用的是比較常規(guī)的加密 算法,那么可以通過IDA的插件
Findcrypt
看看有沒有比較明顯的特征。
需要注意到的是,在WeChat中并沒用打開這個wxapkg的相關(guān)操作。那么斗膽猜測可能是微信重新啟動一個加載器對wxapkg進行裝載。那么我們進行火絨劍全局監(jiān)控,看看有沒對wxapkg進行讀取的操作。
重新監(jiān)控
可以觀察到名為WeChatAppEx.exe
對文件做了兩次讀取的操作,根據(jù)經(jīng)驗我們關(guān)注第二次讀取,雙擊File_read操作,查看調(diào)用棧
?文章來源地址http://www.zghlxwxcb.cn/news/detail-664785.html
解密分析
跟隨堆棧,我們在IDA和X64dbg中分別定位此位置。RVA:0x678353d
可以看到此位置對文件進行了讀取,我們在dbg中看看有沒有文件的相關(guān)數(shù)據(jù)。附加到WeChatAppEx.exe,在對應(yīng)位置下斷點,并運行一個小程序。
輕而易舉的斷下,并且我們觀察到文件大小非常接近打開的加密文件大小,并且在堆棧中出現(xiàn)了相關(guān)文件信息。
我們步過查看readBuf
內(nèi)的數(shù)據(jù)。
可以看到WeChatAppEx.exe
讀取了加密后的文件。那么我們不難理解WeChatAppEx.exe
類似一個加載器,在運行時對文件進行解密裝載。利用程序要對這一片內(nèi)存進行讀寫的特征,我們對這篇內(nèi)存區(qū)域下硬件訪問斷點(Xdbg的內(nèi)存斷點是針對內(nèi)存頁的,可能會斷在奇怪的地方,可能是筆者不太會使用這種特性)
?
可以看到在RVA:2784F7A處斷下,比較奇怪的是此處對文件的前8字節(jié)賦值為0,不太能理解,索性我們對沒有修改的內(nèi)存再次下硬件訪問斷點(不要忘記卸載之前的斷點)。
再次斷下時對之后的8個字節(jié)賦值為-1,同樣比較難解,我們再次對剩余區(qū)域下硬件斷點。
再次在RVA:676C91E處斷下,可以看到這里有對字符串操作的相關(guān)指令,根據(jù)movsb
指令的功能
?
1 2 3 4 5 |
|
拓展的,我們分別觀察RSI與RDI指向的內(nèi)存區(qū)域
尾部部分解密
解密例程分析
可以看到RSI指向的內(nèi)存中有非完整的加密文件的十六進制形式(前8字節(jié),即V1MMWX?
被忽略),有意思的是,復(fù)制操作的指針并沒有指向文件頭部,而是指向了距離除去V1MMWX?
之后的1024字節(jié)之后,這里有分塊加密的特征,但是為什么程序沒有在解密前1024字節(jié)處停下呢,可能是筆者疏忽或者程序先解密尾部部分在解密首部,既然已經(jīng)到這里,我們不妨順藤摸瓜。
可以看到正在向RDI中賦值數(shù)據(jù),步過至字符串操作完成,我們再對這片內(nèi)存區(qū)域下硬件訪問斷點。
之后再次在RVA:676C91E出斷下,類似的,字符串操作完之后,我們再次對RDI下硬件訪問斷點。
再次運行之后再RVA:302772F處斷下
觀察到我們下斷點的內(nèi)存區(qū)域已經(jīng)出現(xiàn)了../image
字樣,這無疑是非常讓人興奮的,可能這就是文件解密的位置。事實確實如此,我們?nèi)∠布帱c,在上一步斷下出的循環(huán)中循環(huán)幾次簡單分析就可以確定,這確實是一個解密點,而且是非常簡單的異或解密。事實上,這一部分解密過程與微信圖片解密相同。
?
?
現(xiàn)在,我們找到了密文的1024字節(jié)之后部分的解密例程。我們默認忽略前8字節(jié)的處理,讀者可自行分析,裝載器并沒有對前8字節(jié)做過多處理,在解密過程中只是簡單的忽略。
找到xorKey
那么我們rbp所對應(yīng)的秘鑰0x34
從何而來呢,追蹤異或Key使我們接下來要做的事情。
有請尊敬的IDA先生,我們在IDA轉(zhuǎn)到RVA:302772A,即異或解密位置追蹤Key從何而來。
根據(jù)分析,a3即是xorKey,至于a3*0x1010101010…
目的是用int8
類型的值a3填滿rbp至8個字節(jié),進行8個字節(jié)分組異或。我們對a3進行簡單的重命名—>xorKey_
可以看到xorKey作為參數(shù)被傳入(為提高辨識度,筆者對此函數(shù)進行簡單的重命名)
查看對此函數(shù)的引用,有兩個運行時調(diào)用,還有一個直接調(diào)用,處于防止跟飛
情況,我們對此函數(shù)頭下斷來找到調(diào)用點。
文件再次斷下,并在堆棧中回溯,我們來到調(diào)用點RVA:302759B
在IDA中我們了解到xorKey在異或解密函數(shù)ContextDecode
中作為第三個參數(shù)傳遞,且類型為int8
,根據(jù)fastcall
調(diào)用約定,我們關(guān)注寄存器R8:000000000014EE34,最后一個字節(jié),即0x34。他是怎么來的呢,我們向上分析
VA:00007FF632C97589處對r8最后一個字節(jié)進行了賦值,我們看[rax+rcx-0x2]
是什么。
可以看到rcx指向一個字符串,實際上這是微信小程序的ID,即AppID,進行簡單的分析,rax是AppID的長度,而減去0x2后,[rax+rcx-0x2]
指向的是AppID字符串的倒數(shù)第二個字符,將字符所對應(yīng)的ascii碼賦值給r8,這樣,我們xorKey就拿到了。我們在everything找搜索對應(yīng)AppId:wxf2a0156c0235fc4c
可以證實上面的說法。至此,尾部部分解密告一段落。
首部1024字節(jié)解密
解密例程分析
我們再次來到RVA:0x678353d上方的ReadFile處下斷,因為根據(jù)之前分析,此處有全部密文出現(xiàn),同樣我們忽視前8字節(jié),對剩余內(nèi)存內(nèi)容下硬件訪問斷點,嘗試尋找前1024字節(jié)解密位置。
重新在此處斷下(RVA:676C91E),同之前分析,補過字符串操作之靈后,我們跟隨目的地(RDI)內(nèi)存區(qū)域,對其下硬件訪問斷點。
再次在此處斷下(RVA:676C91E)斷下,重復(fù)上述步驟,在目的地地址下斷。
運行之后再RVA:40DFE處斷下
?
這里就有比較令人興奮的字段:the iv:16bytes
,部分加密需要一個向量,我們不妨猜測,這里就是加密函數(shù),我們在IDA中來到對應(yīng)位置。
之前筆者已經(jīng)對一些變量進行分析并且重命名,所以看起來似乎一目了然,這些變量的命名,我們之后逐步分析但不是現(xiàn)在的關(guān)注的重點。
引起我們注意的是類似的匯編指令aesdeclast xmm2, xmm1
,注意到字樣“aes”就可以懷疑密文首部采用的是aes加密,事實上確實采用的是這用加密,從學(xué)習(xí)的角度,我們假設(shè)并不知情相關(guān)特征。
既然到了這一步,不妨運行看看相關(guān)內(nèi)存區(qū)域有沒有明文信息。
運行若干步之后,我們在RSI所指內(nèi)存區(qū)域中發(fā)現(xiàn)明文特征,這與之前分析尾部解密中得出的明文十分類似,至此可以確定,這一部分邏輯即是對前1024字節(jié)進行解密的邏輯。
那么我們下一步要解決的問題是:”這是什么加密“,以便我們能找出秘鑰,自行寫出解密腳本。
我們百度aesdeclast xmm2, xmm1
,看看能不能收獲一些有用的信息。
下面是來自于互聯(lián)網(wǎng)的一些資料:
AESDECLAST — Perform Last Round of an AES Decryption Flow
?
Description??
This instruction performs the last round of the AES decryption flow using the Equivalent Inverse Cipher, with the round key from the second source operand, operating on a 128-bit data (state) from the first source operand, and store the result in the destination operand.
128-bit Legacy SSE version: The first source operand and the destination operand are the same and must be an XMM register. The second source operand can be an XMM register or a 128-bit memory location. Bits (MAXVL-1:128) of the corresponding YMM destination register remain unchanged.
VEX.128 encoded version: The first source operand and the destination operand are XMM registers. The second source operand can be an XMM register or a 128-bit memory location. Bits (MAXVL-1:128) of the destination YMM register are zeroed.
請注意描述中的加粗部分,其大概意思是aesdeclast xmm2, xmm1
執(zhí)行的是反向解密的最后一輪解密過程,xmm1是round Key(拓展秘鑰,aes將用戶設(shè)置的秘鑰進行拓展以便于運算),而xmm2即是最后一輪解密的數(shù)據(jù)。
現(xiàn)在我們可以確定這一部分加密使用的是AES加密,我們正在分析的是其對應(yīng)的解密部分。根據(jù)AES加密的對應(yīng)的解密過程,最后一輪解密使用的round Key正是用戶設(shè)定的秘鑰,關(guān)于AES使用類似指令的介紹以及加解密的細節(jié)問題,筆者收集到一篇優(yōu)質(zhì)文章:
Intel AES-NI使用入門 - 被遺忘的海灘 | Nagi's Blog (x-nagi.com)
AES_Key生成例程分析
我們再次來到RVA:40DFE處
結(jié)合引用文章的介紹,我們大概可以得出:
那么我們接下來的關(guān)注點放到了拓展秘鑰緩沖區(qū),我們跟隨秘鑰緩沖區(qū)的生成會進入到秘鑰拓展例程,在那里,我們大概率可以拿到Key,我們在IDA中追蹤秘鑰緩沖區(qū)
?
?
v33對應(yīng)的是Rcx,而拓展秘鑰緩沖區(qū)即keyArry來自于函數(shù)外。我們對此函數(shù)頭下斷進行?;厮?。
函數(shù)頭:
再次斷下后(前幾次斷下并不能得到我們要的調(diào)用棧,因為相關(guān)參數(shù)中找不到密文緩沖區(qū)等特征),根據(jù)fastcall
約定,秘鑰應(yīng)該是r9所指緩沖區(qū),實際上,秘鑰緩沖區(qū)最后十六個字節(jié)作為原始Key的一部分(16字節(jié)),即未拓展的Key,秘鑰拓展例程通過原始Key進行秘鑰拓展,不過不注意這個細節(jié)也沒有關(guān)系,這在之后的分析中將會體現(xiàn)。
我們回溯到RVA:2811EB5
在此函數(shù)中,keyArry已經(jīng)生成,那么我們繼續(xù)棧回溯,來到VA:00007FF6444C1137
[rcx+0x10]即是keyArry,同樣分析此函數(shù),發(fā)現(xiàn)keyArry同樣作為參數(shù)傳入此函數(shù),那么我們繼續(xù)進行調(diào)用?;厮荨?/p>
回退到第二次調(diào)用棧,我們來到VA:00007FF6444C1398,如下圖
同樣的,[rcx+0x10]即是keyArry,同樣是作為參數(shù)傳遞進來的,再次進行堆棧回溯,來到RVA:00000000027F15AB,如下圖,這里再次進行了簡單的轉(zhuǎn)發(fā),再次進行?;厮?。
來到RVA:285B20C,如下圖
?
我們所說的keyArry是aes實例化的一個對象,里面存儲有拓展秘鑰。再此函數(shù)進行簡單分析后發(fā)現(xiàn),key同樣來自函數(shù)外通過參數(shù)傳遞進來。
繼續(xù)堆?;厮?_-||,來到RVA:000000000285AE26
繼續(xù)回溯,來到RVA:000000000285AED8,同樣是一個簡單的轉(zhuǎn)發(fā),繼續(xù)回溯,來到RVA:0000000003026BF2
如下圖
如圖,[[v18]+0x8]指向秘鑰,終于要計算秘鑰了-_-||,本函數(shù)上方有對v18的相關(guān)操作,如下圖
其實看到‘salt’這幾個字符,對秘鑰拓展熟悉的朋友應(yīng)該能馬上反應(yīng)過來這里應(yīng)該就是拓展秘鑰的地方了。我們進到函數(shù)里
如圖
這就非常明確了,我們百度Pbkdf2
PBKDF的全稱是Password-Based Key Derivation Function,簡單的說,PBKDF就是一個密碼衍生的工具。既然有PBKDF2那么就肯定有PBKDF1,那么他們兩個的區(qū)別是什么呢?PBKDF2是PKCS系列的標準之一,具體來說他是PKCS#5的2.0版本,同樣被作為RFC 2898發(fā)布。它是PBKDF1的替代品,為什么會替代PBKDF1呢?那是因為PBKDF1只能生成160bits長度的key,在計算機性能快速發(fā)展的今天,已經(jīng)不能夠滿足我們的加密需要了。所以被PBKDF2替換了。在2017年發(fā)布的RFC 8018(PKCS #5 v2.1)中,是建議是用PBKDF2作為密碼hashing的標準。PBKDF2和PBKDF1主要是用來防止密碼暴力破解的,所以在設(shè)計中加入了對算力的自動調(diào)整,從而抵御暴力破解的可能性。
PBKDF2的工作流程
PBKDF2實際上就是將偽散列函數(shù)PRF(pseudorandom function)應(yīng)用到輸入的密碼、salt中,生成一個散列值,然后將這個散列值作為一個加密key,應(yīng)用到后續(xù)的加密過程中,以此類推,將這個過程重復(fù)很多次,從而增加了密碼破解的難度,這個過程也被稱為是密碼加強。
稍微閱讀以上引用內(nèi)容 ,對比此函數(shù)參數(shù)不難得出:
- 鹽值:saltiest
- 秘鑰:小程序ID
- 秘鑰拓展算法:PBKDF2
- 迭代次數(shù):1000
- 秘鑰長度:256位
既然秘鑰長度是256位,那么可以推測出加密算法是AES_256。
注意到的是,它的偽散列算法是可以替換的,那么我們下一步要找出的是它使用的是哪種散列算法
這里筆者簡單使用js選幾種常見的散列算法試一試
?
1
2
3
4
5
6
7
8
const crypto
=
require(
'crypto'
);
let appid
=
"wxf2a0156c0235fc4c"
;
????
crypto.pbkdf2(appid,
"saltiest"
,
1000
,
32
,
'sha1'
,(err, derivedKey)
=
>
????
{
??????
if
(err) throw err;
??????
console.log(
"The password is "
,derivedKey.toString(
'hex'
));
????
});
對比拓展秘鑰函數(shù)運行之后的返回值[[rax]+0x8]中的值:?
?可以驗證散列算法是‘sha1’,對應(yīng)我們在分析過程中的拓展秘鑰的最后字節(jié)部分。
1 2 3 4 5 6 |
|
至此,我們找到了秘鑰的生成算法。
分組模式以及iv
分組模式以及iv的尋找相對簡單,只要對AES加密流程以及幾種加密模式的區(qū)別熟悉就可以在加密函數(shù)(RVA:0000000000040C30)中分析出加密模式以及向量。這里筆者不再贅述。
經(jīng)過簡單分析,總結(jié)之前分析成果,有如下清單:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
拿到解密出的文件后可以用相應(yīng)的解包腳本進行解壓,網(wǎng)上不乏解壓腳本,遺憾的是筆者并沒有找到能夠徹底解壓并且還原出微信開發(fā)者工具能夠識別的對應(yīng)各式的文件(微信開發(fā)者工具對js等的樣式做了一層封裝,想要能夠調(diào)試源代碼需要將解包后的文件還原成其能夠識別的格式,網(wǎng)上確實有相關(guān)腳本,但大多比較老,微信開發(fā)工具對樣式進行了更新,格式化出現(xiàn)了一些問題,筆者水平有限,就不去修復(fù)。)
下面給出不成熟的C++解密腳本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
|
?
到了這里,關(guān)于微信小程序逆向解密的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!