首页
社区
课程
招聘
[原创]某道翻译接口参数还原调用
2023-6-20 14:10 13838

[原创]某道翻译接口参数还原调用

2023-6-20 14:10
13838

0x01 需求

 

逆向有道翻译接口参数,本文仅供研究与学习使用,切勿用于各种非法用途。

0x02 抓包

1、接口定位

 

打开有道翻译页面,输入你好,无需提交数据,翻译结果便自行生成了,要想实现这种逻辑,推断 Ajax 加载。
打开开发者工具,选择 Fetch/XHR 过滤请求,可以看到有两个 url:webtranslate 和 key。
key 中为提交的中文数据,不做分析,仅看接口 webtranslate。
图片描述

2、响应数据

 

图片描述
都 23 年了,就知道没那么简单,问题不大,有加密必定有解密!

3、接口负载

 

图片描述
一个 sign 值需要分析,mysticTime 为时间戳。

0x03 调试分析

1、下断定位

 

在 XHR/提取断点中下断:webtranslate
图片描述
m 中的值即为经过 url 编码的负载数据:

 

i=%E4%BD%A0%E5%A5%BD&from=auto&to=&dictResult=true&keyid=webfanyi&sign=0d43622e3f5b6fc6578cdc1b849f9d28&client=fanyideskweb&product=webfanyi&appVersion=1.0.0&vendor=web&pointParam=client%2CmysticTime%2Cproduct&mysticTime=1687161867680&keyfrom=fanyi.web

 

跟踪堆栈数据,可定位到如下位置:
图片描述

2、代码解析

1
2
3
4
5
6
const j = (e,t)=>Object(a["a"])("https://dict.youdao.com/webtranslate/key", Object(n["a"])(Object(n["a"])({}, e), O(t)))
  , y = (e,t)=>Object(a["d"])("https://dict.youdao.com/webtranslate", Object(n["a"])(Object(n["a"])({}, e), O(t)), {
    headers: {
        "Content-Type": "application/x-www-form-urlencoded"
    }
})
 

主要看 y 的生成,在该段代码执行后 sign 值便生成了,调试跟踪定位到在其上方的 O 函数:
图片描述

 

e 为固定值:fsdsogkndfokasodnaso
d 为固定值:fanyideskweb
u 为固定值:webfanyi
p 为固定值:1.0.0
b 为固定值:web
m 为固定值:client,mysticTime,product
t 为时间戳
f 为固定值:fanyi.web

0x04 sign 生成

 

经过上述分析可得,sign: h(t, e),sign 是由 h 函数得来的,分析 h 函数:

1
2
3
function h(e, t) {
    return g(`client=${d}&mysticTime=${e}&product=${u}&key=${t}`)
}
 

入参 e 为时间戳,t 为固定字符串:fsdsogkndfokasodnaso,将参数格式化后,又调用 g 函数,进入分析:

1
2
3
function g(e) {
    return r.a.createHash("md5").update(e.toString()).digest("hex")
}
 

入参 e 为:

 

client=fanyideskweb&mysticTime=1687163549482&product=webfanyi&key=fsdsogkndfokasodnaso

 

入参后,创建 md5 函数进行加密,得到结果:

 

785914f401740b9bad913162ba4091ee

 

图片描述
对照分析无误!

0x05 sign 算法还原

1、mysticTime 还原:

1
2
import time
mysticTime = str(int(time.time() * 1000))

2、组包加密

1
2
3
import hashlib
data = "client=fanyideskweb&mysticTime={}&product=webfanyi&key=fsdsogkndfokasodnaso".format(mysticTime)  # 组包
sign = hashlib.md5(data.encode(encoding='utf-8')).hexdigest()  # MD5 加密
 

固定时间戳后,对照网页 sign 值结果无误。

0x06 模拟请求

 

python实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import hashlib
import time
import requests
 
class Youdao():
    def __init__(self):
       self.url = 'https://dict.youdao.com/webtranslate'
 
    def get_enmes(self, input_data):
        mysticTime = str(int(time.time() * 1000))
        print(mysticTime)
        data = "client=fanyideskweb&mysticTime={}&product=webfanyi&key=fsdsogkndfokasodnaso".format(mysticTime)
        sign = hashlib.md5(data.encode(encoding='utf-8')).hexdigest()  # python中的MD5 加密
        print(sign)
 
        data = {
            'i': input_data,
            'from': 'auto',
            'to': '',
            'dictResult': 'true',
            'keyid': 'webfanyi',
            'sign': sign,
            'client': 'fanyideskweb',
            'product': 'webfanyi',
            'appVersion': '1.0.0',
            'vendor': 'web',
            'pointParam': 'client,mysticTime,product',
            'mysticTime': mysticTime,
            'keyfrom': 'fanyi.web'
        }
        headers = {
            'Referer': "https://fanyi.youdao.com",
            "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
                          "Chrome/113.0.0.0 Safari/537.36",
        }
        cookies = {
            "OUTFOX_SEARCH_USER_ID": "-390346570@10.110.96.160",
            "OUTFOX_SEARCH_USER_ID_NCOO": "1229562063.3960004"
        }
 
        response = requests.post(self.url, headers=headers, data=data, cookies=cookies).text
        print(response)
 
 
def start():
    input_data = input('请输入要翻译内容:')
    op.get_enmes(input_data)
 
 
if __name__ == '__main__':
    op = Youdao()
    start()
 

请求后得到密文数据,下一步的目标就清楚了,找到解密函数。
图片描述

0x07 解密方式分析

1、解密点定位

 

可定位的方式有很多,关键字搜索、单步调试分析、堆栈分析等等。
第一次分析时单步调试无疑能让你了解整个执行流程,但真的费时间和精力,对我就是这么干的......
建议还是通过关键字搜索如:decode,其次通过调用堆栈定位!
逐步调试最终定位到如下位置:
图片描述

2、代码解析

1
2
3
4
5
6
.then(o=>{
    an["a"].cancelLastGpt();
    const n = an["a"].decodeData(o, sn["a"].state.text.decodeKey, sn["a"].state.text.decodeIv)
      , a = n ? JSON.parse(n) : {};
    0 === a.code ? e.success && t(e.success)(a) : e.fail && t(e.fail)(a)
})
 

其中 o 为密文,执行完该局代码后生成明文数据 n。
直接看代码:

1
const n = an["a"].decodeData(o, sn["a"].state.text.decodeKey, sn["a"].state.text.decodeIv)
 

全局搜索 decodeKey 可得:
图片描述
通过控制台打印输出得:
图片描述
key 和 iv 对照无误。
哪一种加密函数需要传入密文数据还有 key 值和 iv 向量?
其实大家心中已经有一些答案了,考考 ChatGpt:
图片描述
跟进代码中看看是不是:
图片描述
进入后发现:
图片描述
aes-128-cbc 加密,有 key,有 iv,执行完后生成明文数据,下一步就是还原该解密函数了。

0x08 解密函数还原

1、代码解析

1
2
3
4
5
6
7
8
9
10
T = (t,o,n)=>{
    if (!t)
        return null;
    const a = e.alloc(16, v(o))
      , c = e.alloc(16, v(n))
      , i = r.a.createDecipheriv("aes-128-cbc", a, c);
    let s = i.update(t, "base64", "utf-8");
    return s += i.final("utf-8"),
    s
}
 

首先判断t即传入的加密参数是否为空,为空则返回 null,不为空则继续执行下面的代码。
v 函数为:

1
2
3
function v(e) {
    return r.a.createHash("md5").update(e).digest()
}
 

对密钥和向量进行 MD5 加密,并返回字节数据。
接下来就是正常的 AES 算法了,不懂的可自行百度理解。

2、算法还原

 

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64
from Crypto.Cipher import AES
from Crypto.Hash import MD5
 
def decrypt_data(encrypted_data):
    key = b"ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
    iv = b"ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
    cryptor = AES.new(
        MD5.new(key).digest()[:16], AES.MODE_CBC, MD5.new(iv).digest()[:16]
    )
    decode_mes = cryptor.decrypt(base64.urlsafe_b64decode(encrypted_data)).decode("utf-8")
    return decode_mes
 
encrypted_data = 'Z21kD9ZK1ke6ugku2ccWu4n6eLnvoDT0YgGi0y3g-v0B9sYqg8L9D6UERNozYOHqnYdl2efZNyM6Trc_xS-zKtfTK4hb6JP8XwCzNh0avc8qItQUiIU_4wKKXJlIpvMvfKvJaaZzaX6VEtpkr2FdkfoT_Jgbm2GRSVj3r40autIdlImENG8hC0ZH4ww7utwuTt3Oo_ZpXg0BSq9wePSAB75-ChkiGKF9HTIPeCl2bl84SBD1XDfFCZpkKQhecYSs0JLoXOqP2ltavxRrg58Hp1q5uIgZZ_Oo2-Jmd-t1r4es40drcAq5bjmS62M2VJF8D6ojtOh9JTfNwgzD3CxYn-Pd7-TgHMyNEJEkFXTAyxzpjlFqtrCYDE3SZUYlENkqsL8Wrra1hM-1nTfiB-BLcWAdRBynNpP5_54aq_-GBsq8bB_9yEX5ovzDB4_Ry_spVVuUnb39iplMHCdCnjOD3ngiIDbl9SUz-9npjBX05ZYRdPmFPAl424qdoaxeVqnVoH8jQFPZVqaHMzu4mJg0SICDWFH7GP1zqGRbXd3ESjT_iBInl3gICt2XVuhh_nubcELkTEC6xbqEDRQkPUNMpzXJHjcvsLHtcmSW0S9F0445ho9kT2qZYdMBC3Fs0OaHpUtFu77gZpQn7sGiqh8VliXIcUtfvvop-1c-Vu5QjfUbLn2-s5POR9fGYG6rt6ioe_PGmwWj-Cc00zUM7FybfarKTr4D3Rk57R72qpXN4Ja86ZsCAMmDG-m5z31RQh_V7echJ8Kna3Go3yWKCK4vtSwOWrFhiS5RTz6EkrGc3SkFKbb5vp8Wop_84myBtgnBmj4CczhTq2HcOxrJf4def6yDt2uBxyv4bTVGx9Yx3uB4Gx0iK5kYvfma6B_LnkRWk331wjuXKQtBGYIuWkR8J5QtvBmIRVaa7AA19Z4xMIEAqbcuQ5p4I9FCElthBrJd9YOcouHK4U27xxYWJJXcJoTvzG7zWtiV76fHDeQLgAWvJJ7ww4NFgjhqc6AKA_2afxa4c_lAvVZgFuKL3XSCL7PfKxp6GhjcGKeSRr80PT1gfFw2xi8X4ejjNm_prsUZ'
print(decrypt_data(encrypted_data))
 

执行得到数据:

 

{"code":0,"dictResult":{"ce":{"word":{"trs":[{"voice":"hello&type=2","#text":"hello","#tran":"喂,你好(用于问候或打招呼);喂,你好(打电话时的招呼语);喂,你好(引起别人注意的招呼语);<非正式>喂,嘿 (认为别人说了蠢话或分心);<英,旧>嘿(表示惊讶);招呼,问候;(Hello)(法、印、美、俄)埃洛(人名);说(或大声说)“喂”;打招呼;"},{"voice":"hi&type=2","#text":"hi","#tran":"嗨!(表示问候或用以唤起注意);(Hi)人名;(柬)希;"},{"voice":"how+do+you+do&type=2","#text":"how do you do","#tran":"你好;"}],"phone":"nǐ hǎo","return-phrase":"你好"}}},"translateResult":[[{"tgt":"hello","src":"你好","srcPronounce":"nĭhăo"}]],"type":"zh-CHS2en"}

 

对照网页代码生成数据无误:
图片描述

0x09 完整还原

 

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import hashlib
import time
import requests
import base64
from Crypto.Cipher import AES
from Crypto.Hash import MD5
class Youdao():
    def __init__(self):
       self.url = 'https://dict.youdao.com/webtranslate'
 
    def get_enmes(self, input_data):
        mysticTime = str(int(time.time() * 1000))
        data = "client=fanyideskweb&mysticTime={}&product=webfanyi&key=fsdsogkndfokasodnaso".format(mysticTime)
        sign = hashlib.md5(data.encode(encoding='utf-8')).hexdigest()  # python中的MD5 加密
        data = {
            'i': input_data,
            'from': 'auto',
            'to': '',
            'dictResult': 'true',
            'keyid': 'webfanyi',
            'sign': sign,
            'client': 'fanyideskweb',
            'product': 'webfanyi',
            'appVersion': '1.0.0',
            'vendor': 'web',
            'pointParam': 'client,mysticTime,product',
            'mysticTime': mysticTime,
            'keyfrom': 'fanyi.web'
        }
        headers = {
            'Referer': "https://fanyi.youdao.com",
            "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
                          "Chrome/113.0.0.0 Safari/537.36",
        }
        cookies = {
            "OUTFOX_SEARCH_USER_ID": "-390346570@10.110.96.160",
            "OUTFOX_SEARCH_USER_ID_NCOO": "1229562063.3960004"
        }
        response = requests.post(self.url, headers=headers, data=data, cookies=cookies).text
        decode_mes = self.decrypt_data(response)
        print(decode_mes)
 
    def decrypt_data(self, encrypted_data):
        key = b"ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
        iv = b"ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
        cryptor = AES.new(
            MD5.new(key).digest()[:16], AES.MODE_CBC, MD5.new(iv).digest()[:16]
        )
        decode_mes = cryptor.decrypt(base64.urlsafe_b64decode(encrypted_data)).decode("utf-8")
        return decode_mes
 
def start():
    input_data = input('请输入要翻译内容:')
    op.get_enmes(input_data)
 
 
if __name__ == '__main__':
    op = Youdao()
    start()
 

输入黑丝,执行结果:
图片描述

 

任务完成。


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2023-6-20 14:11 被行简编辑 ,原因:
收藏
点赞7
打赏
分享
最新回复 (2)
雪    币: 222
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
易薇吖 2023-7-19 09:44
2
0
厉害厉害
雪    币: 149
活跃值: (2023)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
saloyun 2023-7-19 11:18
3
0
厉害厉害
游客
登录 | 注册 方可回帖
返回