逆向WeChat(七)

bbqz007發表於2024-10-07

上篇介紹瞭如何透過嗅探MojoIPC抓包小程式的HTTPS資料。

本篇逆向微信客戶端本地資料庫相關事宜。

本篇在部落格園地址https://www.cnblogs.com/bbqzsl/p/18423518

微信PC客戶端有兩種主要的資料儲存型別,一種是基於sqlcipher,另一種是基於protobuf。除了這兩種外還有別的,不在本篇內容。

它們是對應兩個主要的類,StorageBaseConfigInfoStorage。StorageBase使用sqlcipher作為儲存手段,ConfigInfoStorage則使用protobuf儲存KeyValue。

StorageBase是一個單表操作的封裝類,它包含了資料庫名跟表名等資訊。它封裝了開啟,查詢等底層操作。它的StorageBase::init方法完成了開啟資料庫以及必要的設定,包括CipherAndKey的設定。逆向分析到現在,我才發現微信是有用混餚的程式碼段來做程式碼保護,這個段位於WeChatWin.dll的末尾。StorageBase::init呼叫DBFactory::openDBbyName方法完成全部開啟工作。這個DBFactory::openDBbyName方法,它的日誌資訊全部字串都經過混餚處理,顯然是不想讓人知道。並且DBFactory::openDBbyName的主執行體邏輯被編輯在混餚程式碼段。混餚程式碼的目的,不單是讓人變白痴,更加重要是讓逆向的工具變白痴,包括呼叫器。微信使用的混餚,有個特點,就是滿天的call指令。只要你清楚call指令只是jmp&push的等價物,就明白它的噁心了。呼叫器的呼叫棧幀功能,只能用於分析ebp, eip這種中規中舉的呼叫,混餚的call根本不在於call&ret,如果你認為它會在call的下一指令,ret回來執行,你可能等不到它執行。包括逆向工具的程式碼分析功能也同樣被打成白痴。call等同jmp並push eip。這時的eip不是為了ret,而為了開闢esp,並將eip作為後面這個棧位置的內容的解碼因子。或者這個棧位置的內容後面是被直接替換而丟棄的。不單程式碼混餚,連棧結構都被混餚。

所以碰到混餚保護的程式碼,Mother Mary comes to me Speaking words of wisdom let it be。雖然我假設,微信可能在這裡將資料庫的KEY,還在KEY的計算方法保護了起來。我承認我還停留在8年那個安卓版的魔法MD5(imei+uin)。想必PC的計算方法是Foo(myPCInfo, uin),存在於客戶端程式碼某處。既然它們都用混餚程式碼保護起來了,就 so i listened to Mother Mary,let it be。正當我是這麼在想的時候,一處AccountService::setDBKey卻赫然在目。唉?不對吧。於是我趕緊windbg跟蹤。咦?這引數裡面不就有我的資料庫的KEY嗎?前面我還在說,微信煞有介事地用混餚程式碼將sqlcipher開啟資料庫的一連串操作,包括KEY設定,通通都保護起來。現在卻明目張膽地露出來。這是在打我臉,還是在打它自個的臉。這戲到底在唱的是哪一齣,我完全看不懂。它拼命地將那裡遮起來不讓別人知道,但用來遮住那裡的東西卻有一張照片是裸露那裡的。這是在玩彩蛋。直到看了它們的技術post才明白,”安全性。基於不怕被破解,但也不能任何人都能破解的原則“,https://cloud.tencent.com/developer/article/1005575。這詞令有點耳熟,好像在哪裡聽過類似的格式的短句。微信開發團隊為大家指明瞭方向,歡迎來破解,就怕你不會。(原來它們的資料庫還有一個正名WCDB, https://cloud.tencent.com/developer/article/2406614。我卻一直將它當sqlcipher在處理。WCDB是用在移動終端的,PC端的應該不是。)雖然市面上關於這個的破解已經寫到爛大街,只要在github上搜wecaht Db crack關鍵詞,就有一大堆的repos。我就簡單介紹我的分析,還有方法,為觀眾多一種角度。AccountService有三個成員變數,DBKey,RSA公鑰,RSA私鑰。它們都是std::string型別。並且有已知固定的長度。最lucky的是,它們之間的相對位置經歷了這麼多年還有版本都沒有變化過。搜查的步驟,先透過RSA私鑰字串的地址,再將地址值結合字串的長度值找到RSA私鑰成員變數的位置,然後相對偏移後得到DBKey成員變數的位置。就如我們熟悉的幾何定理三點定面一樣,鎖定位置。只要三條指令,就可以用windbg實現目標。

x86

s-a 0 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
        * theRes
s-d wechatwin L2000000 theRes 0 0 0 377 37f
        * theRes2
da poi(theRes2 - 18)
        * check if "-----BEGIN PUBLIC KEY-----"
db poi(theRes2 - a8) Lpoi(theRes2 - a4)

x64

s-a 0 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
		* theRes
s-q wechatwin L2000000 theRes 0 377 37f
		* theRes2
da poi(theRes2 - 20)
		* check if "-----BEGIN PUBLIC KEY-----"
db poi(theRes2 - f8) Lpoi(theRes2 - f0) 

補充一下,windbg沒有像gdb那樣,可以賦值變數,但可以用alias代替。將theRes跟theRes2別名成結果地址, 或者手動替換指令中的theRes跟theRes2。另外一次最多隻能搜尋地址空間0x10000000,因些在第一個地址段找不到,請用下面命令掃描地址空間的全部使用者空間。

s-a 0 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 10000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 20000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 30000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 40000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 50000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 60000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"
s-a 70000000 L10000000 "-----BEGIN RSA PRIVATE KEY-----"

我也跟跟風,寫一個https://github.com/bbqz007/CrackMicroMsgDBKey關於如何用windbg看金鑰。

  

找到DBKey後,當然就想知道計算Key的程式碼。開始我以為會在混餚程式碼段,但在發現AccountService::setDBKey後,再去跟蹤才真相大白。DBKey由伺服器返回,計算不在客戶端,而在伺服器。

有需要的請自行探究,與Auth相關的類。

加密解密兩因素,一是Key,二是Cipher。多年沒碰微信資料庫,我也忘了cipher的設定。隨便找個最新的sqlcipher來用,key對了還是死活打不開資料庫。因為每個sqlcipher版本預設的cipher不同,或者分發軟體的發行者編譯的選項不同,預設的cipher不同。cipher的設定必須也要匹配。我們可以直接透過微信直接查詢它開啟的資料庫。一共有kdf_iter,cipher_page_size, cipher_use_hmac, cipher_plaintext_header_size, cipher_hmac_algorithm,cipher_kdf_algorithm六項。

微信將sqlcipher的訪問封裝成一個單表操作的類。StorageBase。它包含了資料庫名,表名,還有最重要的資料庫控制代碼。畢竟還是sqlite3。我們只要找到api表就可以使用所有c介面了。上圖的示例就是透過api表的c介面訪問StorageBase開啟的資料庫控制代碼。即使StorageBase雖然將開啟資料庫的流程通通用混餚程式碼保護起來。

得到capi表後,sqlite3_exec可以運算元據庫,如上圖演示,sqlite3_prapare可以跟蹤sql,如下圖演示。

我已經在我的KTL工具ver0.9.1gitee倉庫)新增了patch,方便有需要的人,只要在AlgoDataTool.cpp設定你的破解出來的DBKey,就可以瀏覽你的微信資料庫。

資料庫檔案目錄在”我的文件“下,”WeChat Files\wxid_????????\Msg"。如果想知道當前登陸的wxid,可以檢視”WeChat Files\config\AccInfo.dat",字串型別陣列中編號4的數值,而編號10則是暱稱。如何檢視,就是接下來要介紹的另一種資料庫型別,我同樣也在KTL新增了patch,提供對應檢視工具。在本篇,資料庫是一個廣泛的,包括各種格式的資料檔案。比如,類mongodb同樣可以用plaintext的方式作為底層儲存。一種格式的檔案也可以透過mysql儲存引擎介面做成自定義儲存引擎被mysql使用。

另一種資料庫基於protobuf,型別名稱為ConfigInfoStorage。它的主要資料成員是micromsg::KeyValueSetting,這是由proto檔案生成的類。雖然沒有KeyValueSetting的proto檔案,但是由逆向內容可以分析根結構。

根級結構主要有5個成員,它們都是陣列,每個陣列元素皆為一個key-value對。key是整形。知道總的框架結構,在沒有proto檔案情況下,也可以對protobuf進行大概的分析。雖然沒有proto定義檔案,但仍然可以用protoc工具解碼,只是不知道成員的名稱,所有名稱都是整形的位置號。但是有了上面的根級結構就可以開展分析。根1號位是32位整形的value陣列,每個value都有一個名稱對應的編號。陣列每個元素是一個intkey-value的proto,所以元素的1號位是intkey,2號位是value。後面的陣列以同樣的方式。根2號位是buffer型別的陣列,這裡的buffer有些是經序列化的protobuf。可以認為是物件型別的陣列。根3號位是字串陣列,所有字串的value都在這個陣列。根4號位是64位的整形陣列。根5號位是浮點形陣列。

protoc工具不能在沒有proto檔案的情況下,解碼成JSON格式,只能用TextFormat。但是這個TextFormat是真的獨特,似DICT或JSON,但又不同。不能方便使用python或json瀏覽器進行分析。所以我用我的KTL新增了一個功能,可以將protoc工具解碼出來的TextFormat轉換成JSON,並提供可視檢視。有了轉換後的JSON,可以貼上到任意你喜歡的可視工具進行分析,最簡單的就是使用chrome瀏覽器的devtools,貼上到console執行。

資料庫檔案目錄在”我的文件“下,”WeChat Files\config\“。其中AccInfo.dat對應AccoutService類,aconfig.dat對應AccoutStorageMgr類。

本篇相繼介紹了微信如何使用sqlcipher,AccoutService類,基於protobuf的key-value資料庫,ConfigInfoStorage類。資料庫儲存在我們的電腦磁碟,而類執行在我們的記憶體。透過掃描這些東西,就可以蒐集個人資訊。AccoutService類還包含了繫結的手機號。又例如,單純掃描磁碟,不掃描微信程序,不掃描sqlcipher資料庫。也可以得到當前登陸的微訊號,這個微訊號使用了哪些小程式,這些小程式是哪些知名的小程式應用,是哪種型別的小程式應用,這個微訊號有著一些使用習慣喜好以及需求,等等。(每個小程式有一個唯一的AppID,還有繫結的公眾號)。配合掃描微信程序的DBKey,就可以直接窺探更加多內容。

在我的KTL工具ver0.9.1gitee倉庫)新增了兩個patch,patch.sqlcipher檢視sqlcipher資料庫,patch.protobuf檢視protobuf資料(檔案)。

另外直得一提。wechatwin.dll就像一個Library,靜態編譯進了不計其數的開源庫。sqlcipher也是其中之一。我們也可以直接wechatwin.dll。

本篇到這裡,下一篇再見。

逆向WeChat(七,查詢sqlcipher的DBKey,檢視protobuf檔案)

逆向WeChat(六,透過嗅探mojo抓包小程式https,開啟小程式devtool)

逆向WeChat(五,mmmojo, wmpfmojo)

逆向通達信 x 逆向微信 x 逆向Qt (趣味逆向,你未曾見過的signal-slot用法)

逆向WeChat(四,mars, 網路模組)

逆向WeChat(三, EventCenter, 所有功能模組的事件中心)

逆向WeChat (二, WeUIEngine, UI引擎)

逆向wechat(一, 計劃熱身)

我還有逆向通達信系列

我還有一個K線技術工具專案KTL可以用C++14進行公式,QT,資料分析等開發。你的程式碼JustInTime執行。

相關文章