關注微信公眾號:K哥爬蟲,QQ交流群:808574309,持續分享爬蟲進階、JS/安卓逆向等技術乾貨!
宣告
本文章中所有內容僅供學習交流,抓包內容、敏感網址、資料介面均已做脫敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關,若有侵權,請聯絡我立即刪除!
逆向目標
- 目標:某 e 網通登入介面
- 主頁:aHR0cHM6Ly93ZWIuZXd0MzYwLmNvbS9yZWdpc3Rlci8jL2xvZ2lu
- 介面:aHR0cHM6Ly9nYXRld2F5LmV3dDM2MC5jb20vYXBpL2F1dGhjZW50ZXIvdjIvb2F1dGgvbG9naW4vYWNjb3VudA==
逆向引數:
- Request Headers:
sign: 3976F10977FC65F9CB967AEF79E508BD
- Request Payload:
password: "A7428361DEF118911783F446A129FFCE"
- Request Headers:
逆向過程
抓包分析
來到某 e 網通的登入頁面,隨便輸入一個賬號密碼登陸,抓包定位到登入介面為 aHR0cHM6Ly9nYXRld2F5LmV3dDM2MC5jb20vYXBpL2F1dGhjZW50ZXIvdjIvb2F1dGgvbG9naW4vYWNjb3VudA==,請求頭裡,有一個 sign,Payload 裡,密碼 password 被加密處理了。
引數逆向
sign
首先來看一下請求頭的 sign,嘗試直接搜尋一下,發現並不是經過某些請求返回的資料,觀察一下其他請求,可以發現同樣有 sign,而且每次請求的值都不一樣:
由此可以初步判斷這個值應該是通過 JS 生成的,全域性搜尋關鍵字 sign:
,可以分別在 request.js、request.ts 兩個檔案裡面看到疑似 sign 賦值的地方,埋下斷點除錯,成功斷下,原理也很簡單,時間戳加上一串固定的字元,經過 MD5 加密後再轉大寫即可。
使用 Python 實現:
import time
import hashlib
timestamp = str(int(time.time() * 1000))
sign = hashlib.md5((timestamp + 'bdc739ff2dcf').encode(encoding='utf-8')).hexdigest().upper()
print(sign)
password
password 是明文密碼經過加密後得到的值,如果嘗試直接去搜尋的話,會發現出來的值非常非常多,要想找到準確的值難度巨大:
可以看到這條請求是 XHR 請求,本次我們使用 XHR 斷點的方法來定位具體的加密位置,通過本次案例,我們來學習一下具體是如何跟進呼叫棧、如何通過上下文來定位具體的加密位置。
切換到 Network 選項卡,找到登陸請求,滑鼠移動到 Initiator 選項卡下的 JS 上,可以看到其呼叫棧,如果站點的加密方式比較簡單,沒有太多混淆的話,呼叫棧裡面就可以看到 login、send、post、encrypt 等等之類的關鍵詞,這種情況下就可以直接點進去,比較容易找到加密的地方,但是大多數站點對於函式名、變數名都做了混淆,和本案例一樣,呼叫棧裡面顯示的都是一些單個或者多個無規則的字母的函式,無法直接定位,此時就需要我們從最後一個函式往前慢慢找。
點選進入最後一個函式,即 Y 函式,它位於呼叫棧的最頂層,表示經過此函式後,瀏覽器就會傳送登入的請求,密碼的加密過程已經處理完畢。在此函式埋下斷點,可以在右側的 Call Stack 看到呼叫棧,從下到上,表示的是點選登陸後,先後呼叫的函式的執行過程:
想要找到具體的加密位置,我們就要依次往前找,挨個函式進行分析,例如往前定位到倒數第二個呼叫棧,即 o 函式,可以看到傳進來的 params 引數裡面就包含了已加密的密碼資訊,這說明加密操作肯定在此函式之前:
根據這種思路,一步一步往下跟進呼叫棧,可以看到在 utils.ts 裡面執行了一個匿名函式,其中呼叫了一個 passwordEncrypt 函式,通過函式名就可以看出基本上就是密碼加密的函式了:
在此處埋下斷點進行除錯,傳進來的是明文密碼,passwordEncrypt 實際上是呼叫的 encode.ts 中的 O 函式:
跟進 O 函式,引用了 crypto-js 加密模組,很明顯的 AES 加密,本地改寫一下就行了。
本次的案列加密比較簡單,但是加密函式隱藏得比較好,需要耐心跟進呼叫棧,通過直接搜尋的話,結果太多,是不太容易定位加密函式的,本次案例中跟進到一個函式後,可以很清楚的看到加密的地方,那麼有的站點可能混淆得更加厲害,是看不出來有加密函式的,這種情況下就需要我們注意引數的變化情況,如果在這個呼叫棧看到的是加密後的引數,在上一個呼叫棧裡面看到的是明文的引數,那麼加密的操作必定在這兩個呼叫棧之間,埋下斷點,仔細分析即可。
完整程式碼
GitHub 關注 K 哥爬蟲,持續分享爬蟲相關程式碼!歡迎 star !https://github.com/kgepachong/
以下只演示部分關鍵程式碼,不能直接執行!完整程式碼倉庫地址:https://github.com/kgepachong...
JavaScript 加密程式碼
CryptoJS = require("crypto-js")
const key = CryptoJS.enc.Utf8.parse("20171109124536982017110912453698");
const iv = CryptoJS.enc.Utf8.parse('2017110912453698'); //十六位十六進位制數作為金鑰偏移量
function getEncryptedPassword(word) {
let srcs = CryptoJS.enc.Utf8.parse(word);
let encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString().toUpperCase();
}
// 測試樣例
// console.log(getEncryptedPassword("123457"))
Python 登入程式碼
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import hashlib
import execjs
import requests
login_url = '脫敏處理,完整程式碼關注 GitHub:https://github.com/kgepachong/crawler'
session = requests.session()
def get_sign():
timestamp = str(int(time.time()*1000))
sign = hashlib.md5((timestamp + 'bdc739ff2dcf').encode(encoding='utf-8')).hexdigest().upper()
return sign
def get_encrypted_parameter(password):
with open('ewt360_encrypt.js', 'r', encoding='utf-8') as f:
ewt360_js = f.read()
encrypted_password = execjs.compile(ewt360_js).call('getEncryptedPassword', password)
return encrypted_password
def login(sign, username, encrypted_password):
headers = {
'sign': sign,
'timestamp': str(int(time.time()*1000)),
'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'
}
data = {
'autoLogin': True,
'password': encrypted_password,
'platform': 1,
'userName': username
}
response = session.post(url=login_url, headers=headers, json=data)
print(response.json())
def main():
username = input('請輸入登入賬號: ')
password = input('請輸入登入密碼: ')
sign = get_sign()
encrypted_password = get_encrypted_parameter(password)
login(sign, username, encrypted_password)
if __name__ == '__main__':
main()