javascript
【瑞数】维普期刊JS逆向4000字详细流程_1_获取接口签名
文章目錄
-
- 前言
- 過debugger
-
- 定時器debugger
- 死循環(huán)debugger
- 分析搜索接口
- 簽名在何處生成?
- 中場休息
- 簽名代碼來源分析
- 簽名代碼在何處加載到內(nèi)存?
- 注入代碼
-
- 設(shè)置簽名為全局變量
- 剔除debugger
- 小結(jié)
- 簽名測試
前言
我所用的方法基于瀏覽器環(huán)境的,非硬解(頭禿ing??),文章較長,建議收藏。
這是我第一次接觸瑞數(shù)加密,比較難,不過學(xué)到的東西也是挺多的,也是因為我第一次解瑞數(shù),所以文章寫得比較詳細(xì)甚至是啰嗦,這篇文章大致是以我逆向的思路去寫的,應(yīng)該適合像我這樣從未接觸過瑞數(shù)的朋友。
這次逆向總結(jié),估計會寫3到4篇文章。
本文中也會有一些調(diào)試技巧夾在其中,如有問題或更好的建議歡迎提出!
本文以維普期刊的高級檢索接口作為示例,地址:http://qikan.cqvip.com/Qikan/Search/Advancefrom=index
正文開始。
過debugger
定時器debugger
拿到網(wǎng)站后,一如既往的直接打開審查工具,在這一步直接被debugger卡住了。
這是一個定時器無限debugger,如果兩次時間差大于100,那么就會一直讓我們處于debugger狀態(tài)。
當(dāng)遇到這種反調(diào)試的手法時,可在進(jìn)入debugger狀態(tài)后,在console中輸入以下代碼,以此跳過。
for (var i = 1; i < 99999; i++)window.clearInterval(i);參考:爬蟲漫游指南:瑞數(shù)的反調(diào)試陷阱
死循環(huán)debugger
過了這一步后,當(dāng)我回到網(wǎng)頁,又會直接進(jìn)入debugger狀態(tài)。這個debugger是在一個判斷下,這個比較簡單我們直接右鍵選著**“永不在此處暫停”**。
**注意:**以上步驟都是在谷歌瀏覽器中調(diào)試的,使用火狐情況會不一致,建議使用谷歌瀏覽器。
這些if判斷都是在一個while(1)循環(huán)內(nèi),使用火狐會一直在循環(huán)內(nèi),谷歌只要設(shè)置了不在此處暫停就沒事,具體為啥不知道。
分析搜索接口
輸入關(guān)鍵詞,點擊檢索。很容易找到請求路徑為SearchList?...的鏈接就是數(shù)據(jù)接口。
查看這個請求,需要搜索參數(shù)searchParamModel和簽名G5tA5iQ4。
查看這個請求的調(diào)用棧
這里可以先去看看window.advSearch這個方法。這里的url與 data組合后,并不存在這個字段G5tA5iQ4
所以這個字段的值不是在這里構(gòu)建,這個字段對應(yīng)的值就是簽名,也是我們必須要解決的。
先這里提前解釋下,為什么這里明明沒有設(shè)置G5tA5iQ4的值,卻在請求發(fā)送時,含有這個簽名。
原因很簡單,XMLHttpRequest的send方法被修改了。下圖是兩者的對比。
簽名在何處生成?
進(jìn)入第一個函數(shù)
在這里打上斷點,再去點擊檢索按鈕,進(jìn)入調(diào)試。
注意看這個arguments,這是請求的參數(shù)。
由于點擊一次檢索按鈕會有好幾個請求,所以這里會有多個請求經(jīng)過這里。
我們需要調(diào)試的是SearchList這個接口,所以只要不是SearchList的接口參數(shù),直接跳過即可。
結(jié)合上文的接口分析,searchParamsModel就是搜索參數(shù)。
不過,不要忘了。我們的目的是找簽名在何處生成。
**小提示:**上文說過XMLHttpRequest的send方法被修改過,實際上,上圖的_$hp就是所謂的send方法。
此時查看this
繼續(xù)往下找,查看_$a5,(變量名每次請求都不同,知道是它就行),展開并查看它的第一個作用域。
可以看到,在此處的_$q3的值就是一個帶有簽名的鏈接。
_$q3屬于_$M3,那么就可以按Ctrl+F搜索關(guān)鍵字_$M3
注意:_$M3是一個函數(shù),所以你應(yīng)該找到是類似下面這樣的函數(shù)
我們得知_$q3作為參數(shù)傳進(jìn)了_$M3,所以在這個函數(shù)內(nèi)打上斷點,重新調(diào)試
當(dāng)我們進(jìn)來的時候,查看_$q3,發(fā)現(xiàn)_$q3只是請求鏈接,所以由此可知,簽名就是在_$M3中生成并拼接到_$q3上的。
為了方便調(diào)試,我們可以將這里的斷點換成條件斷點,即只調(diào)試_$q3==="/Search/SearchList"這種情況。
將內(nèi)部函數(shù)折疊,可以看到_M$3內(nèi)部只是調(diào)用了一下_$Vq函數(shù)。
展開_$Vq函數(shù),分析代碼。在這個_$Vq函數(shù)里面,多折騰幾下總能找到簽名是何處生成的。
當(dāng)然也可以用點小技巧,我們知道簽名最終會拼接到_$q3上,所以必然存在類似這樣的代碼:
_$q3 += 或者 _$q3 +斷點調(diào)試到此處,可知簽名是調(diào)用_$5Q(_$hp, _$M_, _$No)得來的。
查看這個函數(shù)的三個參數(shù),可知它們分別是0,大小為16的整數(shù)數(shù)組以及一個undefined
在這里,_$M_是整數(shù)數(shù)組,我們可以向上尋找,查看_$M_是如何生成的。
可以找出整數(shù)數(shù)組是將/Search/SearchList作為參數(shù)傳入某個函數(shù)而生成的。
中場休息
分析到這里,我們知道了簽名生成的流程如下:
簽名代碼來源分析
其實你應(yīng)該發(fā)現(xiàn)了,分析了這么久的JS代碼,卻不知道這大段JS存放在哪里?
像下圖這樣,JS來源顯示為VM+數(shù)字的形式,這就說明這些JS代碼是后來加載進(jìn)引擎的。
換句話說就是,這些JS代碼并不是存在一個JS文件里的,實際上是通過eval函數(shù)將一大堆字符串加載進(jìn)了內(nèi)存。
此時就需要尋找以上JS代碼是如何加載進(jìn)內(nèi)存。
這個也是瑞數(shù)加密的一大特色,這些加載JS代碼的代碼本身就是被混淆的,并且存在于Html頁面中。
查看搜索頁面源碼:view-source:http://qikan.cqvip.com/Qikan/Search/Advance?from=index
**Tips:**如果是加載是空白,那么你需要先正常訪問一次搜索頁面再查看源代碼。這個是Cookie的原因,具體的生成機(jī)制及解決辦法會在下一章講解到。
這一行代碼的后面,就是一堆JS代碼,我們可以將整個網(wǎng)頁代碼拷貝至本地編輯器。
將html代碼格式化后查看。
一開始就加載了一個JS文件,為了調(diào)試方便,可以將這個JS文件下載到本地
http://qikan.cqvip.com/NJDrTcXo8msX/leE4DkIasHMb.f22c526.js
查看leE4DkIasHMb.f22c526.js,一堆雜亂的字符,其主要的作用就是為window.$_ts賦值。
html代碼引入leE4DkIasHMb.f22c526.js后,緊接著就是一個自調(diào)用的被混淆的JS代碼。
這段代碼的核心作用就是將leE4DkIasHMb.f22c526.js中的雜亂字符串通過特定方式還原為代碼并加載進(jìn)內(nèi)存。
此時的主要工作就是找到,雜亂字符串變成規(guī)則字符串代碼的位置。
為什么要這么做,在這里舉個簡單的例子。
舉例時間到!
現(xiàn)在這樣一串字符串, Y29uc29sZS5sb2coJ2hpaGloaWhpLi4uJyk=
這很常見,這是通過base64編碼后得到的字符串,那么我們可以通過base64解碼得到本來的字符串,然后使用eval函數(shù)執(zhí)行即可。
以上是原本的功能,打印輸出了hihihihi...
現(xiàn)在我們需要修改這個代碼輸出的內(nèi)容,而這可以通過字符串替換方式實現(xiàn),就像這樣。
那么下面的流程圖,我想應(yīng)該可以理解了。
上方的例子是因為我們知道編碼方式是base64,所以可以輕松的將密文轉(zhuǎn)為明文。
對于不常見的加密方式,我們就只有去調(diào)試找出明文生成的位置,再加上JS代碼本事就是混淆的,所以難度就有所提升。
舉例結(jié)束。
簽名代碼在何處加載到內(nèi)存?
仔細(xì)想一想,一段字符串想以js代碼的形式加載進(jìn)內(nèi)存,必定會使用eval方法。
所以,我們只需要找到哪里使用了調(diào)用eval即可。
搜索關(guān)鍵詞eval,運氣很好可以直接搜索到,下面賦值的操作執(zhí)行即,_$vo就是eval
此時搜索關(guān)鍵詞_$vo,發(fā)現(xiàn)有93個匹配項
_$vo作為函數(shù),如果被調(diào)用,那么調(diào)用的寫法可能是這樣_$vo()
則搜索關(guān)鍵詞_$vo(,匹配項為0個。
函數(shù)的調(diào)用還有一種函數(shù)名.call的方式,所以不妨搜索_$vo.call試試看。
找到一個匹配結(jié)果,在這里打上斷點,調(diào)試過來看看傳入的參數(shù)是什么。
可見,_$kw的值就是代碼字符串了。
我們在eval執(zhí)行前注入自己的代碼即可達(dá)成目的。
注入代碼
為了大家方便閱讀,我將上一步得到的明文代碼字符串稱為簽名代碼
雖然這代碼還有其他的功能,但對我們來說,只想通過這段代碼獲取簽名,僅此而已。
注入代碼是為了讓我們可以更方便的獲取到簽名,最簡單的辦法就是將簽名設(shè)置為一個全局變量。
除此之外,為了方便后續(xù)調(diào)試還可以剔除其中煩人的debugger
設(shè)置簽名為全局變量
如果有些忘記了簽名是如何生成的,可以先翻到第三節(jié)回憶回憶。
由于代碼的變量是變化的,所以我們不能直接使用replace,而是應(yīng)該用正則匹配的方式去替換或插入代碼。
以上是兩次請求簽名生成的代碼行。
在變化中找不變,_$f_[5]和_$A4[5]它們都是取索引值5
所以寫出正則如下:
(_$[wd_$]{2}) += _$[wd_$]{2}[5] + (_$[wd_$]{2}([^)]+);)Tips:這是基于格式化的js代碼寫的正則,實際的簽名代碼是被壓縮的,所以應(yīng)該把多余的空格刪除。
(_$[wd_$]{2})+=_$[wd_$]{2}[5]+(_$[wd_$]{2}([^)]+);)匹配出這一段后,我們可以在原本的代碼后面,再加上一句全局變量賦值。
那么可以寫出JS代碼
簽名代碼.replace(/(_$[wd$]+)+=_$[wd$]+[5]+(_$[wd$]+([^)]+);)/gm, `$1="?"+$2window.genUrl=$1;`);剔除debugger
在最初分析搜索接口時,就遇見了兩個debugger
一個是明文顯示的,這個比較簡單,使用簽名代碼.replace('debugger', '')剔除即可。
另一個定時器debugger則稍稍有點麻煩。
經(jīng)過多次調(diào)式,可以發(fā)現(xiàn)整個代碼也是在一個while循環(huán)中跑,這是瑞數(shù)的一大特色。
且if語句比較的值是沒有變化的,都是變量小于256,這為我們注入代碼提供了方便。
我們以注入的方式,while內(nèi)的第一個if語句的上方插入以下代碼:
console.log(_$Mx)這個_$Mx是一直做比較的,通過層層的if else,最終執(zhí)行某段代碼。
當(dāng)進(jìn)入調(diào)試工具后,只要進(jìn)入了此循環(huán),就會打印_$Mx
而當(dāng)進(jìn)入了定時器debugger,此時循環(huán)停止,通過最后輸出的數(shù)字,就可以找到進(jìn)入定時器debugger的入口
**Tips:**這一步可能會卡著,稍微等等。
注入后的代碼
進(jìn)入定時器debugger后
最后的數(shù)字是388,記著這個388,一路跟著if走就可以看到類似如下代碼:
這個代碼就是進(jìn)入定時器debugger的入口,那么我們只需要將這行代碼注釋或者刪除即可
簽名代碼.replace(/(<389){)[^}]+/gm, `$1`);至此所有的debugger都已去除。
小結(jié)
上方的所有代碼注入都是在html源碼上進(jìn)行的。
這里先理一理我們的流程:
上方的注入是在html源碼中進(jìn)行的。實際情況是使用python來完成代碼注入。
畫個圖來說明下,即使用Python修改html源碼,使得html中的js代碼能過將目標(biāo)代碼注入到簽名代碼中
代碼注入示例:
# -*- coding: utf-8 -*- """ Created on 2021/5/24 17:15 --------- @summary: 注入代碼 1. 去除定時無限debugger 2. 去除死循環(huán)debugger 3. 插入searchList方法,用于生成簽名 4. 將得到的簽名提升至全局變量,可通過 `genUrl` 訪問 --------- @author: @email: @gmail.com """ import redef purify_html():with open("raw.html", encoding="utf-8") as f:text = f.read()r = re.findall("_$[wd$]{2}(79,_$[wd$]+);", text)[0]var_name = re.findall("_$[wd$]{2}(79,(_$[wd$]+));", r)[0]pp = """%(var_name)s = %(var_name)s.replace(/(_$[wd$]+)+=_$[wd$]+[5]+(_$[wd$]+([^)]+);)/gm, `$1="?"+$2window.genUrl=$1;`);%(var_name)s = %(var_name)s.replace(/(<389){)[^}]+/gm, `$1`);%(var_name)s = %(var_name)s.replace("debugger", "");""" % {'var_name': var_name}result = text.replace(r, pp + r)result = result.replace("/NJDrTcXo8msX/leE4DkIasHMb.f22c526.js","http://qikan.cqvip.com/NJDrTcXo8msX/leE4DkIasHMb.f22c526.js")result = re.sub(r"(/dist)", "http://qikan.cqvip.com/dist", result)result = result.replace("</body>",'<script>searchList=function(a){$.ajax({url:"/Search/SearchList",type:"post",dataType:"html",data:{searchParamModel:a},beforeSend:function(){loadding()},complete:function(){loaddingClose()},success:function(){console.log("請求成功")},error:function(){loaddingClose()}})};</script></body>')# print(result)with open('pure.html', "w", encoding="utf-8") as f:f.write(result)print("HTML頁面代碼注入完成")**Tips:**簽名的觸發(fā)機(jī)制是發(fā)送請求,所以注入了一個ajax請求,可供我們手動調(diào)用。
簽名測試
打開搜索頁面
右鍵查看搜索頁面源碼
使用python腳本注入代碼,生成新的html文件
在新的html文件同目錄下,啟動簡單的web服務(wù)
python -m http.server 9000
訪問http://localhost:9000/pure.html
打開審查工具,調(diào)用searchList方法,接著訪問genUrl變量
在瀏覽器中獲取cookie
將cookie和簽名帶入,測試請求是否成功,發(fā)送請求的腳本如下:
-- coding: utf-8 --
“”"
Created on 2021/5/23 16:38
@summary:
@author:
@email: @gmail.com
“”"
import requests
payload = “searchParamModel=%7B%22ObjectType%22%3A1%2C%22SearchKeyList%22%3A%5B%7B%22FieldIdentifier%22%3A%22M%22%2C%22SearchKey%22%3A%22%E5%8C%97%E5%A4%A7%22%2C%22PreLogicalOperator%22%3A%22%22%2C%22IsExact%22%3A%220%22%7D%5D%2C%22SearchExpression%22%3A%22%22%2C%22BeginYear%22%3A%22%22%2C%22EndYear%22%3A%22%22%2C%22JournalRange%22%3A%22%22%2C%22DomainRange%22%3A%22%22%2C%22PageSize%22%3A%220%22%2C%22PageNum%22%3A%221%22%2C%22Sort%22%3A%220%22%2C%22ClusterFilter%22%3A%22%22%2C%22SType%22%3A%22%22%2C%22StrIds%22%3A%22%22%2C%22UpdateTimeType%22%3A%22%22%2C%22ClusterUseType%22%3A%22Article%22%2C%22IsNoteHistory%22%3A1%2C%22AdvShowTitle%22%3A%22%E9%A2%98%E5%90%8D%E6%88%96%E5%85%B3%E9%94%AE%E8%AF%8D%3D%E5%8C%97%E5%A4%A7%22%2C%22ObjectId%22%3A%22%22%2C%22ObjectSearchType%22%3A%220%22%2C%22ChineseEnglishExtend%22%3A%220%22%2C%22SynonymExtend%22%3A%220%22%2C%22ShowTotalCount%22%3A%220%22%2C%22AdvTabGuid%22%3A%224361c899-1a1a-2b2b-eefe-cf94a80612f7%22%7D”
cookies = input("請鍵入cookies:
")
genUrl = input("請鍵入genUrl:
")
session = requests.Session()
session.headers = {
“Accept”: “text/html, /; q=0.01”,
“Accept-Encoding”: “gzip, deflate, br”,
“Accept-Language”: “zh-CN,zh;q=0.9”,
“Cache-Control”: “no-cache”,
“Connection”: “keep-alive”,
“Content-Type”: “application/x-www-form-urlencoded; charset=UTF-8”,
“Cookie”: cookies,
“Host”: “qikan.cqvip.com”,
“Origin”: “http://qikan.cqvip.com”,
“Pragma”: “no-cache”,
“Referer”: “http://qikan.cqvip.com/Qikan/Search/Advance?from=index”,
“sec-ch-ua”: ‘" Not A;Brand";v=“99”, “Chromium”;v=“90”, “Google Chrome”;v=“90”’,
“sec-ch-ua-mobile”: “?0”,
“Sec-Fetch-Dest”: “empty”,
“Sec-Fetch-Mode”: “cors”,
“Sec-Fetch-Site”: “same-origin”,
“User-Agent”: “Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36”,
“X-Requested-With”: “XMLHttpRequest”,
}
resp = session.request(
“POST”,
“http://qikan.cqvip.com/Search/SearchList”+genUrl,
data=payload
)
print(resp.url)
print(resp.status_code)
效果展示
狀態(tài)碼:成功-200,異常-400
總結(jié)
以上是生活随笔為你收集整理的【瑞数】维普期刊JS逆向4000字详细流程_1_获取接口签名的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cmd远程打开服务器,CMD下开启远程桌
- 下一篇: java 小波变换_小波变换教程(八)