創(chuàng)新互聯專注于企業(yè)成都全網營銷推廣、網站重做改版、開州網站定制設計、自適應品牌網站建設、H5開發(fā)、商城系統(tǒng)網站開發(fā)、集團公司官網建設、外貿網站建設、高端網站制作、響應式網頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為開州等各大城市提供網站開發(fā)制作服務。
關注微信公眾號:K哥爬蟲,持續(xù)分享爬蟲進階、JS/安卓逆向等技術干貨!
本文章中所有內容僅供學習交流,抓包內容、敏感網址、數據接口均已做脫敏處理,嚴禁用于商業(yè)用途和非法用途,否則由此產生的一切后果均與作者無關,若有侵權,請聯系我立即刪除!
aHR0cHM6Ly9sZWFybi5vcGVuLmNvbS5jbi8=
aHR0cHM6Ly9sZWFybi5vcGVuLmNvbS5jbi9BY2NvdW50L1VuaXRMb2dpbg==
black_box: eyJ2IjoiR01KM0VWWkVxMG0ydVh4WUd...
本次逆向的目標同樣是一個登錄接口,其中的加密 JS 使用了簡單的混淆,可作為混淆還原的入門級教程,來到登錄頁面,隨便輸入賬號密碼進行登錄,其中登錄的 POST 請求里, Form Data 有個加密參數 black_box,也就是本次逆向的目標,抓包如下:
直接搜索 black_box,在 login.js 里可以很容易找到加密的地方,如下圖所示:
看一下 _fmOpt.getinfo()
這個方法,是調用了 fm.js 里的 OO0O0()
方法,看這個又是 0 又是 O 的,多半是混淆了,如下圖所示:
點進去看一下,整個 fm.js 都是混淆代碼,我們選中類似 OQoOo[251]
的代碼,可以看到實際上是一個字符串對象,也可以直接在 Console 里輸出看到其實際值,這個 OO0O0
方法返回的 oOoo0[OQoOo[448]](JSON[OQoOo[35]](O0oOo[OQoOo[460]]))
,就是 black_box 的值,如下圖所示:
仔細觀察,可以發(fā)現 OQoOo
應該是一個類似數組的東西,通過傳入元素下標來依次取其真實值,隨便搜索一個值,可以在代碼最后面找到一個數組,這個數組其實就是 OQoOo
,可以傳入下標來驗證一下,如下圖所示:
到這里其實就知道了其大致混淆原理,我們可以把這個JS 拿下來,到本地寫個小腳本,將這些值替換一下:
# ==================================
# --*-- coding: utf-8 --*--
# @Time : 2021-11-09
# @Author : 微信公眾號:K哥爬蟲
# @FileName: replace_js.py
# @Software: PyCharm
# @describe: 混淆還原小腳本
# ==================================
# 待替換的值(太多了,僅列出少部分)
# 以實際列表為準,要和 fm_old.js 里的列表一致
item = ['referrer', 'absolute', 'replace',...]
# 混淆后的 JS
with open("fm_old.js", "r", encoding="utf-8") as f:
js_lines = f.readlines()
js = ""
for j in js_lines:
js += j
for i in item:
# Qo00o 需要根據你 fm_old.js 具體的字符串進行替換
str_old = "Qo00o[{}]".format(item.index(i))
js = js.replace(str_old, '"' + i + '"')
# 還原后的 JS
with open("fm_new.js", "w", encoding="utf-8") as f:
f.write(js)
使用此腳本替換后,可能會發(fā)現 JS 會報錯,原因是一些換行符、斜杠解析錯誤,以及雙引號重復使用的問題,可以自己手動修改一下。
這里需要注意的一點,fm.js 后面還有個后綴,類似 t=,t= 等,不同的后綴得到的 JS 內容也有差異,各種函數變量名和那個列表元素順序不同,實際上調用的方法是同一個,所以影響不大,只需要注意替換時列表內容、需要替換的那個字符串和你下載的 JS 文件里的一致即可。
將 JS 還原后,我們可以將還原后的 JS 替換掉網站本身經過混淆后的 JS,這里替換方法有很多,比如使用 Fiddler 等抓包工具替換響應、使用 ReRes 之類的插件進行替換、使用瀏覽器開發(fā)者工具自帶的 Overrides 功能進行替換(Chrome 64 之后才有的功能)等,這里我們使用 Fiddler 的 Autoresponder 功能來替換。
實測這個 fm.js 的后綴短時間內不會改變,所以可以直接復制其完整地址來替換,要嚴謹一點的話,我們可以用正則表達式來匹配這個 t 值,在 Fiddler 里面選擇 AutoResponder,點擊 Add Rule,添加替換規(guī)則,正則表達式的方法寫法如下:regex:https:\/\/static\.tongdun\.net\/v3\/fm\.js\?t=\d+
,注意 regex 前綴必不可少,上方依次選中 Enable rules(應用規(guī)則)、Accept all CONNECTs(接受所有連接)、Unmatched requests passthrough(不匹配規(guī)則的就按照之前的請求地址發(fā)送過去),Enable Latency 是設置延遲生效時間,不用勾選,如下圖所示:
替換后再次登錄,下斷點,可以看到現在的 JS 已經清晰了不少,再看看這個函數最后的 return 語句,oQOQ0["blackBox"]
包含了 it
、os
、t
、v
三個參數,使用 JSON 的 stringify 方法將其轉換成字符串,然后調用 QQo0
方法進行加密,如下圖所示:
我們先來看看 oQOQ0["blackBox"]
里的四個參數,其中 it
、os
、v
三個參數在這個函數開始就已經有定義,v
就是 Q0oQQ["version"]
,是定值,直接搜索可以發(fā)現這個值是在最開始的那個大列表里,os
為定值,it
是兩個時間戳相減的值,O000o
這個方法就是兩個值進行相減,oQOQo
這個時間戳可以搜索 var oQOQo
,是一開始加載就生成的時間戳,JS 一開始加載到點擊登陸進入加密函數,也就一分鐘左右,所以這里我們可以直接生成一個五位隨機數(一分鐘左右在毫秒上的差值在五位數左右)。
現在就剩下一個 t
參數了,往下看 t
其實就是 Q0oQQ["tokens"]
,中間經過了一個 if-else 語句,可以埋下斷點進行調試,發(fā)現其實只執(zhí)行了 else 語句,對 t
賦值也就這一句,所以剩下的代碼其實在扣的時候都可以刪掉。
這個 tokens 多次測試發(fā)現是不變的,嘗試直接搜索一下 token 關鍵字,可以發(fā)現其賦值的地方,對 id
按照 | 符號進行分割,取其第 1 個索引值就是 tokens,再看看 id
的值,并沒有找到明顯的生成邏輯,復制其值搜索一下,發(fā)現是通過一個接口返回的,可以直接寫死,也可以自己先去請求一下這個接口,取其返回的值,如下圖所示:
自此所有參數都找完了,回到原來的 return 位置,還差一個加密函數,即 ooOoO["encode"]()
,直接跟進去,將這個方法扣下來即可,本地調試缺啥補啥,將用到的函數補全就行了。
GitHub 關注 K 哥爬蟲,持續(xù)分享爬蟲相關代碼!歡迎 star !https://github.com/kgepachong/
以下只演示部分關鍵代碼,不能直接運行! 完整代碼倉庫地址:https://github.com/kgepachong/crawler/
function oQ0OQ(Q0o0, o0OQ) {
return Q0o0 < o0OQ;
}
function O000O(Q0o0, o0OQ) {
return Q0o0 >> o0OQ;
}
function Qo0oo(Q0o0, o0OQ) {
return Q0o0 | o0OQ;
}
function OOO0Q(Q0o0, o0OQ) {
return Q0o0 << o0OQ;
}
function OooQo(Q0o0, o0OQ) {
return Q0o0 & o0OQ;
}
function Oo0OO(Q0o0, o0OQ) {
return Q0o0 + o0OQ;
}
var oQoo0 = {};
oQoo0["_keyStr"] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0+/=",
oQoo0["encode"] = function QQQ0(Q0o0) {
var o0OQ = 62;
while (o0OQ) {
switch (o0OQ) {
case 116 + 13 - 65: {}
case 118 + 8 - 63: {}
case 94 + 8 - 40: {}
case 122 + 6 - 63: {}
}
}
};
oQoo0["_utf8_encode"] = function oOQ0(Q0o0) {}
function OOoO0() {
var tokens = "e0ia+fB5zvGuTjFDgcKahQwg2UEH8b0k7EK/Ukt4KwzyCbpm11jjy8Au64MC6s7HvLRacUxd7ka4AdDidJmYAA==";
var version = "+X+3JWoUVBc12xtmgMpwzjAone3cp6/4QuFj7oWKNk+C4tqy4un/e29cODlhRmDy";
var Oo0O0 = {};
Oo0O0["blackBox"] = {};
Oo0O0["blackBox"]["v"] = version;
Oo0O0["blackBox"]["os"] = "web";
Oo0O0["blackBox"]["it"] = parseInt(Math.random() * );
Oo0O0["blackBox"]["t"] = tokens;
return oQoo0["encode"](JSON.stringify(Oo0O0["blackBox"]));
}
// 測試樣例
console.log(OOoO0())
# ==================================
# --*-- coding: utf-8 --*--
# @Time : 2021-11-10
# @Author : 微信公眾號:K哥爬蟲
# @FileName: open_login.py
# @Software: PyCharm
# ==================================
import time
import execjs
import requests
login_url = "脫敏處理,完整代碼關注 GitHub:https://github.com/kgepachong/crawler"
def get_black_box():
with open('get_black_box.js', 'r', encoding='utf-8') as f:
exec_js = f.read()
black_box = execjs.compile(exec_js).call('OOoO0')
return black_box
def login(black_box, username, password):
params = {"bust": str(int(time.time() * 1000))}
data = {
"loginName": username,
"passWord": password,
"validateNum": "",
"black_box": black_box
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
}
response = requests.post(url=login_url, params=params, data=data, headers=headers)
print(response.json())
def main():
username = input("請輸入登錄賬號: ")
password = input("請輸入登錄密碼: ")
black_box = get_black_box()
login(black_box, username, password)
if __name__ == '__main__':
main()