爬蟲篇之JS逆向破解
爬蟲中很重要的一個(gè)點(diǎn)就是JS的逆向破解加密,今天我們來淺析一下
背景
先簡單介紹一下為什么要有JS解密,目前大部分網(wǎng)頁都是采用的前后端分離的方式,所以呢,爬蟲的一般破解之道都是從后端接口來做文章,進(jìn)行突破
不過道高一尺,魔高一丈,網(wǎng)頁開發(fā)會(huì)對(duì)API接口請(qǐng)求參數(shù)進(jìn)行加密,來增加爬蟲抓取的門檻。為此可以通過js逆向來分析破解加密方式,模擬瀏覽器發(fā)送請(qǐng)求獲取接口數(shù)據(jù)。
當(dāng)然,先說明,這篇文章并不是非常專業(yè)的JS解密,因?yàn)镴S的解密涉及很多種,多種行為的解密,本文只是對(duì)其中一種情況進(jìn)行簡單的介紹
來吧,讓我們一起簡單學(xué)習(xí)一下

上面這個(gè)圖是請(qǐng)求翻譯的全過程

我們能清晰的看到這是直接以表單的形式提交的數(shù)據(jù)到后端API層,然后API來執(zhí)行翻譯的作用
接下來我們用python模擬以下這個(gè)過程

一定要注意的是,請(qǐng)求頭寫全,包括cookie和user-agent這些,還有下面的params一定要按照網(wǎng)頁中的來
代碼給到大家
import requests#請(qǐng)求頭headers = {"Accept": "application/json, text/javascript, */*; q=0.01","Accept-Encoding": "gzip, deflate","Accept-Language": "zh-CN,zh;q=0.9","Connection": "keep-alive","Content-Length": "255","Content-Type": "application/x-www-form-urlencoded; charset=UTF-8","Cookie": "OUTFOX_SEARCH_USER_ID_NCOO=1992896419.125546; [email protected]; fanyi-ad-id=306808; fanyi-ad-closed=1; DICT_UGC=be3af0da19b5c5e6aa4e17bd8d90b28a|; JSESSIONID=abcJJxrChyTjz_26EmBgy; ___rl__test__cookies=1656205889631","Host": "fanyi.youdao.com","Origin": "http://fanyi.youdao.com","Referer": "http://fanyi.youdao.com/","user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36","X-Requested-With": "XMLHttpRequest",}#提交參數(shù)params = {"i": "love you , my baby","from": "AUTO","to": "AUTO","smartresult": "dict","client": "fanyideskweb","salt": "16562058896377","sign": "f85458213e7db4207f135599c7ddfac7","lts": "1656205889637","bv": "bdc0570a34c12469d01bfac66273680d","doctype": "json","version": "2.1","keyfrom": "fanyi.web","action": "FY_BY_REALTlME",}url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"#發(fā)起POST請(qǐng)求response = requests.post(url=url,headers=headers,data=params).json()print(response)
一部分是header信息,一部分是params信息
我們可以看到,參數(shù)params中除了我們要傳遞的參數(shù)翻譯內(nèi)容之外,還有好多我們不認(rèn)識(shí)的參數(shù),如果這里錯(cuò)了會(huì)怎么樣呢,隨便改一下其中的一個(gè)參數(shù),我們看看效果

可以看到,直接返回錯(cuò)誤了,很明顯被禁止了,或者說是校驗(yàn)沒通過,屬于非法請(qǐng)求
我們?cè)賮砜匆幌拢桓淖兎g的內(nèi)容,靠這些鹽和簽名是不是能夠成功翻譯呢

結(jié)果發(fā)現(xiàn),我們只改變了要翻譯的內(nèi)容,結(jié)果還是不行,很明顯生成這些校驗(yàn)參數(shù)的過程是和要翻譯的內(nèi)容是相關(guān)的
搜索不同的關(guān)鍵詞,請(qǐng)求body參數(shù)如下,分析發(fā)現(xiàn)除了我們要傳遞的翻譯內(nèi)容外還有4個(gè)參數(shù)是變量:
"salt": "16562058896377",
"sign": "f85458213e7db4207f135599c7ddfac7",
"lts": "1656205889637",
"bv": "bdc0570a34c12469d01bfac66273680d",
這些就是屬于請(qǐng)求鹽和校驗(yàn)參數(shù),有對(duì)應(yīng)的加密格式,接下來我們圍繞這四個(gè)參數(shù)來進(jìn)行破解
接下來我們打開控制臺(tái),打開我們要分析的JS程序,直接ctrl+f全局搜索salt關(guān)鍵字

找到我們要分析的地方,然后在打上斷點(diǎn),重新請(qǐng)求一遍
F10往下一步一步的執(zhí)行

當(dāng)執(zhí)行到如圖所示的位置的時(shí)候,我們把鼠標(biāo)移動(dòng)到r這個(gè)對(duì)象的位置上去,為什么要看這個(gè)對(duì)象呢,因?yàn)槟憧聪旅娴膕alt、sign、lts、bv這些參數(shù)都是屬于r這個(gè)對(duì)象的屬性
我們能夠看到此時(shí)r對(duì)象的這幾個(gè)屬性已經(jīng)被賦予了值了,

接著看看這個(gè)r到底是什么

var r = function(e) {var t = n.md5(navigator.appVersion), r = "" + (new Date).getTime(), i = r + parseInt(10 * Math.random(), 10);return {ts: r,bv: t,salt: i,sign: n.md5("fanyideskweb" + e + i + "Ygy_4c=r#e#4EX^NUGUc5")}};
進(jìn)一步分析發(fā)現(xiàn):
r:當(dāng)前的時(shí)間戳
i:當(dāng)前的時(shí)間戳+(0到10的隨機(jī)數(shù))
salt:salt=i
e:搜索關(guān)鍵字
sign:md5("fanyideskweb" + e + i + "Ygy_4c=r#e#4EX^NUGUc5")
至此完成簽名算法的實(shí)現(xiàn),接下來可以通過python來實(shí)現(xiàn)

代碼如下:
import requestsfrom hashlib import md5import timeimport random#請(qǐng)求地址url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"appVersion = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"headers = {"Accept": "application/json, text/javascript, */*; q=0.01","Accept-Encoding": "gzip, deflate","Accept-Language": "zh-CN,zh;q=0.9","Connection": "keep-alive","Content-Length": "244","Content-Type": "application/x-www-form-urlencoded; charset=UTF-8","Cookie": "[email protected]; JSESSIONID=aaaUggpd8kfhja1AIJYpx; OUTFOX_SEARCH_USER_ID_NCOO=108436537.92676207; ___rl__test__cookies=1597502296408","Host": "fanyi.youdao.com","Origin": "http://fanyi.youdao.com","Referer": "http://fanyi.youdao.com/","user-agent": appVersion,"X-Requested-With": "XMLHttpRequest",}def r(e):# bvt = md5(appVersion.encode()).hexdigest()# ltsr = str(int(time.time() * 1000))# ii = r + str(random.randint(0,9))return {"ts": r,"bv": t,"salt": i,"sign": md5(("fanyideskweb" + e + i + "Ygy_4c=r#e#4EX^NUGUc5").encode()).hexdigest()}def fanyi(word):data = r(word)params = {"i": word,"from": "AUTO","to": "AUTO","smartresult": "dict","client": "fanyideskweb","salt": data["salt"],"sign": data["sign"],"lts": data["ts"],"bv": data["bv"],"doctype": "json","version": "2.1","keyfrom": "fanyi.web","action": "FY_BY_REALTlME",}response = requests.post(url=url,headers=headers,data=params)#返回json數(shù)據(jù)return response.json()if __name__ == "__main__":while True:word = input("請(qǐng)輸入要翻譯的語句:")result = fanyi(word)#對(duì)返回的json數(shù)據(jù)進(jìn)行提取,提取出我們需要的數(shù)據(jù)r_data = result["translateResult"][0]print(r_data[0]["src"])print(r_data[0]["tgt"])

給大家看一看JS的逆向技巧,從網(wǎng)上看到的總結(jié)

下面總結(jié)來源于:http://t.zoukankan.com/Renyi-Fan-p-12650448.html,侵刪
一句話總結(jié)
1. 搜索:全局搜索、代碼內(nèi)搜索
2. debug:常規(guī)debug、XHR debug、行為debug
3. 查看請(qǐng)求調(diào)用的堆棧
4. 執(zhí)行堆內(nèi)存中的函數(shù)
5. 修改堆棧中的參數(shù)值
6. 寫js代碼
7. 打印windows對(duì)象的值
8. 勾子:cookie鉤子、請(qǐng)求鉤子、header鉤子
技巧簡單介紹
1. 搜索
適用于根據(jù)關(guān)鍵詞快速定位關(guān)鍵文件及代碼
當(dāng)前頁面右鍵->檢查,彈出檢查工具


2. debug + 調(diào)試

調(diào)試

如圖所示,我標(biāo)記了1到6,下面分別介紹其含義
執(zhí)行到下一個(gè)端點(diǎn)
執(zhí)行下一步,不會(huì)進(jìn)入所調(diào)用的函數(shù)內(nèi)部
進(jìn)入所調(diào)用的函數(shù)內(nèi)部
跳出函數(shù)內(nèi)部
一步步執(zhí)行代碼,遇到有函數(shù)調(diào)用,則進(jìn)入函數(shù)
Call Stack 為代碼調(diào)用的堆棧信息,代碼執(zhí)行順序?yàn)橛上轮辽希@對(duì)于著關(guān)鍵函數(shù)前后調(diào)用關(guān)系很有幫助
XHR debug
匹配url中關(guān)鍵詞,匹配到則跳轉(zhuǎn)到參數(shù)生成處,適用于url中的加密參數(shù)全局搜索搜不到,可采用這種方式攔截

2.3 行為debug
適用于點(diǎn)擊按鈕時(shí),分析代碼執(zhí)行邏輯

如圖所示,可快速定位點(diǎn)擊探索按鈕后,所執(zhí)行的js。
3 查看請(qǐng)求調(diào)用的堆棧
可以在 Network 選項(xiàng)卡下,該請(qǐng)求的 Initiator 列里看到它的調(diào)用棧,調(diào)用順序由上而下:

4. 執(zhí)行堆內(nèi)存中的函數(shù)
當(dāng)debug到某一個(gè)函數(shù)時(shí),我們想主動(dòng)調(diào)用,比如傳遞下自定義的參數(shù),這時(shí)可以在檢查工具里的console里調(diào)用

此處要注意,只有debug打這個(gè)函數(shù)時(shí),控制臺(tái)里才可以調(diào)用。如果想保留這個(gè)函數(shù),可使用this.xxx=xxx 的方式。之后調(diào)用時(shí)無需debug到xxx函數(shù),直接使用this.xxx 即可。
5. 修改堆棧中的參數(shù)值

6. 寫js代碼

7. 打印windows對(duì)象的值
在console中輸入如下代碼,如只打印_$開頭的變量值
for (var p in window) {if (p.substr(0, 2) !== "_$")continue;console.log(p + " >>> " + eval(p))}
8. 勾子
以chrome插件的方式,在匹配到關(guān)鍵詞處插入斷點(diǎn)
8.1 cookie鉤子:用于定位cookie中關(guān)鍵參數(shù)生成位置
var code = function(){var org = document.cookie.__lookupSetter__('cookie');document.__defineSetter__("cookie",function(cookie){if(cookie.indexOf('TSdc75a61a')>-1){debugger;}org = cookie;});document.__defineGetter__("cookie",function(){return org;});}var script = document.createElement('script');script.textContent = '(' + code + ')()';(document.head||document.documentElement).appendChild(script);script.parentNode.removeChild(script);
當(dāng)cookie中匹配到了 TSdc75a61a, 則插入斷點(diǎn)。
8.2 請(qǐng)求鉤子:用于定位請(qǐng)求中關(guān)鍵參數(shù)生成位置
var code = function(){var open = window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open = function (method, url, async){if (url.indexOf("MmEwMD")>-1){debugger;}return open.apply(this, arguments);};}var script = document.createElement('script');script.textContent = '(' + code + ')()';(document.head||document.documentElement).appendChild(script);script.parentNode.removeChild(script);
當(dāng)請(qǐng)求的url里包含MmEwMD時(shí),則插入斷點(diǎn)
8.3 header鉤子:用于定位header中關(guān)鍵參數(shù)生成位置
var code = function(){var org = window.XMLHttpRequest.prototype.setRequestHeader;window.XMLHttpRequest.prototype.setRequestHeader = function(key,value){if(key=='Authorization'){debugger;}return org.apply(this,arguments);}}var script = document.createElement('script');script.textContent = '(' + code + ')()';(document.head||document.documentElement).appendChild(script);script.parentNode.removeChild(script);
當(dāng)header中包含Authorization時(shí),則插入斷點(diǎn)
8.4 manifest.json:插件的配置文件
{"name": "Injection","version": "2.0","description": "RequestHeader鉤子","manifest_version": 2,"content_scripts": [{"matches": ["<all_urls>"],"js": ["inject.js"],"all_frames": true,"permissions": ["tabs"],"run_at": "document_start"}]}
使用方法
a、如圖所示,創(chuàng)建一個(gè)文件夾,文件夾中創(chuàng)建一個(gè)鉤子函數(shù)文件inject.js 及 插件的配置文件 mainfest.json 即可

b、打開chrome 的擴(kuò)展程序, 加載已解壓的擴(kuò)展程序,選擇步驟1創(chuàng)建的文件夾即可

c、切換回原網(wǎng)頁,刷新頁面,若鉤子函數(shù)關(guān)鍵詞匹配到了,則觸發(fā)debug

結(jié)束語
后續(xù)所有的文章都會(huì)更新到這里,點(diǎn)贊關(guān)注不迷路
https://github.com/DayuMM2021/Java

