鍵.png)
node.js + express + docker + mysql + jwt 實(shí)現(xiàn)用戶管理restful api
可以大致看一下交互方式,通過HTTPS POST交互,POST了一串params的內(nèi)容,內(nèi)容加密,返回JSON內(nèi)容,我要做的重點(diǎn)就在于解析params的生成方式,用于模擬這次交互。
(Tan1993:這是后續(xù)編寫的內(nèi)容,截圖很多都是后補(bǔ)的,所以可能會(huì)出現(xiàn)使用不同的調(diào)試工具,不同的環(huán)境,不同的時(shí)間等,不影響閱讀。另外本人工作主要是linux網(wǎng)絡(luò)方向的,像是這次只是我的一點(diǎn)業(yè)余愛好,也很少會(huì)去逆向東西,如果出現(xiàn)一些比較業(yè)余的操作或想法時(shí),還望指出)
下載最新版PC版網(wǎng)易云安裝(目前是2.3.0.196231版本),分析在程序所在目錄下的文件。
第一個(gè)最讓我注意的時(shí)libcurl,這個(gè)網(wǎng)絡(luò)庫可以用于HTTP協(xié)議交互,如果通過該庫與服務(wù)器交互, od斷點(diǎn)到curl_easy_perform再往回推就可以判斷轉(zhuǎn)換算法位置了,然而事實(shí)比我想象的復(fù)雜多了,這個(gè)庫僅在程序剛運(yùn)行時(shí)用于一些無關(guān)的網(wǎng)絡(luò)交互(Tan1993:記不清了,好像是版本還是客戶端信息相關(guān)的請(qǐng)求)。
第二個(gè)是libcef,這個(gè)是個(gè)基于C/C++的Web browser控件,可以簡(jiǎn)單理解為就是個(gè)瀏覽器的殼子(Tan:為什么說關(guān)鍵API沒用到libcurl庫,因?yàn)槌碎_始時(shí)cef框架還沒初始化前網(wǎng)絡(luò)交互用到那個(gè)庫而已,一點(diǎn)cef環(huán)境起來了,都是通過JS ajax交互了)。
其他的除了cef依賴的dll外,兩個(gè)主程序和cloudmusic.dll都比較值得關(guān)注。
除了在package下的其他都是cef庫依賴的資源文件。
都是未知的格式,一般看到未知格式的文件,我都會(huì)用7z嘗試打開看看,是不是某種歸檔格式文件,這個(gè)一下就蒙中了,是zip格式的。
除了幾個(gè)通過后綴就能看出來的皮膚文件,還有兩個(gè)比較可疑的文件,翻一翻比較大的orpheus.ntpk文件,里面可以看到都是網(wǎng)頁相關(guān)的資源文件,看到那個(gè)core.js,就讓我聯(lián)想到網(wǎng)頁版API提取時(shí)用到的那個(gè)core.js文件了,腦海里就想著替換然后對(duì)轉(zhuǎn)換流程動(dòng)態(tài)分析了,事實(shí)有點(diǎn)不盡人意,該zip文件加密了。
OK,調(diào)研階段結(jié)束,在不進(jìn)行逆向解析前,能了解到的也就止步于此了。
其實(shí)一開始我是把目光放在libcurl上面的,在斷點(diǎn)到curl庫的函數(shù)上時(shí)發(fā)現(xiàn)只有程序剛運(yùn)行時(shí)觸發(fā)過幾次,后面所有網(wǎng)絡(luò)交互都不用這個(gè)庫了,就轉(zhuǎn)戰(zhàn)到cef上。而cef的重點(diǎn)在于內(nèi)部的JS文件,能提取到該文件才是關(guān)鍵的。
0x2712即CURLOPT_URL宏,eax中存放著url的字符串指針,基本上都是無關(guān)的url。
第一個(gè)任務(wù)來了,逆向?qū)ふ姨卣鞔簿褪敲艽a,這里斷點(diǎn)到系統(tǒng)文件操作API上,斷到CreateFileW,一頓的F9后可以看到加載到default.skin文件了(圖中是native.ntpk,同類型的加密ZIP文件),后續(xù)就單步調(diào)試下去。
然后看到一個(gè)比較特別的內(nèi)存塊,一看就是PNG格式的文件頭,就可以判斷這一步資源已經(jīng)解壓縮到內(nèi)存了。
往上推幾步,斷點(diǎn),縮小范圍,再跟下來,看看哪里做了解壓操作,再一步步跟函數(shù)。(Tan1993:可能比較業(yè)余,但我也只能一點(diǎn)點(diǎn)縮小范圍在一點(diǎn)點(diǎn)看流程,憑經(jīng)驗(yàn)判斷可能會(huì)做什么操作,縮短到比較短的范圍,不然一堆匯編碼真的會(huì)受不了,感謝世界上程序員的思想都是接近的吧)。?
得知密碼后,就可以解壓出core.js文件了(Tan1993:這里僅提供思路,不提供便民服務(wù)哈)
又是這一堆讓人窒息的混淆,卡得懷疑人生,先解壓縮再看吧。
解壓后,搜幾個(gè)關(guān)鍵字,比如params,eapi,batch等最上面HTTP交互時(shí)的一些特征
關(guān)鍵代碼,像這樣混淆的JS代碼,如果不通過調(diào)試器跟蹤,很難看懂,目前能可以看出也只有channel.serialData應(yīng)該時(shí)比較關(guān)鍵的轉(zhuǎn)換函數(shù),但是搜索了整個(gè)JS文件都找不到函數(shù)定義,不知道是不是混淆到哪個(gè)奇怪的地方了。
雖然cef自帶DevTools,但是已經(jīng)被屏蔽掉了也無法在程序里調(diào)出來,所以我想在JS文件中加上alert調(diào)試關(guān)鍵參數(shù)。然后我修改了core.js文件,按原來的密碼壓縮回去。但程序根本就起不來,為什么呢,看看原版的.ntpk文件,很明顯還有一些奇怪的東西和zip文件一起合成了這個(gè)ntpk文件格式。根據(jù)經(jīng)驗(yàn)判斷很可能時(shí)類似于數(shù)字簽名的東西(Tan1993:之前我也會(huì)對(duì)一些可能被篡改的檔案末尾對(duì)整個(gè)文件加鹽生成一個(gè)hash值用于校驗(yàn),但是后續(xù)跟完網(wǎng)易云的數(shù)字簽名方式讓我又學(xué)習(xí)了不少)。
為了方便調(diào)試,我需要替換掉資源文件中的core.js文件,但是該資源文件不僅僅加密壓縮了,還有一些其他內(nèi)容存在,所以這次跟代碼就是為了了解除了zip文件本身以外其他部分內(nèi)容的作用。
還是斷到CreateFileW函數(shù)上,其實(shí)第一輪跟代碼的時(shí)候我就已經(jīng)發(fā)現(xiàn)了部分調(diào)用系統(tǒng)加密服務(wù)提供程序 (CSP)庫的函數(shù)。
一步步跟過來,發(fā)現(xiàn)用的是SHA1數(shù)字簽名算法(Tan1993:不是很了解CSP庫,但這個(gè)是為Windows系列操作系統(tǒng)制訂的底層加密接口,和我理解的SHA不太一樣,我姑且將程序內(nèi)部的那部分稱為公鑰,與文件頭部的校驗(yàn)數(shù)據(jù)進(jìn)行校驗(yàn))。
文件頭NTPK,文件長度0x0D5C5B,校驗(yàn)串長度0x100
剛好差了0x110長度,除了0x100用于校驗(yàn)的數(shù)據(jù),還有0x10的頭部。
由于我是無法在不知道私鑰的情況下,再次對(duì)該文件進(jìn)行簽名的,所以我只能把程序內(nèi)部的用于校驗(yàn)的公鑰一并替換,再生成一個(gè)對(duì)應(yīng)的檢驗(yàn)數(shù)據(jù),從而通過系統(tǒng)驗(yàn)證,或者直接把驗(yàn)證部分的代碼跳轉(zhuǎn)邏輯修改掉(Tan1993:其實(shí)可能改分支流程修改會(huì)更簡(jiǎn)單也說不定,但我一開始選擇的是替換公鑰重新生成校驗(yàn)數(shù)據(jù))。
截了一部分代碼,用于修改cloudmusic.dll中的二進(jìn)制數(shù)據(jù),偏移是根據(jù)內(nèi)存加載地址與基址算的,直接固定偏移修改即可。
到這一步其實(shí)我已經(jīng)可以替換掉core.js文件并且可以alert彈出對(duì)話框,顯示一些JS運(yùn)行時(shí)數(shù)據(jù)了,雖然alert彈框并不是那么好用。
通過alert我可以看到加密前的內(nèi)容,也就是具體發(fā)了哪些數(shù)據(jù),以及加密后是什么樣子的,很可惜的是當(dāng)我嘗試alert(channel.serialData)時(shí)發(fā)現(xiàn)是[native code],按我個(gè)人理解應(yīng)該是系統(tǒng)二進(jìn)制函數(shù)才會(huì)顯示這個(gè)的吧(對(duì)JS并不是非常了解),懷疑是庫函數(shù),但查詢無果,后來想了想會(huì)不會(huì)是JS調(diào)用了C++代碼(憑我對(duì)cef粗糙的理解),我嘗試去查了一下,果然是可以的,那么很有可能這部分加密轉(zhuǎn)換的代碼還是在主程序中,這就很頭疼了,剛從主程序逆向脫離出來到JS這個(gè)自由的世界,又要回到看匯編碼的環(huán)境了。
這一輪主要目的是找到channel.serialData在主程序的位置,根據(jù)我對(duì)cef的理解,應(yīng)該是在程序啟動(dòng)時(shí),注冊(cè)了一部分回調(diào)函數(shù),可以從注冊(cè)的時(shí)候找到回調(diào)函數(shù)入口,然后等觸發(fā)channel.serialData動(dòng)作時(shí),從回調(diào)函數(shù)跟代碼跟下來。
根據(jù)DLL版本,我找到了對(duì)應(yīng)的cef源碼版本,cef注冊(cè)回調(diào)時(shí)是整個(gè)結(jié)構(gòu)體的,必須找到對(duì)應(yīng)的版本避免新版本結(jié)構(gòu)體不一樣導(dǎo)致偏移位置有差異。
在看源碼的過程中發(fā)現(xiàn)結(jié)構(gòu)體里有個(gè)很有意思的字段,一個(gè)debug端口,調(diào)研了一下,這個(gè)端口很有用了,可以遠(yuǎn)程DevTools,這樣還用什么alert。
如果要在調(diào)用初始化前把結(jié)構(gòu)體改掉,要么API Hook修改,要么靜態(tài)文件修改,文件修改的話只能舍棄一些無用代碼來改這個(gè)結(jié)構(gòu)體了,我選了一個(gè)不影響的賦值語句,改成給這個(gè)地址賦9222。
對(duì)照源碼中結(jié)構(gòu)體計(jì)算偏移值
原本修改cloudmusic.dll的代碼中增加個(gè)代碼段修改的方法
現(xiàn)在我就可以通過http://127.0.0.1:9222遠(yuǎn)程訪問DevTools了。可當(dāng)我打開網(wǎng)頁時(shí)一片空白,這時(shí)候又憑借我對(duì)cef粗略的了解,在程序目錄下,并沒有devtools相關(guān)的資源,其實(shí)只要把資源文件補(bǔ)上就可以了(官網(wǎng)已經(jīng)沒有這么老的資源文件檔案了,這個(gè)還是我網(wǎng)上找的3.1916版本的devtools資源文件)
這時(shí)候所有JS調(diào)試命令都可以改成console.log來進(jìn)行了,方便了好多。
回到正題,從注冊(cè)來跟代碼實(shí)在是太痛苦了。一個(gè)是注冊(cè)的內(nèi)容比較多,一層疊一層的,而且程序用的是C++ warp的C語言版本的cef庫,和源碼對(duì)照跟的時(shí)候還是有點(diǎn)差別的。這時(shí)候我想到一個(gè)非常好的方法,那就是制造一個(gè)死循環(huán)。
上面就提到了,我放棄了從注冊(cè)一步步跟蹤回調(diào)函數(shù)的麻煩方案,而是在JS中知道一個(gè)死循環(huán),不停的調(diào)用channel.serialData函數(shù),等程序單核滿載時(shí),只需要將調(diào)試器附加程序,點(diǎn)一點(diǎn)暫停,基本上就是這個(gè)函數(shù)相關(guān)業(yè)務(wù)流程的代碼了(JS到機(jī)器碼代碼按我理解應(yīng)該在堆上,而加密的代碼應(yīng)該在程序代碼段上,所以我定位的時(shí)候可以忽略掉很多JS的代碼,找到真正相關(guān)的代碼位置)
實(shí)際上,channel.serialData的匯編碼也非常多,流程也分了好多部分,這部分工作量實(shí)在是降不下來,但是很多可能是為了防止靜態(tài)分析的代碼,部分特征串是運(yùn)行時(shí)生成的,但是因?yàn)檫@部分特征串都是固定的,所以是可以不用去仔細(xì)琢磨的(然而我花了一兩天來看那一堆匯編碼來算出特征串,非常郁悶,早知道就逆推就好,但說實(shí)話,光逆推也會(huì)很難,主要是要有一定理解)
簡(jiǎn)單說明一下轉(zhuǎn)換流程
1、 輸入url(請(qǐng)求部分)和data(提交的json數(shù)據(jù))
2、 拼成”nobody” + url + “use” + data + “md5forencrypt”字符串
3、 對(duì)字符串計(jì)算MD5
4、 二次拼接url + “-36cd479b6b5-” + data + “-36cd479b6b5-” + md5
5、 0x10對(duì)齊,缺少的部分會(huì)以缺少的位數(shù)來填充
6、 私有轉(zhuǎn)換方法(也許是我不知道的一種加密方式?)
附上一部分分析的圖
待加密數(shù)據(jù),0x10字節(jié)對(duì)齊,每次處理0x10字節(jié)的數(shù)據(jù)
輔助加密數(shù)據(jù)(動(dòng)態(tài)生成,但是是固定的,我還傻傻去復(fù)現(xiàn)了一遍生成流程)
開始對(duì)0x10進(jìn)行轉(zhuǎn)換
一堆異或和位移計(jì)算,這個(gè)還是很好復(fù)現(xiàn)到C的代碼中的,這個(gè)比較長就不全粘貼了。
循環(huán)轉(zhuǎn)換完后再按照”%02X”格式snprintf到字符串即可。我沒有過多去理解這個(gè)加密算法究竟是什么原理,只是直譯匯編碼。
后來嘗試反過來解析,看了一早上沒看出來,簡(jiǎn)單描述一下為什么難以逆轉(zhuǎn)的問題。
內(nèi)存塊mem
然后在得知后面的eax,ebx,ecx,edx逆推原來的,感覺不太可能,但是mem并不是沒有規(guī)律的一個(gè)內(nèi)存塊,而且數(shù)組索引時(shí)也做了些巧妙的偏移,事實(shí)上內(nèi)存塊確實(shí)有不少規(guī)律(比如a1是偶數(shù)時(shí)b1是a1的一半,c1是a1 ^ b1),而且和索引時(shí)的偏移可能會(huì)相得益彰,如果能看出竅門說不定還是能解的,有興趣的小伙伴也可以研究一下(Tan1993:個(gè)人沒學(xué)過加密學(xué),只略懂一部分概念)
其實(shí)到這一步,我可以通過遠(yuǎn)程devtools來看發(fā)送前未加密的內(nèi)容以及結(jié)構(gòu),同時(shí)我也可以通過已經(jīng)復(fù)現(xiàn)的加密方法,對(duì)不同業(yè)務(wù)數(shù)據(jù)加密發(fā)送出去。我發(fā)現(xiàn)有一部分請(qǐng)求數(shù)據(jù)返回內(nèi)容也是加密的,但這個(gè)是可以在客戶端控制e_r的值來控制是否需要返回加密內(nèi)容的。
寫個(gè)模擬客戶端下載歌曲的小Demo,本來發(fā)送和接收都是加密的數(shù)據(jù)的下載接口,就可以通過服務(wù)器驗(yàn)證實(shí)現(xiàn)下載了,解析到此告一段落,雖然過程中還有很多內(nèi)容值得研究,如果有機(jī)會(huì)以后會(huì)繼續(xù)挖掘。
由于并沒有找到任何的參考資料,斷斷續(xù)續(xù)也研究了一周時(shí)間。除了實(shí)現(xiàn)了目標(biāo)以外,還是有不少收獲的,比如比較有趣的加密算法,數(shù)字簽名方法,cef庫,還有一些逆向的思路。
比較遺憾的是沒有把解密的算法也解析出來,同時(shí)在客戶端控制e_r的值來控制返回?cái)?shù)據(jù)是否加密顯然不是好方法,官方只需要忽略這個(gè)參數(shù)強(qiáng)制對(duì)部分API返回加密數(shù)據(jù),正常的客戶端也沒有任何影響(難道有平臺(tái)相關(guān)性所以才把這個(gè)參數(shù)放到客戶端的嗎?)。
(Tan1993:視情況考慮是否在github提供源碼)
將一件有趣的事,當(dāng)時(shí)我嘗試在一臺(tái)國外IP的服務(wù)器上調(diào)用web的api接口時(shí)發(fā)現(xiàn)不能適用,獲取不到數(shù)據(jù),然后我又跟了一便JS代碼發(fā)現(xiàn)邏輯不一樣,其中發(fā)現(xiàn)了一個(gè)很有意思的特征串(在你們看不到的地方,總有調(diào)皮的程序員):
文章轉(zhuǎn)自微信公眾號(hào)@FreeBuf
node.js + express + docker + mysql + jwt 實(shí)現(xiàn)用戶管理restful api
nodejs + mongodb 編寫 restful 風(fēng)格博客 api
表格插件wpDataTables-將 WordPress 表與 Google Sheets API 連接
手把手教你用Python和Flask創(chuàng)建REST API
使用 Django 和 Django REST 框架構(gòu)建 RESTful API:實(shí)現(xiàn) CRUD 操作
ASP.NET Web API快速入門介紹
2024年在線市場(chǎng)平臺(tái)的11大最佳支付解決方案
完整指南:如何在應(yīng)用程序中集成和使用ChatGPT API
選擇AI API的指南:ChatGPT、Gemini或Claude,哪一個(gè)最適合你?
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)