某麥最新 analysis 算法逆向分析(多種語言實(shí)現(xiàn))
“
閱讀本文大概需要 13 分鐘。
? ? ??特別聲明:本公眾號文章只作為學(xué)術(shù)研究,不作為其它不法用途;如有侵權(quán)請聯(lián)系作者刪除。

趣味模塊
? ? ??小明是一名工程師,最近小明遇到了一個棘手的問題:小明發(fā)現(xiàn)自己曾經(jīng)熬了幾個通宵搞定的某麥算法居然升級了。小明惆悵進(jìn)行中....,按照之前的套路進(jìn)行加密定位和還原,發(fā)現(xiàn)都不好使了。這篇文章中,將解決小明遇到的困境,讓我們一起去看小明遇到的問題并通過多種語言去實(shí)現(xiàn)算法還原吧!
一、前言介紹
前言:? 想必 大家都了解什么是JS逆向吧?我們在以往的文章中涉及到JS逆向的網(wǎng)站,采用的方式都是去還原JS代碼,然后使用Python去調(diào)用JS源碼傳遞參數(shù)返回結(jié)果。這個過程雖然沒有問題,但是對于爬取效率的提升、代碼的性能都會受到影響。雖然爬蟲的職責(zé)是首先確保能不能拿到數(shù)據(jù),但是我覺得還有一個問題,那就是爬取效率。如果這兩者都具備了,那將是最完美的!對于一些加密邏輯不是太復(fù)雜的網(wǎng)站,我們完全可以進(jìn)行源碼跨語言還原。耐心看完本文,你會受益匪淺的!
二、參數(shù)分析
備注
:如果想看舊版文章的,可以點(diǎn)擊此鏈接:某麥analysis參數(shù)算法分析
1、首先打開我們今天要模擬的網(wǎng)站,刷新當(dāng)前頁面,首頁截圖如下:

2、然后打開開發(fā)者工具,查找指定的XHR請求數(shù)據(jù)包,截圖如下所示:

3、接下來,我們對該接口參數(shù)進(jìn)行初判斷及整理分析:
params參數(shù)分析:
-
analysis? ? ? ? ? ? ? 初步懷疑是base64加密
-
country? ? ? ? ? ? ? ?固定值
-
version? ? ? ? ? ? ? ? 搜索版本
-
search、advise? 搜索關(guān)鍵字
headers參數(shù)分析:
說明: 由于headers參數(shù)沒有重要參數(shù)影響,故不作說明。
三、斷點(diǎn)調(diào)試
1、使用最簡單的方式,查詢指定關(guān)鍵字、加密方法,定位加密參數(shù)具體坐標(biāo)文件,截圖如下:

說明:
經(jīng)過查詢,我們可以肯定的是代碼中沒有用到這個變量名,然后我們?nèi)ニ阉骷用芊椒?,發(fā)現(xiàn)能搜到結(jié)果,但是和我們的加密參數(shù)關(guān)聯(lián)不大,截圖如下:
3、接下來,我們還是使用XHR打斷點(diǎn),回溯堆棧的方式查找吧,截圖如下:

說明:查看斷點(diǎn)發(fā)包的請求函數(shù),我們發(fā)現(xiàn)標(biāo)紅的地方已經(jīng)計算出了加密參數(shù),顯然我們的斷點(diǎn)是置后的,不過沒有關(guān)系,我們查看函數(shù)執(zhí)行堆棧進(jìn)行回溯查找。
4、由于需要堆棧調(diào)試,過濾掉一些沒用的流程,我們直接定位到加密地方,截圖如下:

總結(jié):此時上圖中參數(shù)e即為我們想要還原的加密參數(shù),整個流程貫通后,我們只需要對js算法進(jìn)行還原即可。
四、算法還原
1、JS版本算法還原
var n = {};
function i(e) {
var t, a = (t = "",
????????["66",?"52",?"6d",?"6d",?"43",?"68",?"61",?"72",?"43",?"6f",?"64",?"65"].forEach((function?(e)?{
t += unescape("%u00" + e)
}
)),
t);
return String[a](e)
}
function h(e) {
return function (e) {
try {
return btoa(e)
} catch (t) {
return Buffer.from(e).toString("base64")
}
}(encodeURIComponent(e).replace(/%([0-9A-F]{2})/g, (function (e, t) {
return i("0x" + t)
}
)))
}
function g(e, t) {
t || (t = s());
for (var a = (e = e.split("")).length, n = t.length, o = "charCodeAt", r = 0; r < a; r++)
e[r] = i(e[r][o](0) ^ t[(r + 10) % n][o](0));
return e.join("")
}
n.cv = h;
n.oZ = g;
function get_enCdata(keyword, path) {
var l = "xyz517cda96abcd";
var d = "@#";
var s = 687;
var o = +new Date - (s || 0) - 1661224081041;
var r = ["cn", "ios14", keyword, keyword];
r = r.sort().join("");
r = n.cv(r);
r += d + path + d + o + d + 3;
a = (0,
n.cv)((0,
n.oZ)(r, l));
console.log(a);
return a
}
get_enCdata("王者榮耀", "/search/index")
console打印結(jié)果如下:

Python執(zhí)行JS代碼編寫如下:
def js_load():
with open('encrypt.js', 'r', encoding='UTF-8') as f:
lines = f.read()
ctx = execjs.compile(lines)
return ctx
headers = {
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"',
'accept': 'application/json, text/plain, */*',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'accept-language': 'zh-CN,zh;q=0.9',
}
ctx = js_load()
keyword = "王者榮耀"
url = "https://xxxxxx/search/index"
analysis?=?ctx.call("get_enCdata",?keyword,?urlparse(url).path)
params = (
('country', 'cn'),
('search', keyword),
('advise', keyword),
('version', 'ios14'),
)
params = dict(params)
params.update({
"analysis":analysis,
})
response = requests.get(url, headers=headers, params=params)
print(response.text)
打印結(jié)果如下:

2、Python版本算法還原完整代碼:
import time
import requests
import base64
from urllib.parse import urlparse
def get_analysis(params, path):
salt = "xyz517cda96abcd"
data = list(dict(params).values())
data.sort()
first_ba64 = base64.b64encode("".join(data).encode()).decode()
first_ba64 += f"@#{path}@#{int(time.time()) * 1000 - 687 - 1661224081041}@#3"
base_chr = ''.join(chr(ord(v) ^ ord(salt[(r + 10) % len(salt)])) for r, v in enumerate(first_ba64))
analysis = base64.b64encode(base_chr.encode()).decode()
return analysis
def start_requests(keyword):
headers = {
'authority': 'api.qimai.cn',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"',
'accept': 'application/json, text/plain, */*',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36',
????????'sec-ch-ua-platform':?'"macOS"',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
????????'sec-fetch-dest':?'empty',
'accept-language': 'zh-CN,zh;q=0.9',
}
url = "https://xxxxx/search/index"
params = (
('country', 'cn'),
('search', keyword),
('advise', keyword),
('version', 'ios14'),
)
analysis = get_analysis(params, urlparse(url).path)
params = dict(params)
params.update({
"analysis": analysis,
})
response = requests.get(url, headers=headers, params=params)
print(response.text)
if __name__ == '__main__':
start_requests("王者榮耀")
Pycharm打印結(jié)果如下:

3、GoLang版本算法還原完整代碼:
// Package main ------------------
// @author : 逆向與爬蟲故事公眾號
// -------------------------------
package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
func getAnalysis(keyword, path string) string {
salt := "xyz517cda96abcd"
baseStr := fmt.Sprintf("cnios14%s%s", keyword, keyword)
baseBase64 := base64.StdEncoding.EncodeToString([]byte(baseStr))
baseBase64 += fmt.Sprintf("@#%s@#%d@#3", path, time.Now().Unix()*1000-687-1661224081041)
dataStr := ""
for i := 0; i < len(baseBase64); i++ {
dataStr += string(baseBase64[i] ^ salt[(i+10)%len(salt)])
}
analysis := base64.StdEncoding.EncodeToString([]byte(dataStr))
return analysis
}
func main() {
client := &http.Client{}
keyword := "王者榮耀"
path := "/search/index"
analysis := getAnalysis(keyword, path)
fmt.Println(analysis)
req, err := http.NewRequest("GET", "https://xxxx/search/index?analysis="+analysis+"&country=cn&version=ios14&search="+keyword+"&advise="+keyword+"", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("cache-control", "no-cache")
req.Header.Set("cookie", "gr_user_id=6326ec0c-e4bb-40f4-80fa-f136f53ec138; qm_check=A1sdRUIQChtxen8tJ0NMOQkKWVIeEHhARFQEQi5VVFk1RVJcd3UQABZQS0FIWhoSUFRZEgMSBBRRTlNISFVKF0o%3D; Hm_lvt_ff3eefaf44c797b33945945d0de0e370=1665841750,1665843752,1665999455,1666149354; tgw_l7_route=d09474674af82c17375cfcdd775c0c28; PHPSESSID=c8sbl02kml6qbdktrgkf2l7iia; ada35577182650f1_gr_session_id=25228f5f-bbba-4660-86f5-d45ebd688bb6; ada35577182650f1_gr_session_id_25228f5f-bbba-4660-86f5-d45ebd688bb6=true; synct=1666149373.597; Hm_lpvt_ff3eefaf44c797b33945945d0de0e370=1666149374; syncd=-1222")
req.Header.Set("pragma", "no-cache")
req.Header.Set("sec-ch-ua", `"Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"macOS"`)
req.Header.Set("sec-fetch-dest", "empty")
//req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-site", "same-site")
req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
GoLand打印結(jié)果如下:

五、思路總結(jié)
回顧整個分析流程,本次難點(diǎn)主要概括為以下幾點(diǎn):
- 如何快速定位加密參數(shù)的位置
-
堆?;厮萑绾芜^濾無用代碼
-
如何扣JS代碼并能run起來
- Python還原加密算法實(shí)現(xiàn)
- GoLang還原加密算法實(shí)現(xiàn)
-
熟練掌握數(shù)據(jù)類型及邏輯運(yùn)算

End
崔慶才的新書《Python3網(wǎng)絡(luò)爬蟲開發(fā)實(shí)戰(zhàn)(第二版)》已經(jīng)正式上市了!書中詳細(xì)介紹了零基礎(chǔ)用 Python 開發(fā)爬蟲的各方面知識,同時相比第一版新增了 JavaScript 逆向、Android 逆向、異步爬蟲、深度學(xué)習(xí)、Kubernetes 相關(guān)內(nèi)容,?同時本書已經(jīng)獲得 Python 之父 Guido 的推薦,目前本書正在七折促銷中!
內(nèi)容介紹:《Python3網(wǎng)絡(luò)爬蟲開發(fā)實(shí)戰(zhàn)(第二版)》內(nèi)容介紹

掃碼購買

點(diǎn)個在看你最好看
