javascript
【JS 逆向百例】转变思路,少走弯路,X米加密分析
文章目錄
- 聲明
- 逆向目標
- 逆向過程
- 抓包分析
- 參數(shù)逆向
- 基本參數(shù)
- hash
- 總結(jié)
- 完整代碼
- Python 登錄關(guān)鍵代碼
聲明
本文章中所有內(nèi)容僅供學習交流,抓包內(nèi)容、敏感網(wǎng)址、數(shù)據(jù)接口均已做脫敏處理,嚴禁用于商業(yè)用途和非法用途,否則由此產(chǎn)生的一切后果均與作者無關(guān),若有侵權(quán),請聯(lián)系我立即刪除!
逆向目標
- 目標:X米賬號登錄
- 主頁:aHR0cHM6Ly9hY2NvdW50LnhpYW9taS5jb20v
- 接口:aHR0cHM6Ly9hY2NvdW50LnhpYW9taS5jb20vcGFzcy9zZXJ2aWNlTG9naW5BdXRoMg==
- 逆向參數(shù):Form Data:hash: FCEA920F7412B5DA7BE0CF42B8C93759
逆向過程
抓包分析
來到X米的登錄頁面,隨便輸入一個賬號密碼登陸,抓包定位到登錄接口為 aHR0cHM6Ly9hY2NvdW50LnhpYW9taS5jb20vcGFzcy9zZXJ2aWNlTG9naW5BdXRoMg==
POST 請求,Form Data 里的參數(shù)比較多,分析一下主要參數(shù):
- serviceParam: {"checkSafePhone":false,"checkSafeAddress":false,"lsrp_score":0.0},從參數(shù)的字面意思來看,似乎是在檢查手機和地址是否安全,至于具體是什么含義,暫時不得而知,也不知道是在哪個地方設(shè)置的。
- callback: http://order.xxx.com/login/callback?followup=https%3A%2F%2Fwww.xx......,回調(diào)鏈接,一般來說是固定的,后面帶有 followup 和 sid 參數(shù)。
- qs: %3Fcallback%3Dhttp%253A%252F%252Forder.xxx.com%252Flogin%252Fcallback%2......,把 qs 的值格式化一下可以發(fā)現(xiàn),其實是 callback、sign、sid、_qrsize 四個值按照 URL 編碼進行組合得到的。
- _sign: w1RBM6cG8q2xj5JzBPPa65QKs9w=,這個一串看起來是經(jīng)過某種加密后得到的,也有可能是網(wǎng)頁源碼中的值。
- user: 15555555555,明文用戶名。
- hash: FCEA920F7412B5DA7BE0CF42B8C93759,加密后的密碼。
參數(shù)逆向
基本參數(shù)
先來看一下 serviceParam 等基本參數(shù),一般思路我們是先直接搜索一下看看能不能直接找到這個值,搜索發(fā)現(xiàn) serviceParam 關(guān)鍵字在一個 302 重定向請求里:
我們注意到,當只輸入登錄的主頁 aHR0cHM6Ly9hY2NvdW50LnhpYW9taS5jb20v,它會有兩次連續(xù)的 302 重定向,來重點分析一下這兩次重定向。
第一次重定向,新的網(wǎng)址里有 followup、callback、sign、sid 參數(shù),這些我們都是在后面的登錄請求中要用到的。
第二次重定向,新的網(wǎng)址里同樣有 followup、callback、sign、sid 參數(shù),此外還有 serviceParam、qs 參數(shù),同樣也是后面的登錄請求需要用到的。
找到了參數(shù)的來源,直接從第二次重定向的鏈接里提取各項參數(shù),這里用到了 response.history[1].headers['Location'] 來提取頁面第二次重定向返回頭里的目標地址,urllib.parse.urlparse 來解析重定向鏈接 URL 的結(jié)構(gòu),urllib.parse.parse_qs 提取參數(shù),返回字典,代碼樣例:
import requests import urllib.parseheaders = {'Host': '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler','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' } index_url = '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler' response = requests.get(url=index_url, headers=headers) location_url = response.history[1].headers['Location'] urlparse = urllib.parse.urlparse(location_url) query_dict = urllib.parse.parse_qs(urlparse.query) print(query_dict)need_theme = query_dict['needTheme'][0] show_active_x = query_dict['showActiveX'][0] service_param = query_dict['serviceParam'][0] callback = query_dict['callback'][0] qs = query_dict['qs'][0] sid = query_dict['sid'][0] _sign = query_dict['_sign'][0]print(need_theme, show_active_x, service_param, callback, qs, sid, _sign)hash
其他參數(shù)都齊全了,現(xiàn)在還差一個加密后的密碼 hash,一般來講這種都是通過 JS 加密的,老方法,全局搜索 hash 或者 hash:,可以在 78.4da22c55.chunk.js 文件里面看到有一句:hash: S()(r.password).toUpperCase(),很明顯是將明文的密碼經(jīng)過加密處理后再全部轉(zhuǎn)為大寫:
重點是這個 S(),鼠標移上去會發(fā)現(xiàn)其實是調(diào)用了 78.4da22c55.chunk.js 的一個匿名函數(shù),我們在匿名函數(shù)的 return 位置埋下斷點進行調(diào)試:
e.exports = function(e, n) {if (void 0 === e || null === e)throw new Error("Illegal argument " + e);var r = t.wordsToBytes(u(e, n));return n && n.asBytes ? r : n && n.asString ? s.bytesToString(r) : t.bytesToHex(r) }可以看到傳進來的 e 是明文的密碼,最后的 return 語句是一個三目運算符,由于 n 是 undefined,所以最后 return 的實際上是 t.bytesToHex(r),其值正是加密后的密碼,只不過所有字母都是小寫,按照正常思維,我們肯定是開始扣 JS 了,這里傳入了參數(shù) r,var r = t.wordsToBytes(u(e, n));,先跟進 u 這個函數(shù)看看:
可以看到 u 函數(shù)實際上是用到了 567 這個對象方法,在這個對象方法里面,還用到了 129、211、22 等非常多的方法,這要是挨個去扣,那還不得扣到猴年馬月,而且還容易出錯,代碼太多也不好定位錯誤的地方,所以這里需要轉(zhuǎn)變一下思路,先來看看 t.bytesToHex(r) 是個什么東東,跟進到這個函數(shù):
bytesToHex: function(e) {for (var t = [], n = 0; n < e.length; n++)t.push((e[n] >>> 4).toString(16)),t.push((15 & e[n]).toString(16));return t.join("") }解讀一下這段代碼,傳進來的 e 是一個 16 位的 Array 對象,定義了一個 t 空數(shù)組,經(jīng)過一個循環(huán),依次取 Array 對象里的值,第一次經(jīng)過無符號右移運算(>>>)后,轉(zhuǎn)為十六進制的字符串,將結(jié)果添加到 t 數(shù)組的末尾。第二次進行位運算(&)后,同樣轉(zhuǎn)為十六進制的字符串,將結(jié)果添加到 t 數(shù)組的末尾。也就是說,原本傳進來的 16 位的 Array 對象,每一個值都經(jīng)過了兩次操作,那么最后結(jié)果的 t 數(shù)組中就會有 32 個值,最后再將 t 數(shù)組轉(zhuǎn)換成字符串返回。
結(jié)合一下調(diào)用的函數(shù)名稱,我們來捋一下整個流程,首先調(diào)用 wordsToBytes() 方法將明文密碼字符串轉(zhuǎn)為 byte 數(shù)組,無論密碼的長度如何,最后得到的 byte 數(shù)組都是 16 位的,然后調(diào)用 bytesToHex() 方法,循環(huán)遍歷生成的 byte 類型數(shù)組,讓其生成 32 位字符串。
無論密碼長度如何,最終得到的密文都是 32 位的,而且都由字母和數(shù)字組成,這些特點很容易讓人想到 MD5 加密,將明文轉(zhuǎn)換成 byte 數(shù)組后進行隨機哈希,對 byte 數(shù)組進行摘要,得到摘要 byte 數(shù)組,循環(huán)遍歷 byte 數(shù)組,生成固定位數(shù)的字符串,這不就是 MD5 的加密過程么?
直接把密碼拿來進行 MD5 加密,和網(wǎng)站的加密結(jié)果進行對比,可以發(fā)現(xiàn)確實是一樣的,驗證了我們的猜想是正確的:
既然如此,直接可以使用 Python 的 hashlib 模塊來實現(xiàn)就 OK 了,根本不需要去死扣代碼,代碼樣例:
import hashlibpassword = "1234567" encrypted_password = hashlib.md5(password.encode(encoding='utf-8')).hexdigest().upper() print(encrypted_password) # FCEA920F7412B5DA7BE0CF42B8C93759總結(jié)
有的時候需要我們轉(zhuǎn)變思路,不一定每次都要死扣 JS 代碼,相對較容易的站點的加密方式無非就是那么幾種,有的是稍微進行了改寫,有的是把密鑰、偏移量等參數(shù)隱藏了,有的是把加密解密過程給你混淆了,讓你難以理解,如果你對常見的加密方式和原理比較熟悉的話,有時候只需要搞清楚他用的什么加密方式,或者拿到了密鑰、偏移量等關(guān)鍵參數(shù),就完全可以自己還原整個加密過程!
完整代碼
GitHub 關(guān)注 K 哥爬蟲,持續(xù)分享爬蟲相關(guān)代碼!歡迎 star !
https://github.com/kgepachong
以下只演示部分關(guān)鍵代碼,完整代碼倉庫地址:
https://github.com/kgepachong/crawler
Python 登錄關(guān)鍵代碼
#!/usr/bin/env python3 # -*- coding: utf-8 -*-import json import hashlib import urllib.parseimport requestsindex_url = '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler' login_url = '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler' headers = {'Host': '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler','Origin': '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler','Referer': '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler','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_encrypted_password(password):encrypted_password = hashlib.md5(password.encode(encoding='utf-8')).hexdigest().upper()return encrypted_passworddef get_parameter():response = requests.get(url=index_url, headers=headers)location_url = response.history[1].headers['Location']urlparse = urllib.parse.urlparse(location_url)query_dict = urllib.parse.parse_qs(urlparse.query)# print(query_dict)return query_dictdef login(username, encrypted_password, query_dict):data = {'bizDeviceType': '','needTheme': query_dict['needTheme'][0],'theme': '','showActiveX': query_dict['showActiveX'][0],'serviceParam': query_dict['serviceParam'][0],'callback': query_dict['callback'][0],'qs': query_dict['qs'][0],'sid': query_dict['sid'][0],'_sign': query_dict['_sign'][0],'user': username,'cc': '+86','hash': encrypted_password,'_json': True}response = session.post(url=login_url, data=data, headers=headers)response_json = json.loads(response.text.replace('&&&START&&&', ''))print(response_json)return response_jsondef main():username = input('請輸入登錄賬號: ')password = input('請輸入登錄密碼: ')encrypted_password = get_encrypted_password(password)parameter = get_parameter()login(username, encrypted_password, parameter)if __name__ == '__main__':main()總結(jié)
以上是生活随笔為你收集整理的【JS 逆向百例】转变思路,少走弯路,X米加密分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 女强人募资出手就是25个亿!腾讯、娃哈哈
- 下一篇: 【JS 逆向百例】网洛者反爬练习平台第一