JavaScript逆向之有道翻译加解密全过程解析
本篇文章用于解析有道翻译中的加解密全过程
url:https://fanyi.youdao.com/index.html#/
加密
访问网址,输入框中随便输入一个英文单词,查看触发流量包,只看Fetch/XHR类型的。
这里主要关注webtranslate
的这条,请求参数和响应数据都是有加密的,主要了解其的加解密逻辑。
根据url定位法,全局搜索webtranslate
关键词,对应的就只有一个。
打断点,这里打断点要注意,由于是想要知道加解密逻辑,所以应该在里url最接近的地方打断点。
在输入框中在输入一旦内容,触发断点。
只有两个函数,一个是Object
,一个是E
,其中Object
是原生代码,肯定不是加密逻辑,定位E
函数的具体实现。
在该函数的第一行打上断点,让程序运行到这里。
流量包的传参中只有sign
和mysticTime
两个参数的值不知道是怎么得来的,所以在该函数只需要关注这两个参数值的生成即可。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,看下a
和i
的生成。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);
运行结果如下: