【Go】彩云小译翻译接口js逆向解密返回值
一、前言
对彩云小译网页版进行抓包分析,将js算法代码转换成go代码,使用go发送http请求编写一个翻译小工具。
主要实现:
- 翻译(解密翻译结果)
- 单词字典查询
- 生成JWT(保持有效期)
二、抓包
打开网页按F12调出开发人员工具,再切换到网络选项卡,在输入框里输入内容开始抓包,会抓到两个接口,translator为翻译接口,dict为查询字典的接口。
在 translator
接口的响应里面,发现并没有找到翻译的结果??其实 target
就是翻译结果,只是进行了加密,需要对其进行解密才能得到翻译结果。
{
"isdict": 1,
"confidence": 1.21429,
"target": "5Y2t5nJ9",
"rc": 0
}
三、分析
我们先对 translator
响应的 target
进行逆向分析
我们接着通过上一步抓包抓到的接口,在发起程序选项卡中找到对应的调用点
这个很明显就是调用翻译接口的地方,我们点击右边蓝色的js文件,查看源代码
这里我们很容易就看到了我们要找到target,这里应该是已经请求完接口的返回数据了,dh为请求函数,获取到target并且赋值给了变量o,接着是一个三元表达式,通过typeof来判断o的类型是否为string,我们知道其实target就是一串字符串,所以只需要看问号后面的表达式了,问号后面也是一个赋值操作,我们直接下一个断点,再到页面中进行翻译操作
已经断下来了,我们看到o确实是target密文,我们把鼠标悬浮到Zs函数上,点击蓝色的链接跳转到对应的代码
可以看到Zs首先是调用了一下vh函数,并且把target传入进去,进行一些字符串操作后返回给了变量t,我们直接把vh函数代码抠下来,这段代码是可以直接运行的
function vh(e) {
const t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
, i = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"
, a = n=>t.indexOf(n)
, o = n=>a(n) > -1 ? i[a(n)] : n;
return e.split("").map(o).join("")
}
再调用Ah函数来解密,我们看到在Ah函数中调用了oh.decode()函数,decode很明显是解密了,我们跟之前的操作一样,进入到decode函数中查看代码
decode就是N函数,我们发现N函数中又调用了几个函数嵌套,我们按照上一步的方法,把使用到的函数全部抠下来
const vh = (e)=>{
const t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
, i = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"
, a = n=>t.indexOf(n)
, o = n=>a(n) > -1 ? i[a(n)] : n;
return e.split("").map(o).join("")
}
const b = function(I) {
switch (I.length) {
case 4:
var U = (7 & I.charCodeAt(0)) << 18 | (63 & I.charCodeAt(1)) << 12 | (63 & I.charCodeAt(2)) << 6 | 63 & I.charCodeAt(3)
, F = U - 65536;
return String.fromCharCode((F >>> 10) + 55296) + String.fromCharCode((F & 1023) + 56320);
case 3:
return String.fromCharCode((15 & I.charCodeAt(0)) << 12 | (63 & I.charCodeAt(1)) << 6 | 63 & I.charCodeAt(2));
default:
return String.fromCharCode((31 & I.charCodeAt(0)) << 6 | 63 & I.charCodeAt(1))
}
}
const w = function(I) {
const y = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g
return I.replace(y, b)
}
const E = function(I) {
return window.atob(I)
}
const C = function(I) {
return w(E(I))
}
const k = function(I) {
return String(I).replace(/[-_]/g, function(U) {
return U == "-" ? "+" : "/"
}).replace(/[^A-Za-z0-9\+\/]/g, "")
}
const N = function(I) {
return C(k(I))
}
抠下来之后运行发现报错,找不到o,我们通过下断点,然后在控制台输入出o,发现o就是内置函数 fromCharCode
,因此我们只需要把o替换成 String.fromCharCode
就行
我们尝试运行一下代码,发现可以成功解密,那就代表我们找的解密算法是对的,那么我们如何在go中使用呢?go中应该也有像python中的execjs那样直接调用js脚本的,但是我们这次不是用那种方法,我们下面把js代码转换成go代码
四、转换
将js算法转换成go代码,并且进行简化代码
package main
import (
"encoding/base64"
"fmt"
)
// 将字母表中的字母替换为另一个字母表中的字母
func substituteAlphabet(input string) string {
alphabet := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
substitution := "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"
findIndex := func(n byte) int {
for index, b := range alphabet {
if byte(b) == n {
return index
}
}
return -1
}
substitute := func(n byte) byte {
if findIndex(n) > -1 {
return substitution[findIndex(n)]
}
return n
}
result := ""
for _, c := range input {
result += string(substitute(byte(c)))
}
return result
}
// 从字符串中删除双字节字符
func removeDoubleByte(input string) string {
bytes := []byte(input)
for i := 0; i < len(bytes); i++ {
if bytes[i] == 194 && i+1 < len(bytes) && bytes[i+1] >= 128 && bytes[i+1] <= 191 {
bytes[i] = bytes[i+1]
bytes[i+1] = 0
}
}
return string(bytes)
}
// 对输入的字符串进行base64解码
func decodeBase64(input string) string {
decoded, err := base64.StdEncoding.DecodeString(input)
if err != nil {
panic(err)
}
return removeDoubleByte(string(decoded))
}
func main() {
code := substituteAlphabet("5Y2t5nJ977lZ5YvJ55JZ77lO")
text := decodeBase64(code)
fmt.Println(text)
}
五、请求
我们再次对网页进行抓包,然后在开发者工具中右键对应接口,复制curl
再到https://curlconverter.com/#go中将curl转换成对应的go代码,生成的代码放到go里面是可以直接运行的
我们需要先请求接口,通过返回的数据拿到target,再使用解密对target进行解密,拿到最终的翻译结果
六、总结
我写完之后发现已经写了220多行了,go的代码量确实比python的要多
加了一个dict字典查询接口,还有生成jwt的接口(可以防止jwt过期)
完整代码已经上传到Github
以上内容仅用于学习研究
热门相关:斗神战帝 战神 梦回大明春 重生之至尊千金 特工重生:快穿全能女神