文章插圖

文章插圖
聲明
本文章中所有內容僅供學習交流 , 抓包內容、敏感網址、數據接口均已做脫敏處理 , 嚴禁用于商業用途和非法用途 , 否則由此產生的一切后果均與作者無關 , 若有侵權 , 請聯系我立即刪除!
逆向目標
本次的逆向目標是WB的登錄 , 雖然登錄的加密參數沒有太多 , 但是登錄的流程稍微復雜一點 , 經歷了很多次中轉 , 細分下來大約要經過九次處理才能成功登錄 。
在登錄過程中遇到的加密參數只有一個 , 即密碼加密 , 加密后的密碼在獲取 token 的時候會用到 , 獲取 token 是一個 POST 請求 , 其 Form Data 里的 sp 值就是加密后的密碼 , 類似于:e23c5d62dbf9f8364005f331e487873c70d7ab0e8dd2057c3e66d1ae5d2837ef1dcf86……
登錄流程
首先來理清一下登錄流程 , 每一步特殊的參數進都行了說明 , 沒有提及的參數表示是定值 , 直接復制即可 。
大致流程如下:
預登陸獲取加密密碼獲取 token獲取加密后的賬號發送驗證碼校驗驗證碼訪問 redirect url訪問 crossdomain2 url通過 passport url 登錄1.預登陸
xxxxSSOController.preloginCallBack({"retcode": 0,"servertime": 1627461942,"pcid": "gz-1cd535198c0efe850b96944c7945e8fd514b","nonce": "GWBOCL","pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245......","rsakv": 1330428213,"exectime": 16})2.獲取加密后的密碼密碼的加密使用的是 RSA 加密 , 可以通過 Python 或者 JS 來獲取加密后的密碼 , JS 加密的逆向在后面拿出來單獨分析 。
3.獲取 token
su:用戶名經過 base64 加密得到servertime:通過第1步預登陸返回的 JSON 里面獲取nonce:通過第1步預登陸返回的 JSON 里面獲取rsakv:通過第1步預登陸返回的 JSON 里面獲取sp:加密后的密碼prelt:隨機值
返回數據為 HTML 源碼 , 可以從里面提取 token 值 , 類似于:2NGFhARzFAFAIp_QwX70Npj8gw4lgj7RbCnByb3RlY3Rpb24. , 如果返回的 token 不是這種 , 則說明賬號或者密碼錯誤 。
4.獲取加密后的賬號
5.發送驗證碼
6.校驗驗證碼
{"retcode": 20000000,"msg": "succ","data": {"redirect_url": "https://login.xxxx.com.cn/sso/login.php?entry=xxxxx&returntype=META&crossdomain=1&cdult=3&alt=ALT-NTcxNjMyMTA2OA==-1630292617-yf-78B1DDE6833847576B0DC4B77A6C77C4-1&savestate=30&url=https://xxxxx.com"}}7.訪問 redirect url返回的數據是 HTML 源碼 , 我們要從中提取 crossdomain2 的 URL , 提取的結果類似于:https://login.xxxx.com.cn/crossdomain2.php?action=login&entry=xxxxx…… , 同樣的 , 這個 URL 也是接下來需要訪問的頁面 。
8.訪問 crossdomain2 url
返回的數據同樣是 HTML 源碼 , 我們要從中提取真正的登錄的 URL , 提取的結果類似于:https://passport.xxxxx.com/wbsso/login?ssosavestate=1661828618&url=https…… , 最后一步只需要訪問這個真正的登錄 URL 就能實現登錄操作了 。
9.通過 passport url 登錄
返回的數據包含了登錄結果、用戶 ID 和用戶名 , 類似于:
({"result":true,"userinfo":{"uniqueid":"5712321368","displayname":"tomb"}});自此 , WB的完整登錄流程已完成 , 可以直接拿登錄成功后的 cookies 進行其他操作了 。加密密碼逆向
在登錄流程中 , 第2步是獲取加密后的密碼 , 在登錄的第3步獲取 token 里 , 請求的 Query String Parameters 包含了一個加密參數 sp , 這個就是加密后的密碼 , 接下來我們對密碼的加密進行逆向分析 。
直接全局搜索 sp 關鍵字 , 發現有很多值 , 這里我們又用到了前面講過的技巧 , 嘗試搜索 sp=、sp: 或者 var sp 等來縮小范圍 , 在本案例中 , 我們嘗試搜索 sp= , 可以看到在 index.js 里面只有一個值 , 埋下斷點進行調試 , 可以看到 sp 其實就是 b 的值:
PS:搜索時要注意 , 不能在登錄成功后的頁面進行搜索 , 此時資源已刷新 , 重新加載了 , 加密的 JS 文件已經沒有了 , 需要在登錄界面輸入錯誤的賬號密碼來抓包、搜索、斷點 。
f.setPublic(me.rsaPubkey, "10001");b = f.encrypt([me.servertime, me.nonce].join("t") + "n" + b)me.rsaPubkey、me.servertime、me.nonce 都是第1步預登陸返回的數據 。把鼠標移到 f.setPublic 和 f.encrypt , 可以看到分別是 br 和 bt 函數:
navigator = {appName: "Netscape",appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"}繼續調試會發現在 var c = this.doPublic(b); 提示對象不支持此屬性或方法 , 搜索 doPublic 發現有一句 bq.prototype.doPublic = bs; , 這里直接將其改為 doPublic = bs; 即可 。分析整個 RSA 加密邏輯 , 其實也可以通過 Python 來實現 , 代碼示例(pubkey 需要補全):
import rsaimport binasciipre_parameter = {"retcode": 0,"servertime": 1627461942,"pcid": "gz-1cd535198c0efe850b96944c7945e8fd514b","nonce": "GWBOCL","pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245......","rsakv": 1330428213,"exectime": 16}password = '12345678'public_key = rsa.PublicKey(int(pre_parameter['pubkey'], 16), int('10001', 16))text = '%st%sn%s' % (pre_parameter['servertime'], pre_parameter['nonce'], password)encrypted_str = rsa.encrypt(text.encode(), public_key)encrypted_password = binascii.b2a_hex(encrypted_str).decode()print(encrypted_password)完整代碼GitHub 關注 K 哥爬蟲 , 持續分享爬蟲相關代碼!歡迎 star !https://github.com/kgepachong/
**以下只演示部分關鍵代碼 , 不能直接運行!**完整代碼倉庫地址:https://github.com/kgepachong/crawler/
關鍵 JS 加密代碼架構
navigator = {appName: "Netscape",appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"}function bt(a) {}function bs(a) {}function br(a, b) {}// 此處省略 N 個函數bl.prototype.nextBytes = bk;doPublic = bs;bq.prototype.setPublic = br;bq.prototype.encrypt = bt;this.RSAKey = bqfunction getEncryptedPassword(me, b) {br(me.pubkey, "10001");b = bt([me.servertime, me.nonce].join("t") + "n" + b);return b}// 測試樣例// var me = {//"retcode": 0,//"servertime": 1627283238,//"pcid": "gz-a9243276722ed6d4671f21310e2665c92ba4",//"nonce": "N0Y3SZ",//"pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245A87AC253062882729293E5506350508E7F9AA3BB77F4333231490F915F6D63C55FE2F08A49B353F444AD3993CACC02DB784ABBB8E42A9B1BBFFFB38BE18D78E87A0E41B9B8F73A928EE0CCEE1F6739884B9777E4FE9E88A1BBE495927AC4A799B3181D6442443",//"rsakv": "1330428213",//"exectime": 13// }// var b = '12312312312'// 密碼// console.log(getEncryptedPassword(me, b))Python 登錄關鍵代碼【js文件里面加載js文件 html重新加載js】
#!/usr/bin/env python3# -*- coding: utf-8 -*-import reimport jsonimport timeimport base64import binasciiimport rsaimport execjsimport requestsfrom lxml import etree# 判斷某些請求是否成功的標志response_success_str = 'succ'pre_login_url = '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler'get_token_url = '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler'protection_url = '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler'send_code_url = '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler'confirm_url = '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler'headers = {'Host': '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler','Referer': '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler','sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}session = requests.session()def get_pre_parameter(username: str) -> dict:su = base64.b64encode(username.encode())time_now = str(int(time.time() * 1000))params = {'entry': '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler','callback': '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler','su': su,'rsakt': 'mod','checkpin': 1,'client': 'ssologin.js(v1.4.19)','_': time_now,}response = session.get(url=pre_login_url, params=params, headers=headers).textparameter_dict = json.loads(re.findall(r'((.*))', response)[0])# print('1.【pre parameter】: %s' % parameter_dict)return parameter_dictdef get_encrypted_password(pre_parameter: dict, password: str) -> str:# 通過 JS 獲取加密后的密碼# with open('encrypt.js', 'r', encoding='utf-8') as f:#js = f.read()# encrypted_password = execjs.compile(js).call('getEncryptedPassword', pre_parameter, password)# # print('2.【encrypted password】: %s' % encrypted_password)# return encrypted_password# 通過 Python 的 rsa 模塊和 binascii 模塊獲取加密后的密碼public_key = rsa.PublicKey(int(pre_parameter['pubkey'], 16), int('10001', 16))text = '%st%sn%s' % (pre_parameter['servertime'], pre_parameter['nonce'], password)encrypted_str = rsa.encrypt(text.encode(), public_key)encrypted_password = binascii.b2a_hex(encrypted_str).decode()# print('2.【encrypted password】: %s' % encrypted_password)return encrypted_passworddef get_token(encrypted_password: str, pre_parameter: dict, username: str) -> str:su = base64.b64encode(username.encode())data = http://www.mnbkw.com/jxjc/187778/{'entry': '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler','gateway': 1,'from': '','savestate': 7,'qrcode_flag': False,'useticket': 1,'pagerefer': '','vsnf': 1,'su': su,'service': 'miniblog','servertime': pre_parameter['servertime'],'nonce': pre_parameter['nonce'],'pwencode': 'rsa2','rsakv': pre_parameter['rsakv'],'sp': encrypted_password,'sr': '1920*1080','encoding': 'UTF-8','prelt': 38,'url': '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler','returntype': 'META'}response = session.post(url=get_token_url, headers=headers, data=http://www.mnbkw.com/jxjc/187778/data)# response.encoding ='gbk'ajax_login_url = re.findall(r'replace("(.*)")', response.text)[0]token = ajax_login_url.split('token%3D')[-1]if 'weibo' not in token:# print('3.【token】: %s' % token)return tokenelse:raise Exception('登錄失敗! 用戶名或者密碼錯誤!')def get_encrypted_mobile(token: str) -> str:params = {'token': token,'callback_url': '脫敏處理 , 完整代碼關注 GitHub:https://github.com/kgepachong/crawler'}response = session.get(url=protection_url, params=params, headers=headers)tree = etree.HTML(response.text)encrypted_mobile = tree.xpath("http://input[@name='encrypt_mobile'][email protected]")[0]# print('4.【encrypted mobile】: %s' % encrypted_mobile)return encrypted_mobiledef send_code(token: str, encrypt_mobile: str) -> str:params = {'token': token}data = http://www.mnbkw.com/jxjc/187778/{'encrypt_mobile': encrypt_mobile}response = session.post(url=send_code_url, params=params, data=http://www.mnbkw.com/jxjc/187778/data, headers=headers).json()if response['msg'] == response_success_str:code = input('請輸入驗證碼: ')# print('5.【code】: %s' % code)return codeelse:# print('5.【failed to send verification code】: %s' % response)raise Exception('驗證碼發送失敗: %s' % response)def confirm_code(encrypted_mobile: str, code: str, token: str) -> str:params = {'token': token}data = http://www.mnbkw.com/jxjc/187778/{'encrypt_mobile': encrypted_mobile,'code': code}response = session.post(url=confirm_url, params=params, data=http://www.mnbkw.com/jxjc/187778/data, headers=headers).json()if response['msg'] == response_success_str:redirect_url = response['data']['redirect_url']# print('6.【redirect url】: %s' % redirect_url)return redirect_urlelse:# print('6.【驗證碼校驗失敗】: %s' % response)raise Exception('驗證碼校驗失敗: %s' % response)def get_cross_domain2_url(redirect_url: str) -> str:response = session.get(url=redirect_url, headers=headers).textcross_domain2_url = re.findall(r'replace("(.*)")', response)[0]# print('7.【cross domain2 url】: %s' % cross_domain2_url)return cross_domain2_urldef get_passport_url(cross_domain2_url: str) -> str:response = session.get(url=cross_domain2_url, headers=headers).textpassport_url_str = re.findall(r'setCrossDomainUrlList((.*))', response)[0]passport_url = json.loads(passport_url_str)['arrURL'][0]# print('8.【passport url】: %s' % passport_url)return passport_urldef login(passport_url: str) -> None:response = session.get(url=passport_url, headers=headers).textlogin_result = json.loads(response.replace('(', '').replace(');', ''))if login_result['result']:user_unique_id = login_result['userinfo']['uniqueid']user_display_name = login_result['userinfo']['displayname']print('登錄成功!用戶 ID:%s , 用戶名:%s' % (user_unique_id, user_display_name))else:raise Exception('登錄失?。?s' % login_result)def main():username = input('請輸入登錄賬號: ')password = input('請輸入登錄密碼: ')# 1.預登陸 , 獲取一個字典參數 , 包含后面要用的 servertime、nonce、pubkey、rsakvpre_parameter = get_pre_parameter(username)# 2.通過 JS 或者 Python 獲取加密后的密碼encrypted_password = get_encrypted_password(pre_parameter, password)# 3.獲取 tokentoken = get_token(encrypted_password, pre_parameter, username)# 4.通過 protection url 獲取加密后的手機號encrypted_mobile = get_encrypted_mobile(token)# 5.發送手機驗證碼code = send_code(token, encrypted_mobile)# 6.校驗驗證碼 , 校驗成功則返回一個重定向的 URLredirect_url = confirm_code(encrypted_mobile, code, token)# 7.訪問重定向的 URL , 提取 crossdomain2 URLcross_domain2_url = get_cross_domain2_url(redirect_url)# 8.訪問 crossdomain2 URL , 提取 passport URLpassport_url = get_passport_url(cross_domain2_url)# 9.訪問 passport URL 進行登錄操作login(passport_url)if __name__ == '__main__':main()
- bat格式用什么打開 如何用bat打開文件
- linux如何改文件名字 linux如何改文件名稱
- ftp無法訪問此文件夾 無法與服務器建立連接 ftp無法訪問此文件夾,參數錯誤
- office圖標不見了,但是office里面的東西都還在 office圖標不見了影響使用嗎
- mac鏡像文件iso下載迅雷 mac鏡像文件iso下載后怎么安裝在虛擬機上
- 格式化文件恢復工具 恢復格式化的軟件
- linux如何創建文件夾快捷方式 linux如何創建文件夾和文件
- 文件夾加密軟件哪個最好用 加密文件用什么軟件好
- nfs是什么文件系統 nfs全稱是什么
- pkg文件用什么打開手機 pkg文件用什么打開win10
