逆向目標(biāo)
- 目標(biāo):有道翻譯接口參數(shù)
- 主頁:https://fanyi.youdao.com/
- 接口:https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule
- 逆向參數(shù):
- Form Data:
salt: 16261583727540
sign: 151e4b19b07ae410e0e1861a6706d30c
bv: 5b3e307b66a6c075d525ed231dcc8dcd
- Form Data:
逆向過程
抓包分析
我們?cè)谟械婪g頁面隨便輸入文字裹唆,可以看到?jīng)]有刷新頁面,翻譯結(jié)果就出來了,由此可以推斷是 Ajax 加載的碧查,打開開發(fā)者工具筝家,選擇 XHR 過濾 Ajax 請(qǐng)求夭咬,可以看到有一條 URL 為 https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule 的 POST 請(qǐng)求恭取,當(dāng)我們輸入“測(cè)試”的時(shí)候盹牧,他返回的數(shù)據(jù)類似于如下結(jié)構(gòu):
{
"translateResult": [
[{
"tgt": "test",
"src": "測(cè)試"
}]
],
"errorCode": 0,
"type": "zh-CHS2en",
"smartResult": {
"entries": ["", "[試驗(yàn)] test\r\n", "measurement\r\n"],
"type": 1
}
}
translateResult
是翻譯的結(jié)果钞瀑,smartResult
是智能推薦的其他翻譯沈撞,那么這個(gè) URL 就是我們需要的翻譯接口了。
由于是 POST 請(qǐng)求雕什,我們觀察它的 Form Data:
-
i
:待翻譯的字符串缠俺; -
from
:待翻譯的語言显晶; -
to
:目標(biāo)語言; -
lts
:時(shí)間戳壹士; -
smartresult
磷雇、client
、doctype
躏救、version
倦春、keyfrom
:固定值; -
action
:實(shí)時(shí)翻譯FY_BY_REALTlME
落剪、手動(dòng)點(diǎn)擊翻譯FY_BY_CLICKBUTTION
睁本; -
salt
、sign
忠怖、bv
的值每次會(huì)改變呢堰,需要進(jìn)一步分析。
參數(shù)逆向
salt
凡泣、sign
枉疼、bv
三個(gè)加密參數(shù),全局搜索任意一個(gè)鞋拟,搜索結(jié)果比較多骂维,依次對(duì)比,可以發(fā)現(xiàn) fanyi.min.js 文件第 8969 行左右開始贺纲,F(xiàn)orm Data 所有的參數(shù)都齊全了航闺,埋下斷點(diǎn)調(diào)試一下,可以看到所有數(shù)據(jù)和最終結(jié)果一致猴誊,加密的四個(gè)參數(shù)都在 r
當(dāng)中取值潦刃,跟蹤 r
,往上找可以看到 r = v.generateSaltSign(n);
懈叹,其中 n 是輸入的待翻譯的字符串:
繼續(xù)跟進(jìn) generateSaltSign
函數(shù)乖杠,點(diǎn)擊跳轉(zhuǎn)到 r
函數(shù),這里可以看到關(guān)鍵的加密代碼:
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 + "Y2FYu%TNSbMCxc3t2u^XT")
}
};
分析這段關(guān)鍵加密代碼:
navigator.appVersion
就是 UserAgentbv
的值由 UserAgent 經(jīng)過 MD5 加密得到ts
的值為 13 位時(shí)間戳salt
的值由ts
的值加上一個(gè) 0-9 的隨機(jī)整數(shù)得到sign
的值由待翻譯的字符串澄成、salt
的值和另外兩個(gè)固定的字符串組成胧洒,再由 MD5 加密得到最終結(jié)果
這個(gè)過程比較簡(jiǎn)單,可以直接使用 Python 來復(fù)現(xiàn):
import time
import random
import hashlib
query = "待翻譯字符串"
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
lts = str(int(time.time() * 1000)) # 以毫秒為單位的 13 位時(shí)間戳
salt = lts + str(random.randint(0, 9)) # 13 位時(shí)間戳+隨機(jī)數(shù)字墨状,生成 salt 值
sign = "fanyideskweb" + query + salt + "Y2FYu%TNSbMCxc3t2u^XT" # 拼接字符串組成 sign
sign = hashlib.md5(sign.encode()).hexdigest() # 將 sign 進(jìn)行 MD5 加密卫漫,生成最終 sign 值
bv = hashlib.md5(user_agent.encode()).hexdigest() # 對(duì) UA 進(jìn)行 MD5 加密,生成 bv 值
或者直接引用 JS歉胶,使用 nodejs 里面的加密模塊 CryptoJS 來進(jìn)行 MD5 加密汛兜,改寫 JS 如下:
// 引用 crypto-js 加密模塊
var CryptoJS = require('crypto-js')
function getEncryptedParams(data, ua) {
var bv = CryptoJS.MD5(ua).toString()
, lts = "" + (new Date).getTime()
, salt = lts + parseInt(10 * Math.random(), 10)
var sign = CryptoJS.MD5('fanyideskweb'+data+salt+']BjuETDhU)zqSxf-=B#7m').toString()
return {bv: bv, lts: lts, salt: salt, sign: sign}
}
完整代碼
youdao_encrypt.js
獲取加密參數(shù) salt
、sign
通今、bv
:
// 引用 crypto-js 加密模塊
var CryptoJS = require('crypto-js')
function getEncryptedParams(data, ua) {
var bv = CryptoJS.MD5(ua).toString(),
lts = "" + (new Date).getTime(),
salt = lts + parseInt(10 * Math.random(), 10)
var sign = CryptoJS.MD5('fanyideskweb' + data + salt + ']BjuETDhU)zqSxf-=B#7m').toString()
return { bv: bv, lts: lts, salt: salt, sign: sign }
}
// var ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
// var data = "測(cè)試"
// console.log(getEncryptedParams(data, ua));
youdaofanyi.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import random
import hashlib
import execjs
import requests
translate_url = 'https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
def get_translation_result(parameters):
headers = {
'User-Agent': user_agent,
'Host': 'fanyi.youdao.com',
'Origin': 'https://fanyi.youdao.com',
'Referer': 'https://fanyi.youdao.com/',
'X-Requested-With': 'XMLHttpRequest',
'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
'Cookie': 'OUTFOX_SEARCH_USER_ID="-1848382357@10.169.0.84"; ___rl__test__cookies=1625907853887; OUTFOX_SEARCH_USER_ID_NCOO=132978720.55854891'
}
response = requests.post(url=translate_url, headers=headers, data=parameters)
result = response.json()['translateResult'][0][0]['tgt']
return result
def get_parameters_by_python(query, translate_from, translate_to):
lts = str(int(time.time() * 1000)) # 以毫秒為單位的 13 位時(shí)間戳
salt = lts + str(random.randint(0, 9)) # 13 位時(shí)間戳+隨機(jī)數(shù)字粥谬,生成 salt 值
sign = "fanyideskweb" + query + salt + "Y2FYu%TNSbMCxc3t2u^XT" # 拼接字符串組成 sign
sign = hashlib.md5(sign.encode()).hexdigest() # 將 sign 進(jìn)行 MD5 加密肛根,生成最終 sign 值
bv = hashlib.md5(user_agent.encode()).hexdigest() # 對(duì) UA 進(jìn)行 MD5 加密,生成 bv 值
parameters = {
'i': query,
'from': translate_from,
'to': translate_to,
'smartresult': 'dict',
'client': 'fanyideskweb',
'salt': salt,
'sign': sign,
'lts': lts,
'bv': bv,
'doctype': 'json',
'version': '2.1',
'keyfrom': 'fanyi.web',
'action': 'FY_BY_REALTlME'
}
return parameters
def get_parameters_by_javascript(query, translate_from, translate_to):
with open('youdao_encrypt.js', 'r', encoding='utf-8') as f:
youdao_js = f.read()
params = execjs.compile(youdao_js).call('get_params', query, user_agent) # 通過 JavaScript 代碼獲取各個(gè)參數(shù)
bv = hashlib.md5(user_agent.encode()).hexdigest() # 對(duì) UA 進(jìn)行 MD5 加密漏策,生成 bv 值
parameters = {
'i': query,
'from': translate_from,
'to': translate_to,
'smartresult': 'dict',
'client': 'fanyideskweb',
'salt': params['salt'],
'sign': params['sign'],
'lts': params['lts'],
'bv': bv,
'doctype': 'json',
'version': '2.1',
'keyfrom': 'fanyi.web',
'action': 'FY_BY_REALTlME'
}
return parameters
def main():
query = input('請(qǐng)輸入要翻譯的文字:')
# 原始語言派哲,目標(biāo)語言,默認(rèn)自動(dòng)處理
translate_from = translate_to = 'AUTO'
# 通過 Python 獲取加密參數(shù)或者通過 JavaScript 獲取參數(shù)掺喻,二選一
param = get_parameters_by_python(query, translate_from, translate_to)
# param = get_parameters_by_javascript(query, translate_from, translate_to)
result = get_translation_result(param)
print('翻譯的結(jié)果為:', result)
if __name__ == '__main__':
main()