站在用戶的角度思考問題,與客戶深入溝通,找到云龍網(wǎng)站設計與云龍網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網(wǎng)站建設、成都網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、空間域名、網(wǎng)絡空間、企業(yè)郵箱。業(yè)務覆蓋云龍地區(qū)。
關(guān)注微信公眾號:K哥爬蟲,持續(xù)分享爬蟲進階、JS/安卓逆向等技術(shù)干貨!
本文章中所有內(nèi)容僅供學習交流,抓包內(nèi)容、敏感網(wǎng)址、數(shù)據(jù)接口均已做脫敏處理,嚴禁用于商業(yè)用途和非法用途,否則由此產(chǎn)生的一切后果均與作者無關(guān),若有侵權(quán),請聯(lián)系我立即刪除!
aHR0cHM6Ly9kLndlaWRpYW4uY29tLw==
aHR0cHM6Ly9zc28xLndlaWRpYW4uY29tL3VzZXIvbG9naW4=
ua: H4sIAAAAAAAAA91ViZUbMQhtiVOIcnRRxRafr%2FGuN5ukgoyfLUZC...
OB 混淆全稱 Obfuscator,Obfuscator 其實就是混淆的意思,官網(wǎng):https://obfuscator.io/ ,其作者是一位叫 Timofey Kachalov 的俄羅斯 JavaScript 開發(fā)工程師,早在 2016 年就發(fā)布了第一個版本。
一段正常的代碼如下:
function hi() {
console.log("Hello World!");
}
hi();
經(jīng)過 OB 混淆后的結(jié)果:
function _0x3f26() {
var _0x2dad75 = ['kTCKCP', 'Hello\x20World!', '600mDvfGa', 'jYNxbu', 'cEvuvT', 'log', '18sKjcFY', 'eMgFSU', 'FUKcuE', 'OzpdFI', 'JqcGMg'];
_0x3f26 = function () {
return _0x2dad75;
};
return _0x3f26();
}
(function (_0x307c88, _0x4f8223) {
var _0xd = _0x1fe9, _0x330c58 = _0x307c88();
while (!![]) {
try {
var _0x5d6354 = parseInt(_0xd(0x6f)) / 0x1 + parseInt(_0xd(0x6e)) / 0x2 + parseInt(_0xd(0x70)) / 0x3 + -parseInt(_0xd(0x69)) / 0x4 + parseInt(_0xd(0x71)) / 0x5 + parseInt(_0xd(0x6c)) / 0x6 * (parseInt(_0xd(0x6a)) / 0x7) + -parseInt(_0xd(0x73)) / 0x8 * (parseInt(_0xd(0x6d)) / 0x9);
if (_0x5d6354 === _0x4f8223) break; else _0x330c58['push'](_0x330c58['shift']());
} catch (_0x3f18e4) {
_0x330c58['push'](_0x330c58['shift']());
}
}
}(_0x3f26, 0xaa023));
function _0x1fe9(_0xa907e7, _0x410a46) {
var _0x3f261f = _0x3f26();
return _0x1fe9 = function (_0x1fe950, _0x5a08da) {
_0x1fe950 = _0x1fe950 - 0x69;
var _0x82a06 = _0x3f261f[_0x1fe950];
return _0x82a06;
}, _0x1fe9(_0xa907e7, _0x410a46);
}
function hi() {
var _0x12a222 = _0x1fe9;
console[_0x12a222(0x6b)](_0x12a222(0x72));
}
hi();
OB 混淆具有以下特征:
1、一般由一個大數(shù)組或者含有大數(shù)組的函數(shù)、一個自執(zhí)行函數(shù)、解密函數(shù)和加密后的函數(shù)四部分組成;
2、函數(shù)名和變量名通常以 _0x
或者 0x
開頭,后接 1~6 位數(shù)字或字母組合;
3、自執(zhí)行函數(shù),進行移位操作,有明顯的 push、shift 關(guān)鍵字;
例如在上面的例子中,_0x3f26()
方法就定義了一個大數(shù)組,自執(zhí)行函數(shù)里有 push、shift 關(guān)鍵字,主要是對大數(shù)組進行移位操作,_0x1fe9()
就是解密函數(shù),hi()
就是加密后的函數(shù)。
點擊登陸抓包,可以看到有個 ua 參數(shù),經(jīng)過了加密,每次登陸是會改變的,如下圖所示:
如果直接搜索 ua 的話,結(jié)果太多,不方便篩選,通過 XHR 斷點比較容易找到加密的位置,如下圖所示,最后提交的 r 參數(shù)包含 ua 值,往上找可以看到是 i 的值經(jīng)過了 URL 編碼,再往上看,i 的值通過 window.getUa()
獲取,這個實際上是 uad.js 里面的一個匿名函數(shù)。
跟進到 uad.js,可以看到調(diào)用了 window[_0x4651('0x710')]
這個方法,最后返回的 _0x
就是加密后的 ua 值,用鼠標把類似 _0x4651('0x710')
、_0x4651('0x440')
的值選中,可以看到實際上是一些字符串,這些字符串通過直接搜索,可以發(fā)現(xiàn)是在頭部的一個大數(shù)組里,如下圖所示:
一個大數(shù)組,一個有明顯的 push、shift 關(guān)鍵字的進行移位操作的自執(zhí)行函數(shù),是 OB 混淆無疑了,那么我們應該怎樣去處理,讓其看起來更順眼一些呢?
你可以手動在瀏覽器選中查看值,在本地去替換,當然不用全部去替換,跟棧走,用到的地方替換就行了,不要傻傻的全部去挨個手動替換,這種方法適用于不太復雜的代碼。
如果遇到代碼很多的情況,建議使用反混淆工具去處理,這里推薦國內(nèi)的猿人學OB混淆專解工具和國外的 de4js,猿人學的工具還原程度很高,但是部分 OB 混淆還原后運行會報錯,實測本案例的 OB 混淆經(jīng)過猿人學的工具處理后就不能正常運行,可能需要自己預先處理一下才行,de4js 這個工具是越南的一個作者開發(fā)的,開源的,你可以部署到自己的機器上,它支持多種混淆還原,包括 Eval、OB、JSFuck、AA、JJ 等,可以直接粘貼代碼,自動識別混淆方式,本案例推薦使用 de4js,如下圖所示:
我們將還原后的結(jié)果復制到本地文件,使用 Fiddler 的 Autoresponder 功能對響應進行替換,如下圖所示:
如果此時開啟抓包,刷新頁面,你會發(fā)現(xiàn)請求狀態(tài) status 顯示的是 CORS error,JS 替換不成功,在控制臺里還可以看到報錯 No 'Access-Control-Allow-Origin' header is present on the requested resource.
如下圖所示:
CORS (Cross-Origin Resource Sharing,跨域資源共享)是一個 W3C 標準,該標準使用附加的 HTTP 頭來告訴瀏覽器,允許運行在一個源上的 Web 應用訪問位于另一不同源的資源。一個請求 URL 的協(xié)議、域名、端口三者之間任意與當前頁面地址不同即為跨域。常見的跨域問題就是瀏覽器提示在 A 域名下不可以訪問 B 域名的 API,有關(guān) CORS 的進一步理解,可以參考 W3C CORS Enabled。
簡要流程如下:
1、消費者發(fā)送一個 Origin 報頭到提供者端:Origin: http://www.site.com;
2、提供者發(fā)送一個 Access-Control-Allow-Origin
響應報頭給消費者,如果值為 *
或 Origin 對應的站點,則表示允許共享資源給消費者,如果值為 null 或者不存在,則表示不允許共享資源給消費者;
3、除了 Access-Control-Allow-Origin
以外,部分站點還有可能檢測 Access-Control-Allow-Credentials
,為 true 表示允許;
4、瀏覽器根據(jù)提供者的響應報文判斷是否允許消費者跨域訪問到提供者源;
我們根據(jù)前面在控制臺的報錯信息,可以知道是響應頭缺少 Access-Control-Allow-Origin
導致的,在 Fiddler 里面有兩種方法為響應頭添加此參數(shù),下面分別介紹一下:
第一種是利用 Fiddler 的 Filter 功能,在 Response Headers 里設置即可,分別填入 Access-Control-Allow-Origin 和允許的域名,如下圖所示:
第二種是修改 CustomRules.js 文件,依次選擇 Rules —> Customize Rules,在 static function OnBeforeResponse(oSession: Session)
模塊下增加以下代碼:
if(oSession.uriContains("要處理的 URL")){
oSession.oResponse["Access-Control-Allow-Origin"] = "允許的域名";
}
兩種方法二選一,設置完畢后,就可以成功替換了,刷新再次調(diào)試就可以看到是還原后的 JS 了,如下圖所示:
很明顯 window.getUa
是主要的加密函數(shù),所以我們先來分析一下這個函數(shù):
window.getUa = function() {
var _0x7dfc34 = new Date().getTime();
if (_0x4a9622) {
_0x2644f4();
}
_0x55b608();
var _0x = _0x1722c3(_0x2e98dd) + '|' + _0x1722c3(_0x) + '|' + _0x7dfc34.toString(0x10);
_0x = btoa(_0x570bef.gzip(_0x, {
'to': 'string'
}));
return _0x;
};
_0x7dfc34
是時間戳,接著一個 if 判斷,我們可以鼠標放到判斷里去看看,發(fā)現(xiàn)判斷的 _0x4a9622
是 false,那么 _0x2644f4()
就不會被執(zhí)行,然后執(zhí)行了 _0x55b608()
方法,_0x
的值,主要調(diào)用了 _0x1722c3()
方法得到的,前后依次傳入了 _0x2e98dd
和 _0x
,很明顯這兩個值比較關(guān)鍵,分別搜索一下,可以發(fā)現(xiàn):
_0x2e98dd
定義了一些 header、瀏覽器的信息、屏幕信息、系統(tǒng)字體信息等,這些信息可以作為定值直接傳入,如下圖所示:
_0x
搜索有用的結(jié)果就是僅定義了一個空對象,在控制臺輸出一下可以看到實際上包含了一些鍵盤、鼠標點擊移動的數(shù)據(jù),實際上經(jīng)過測試發(fā)現(xiàn), _0x
的值并不是強校驗的,可以使用隨機數(shù)模擬生成,也可以直接復制一個定值。
_0x2e98dd
和 _0x
這兩個參數(shù)都沒有進行強校驗,完全可以以定值的方式傳入,這兩個值都是 JSON 格式,我們可以直接在控制臺使用 copy
語句復制其值,或者使用 JSON.stringify()
語句輸出結(jié)果再手動復制。
里面各個函數(shù)相互調(diào)用,比較多,可以直接把整個 JS copy 下來,我們注意到整個函數(shù)是一個自執(zhí)行函數(shù),在本地調(diào)用時,我們可以定義一個全局變量,然后在 window.getUa
函數(shù)里,將 _0x
的值賦值給全局變量,也就相當于導出值,最后取這個全局變量即可,還有一種方法就是不讓它自執(zhí)行,改寫成正常一般的函數(shù),然后調(diào)用 window.getUa
方法得到 ua 值。
首先我們把 _0x2e98dd
和 _0x
的值在本地定義一下,這里有個小細節(jié),需要把原 JS 代碼里這兩個值定義的地方注釋掉,防止起沖突。
在本地調(diào)試時,會提示 window
、location
、document
未定義,定義一下為空對象即可,然后又提示 attachEvent
未定義,搜索一下,是 _0x13cd5a
的一個原型對象,除了 attachEvent
以外,還有個 addEventListener
,addEventListener()
方法用于向指定元素添加事件句柄,在 IE 中使用 attachEvent()
方法來實現(xiàn),我們在 Google Chrome 里面埋下斷點調(diào)試一下,刷新頁面會直接進入 addEventListener()
方法,其中的事件是 keydown
,即鍵盤按下,就調(diào)用后面的 _0x5cec90
方法,輸出一下后面返回的 this,實際上并沒有產(chǎn)生什么有用的值,所以 _0x13cd5a.prototype.bind
方法我們可以直接將其注釋掉,實際測試也沒有影響。
接著本地調(diào)試,又會提示 btoa
未定義,btoa
和 atob
是 window 對象的兩個函數(shù),其中 btoa
是 binary to ascii,用于將 binary 的數(shù)據(jù)用 ascii 碼表示,即 Base64 的編碼過程,而 atob
則是 ascii to binary,用于將 ascii 碼解析成 binary 數(shù)據(jù),即 Base64 的解碼過程。
在 NodeJS 里,提供了一個稱為 Buffer
的本地模塊,可用于執(zhí)行 Base64 編碼和解碼,這里不做詳細介紹,可自行百度,window.getUa
方法里的原 btoa
語句是這樣的:
_0x = btoa(_0x570bef.gzip(_0x, {'to': 'string'}));
在 NodeJS 里,我們可以這樣寫:
_0x = Buffer.from(_0x570bef.gzip(_0x, {'to': 'string'}), "latin1").toString('base64');
注意:Buffer.from()
傳入了一個 latin1
參數(shù),這是由于 _0x570bef.gzip(_0x, {'to': 'string'})
的結(jié)果是 Latin1(ISO-8859-1 的別名)編碼,如果不傳,或者傳入其他參數(shù),則最終結(jié)果可能和 btoa
方法得出的結(jié)果不一樣!
自此,本地聯(lián)調(diào)完畢,就可以得到正確的 ua 值了!
GitHub 關(guān)注 K 哥爬蟲,持續(xù)分享爬蟲相關(guān)代碼!歡迎 star !https://github.com/kgepachong/
以下只演示部分關(guān)鍵代碼,不能直接運行! 完整代碼倉庫地址:https://github.com/kgepachong/crawler/
var window = {};
var location = {};
var document = {};
var _0x5a577d = function () {}();
var _0xe26ae = function () {}();
var _0x3204b9 = function () {}();
var _0x3c7e70 = function () {}();
var _0x4a649b = function () {}();
var _0xf = function () {}();
var _0x2b0d61 = function () {}();
var _0xa = function () {}();
var _0x570bef = function () {}();
var _0xd05c32 = function (_0x5c6c0c) {};
window.CHLOROFP_STATUS = 'start';
// 此處省略 N 個函數(shù)
var _0x2e98dd = {
// 對象具體的值已省略
"basic": {},
"header": {},
"navigator": {},
"screenData": {},
"sysfonts": [],
"geoAndISP": {},
"browserType": {},
"performanceTiming": {},
"canvasFp": {},
"visTime": [],
"other": {}
}
var _0x = {
// 對象具體的值已省略
"keypress": true,
"scroll": true,
"click": true,
"mousemove": true,
"mousemoveData": [],
"keypressData": [],
"mouseclickData": [],
"wheelDeltaData": []
}
window.getUa = function () {
var _0x7dfc34 = new Date().getTime();
if (_0x4a9622) {
_0x2644f4();
}
_0x55b608();
var _0x = _0x1722c3(_0x2e98dd) + '|' + _0x1722c3(_0x) + '|' + _0x7dfc34.toString(0x10);
// _0x = btoa(_0x570bef.gzip(_0x, {'to': 'string'}));
_0x = Buffer.from(_0x570bef.gzip(_0x, {'to': 'string'}), "latin1").toString('base64');
return _0x;
};
// 測試輸出
// console.log(window.getUa())
# ==================================
# --*-- coding: utf-8 --*--
# @Time : 2021-11-15
# @Author : 微信公眾號:K哥爬蟲
# @FileName: weidian_login.py
# @Software: PyCharm
# ==================================
import execjs
import requests
from urllib import parse
index_url = "脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler"
login_url = "脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler"
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
session = requests.session()
def get_encrypted_ua():
with open('get_encrypted_ua.js', 'r', encoding='utf-8') as f:
uad_js = f.read()
ua = execjs.compile(uad_js).call('window.getUa')
ua = parse.quote(ua)
return ua
def get_wd_token():
headers = {"User-Agent": UserAgent}
response = session.get(url=index_url, headers=headers)
wd_token = response.cookies.get_dict()["wdtoken"]
return wd_token
def login(phone, password, ua, wd_token):
headers = {
"user-agent": UserAgent,
"origin": "脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler",
"referer": "脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler",
}
data = {
"phone": phone,
"countryCode": "86",
"password": password,
"version": "1",
"subaccountId": "",
"clientInfo": '{"clientType": 1}',
"captcha_session": "",
"captcha_answer": "",
"vcode": "",
"mediaVcode": "",
"ua": ua,
"scene": "PCLogin",
"wdtoken": wd_token
}
response = session.post(url=login_url, headers=headers, data=data)
print(response.json())
def main():
phone = input("請輸入登錄手機號: ")
password = input("請輸入登錄密碼: ")
ua = get_encrypted_ua()
wd_token = get_wd_token()
login(phone, password, ua, wd_token)
if __name__ == '__main__':
main()