天天看點

【JS 逆向百例】某道翻譯接口參數逆向

【JS 逆向百例】某道翻譯接口參數逆向

聲明

本文章中所有内容僅供學習交流,抓包内容、敏感網址、資料接口均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此産生的一切後果均與作者無關,若有侵權,請聯系我立即删除!

逆向目标

  • 目标:某道翻譯接口參數
  • 首頁:aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLw==
  • 接口:aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tL3RyYW5zbGF0ZV9vP3NtYXJ0cmVzdWx0PWRpY3Qmc21hcnRyZXN1bHQ9cnVsZQ==
  • 逆向參數:
    • Form Data:
      • salt: 16261583727540

      • sign: 151e4b19b07ae410e0e1861a6706d30c

      • bv: 5b3e307b66a6c075d525ed231dcc8dcd

逆向過程

抓包分析

我們在有道翻譯頁面随便輸入文字,可以看到沒有重新整理頁面,翻譯結果就出來了,由此可以推斷是 Ajax 加載的,打開開發者工具,選擇 XHR 過濾 Ajax 請求,可以看到有一條 URL 為 aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tL3RyYW5zbGF0ZV9vP3NtYXJ0cmVzdWx0PWRpY3Qmc21hcnRyZXN1bHQ9cnVsZQ== 的 POST 請求,當我們輸入“測試”的時候,他傳回的資料類似于如下結構:

{
    "translateResult": [
        [{
            "tgt": "test",
            "src": "測試"
        }]
    ],
    "errorCode": 0,
    "type": "zh-CHS2en",
    "smartResult": {
        "entries": ["", "[試驗] test\r\n", "measurement\r\n"],
        "type": 1
    }
}           

translateResult

是翻譯的結果,

smartResult

是智能推薦的其他翻譯,那麼這個 URL 就是我們需要的翻譯接口了。

由于是 POST 請求,我們觀察它的 Form Data:

  • i

    :待翻譯的字元串;
  • from

    :待翻譯的語言;
  • to

    :目智語言;
  • lts

    :時間戳;
  • smartresult

    client

    doctype

    version

    keyfrom

    :固定值;
  • action

    :實時翻譯

    FY_BY_REALTlME

    、手動點選翻譯

    FY_BY_CLICKBUTTION

  • salt

    sign

    bv

    的值每次會改變,需要進一步分析。
【JS 逆向百例】某道翻譯接口參數逆向

參數逆向

salt

sign

bv

三個加密參數,全局搜尋任意一個,搜尋結果比較多,依次對比,可以發現 fanyi.min.js 檔案第 8969 行左右開始,Form Data 所有的參數都齊全了,埋下斷點調試一下,可以看到所有資料和最終結果一緻,加密的四個參數都在

r

當中取值,跟蹤

r

,往上找可以看到

r = v.generateSaltSign(n);

,其中 n 是輸入的待翻譯的字元串:

【JS 逆向百例】某道翻譯接口參數逆向

繼續跟進

generateSaltSign

函數,點選跳轉到

r

函數,這裡可以看到關鍵的加密代碼:

【JS 逆向百例】某道翻譯接口參數逆向
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")
    }
};           

分析這段關鍵加密代碼:

  • navigator.appVersion

    就是 UserAgent
  • bv

    的值由 UserAgent 經過 MD5 加密得到
  • ts

    的值為 13 位時間戳
  • salt

    的值由

    ts

    的值加上一個 0-9 的随機整數得到
  • sign

    的值由待翻譯的字元串、

    salt

    的值和另外兩個固定的字元串組成,再由 MD5 加密得到最終結果

這個過程比較簡單,可以直接使用 Python 來複現:

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 位時間戳
salt = lts + str(random.randint(0, 9))                            # 13 位時間戳+随機數字,生成 salt 值
sign = "fanyideskweb" + query + salt + "Y2FYu%TNSbMCxc3t2u^XT"    # 拼接字元串組成 sign
sign = hashlib.md5(sign.encode()).hexdigest()                     # 将 sign 進行 MD5 加密,生成最終 sign 值
bv = hashlib.md5(user_agent.encode()).hexdigest()                 # 對 UA 進行 MD5 加密,生成 bv 值           

或者直接引用 JS,使用 nodejs 裡面的加密子產品 CryptoJS 來進行 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}
}           

完整代碼

GitHub 關注 K 哥爬蟲:

https://github.com/kuaidaili

,持續分享爬蟲相關代碼!歡迎 star !

以下隻示範部分關鍵代碼,完整代碼倉庫位址:

https://github.com/kuaidaili/crawler/

參數 JS 加密關鍵代碼

擷取加密參數

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 = "測試"
// console.log(getEncryptedParams(data, ua));           

Python 關鍵代碼

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import time
import random
import hashlib

import execjs
import requests


translate_url = '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/'
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': '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/',
        'Origin': '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/',
        'Referer': '脫敏處理,完整代碼關注 GitHub:https://github.com/kuaidaili/crawler/',
        'X-Requested-With': 'XMLHttpRequest',
        'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
        'Cookie': 'OUTFOX_SEARCH_USER_ID="[email protected]"; ___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 位時間戳
    salt = lts + str(random.randint(0, 9))                            # 13 位時間戳+随機數字,生成 salt 值
    sign = "fanyideskweb" + query + salt + "Y2FYu%TNSbMCxc3t2u^XT"    # 拼接字元串組成 sign
    sign = hashlib.md5(sign.encode()).hexdigest()                     # 将 sign 進行 MD5 加密,生成最終 sign 值
    bv = hashlib.md5(user_agent.encode()).hexdigest()                 # 對 UA 進行 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 代碼擷取各個參數
    bv = hashlib.md5(user_agent.encode()).hexdigest()                           # 對 UA 進行 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('請輸入要翻譯的文字:')
    # 原始語言,目智語言,預設自動處理
    translate_from = translate_to = 'AUTO'
    # 通過 Python 擷取加密參數或者通過 JavaScript 擷取參數,二選一
    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('翻譯的結果為:', result)


if __name__ == '__main__':
    main()