JavaScript逆向之有道翻译加解密全过程解析

本篇文章用于解析有道翻译中的加解密全过程

url:https://fanyi.youdao.com/index.html#/

加密

访问网址,输入框中随便输入一个英文单词,查看触发流量包,只看Fetch/XHR类型的。

这里主要关注webtranslate的这条,请求参数和响应数据都是有加密的,主要了解其的加解密逻辑。


根据url定位法,全局搜索webtranslate关键词,对应的就只有一个。

打断点,这里打断点要注意,由于是想要知道加解密逻辑,所以应该在里url最接近的地方打断点。

在输入框中在输入一旦内容,触发断点。

只有两个函数,一个是Object,一个是E,其中Object是原生代码,肯定不是加密逻辑,定位E函数的具体实现。


在该函数的第一行打上断点,让程序运行到这里。

流量包的传参中只有signmysticTime两个参数的值不知道是怎么得来的,所以在该函数只需要关注这两个参数值的生成即可。sign: k(o, e)mysticTime: o,其中o是时间戳,那么mysticTime就是时间戳;要想搞清楚sign的生成就得知道k函数的实现,定位,打断点,运行到这里。

k函数调用了j函数,还得知道j函数的实现。

j函数就是一个计算md5值的函数,那么k函数就是对client=${u}&mysticTime=${e}&product=${d}&key=${t}这样的一个字符串计算md5值。输出一下。

其中只有mysticTime这个变量会改变,key是个定值,整个的加密逻辑就非常清晰了。
python代码如下:

点击查看代码
import requests
from hashlib import md5
import time

url = "https://dict.youdao.com/webtranslate"
word = input("请输入一个单词:")
tm = str(int(time.time() * 1000))
str_temp = f"client=fanyideskweb&mysticTime={tm}&product=webfanyi&key=fsdsogkndfokasodnaso"
obj = md5()
obj.update(str_temp.encode("utf-8"))
sign = obj.hexdigest()
data = {
    "i": word,
    "from": "auto",
    "to": "",
    "dictResult": "true",
    "keyid": "webfanyi",
    "sign": sign,
    "client": "fanyideskweb",
    "product": "webfanyi",
    "appVersion": "1.0.0",
    "vendor": "web",
    "pointParam": "client,mysticTime,product",
    "mysticTime": tm,
    "keyfrom": "fanyi.web",
    "mid": "1", "screen": "1",
    "model": "1",
    "network": "wifi",
    "abtest": "0",
    "yduuid": "abcdefg",
}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 "
                  "Safari/537.36", "Referer": "https://fanyi.youdao.com/",
    "Cookie": "OUTFOX_SEARCH_USER_ID_NCOO=xxxxx; OUTFOX_SEARCH_USER_ID=xxxxxx", }

session = requests.session()
resp = session.post(url, data=data, headers=headers)
print(resp.text)

运行结果如下:

解密

由于跟webtranslate相关的只有如下的这一行代码,所以解密逻辑肯定也在这行代码中。

在讲解下面的步骤之前,先介绍一个箭头函数。

点击查看代码
// JavaScript
var mm = () => {
    return 123;
};

//箭头函数如果没有大括号,那么直接返回内容
var kk = () => 456789;
console.log(mm());      // 123
console.log(kk());      // 456789

从这个代码中可以看出箭头函数有两种写法,一个是通过大括号和return来返回值,一个是直接在箭头函数背后写上返回值。再看T = (e,t)=>Object(a["d"])("https://dict.youdao.com/webtranslate", Object(n["a"])(Object(n["a"])({}, e), E(t)), { headers: { "Content-Type": "application/x-www-form-urlencoded" } })这行代码,就是箭头函数的第二种写法,箭头函数背后的整体就是返回的内容,输出看一下。

是个Promise对象,所以就得知道这个Promise对象返回给了谁,这时候就可以看调用栈了。

Qo调用了T,说明Promise对象是返回到Qo中的,定位到Qo

在这段代码中,可以很容易的看到一个关键词decodeData,不用想这肯定就是解密逻辑了,定位一下。

打断点,运行到这里。

就可以看到是个AES加密了,CBC模式,下面只需要知道key和iv即可。看下createDecipheriv函数的实现。

从这里就可以知道a就是key,i是iv,看下ai的生成。a = e.alloc(16, y(o))e.alloc(16, y(n)),先看y函数的实现。

就是一个计算md5值的过程。输出一下e.alloc(16, y(o))的结果,发现跟计算md5后的结果一样,所以就可以简单的认为是个md5值计算即可。


两者都是16字节,跟AES加密所需要的key和value的大小一样,并且还是个定值,这样就简单了。(如果不能判断是不是个定值,可以多调试一下,看变量的值会不会改变)
python代码如下:

点击查看代码
from hashlib import md5
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

def my_md5(data):
    obj2 = md5()
    obj2.update(data.encode("utf-8"))
    ret = obj2.digest()  # 字节
    return ret


t = "Z21kD9ZK1ke6ugku2ccWu-MeDWh3z252xRTQv-wZ6jddVo3tJLe7gIXz4PyxGl73nSfLAADyElSjjvrYdCvEP4pfohVVEX1DxoI0yhm36ytQNvu-WLU94qULZQ72aml6JKK7ArS9fJXAcsG7ufBIE0gd6fbnhFcsGmdXspZe-8whVFbRB_8Fc9JlMHh8DDXnskDhGfEscN_rfi-A-AHB3F9Vets82vIYpkGNaJOft_JA-m5cGEjo-UNRDDpkTz_NIAvo5PbATpkh7PSna2tHcE6Hou9GBtPLB67vjScwplB96-zqZKXJJEzU5HGF0oPDY_weAkXArzXyGLBPXFCnn_IWJDkGD4vqBQQAh2n52f48GD_cb-PSCT_8b-ESsKUI9NJa11XsdaUZxAc8TzrYnXwdcQbtl_kZGKhS6_rCtuNEBouA_lvM2CbS7TTtV2U4zVmJKpp-c6nt3yZePK3Av01GWn1pH_3sZbaPEx8DUjSbdp4i4iK-Mj4p2HPoph67DR7B9MFETYku_28SgP9xsKRRvFH4aHBHESWX4FDbwaU="
o = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
n = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'

key = my_md5(o)
iv = my_md5(n)
t = t.replace("_","/").replace("-","+")  # 由于密文其实采用的是个叫url安全的base64编码,会把"+"和"/"替换成"-"和"_",所以在解密的时候得先还原
aes = AES.new(key=key, iv=iv, mode=AES.MODE_CBC)
ming_bs = aes.decrypt(base64.b64decode(t))
ming_bs = unpad(ming_bs, 16)
print(ming_bs.decode("utf-8"))

运行结果如下:

把加解密的代码整合一下。

点击查看代码
import requests
from hashlib import md5
import time
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

url = "https://dict.youdao.com/webtranslate"
word = input("请输入一个单词:")
tm = str(int(time.time() * 1000))
str_temp = f"client=fanyideskweb&mysticTime={tm}&product=webfanyi&key=fsdsogkndfokasodnaso"
obj = md5()
obj.update(str_temp.encode("utf-8"))
sign = obj.hexdigest()
data = {
    "i": word,
    "from": "auto",
    "to": "",
    "dictResult": "true",
    "keyid": "webfanyi",
    "sign": sign,
    "client": "fanyideskweb",
    "product": "webfanyi",
    "appVersion": "1.0.0",
    "vendor": "web",
    "pointParam": "client,mysticTime,product",
    "mysticTime": tm,
    "keyfrom": "fanyi.web",
    "mid": "1", "screen": "1",
    "model": "1",
    "network": "wifi",
    "abtest": "0",
    "yduuid": "abcdefg",
}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 "
                  "Safari/537.36", "Referer": "https://fanyi.youdao.com/",
    "Cookie": "OUTFOX_SEARCH_USER_ID_NCOO=xxxxxxx; OUTFOX_SEARCH_USER_ID=xxxxxx", }

session = requests.session()
resp = session.post(url, data=data, headers=headers)


def my_md5(data):
    obj2 = md5()
    obj2.update(data.encode("utf-8"))
    ret = obj2.digest()  # 字节
    return ret


t = resp.text
o = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
n = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'

key = my_md5(o)
iv = my_md5(n)
t = t.replace("_","/").replace("-","+")
aes = AES.new(key=key, iv=iv, mode=AES.MODE_CBC)
ming_bs = aes.decrypt(base64.b64decode(t))
ming_bs = unpad(ming_bs, 16)
print(ming_bs.decode("utf-8"))

运行结果如下:

补充

想要知道createDecipheriv函数的实现还有另外一种思路,直接百度搜索。

发现它其实是Nodejs中crypto库下的一个函数,所以可以直接调用crypto库来实现。alloc其实也是Nodejs中Buffer库下的,也可以直接调用。

js测试代码如下:

点击查看代码
var crypto = require("crypto");

function y(e) {
    return crypto.createHash("md5").update(e).digest();
}

function jm(t,o,n){
    if (!t)
        return null;
    const a = Buffer.alloc(16, y(o))
      , i = Buffer.alloc(16, y(n))
      , r = crypto.createDecipheriv("aes-128-cbc", a, i);
    let s = r.update(t, "base64", "utf-8");
    return s += r.final("utf-8"),
    s
}

var t = 'Z21kD9ZK1ke6ugku2ccWu-MeDWh3z252xRTQv-wZ6jddVo3tJLe7gIXz4PyxGl73nSfLAADyElSjjvrYdCvEP4pfohVVEX1DxoI0yhm36ytQNvu-WLU94qULZQ72aml6cYBVU_O5b4O-ND0rEWbfEEp-CNsPQEwjRV-iM008hyg4psnuxT9hhBXjpzH8uzBxqR9VBh9v2T35qVb4ipPvX70IaGcbG17rvXyras-RWT24bdytS47IgDmQVMfd1cQrTyDVZo1ByopBhc63S3QOKtcY7Aip1m9eLirsRpmecA4zC-0Pn2FSpsOJi3Gw6g2Cy8ryt-pnNGsoO2wf_9lWV4Y7LHqAeQfAMcFERawC1PVDQzouFE2Xvyw20H4XCtJMvcdMgnQgmpo42vaU4Uj6SbNLQbWjrCEXEtcrKu9AVGU03oJeo_Stun9oboBASa5gzP7pecQ8NPIB9xciY5O_jk_QuNG-vZVRdTecK1qSEHMpuSgM5dRMN9iTCD93qAwQYE5qOMCuOfeNnWhUewho3XJfMewpsDstvjSesdXfR5rtrrydSw76xvqQotdSH7J0RAUGjj5Wv-WVxelEFbDYQpPvOWHg3wXOa1pORzELoEG6v8_WR5_5C-buAoAC4d9xa1O76dd1nj1e_NzvdicnBPNj0HSt_APXvtqDzmBLEZUwa9b5_ZhgyJldGwdWHzLwxx8xPM34M_YlW28J8msT5RBMgPv9ahAJSsKQReQD4pboP2cwxWC9yPeloFc68TOLIeSrMvXrT8aJw8S5MpAX0C3-2yN8g0RY80n9QjzsnHIgwXERlrw66hMOHch4DIp-AvbPZl9WTncFPL_A2x-Y4EVkUtvV_JNzDJsVgoITdkekhKHkFmAyMwvKbYvW9-4pFisyOozkh83rko35YZSqDvvIXEWZyWVpGld2SI_xESEP82gtzgrplZzUuVq_WVDegypnD5DZmZsidnHkau-j5FSYwpivm-96QgVDIBuYTjvV9Lv9GatSYyZBCjjx0Xi8I-JuH3x12GITyiXmmn5xjbvpLQSBVly_urZpNy1SnH2XCVrZOOauAy_ZgAByrlGlhzxBuGL_fBd2r6Lv4kERgaD6tnGVlBk2inBiPbW8Sex90bE7g0T5ps6w4_jzVmcmQZ-M2cX7u1y7mlV_vF-puzU81na5nvQIaJmWQn0_9-466JGALXEaW7CHb-BqNZTCdLYJgj6n-DbX9sZs8raeEeJsE3aK1P4YmnUgn6wQHQCYLKmDzTC79KoL-6eCpHe2VD-P48bDfCKLOrqSeLYiWKnZyTAq42h-f9zZtd-fapb4sHO8O0IUF_6DBQ987P9-dvJhghTj8_UIpMjFqBrGDJbnhbGy5Yjn_G1uUtT1usUGwFoBuEJ96r_VPKlxXYjqrgaW3w3ElKr2pEfkKhTKDSLL_inlWGYhTogPsRX8kJubNsZ5V9FcmU2tlGFh5dlDwA-fTTQunsrLt_G40RrK0iWqSvyEjMZi7DqzdeITNK_sk1OlYMguTK6z12P4tzKgV3Ynq6Xo9ToRQE_g9oSH1OEo8mUw7AQ8dNi7FxXjpAJHbfdbKwIjAqUeOfCUJ3UVBMGGR5Um4w80nDvf-gLaxdCePHELy_ev8E-s3K5kADoaE9iFcM5Kz8N_4bfZ_trx6-LufqiDUcT4rNKa42o3pEOUeS9m61MLyRLp2O1YwxypYPNUbl8yK8Q_gTBrTQK3bfo3hs0vDYTpIL1CBLjMNTfip6nv_mE7mZzhOweBpGyoYl1k3kOPSc_0fviR0jdaeYPsaHx-R1V1erV0okwp8kexYr_5FqyFXEnJxGATUoCY5Tc6OfgOh7grXUnVhf2aTG6Uc1ODQk2_h0DkxlFcwUXJf45N3LadYQDs6nN10tiyh6yTL5aZADP1JGgAFAWpXW4GFzIWf4DmjQ3VQKhulLeUEFHOuJgUmzWQtrV831E4EYBVhWAH5050MrhsPbcd1tvYDnvdEOmSi-pUmu7MWoxK95rzfo71bgUHdEbFN3IDSVbYtUYYwegifnjFS17dcyP2q6HTIygor00eEarvj32vWH7TENiqonqGLOuWYcLeRtjRtblY6MAlDvSSxEDeg4cO5PD-MtsgIMvUnGxYUafUp8uAKR1E4LKwA9VLYM5Bl5RMVDLtYyqsUn5zBOZU';
var o = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl';
var n = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4';
let jm1 = jm(t,o,n);
console.log(jm1);

运行结果如下:

posted @ 2024-03-11 14:40  死不悔改奇男子  阅读(594)  评论(0编辑  收藏  举报