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