光说不练瞎把式,今天开始我们的逆向实战第一站,逆向有道翻译接口

有道翻译http://fanyi.youdao.com/

抓包

当我们有道翻译左侧输入框中输入英文的时候,右侧自动出现了翻译后的结果,但是网页并没有发生刷新,初步判断此处为 AJAX 请求

打开 Chrome调试工具,切换至 Network / xhr,重新输入单词,观察抓包结果,果然是 AJAX 请求

根据返回结果,确认了该条请求就是我们需要的有道翻译接口,切换至 headers,观察其请求体内容

分析

可以发现这是一个 post 请求,且请求参数很多

i: as
from: AUTO
to: AUTO
smartresult: dict
client: fanyideskweb
salt: 16152774048879
sign: 0e7979669e4005d1b87f9ca2cd20c440
lts: 1615277404887
bv: d9e85942912a84a2c1b8691553d518d7
doctype: json
version: 2.1
keyfrom: fanyi.web
action: FY_BY_REALTlME

修改要翻译的单词,观察参数的变化,发现只有如下 4个参数发生了变化

i: as                    搜索的单词
salt: 16152774844242     貌似是 lts + 一位数字
sign: b3666f65ed9efb543163f97ff825005c  不清楚
lts: 1615277484424       毫秒时间戳

其余三个参数我们都有了大致的猜测,但是 sign 参数却不知道是怎么生成的。此时我们可以通过切换至 Initiator 选项卡,观察其堆栈调用

点击 ajax 右侧的 JS文件路径,切入其源码内部,格式化代码后,在 send() 处打上断点,重新输入单词后进入断点位置,但是此处已经到了ajax请求发送的位置,我们点击右侧 call stack 进入上一个堆栈调用,查看 Ajax 参数信息

进入后发现,ajax请求被包裹在一个函数中,而请求的参数则是通过 函数的形参e 传递过来的,于是我们继续向上切换堆栈,来到函数调用的位置

此时,可以发现我们需要的三个参数都是包含在 r 对象内部的。同时呢,可以发现 r 对象是由 generateSaltSign 参数返回的。因此,我们切换至 generateSaltSign 函数内部分析 r 的生成逻辑

进入函数内部后,代码并没有任何混淆,我们可以清晰的看出三个参数的生成方式

  • lts:毫秒时间戳
  • salt:lts + 一位随机数字
  • sign:fanyideskweb" + 搜索词 + 时间戳 + "Tbh5E8=q6U3EXe+&L[4c@ 的 md5 结果

Python代码实现

# @Time : 2021-03-09
# @Author : zzzzls
# @Github : https://github.com/zzzzls
# @Blog : https://blog.csdn.net/qq_36078992

import time
import random
import hashlib
import requests


def md5Encrypt(obj):
    """MD5加密"""
    md5 = hashlib.md5()
    md5.update(obj.encode())
    return md5.hexdigest()


def youdao_translate(search_key: str) -> str:
    """ 有道翻译 API :param search_key: 将要翻译的词 :return: str or None, 翻译结果 """

    # 构建加密参数
    ts = int(time.time() * 1000)
    salt = str(ts) + str(random.randint(1, 9))
    bv = md5Encrypt(
        '5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36')
    sign = md5Encrypt("fanyideskweb" + search_key + salt + "Tbh5E8=q6U3EXe+&L[4c@")

    url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
    headers = {
   
        'Host': 'fanyi.youdao.com',
        'Origin': 'http://fanyi.youdao.com',
        'Referer': 'http://fanyi.youdao.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'Cookie': 'OUTFOX_SEARCH_USER_ID=-1644523005@10.108.160.100; OUTFOX_SEARCH_USER_ID_NCOO=857549368.4207594; JSESSIONID=aaahQdbLzSjY3dSU_V1Fx; DICT_UGC=be3af0da19b5c5e6aa4e17bd8d90b28a|; JSESSIONID=abcXf1BO34WgqbzPvg3Fx; _ntes_nnid=550e7be268d446cac20d6f763fbdc8c7,1614758394523; ___rl__test__cookies=1615258741826'
    }
    data = {
   
        "i": search_key,
        "from": "AUTO",
        "to": "AUTO",
        "smartresult": "dict",
        "client": "fanyideskweb",
        "salt": salt,
        "sign": sign,
        "lts": ts,
        "bv": bv,
        "doctype": "json",
        "version": "2.1",
        "keyfrom": "fanyi.web",
        "action": "FY_BY_REALTlME"
    }

    try:
        res = requests.post(url, headers=headers, data=data).json()
        result = res['translateResult'][0][0]['tgt']
    except:
        print('请求失败,请检查参数!')
        result = None

    return result


if __name__ == '__main__':
    kw = input('请输入待翻译内容:')
    result = youdao_translate(kw)
    print(result)

Github 仓库:zzzzls-Github