一、前言
對彩云小譯網頁版進行抓包分析,將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
以上內容僅用于學習研究
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/552711.html
標籤:其他
上一篇:資料分析缺失值處理(Missing Values)——洗掉法、填充法、插值法
下一篇:返回列表
