微信PC端技術研究-保存聊天語音
by anhkgg(公衆號:漢客兒)
2019年1月31日
2.6.6.28
0x0. 前言
雖然一直知道CE,也用過一段時間,但一直用不好,可能太笨。
最近又學習了某位大佬用CE的方法,大佬的一句話有點醍醐灌頂,然後有了新的感覺,然後開始嘗試實踐這篇文章。
自己總結一下CE用法的核心思路:通過各種技巧搜索找到內存中關鍵數據,然後結合動態調試找到操作數據的函數。
準備工具:Cheat Engine,OllyDbg,IDA。
0x1. 瞭解CE
官網:https://www.cheatengine.org/
看看來自百科的介紹:
Cheat Engine 是一款內存修改編輯工具 ,它允許你修改你的遊戲或軟件內存數據,以得到一些其他功能。它包括16進制編輯,反彙編程序,內存查找工具。與同類修改工具相比,它具有強大的反彙編功能,且自身附帶了外掛製作工具,可以用它直接生成外掛。
在我看來,CE做的最好的就是各種策略的內存搜索能力。
-
支持準確數據(整數、字符串、十六進制、浮點數、字節數組等等)搜索,針對目標數據明確效果顯著,比如金幣數。
-
支持數據範圍的搜索,比如大於某個值,小於某個值等等。比如想找到沒有顯示數值的血量數據。
-
支持多組數據同時搜索,針對數據結構複雜的情況
-
支持搜索結果的多次過濾(圖中框選的Next Scan),最終找到目標數據。比如血量未知時,通過加血、減血多次搜索最終找到血量地址。
說到底CE內存搜索的能力就是通過各種策略幫助你找到遊戲中需要修改的數據(比如血量、分數、金幣等等),然後通過內存修改能力(直接改血量)打破遊戲平衡,外掛製作工具生成外掛,助你超神!
更多CE的高級應用可以訪問:
https://blog.csdn.net/cgs_______/article/details/77799091
https://blog.csdn.net/zhaobisheng1/article/details/79259460
0x2. 分析
進入正題,本文是要拿到微信聊天的語音消息,然後dump保存下來。
要按以前我的思路,會通過網絡通信找到接受消息的函數,然後找到語音數據,看起來很簡單,但是有點難。
因爲函數真的很多,網絡消息也會受到很多幹擾。
現在用CE了,應該怎麼辦呢?
找到關鍵數據
關鍵數據肯定是語音消息了,但是怎麼搜索呢,肯定搜語音內容不現實,所以轉了彎,先看看文字消息,找到接受文字消息處理函數之後,猜測語音處理函數會相同或者在不同分支。
接着,如何搜索文字消息呢?已經收到的顯示在聊天窗口的內容當然可以通過CE找到,但是沒用啊,它和接受文字消息處理函數已經沒關係了,流程已經處理完成了。
那麼在測試中肯定知道發送的消息內容,通過CE來搜索可以嗎?
額,我覺得不行,還沒收到消息呢,內存中也沒有這個文字消息,搜索不到(如果可以,請大佬指點一下)。
能想到的是,在接受到消息某一點通過調試器斷下來,然後CE搜索,這樣可以,但是這個斷點找不到阿,放棄。
那怎麼辦呢?
看到左側聊天列表中顯示的最新一條消息,有了新的思路。
每次收到新消息後,都會在列表中顯示最新消息內容(圖中綠框指示位置、注意是unicode字符)。
那麼,先用CE(First Scan)搜索當前搜到的消息內容,找到可能的內存地址。多次接受不同消息後,Next Scan按鈕搜索每次新的消息內容,最終確定聊天列表中顯示的最新消息內容的內存地址。
多次刷選之後,留下兩個地址,通過CE修改內容,在界面中查看是否改變,最終確認第二個地址就是我們的目標,暫把該地址記錄爲MsgAddr。
分析消息接收函數
關鍵數據地址已經找到,下面的工作複雜也不復雜,就看微信是如何實現的了。
猜測微信實現消息顯示的流程是這樣的:
-
recv收到消息,組裝完整包後,分發給消息處理函數
-
根據wxid找到要顯示消息的列表項,如果不在已聊天消息列表,就新建一個項
-
在列表中顯示消息,如果是表情顯示[文字],語音顯示爲[語音],消息插入wxid對應消息隊列,或者存入數據庫
步驟3中肯定要寫前面找到的MsgAddr內存,把最新消息顯示到界面中,這個流程肯定在消息處理函數內部。
So,通過OD對MsgAddr下內存寫入斷點,回溯堆棧就可以找到消息處理函數。
具體操作如下:
OD掛載Wechat.exe進程後,在左下角內存窗口處Ctrl+G,輸入找到的MsgAddr(11A11F34)回車,定位到該數據,然後再HEX數據處,右鍵彈出菜單,選擇斷點->內存寫入。
斷點設置完成後,測試發送文字消息,OD斷住,代碼窗口顯示的就是修改MsgAddr的代碼位置,如上圖10CE412C處。
Alt+K查看當前堆棧。
調用堆棧
地址 堆棧 函數過程 / 參數 調用來自 結構
0012E068 106BD6F3 WeChatWi.10CE4110 WeChatWi.106BD6EE 0012E064 //wcsncpy
0012E088 106BD769 WeChatWi.106BD67E WeChatWi.106BD764 0012E084
0012E09C 1011DD8B WeChatWi.106BD753 WeChatWi.1011DD86 0012E098
0012E0EC 10206C67 包含WeChatWi.1011DD8B WeChatWi.10206C64 0012E0E8
0012E600 1020E8F1 ? WeChatWi.10206460 WeChatWi.1020E8EC 0012E5FC //界面操作
看到這個調用棧是不是感覺好少,分析起來肯定簡單。但,其實是OD顯示的並不全,此時真的很想用windbg。
在OD的右下角堆棧窗口,可以看到當前調用棧的參數和預覽數據。F8單步(或者Alt+F8執行到返回)逐步的回溯每層堆棧。關注MsgAddr的數據是如何生成的,也就是找到數據來源,然後找到消息處理函數。
跟蹤過程不贅述(需要熟悉彙編知識),直到看到的最頂層的WeChatWi.10206460處,發現是把收到的消息內容顯示到聊天列表處的一個界面功能函數。
那這裏不是可以拿到消息了嗎,是的,普通文字消息已經可以拿到,但是語音內容不行。
通過觀察內存窗口的數據,整理WeChatWi.10206460處的關於消息參數的大致結構。
//聊天列表框信息
struct chat_list_msg {
DWORD unk;//
wstring wxid;//
//wchar_t* wxid;//4
//int len;//8
//int maxlen;//c
DWORD unk1;//10
DWORD unk2;//14
wstring name;
//wchar_t* name;//18微信名
//int len;//1c
//int maxlen;//20
…
wstring msg; //
//wchar_t* msg;//3c
//int len;//
//int maxlen;
}
wstring msg字段就是文字消息內容,而語音消息則是預覽中看到的[語音]兩字,並沒有實際能夠聽到的語音數據,所以還得繼續往前找。
繼續往上回溯了3層左右,進入了102DDC50,找到了語音消息的新信息。
struct msg_xx
{
char unk[0x40];//
wstring wxid1;//40
wstring wxid2;//4c
char unk1[0x10];//58
wstring msg;//68
char unk2[0x10];//74
;//84
}
在wstring msg處就是普通文字消息內容,而語音消息並不是我想象的就是直接語音的數據,而是…如下:
<msg><voicemsg endflag="1" cancelflag="0" forwardflag="0" voiceformat="4" voicelength="1176" length="1334" bufid="147445261304397871" clientmsgid="416261363964373964444633636200230013013119fdd53b1f494102" fromusername="wxid_xxxxxxxxx" /></msg>
<?xml version="1.0"?>
<msg>
<img aeskey="3cdc5f2b0b541c34e594d0def4fa280c" encryver="0" cdnthumbaeskey="3cdc5f2b0b541c34e594d0def4fa280c" cdnthumburl="3053020100044c304a02010002042a27254202032f4f560204ad7ac2dc02045c7800220425617570696d675f653233653532396234343462303038665f31353531333638323234363930020401051a020201000400" cdnthumblength="2779" cdnthumbheight="120" cdnthumbwidth="62" cdnmidheight="0" cdnmidwidth="0" cdnhdheight="0" cdnhdwidth="0" cdnmidimgurl="3053020100044c304a02010002042a27254202032f4f560204ad7ac2dc02045c7800220425617570696d675f653233653532396234343462303038665f31353531333638323234363930020401051a020201000400" length="111333" md5="96fdf204e1bfd965fa82c82b1e580441" hevc_mid_size="31427" />
</msg>
真是一波三折,還不是語音的數據,而是關於語音信息的xml,有語音的大小,來自誰,在語音緩衝區中的id(bufid)等等信息。
繼續往前找唄,最後回溯到了所有消息處理的分發函數10323FF0中。這個函數處理邏輯很複雜,我並沒有很快就找到如何生成語音消息的xml,以及處理語音數據的函數。
一度卡住,重複分析了很多次。
後來又回神想到了逆向神器IDA,xml中數據如voicemsg肯定是模塊中會在代碼中用到,看看有沒有有用的信息。
用IDA打開Wechatwin.dll,shift+F12分析出所有字符串,Ctrl+F找到關鍵字voicemsg,看來有戲。
真的是柳暗花明又一村。
點擊字符串跳到代碼窗口,按下x,跳到引用該數據的位置。
找到了解析語音xml數據和解碼語音數據的關鍵函數。
f_parseVoiceXmlInfo_103148E0
text:103149DD 0E4 0F 84 74 02 00 00 jz loc_10314C57
.text:103149E3 0E4 68 D0 06 F0 10 push offset aVoicemsg ; "voicemsg"
.text:103149E8 0E8 8B CF mov ecx, edi
.text:103149EA 0E8 E8 31 28 3E 00 call f_xml_subnode_106F7220
.text:103149EF 0E4 85 C0 test eax, eax
.text:103149F1 0E4 0F 84 60 02 00 00 jz loc_10314C57
.text:103149F7 0E4 8D 70 2C lea esi, [eax+2Ch]
.text:103149FA 0E4 68 C4 06 F0 10 push offset aClientmsgid ; "clientmsgid"
.text:103149FF 0E8 8B CE mov ecx, esi
.text:10314A01 0E8 E8 CA 3A 3E 00 call f_xml_getvalue_106F84D0
函數103148E0解析xml拿到幾個字段的內容,返回上層函數調用一個語音解碼的函數進行處理,而這個解碼函數就會直接操作語音數據。
(*(void (__thiscall **)(int *, _DWORD, _DWORD, int *, signed int))(*v7
+ 28))(//
v7,
*(_DWORD *)(voice_msg + 48), // 語音內容
*(_DWORD *)(voice_msg + 52), // 語音長度
v17,
v4);
函數103148E0回溯再看看,進入了分發函數10323FF0中,在一個循環中處理了多種流程,包括顯示界面最新消息的流程和解碼語音的流程。所以前面找的方向並沒有問題,只是缺少認真分析數據和代碼的耐心。
不過,目的都達到了,找到了數據處理函數,最後通過hook這個函數就能拿到語音數據。
另外可以看到語音數據中包含SILK_V3的字符,這種編碼音頻格式是Skpye曾經使用的一種編碼方式,後來開源了。目前播放器並不能直接播放該編碼音頻文件,所以需要轉碼爲MP3等格式。不過可喜的是已經有大佬完成了這個工作,並開源了工具silk-v3-decoder。所以把代碼拿來整合一下,就可以完整的實現實時dump語音聊天數據,轉換爲mp3進行保存,完美。
0x3. 總結
這是第一次比較成功的應用CE,整個看來,確實省下來很多定位數據和函數的工作。
但CE並不是萬能的,要找對方法,找對目標數據纔可能成功,對於某些沒有明顯數據的功能,可能也是無能爲力。
最終還是得提高對大型軟件的逆向能力,總體實現思路的猜測以及調試驗證。
最後,時間倉促,目前只是將保存語音的demo更新到到SuperWeChatPC項目中,後續會持續更新,歡迎關注。