【2023.6】简单js逆向之百度翻译、有道翻译

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

话不多说,直接切入正题

目录

一、百度翻译--------“https://fanyi.baidu.com/”

1、分析参数

2、代码奉上!!!

二、有道翻译------------‘https://fanyi.youdao.com/index.html’

1、先分析参数的变化

2、破解响应加密

3、直接上代码

总结


一、百度翻译--------“https://fanyi.baidu.com/

1、分析参数

点击此网站进入,右键检查->network-->xhr,尝试输入翻译的句子或词语,抓取相关的包,抓取的包如图

 点击Headers查看后得出该包发送的是post请求,poyload是携带的参数。

接下来分析参数的变化 ,输入多个要翻译的词语,进行对比后得知,如下图

在这里看到了token,虽然这两次的抓包token的值没有发生变化,但是在很多的案例当中token是我们着重破解的参数,在后面寻找构造方法时,顺便也找一下token的构造方法是否为定值。

因此我们只剩下一个参数需要需要寻找。全局搜索ctrl+shift+f 输入sign,出现如下的包,如下图

打开进入 局部搜索ctrl+f,输入sign,找出疑似的地方,如图

 此处出现了我们发送post请求包时的参数,我们打上断点尝试运行一下

这边并没有直接显示,鼠标悬停才会显示,为了方便观察参数的变化,我们在右侧watch面板输入各个参数。

 根据各个值的显示,这就是我们想要找的地方。注意到sign值是w(e)生成的,我们选中w,鼠标悬停该处。

 该方法给我们带到了这个地方,我们再次输入测试的数据,看是否经过该函数,并看该方法的运行过程是怎样的

 

这个函数确实是我们想要找的,输入的数据经过这个函数进行了处理。

在pycharm当中创建js文件,将这段代码扣下到pycharm当中,更改函数名为result

此处需要node.js环境,搜索一下有很多的教程安装一下就好了

 尝试运行一下

发现报错,r 没有定义,点击路径到报错的位置。回到网页,选中 r 鼠标悬停,发现是一个具体的

值,测试一下,我们输入多个数据查看是不是固定的值。

 测试过后发现,我们怎么改变测试的值,该值都不会发生变化,因此直接写死 r 

再次运行

 n 没有定义,点击报错的路径,回到浏览器的该位置,选中n, 鼠标悬停,打开该方法的位置。

发现该方法就在上面,我们抠出来放到pycharm当中 ,再次运行

 运行成功,这个值跟我们在抓包时遇到的sign值非常的相似。因此,sign的构造方法找到并获取完毕。

我们顺便再看看token,是不是一个写死的值。老方法全局搜索token。找到相关的包

 点击进入,发现token就是一个写死的字符串。到此所有的参数都已经分析完毕!

剩下的就是敲代码啦!!!

2、代码奉上!!!

# ! /usr/bin/nev python
# -*-coding:utf8-*-
import time
import requests
import execjs


class BaiduTranslate(object):
    def __init__(self):
        input_key = input("请输入你想要翻译的词语或句子:")
        # input_key = '你会回到我身边的'
        self.headers = {
            'Host': 'fanyi.baidu.com',
            'Cookie': 'BIDUPSID=BDAACC6CD97DC76CC40D1EFA3841FBC8; PSTM=1686281380; BAIDUID=EDEE1C25592CA16B843963B0DE282004:FG=1; H_PS_PSSID=; BDORZ=FFFB88E999055A3F8A630C64834BD6D0; BAIDUID_BFESS=EDEE1C25592CA16B843963B0DE282004:FG=1; delPer=0; PSINO=6; BCLID=8204938500369048893; BCLID_BFESS=8204938500369048893; BDSFRCVID=xnFOJexroG0ZmSbf7dgZuOnU0uweG7bTDYrEOwXPsp3LGJLVFakFEG0Pts1-dEu-S2OOogKKLmOTHpKF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; BDSFRCVID_BFESS=xnFOJexroG0ZmSbf7dgZuOnU0uweG7bTDYrEOwXPsp3LGJLVFakFEG0Pts1-dEu-S2OOogKKLmOTHpKF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; H_BDCLCKID_SF=tRAOoC_-tDvDqTrP-trf5DCShUFshP6lB2Q-XPoO3KJADfOPb6JfbpI0XUTPJnQf5mkf3fbgy4op8P3y0bb2DUA1y4vp0toW3eTxoUJ2-KDVeh5Gqq-KXU4ebPRiWPr9QgbjahQ7tt5W8ncFbT7l5hKpbt-q0x-jLTnhVn0MBCK0HPonHjKBj5cQ3e; H_BDCLCKID_SF_BFESS=tRAOoC_-tDvDqTrP-trf5DCShUFshP6lB2Q-XPoO3KJADfOPb6JfbpI0XUTPJnQf5mkf3fbgy4op8P3y0bb2DUA1y4vp0toW3eTxoUJ2-KDVeh5Gqq-KXU4ebPRiWPr9QgbjahQ7tt5W8ncFbT7l5hKpbt-q0x-jLTnhVn0MBCK0HPonHjKBj5cQ3e; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1686628272; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1686645641; ab_sr=1.0.1_MTU3NmVhMDY0MDJhODQ2ODI0M2Q5NWYzNTNmMGFkNGZhYjRkOGM3ZjQxNWViNzFhY2QxNmI3ZGUzYjlhMWNkZDc5MTcxNWY3YjUxM2M2N2MzNWE1NmRiYjlmMzIyMmEwZDhkYzZjOGRiZTNmYWNjMDY4ZWZmOWYwY2JlZTEzOWNkZmMyYzgxMjIzNGFmMDAwYmUxNjhkMjNmYTgwNjJmMw==',
            'Origin': 'https://fanyi.baidu.com',
            'Connection': 'keep-alive',
            'Pragma': 'no-cache',
            'Referer': 'https://fanyi.baidu.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'
        }
        self.data = {
            'from': 'zh',
            'to': 'en',
            'query': input_key,
            'transtype': 'realtime',
            'simple_means_flag': '3',
            'sign': self.get_sign(input_key),
            'token': '724b81baa4603c1cdd08df6a5ba0e35f',
            'domain': 'common',
            'ts': str(int(time.time()*1000))
        }
        self.url = 'https://fanyi.baidu.com/v2transapi?from=zh&to=en'

    def get_sign(self, input_key):
        with open("../js/youdao_translation_js.js", "r", encoding="utf-8")as f:
            sign_js = f.read()
        sign = execjs.compile(sign_js).call("result", input_key)
        return sign

    def translate(self):
        is_right = True
        while is_right:
            try:
                response = requests.post(url=self.url, headers=self.headers, data=self.data).json()
                print(response['trans_result']['data'][0]['dst'])
                is_right = False
            except:
                continue

    def main(self):
        self.translate()


if __name__ == '__main__':
    tra = BaiduTranslate()
    tra.main()

js代码部分

var r = '320305.131321201'
function result(t) {
            var o, i = t.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
            if (null === i) {
                var a = t.length;
                a > 30 && (t = "".concat(t.substr(0, 10)).concat(t.substr(Math.floor(a / 2) - 5, 10)).concat(t.substr(-10, 10)))
            } else {
                for (var s = t.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), c = 0, l = s.length, u = []; c < l; c++)
                    "" !== s[c] && u.push.apply(u, function(t) {
                        if (Array.isArray(t))
                            return e(t)
                    }(o = s[c].split("")) || function(t) {
                        if ("undefined" != typeof Symbol && null != t[Symbol.iterator] || null != t["@@iterator"])
                            return Array.from(t)
                    }(o) || function(t, n) {
                        if (t) {
                            if ("string" == typeof t)
                                return e(t, n);
                            var r = Object.prototype.toString.call(t).slice(8, -1);
                            return "Object" === r && t.constructor && (r = t.constructor.name),
                            "Map" === r || "Set" === r ? Array.from(t) : "Arguments" === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) ? e(t, n) : void 0
                        }
                    }(o) || function() {
                        throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")
                    }()),
                    c !== l - 1 && u.push(i[c]);
                var p = u.length;
                p > 30 && (t = u.slice(0, 10).join("") + u.slice(Math.floor(p / 2) - 5, Math.floor(p / 2) + 5).join("") + u.slice(-10).join(""))
            }
            for (var d = "".concat(String.fromCharCode(103)).concat(String.fromCharCode(116)).concat(String.fromCharCode(107)), h = (null !== r ? r : (r = window[d] || "") || "").split("."), f = Number(h[0]) || 0, m = Number(h[1]) || 0, g = [], y = 0, v = 0; v < t.length; v++) {
                var _ = t.charCodeAt(v);
                _ < 128 ? g[y++] = _ : (_ < 2048 ? g[y++] = _ >> 6 | 192 : (55296 == (64512 & _) && v + 1 < t.length && 56320 == (64512 & t.charCodeAt(v + 1)) ? (_ = 65536 + ((1023 & _) << 10) + (1023 & t.charCodeAt(++v)),
                g[y++] = _ >> 18 | 240,
                g[y++] = _ >> 12 & 63 | 128) : g[y++] = _ >> 12 | 224,
                g[y++] = _ >> 6 & 63 | 128),
                g[y++] = 63 & _ | 128)
            }
            for (var b = f, w = "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(97)) + "".concat(String.fromCharCode(94)).concat(String.fromCharCode(43)).concat(String.fromCharCode(54)), k = "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(51)) + "".concat(String.fromCharCode(94)).concat(String.fromCharCode(43)).concat(String.fromCharCode(98)) + "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(102)), x = 0; x < g.length; x++)
                b = n(b += g[x], w);
            return b = n(b, k),
            (b ^= m) < 0 && (b = 2147483648 + (2147483647 & b)),
            "".concat((b %= 1e6).toString(), ".").concat(b ^ f)
}
function n(t, e) {
            for (var n = 0; n < e.length - 2; n += 3) {
                var r = e.charAt(n + 2);
                r = "a" <= r ? r.charCodeAt(0) - 87 : Number(r),
                r = "+" === e.charAt(n + 1) ? t >>> r : t << r,
                t = "+" === e.charAt(n) ? t + r & 4294967295 : t ^ r
            }
            return t
        }

二、有道翻译------------‘https://fanyi.youdao.com/index.html

照样话不多说,直接入题!!!

右键检查,network--->xhr, 输入几个测试的数据,得到如下几个包。

点击响应,看返回的数据判断是否是我们需要的包

 

啊。这。。。对于返回的数据进行了加密处理。。那就只好一步一步的来。

1、先分析参数的变化

老样子,全局搜索sign,发现该js文件

点击进入,找到疑似构造方法的位置

 

 打上断点测试一下,看看测试的数据显示是怎么样的

 我们发现,e 这个参数的值直接显示出来了,跟我们输入的数据的变化没有关系。是否代表着这是个定值呢,我们多测试几遍,看每次输出的值是否一样

我们回过头来看生成sign值的方法,老样子选中 h 方法,鼠标悬停,点击该方法位置路径 

 

 方法就在上面,发现,该方法只有一个返回值,并且返回值又调用了一个方法 g ,并且携带了一些参数。

顺着找到,g 方法就紧挨着,并且观察得知,进行了 md5 加密,是一个连环的嵌套,

这一下就疏通了,各个参数都已经解决,剩下复原代码就变得很简单了

调试一下看是不是这样的 

确实能获取到正确的值 

这次不用复制该 js 代码,python自带的加密工具就可以实现,试试看

import time
import hashlib


def get_sign():
    d = 'fanyideskweb'
    ee = 'fsdsogkndfokasodnaso'
    u = 'webfanyi'
    ti = str(int(time.time()*1000))

    def g(e):
        return hashlib.md5(e.encode("utf-8")).hexdigest()

    def h(e, t):
        md5_str = f'client={d}&mysticTime={e}&product={u}&key={t}'
        return g(md5_str)
    return h(ti, ee)


data = {
    'i': '我找到你了',
    'from': 'auto',
    'to': '',
    'dictResult': True,
    'keyid': 'webfanyi',
    'sign': get_sign(),
    'client': 'fanyideskweb',
    'product': 'webfanyi',
    'appVersion': '1.0.0',
    'vendor': 'web',
    'pointParam': 'client,mysticTime,product',
    'mysticTime': str(int(time.time()*1000)),
    'keyfrom': 'fanyi.web'
}
headers = {
    'Cookie': 'OUTFOX_SEARCH_USER_ID=-197651357@10.112.57.88; OUTFOX_SEARCH_USER_ID_NCOO=2126674871.429606',
    'Host': 'dict.youdao.com',
    'Origin': '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',
    'Referer': 'https://fanyi.youdao.com/',
}
url = 'https://dict.youdao.com/webtranslate'
response = requests.post(url, data=data, headers=headers).text
print(response)

也能够输出正确的结果,但是人家加密了,看不到,气不气。

 从这一步开始,破解响应数据。

2、破解响应加密

首先我们在最开始找到sign打断点的位置,我们点击step over next function按钮,或者直接F10也行,一步一步的往下执行,看能否在某个方法当中找到生成的这个值,找出疑似构造方法的位置

点击点击点击,这个方法很闷,也许有的案例中间有很多的步骤,遇上个成百上千的循环岂不是噶屁了,但是本次案例当中可以使用该方法。

运行到这里,出现了response当中的值。。。emmm。。。但是测试了一下好像不太对

继续点,找到了这个位置,好像是构造方法,打上断点测试一下

 

 发现 n 的值上面有解析的内容,是我们想要找的东西。因此足以说明,加密方法就在这里。回过头来,查看该构造方法,发现有decodekey,和decodeiv这两个玩意儿,那是不是AES加密呢。有可能

 

 再回个头,n 的生成是首先调用了decodeData,再传入了三个参数之后生成的。

那就找一下decodeData。全局搜索decodeData,找到如图

 这个值又是T生成的,再找 T,  发现T的构造就在上面,我们观察一下过程

实锤了,妥妥的AES加密,但是注意一点,a,c  两个值又是 v 函数处理过才传进去的。v 函数怎么感觉好像在哪见过呢

回头找找,发现了在找sign生成的时候,这个方法写在了旁边。不过也可以在右侧watch面板上写入 v  方法找到函数位置, 或者直接选中 v 方法查看,任选其一即可,如图

 

 

发现是MD5加密,

好了。decodeData函数分析完毕,但是回过头来,还有两个传入参数decodeKey和decodeIv的值是不知的。

 

老样子,那既然有这两个值,就一定有构造该值的方法

直接全局搜索decodekey,和decodeiv,发现是两个写死的字符串,如下

 全部的参数都已经分析完毕。

3、直接上代码

# ! /usr/bin/nev python
# -*-coding:utf8-*-
import requests,base64
from Crypto.Cipher import AES
import time
import hashlib


def get_sign():
    d = 'fanyideskweb'
    ee = 'fsdsogkndfokasodnaso'
    u = 'webfanyi'
    ti = str(int(time.time()*1000))

    def g(e):
        return hashlib.md5(e.encode("utf-8")).hexdigest()

    def h(e, t):
        md5_str = f'client={d}&mysticTime={e}&product={u}&key={t}'
        return g(md5_str)
    return h(ti, ee)


data = {
    'i': input("输入想要翻译的词语或者句子:"),
    'from': 'auto',
    'to': '',
    'dictResult': True,
    'keyid': 'webfanyi',
    'sign': get_sign(),
    'client': 'fanyideskweb',
    'product': 'webfanyi',
    'appVersion': '1.0.0',
    'vendor': 'web',
    'pointParam': 'client,mysticTime,product',
    'mysticTime': str(int(time.time()*1000)),
    'keyfrom': 'fanyi.web'
}
headers = {
    'Cookie': 'OUTFOX_SEARCH_USER_ID=-197651357@10.112.57.88; OUTFOX_SEARCH_USER_ID_NCOO=2126674871.429606',
    'Host': 'dict.youdao.com',
    'Origin': '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',
    'Referer': 'https://fanyi.youdao.com/',
}
url = 'https://dict.youdao.com/webtranslate'
response = requests.post(url, data=data, headers=headers).text
#   偏移量  
decodeiv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
# 秘钥
decodekey = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
# 在本案例中,先是对两个参数进行了参数加密                  
key = hashlib.md5(decodekey.encode(encoding='utf-8')).digest()
iv = hashlib.md5(decodeiv.encode(encoding='utf-8')).digest()
# AES解密
aes_en = AES.new(key, AES.MODE_CBC, iv)
# 将已经加密的数据放进该方法
data_new = base64.urlsafe_b64decode(response)
# 参数准备完毕后,进行解密
result = aes_en.decrypt(data_new).decode('utf8')
print(result)

总结

这是我在学习js逆向当中,作为新手的练习项目。在完成后记录一下。如果有兴趣的小伙伴,有错误的地方或者有更好的方法,欢迎指正分享!!!

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值