聲明
本文章中所有內容僅供學習交流使用,不用於其他任何目的,不提供完整代碼,抓包內容、敏感網址、數據接口等均已做脱敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關!
本文章未經許可禁止轉載,禁止任何修改後二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K哥爬蟲】聯繫作者立即刪除!
前言
K 哥之前在【JS 逆向百例】專欄中寫過一篇文章:【JS 逆向百例】房某下登錄接口參數逆向,該站如果通過輸入賬號和密碼的方式進行登錄,POST 請求參數中,密碼 pwd 被加密處理了,對其進行了逆向分析。最近在某博客平台上,有粉絲在該篇文章的評論區詢問能不能出一期該站的滑塊逆向文章,經過研究發現通過手機動態碼的方式登錄,點擊獲取短信驗證碼時,會彈出滑塊驗證,本文將對另一種登錄方式的反爬策略進行研究分析,既是滿足粉絲需求,也是對該站登錄逆向的補充完善。
逆向目標
- 目標:房某下手機動態碼登錄,滑塊驗證碼逆向分析
- 網站:aHR0cHM6Ly9wYXNzcG9ydC5mYW5nLmNvbS8=
抓包分析
隨便輸入一串手機號碼,點擊獲取短信驗證碼,即會彈出滑塊驗證,getslidecodeinit.api 接口響應返回 challenge 和 gt 參數的值,這兩個參數在後面校驗滑塊驗證和獲取短信驗證碼的時候會用到:
c=index&a=jigsaw 接口響應返回的參數中,surl 為滑塊驗證碼的背景圖片,url 為滑塊圖片,完整的下載地址需要在前面加上 https://static.soufunimg.com/common_m/m_recaptcha/jigsawimg/:
需要注意的是,下載下來的背景圖片(320x160)以及滑塊圖片(60x158)的長寬與網頁上渲染出來的是不一致的:
渲染出來的背景圖片為 300x150,滑塊為 57x150,需要先對獲取到的圖片進行縮放處理後,再識別缺口距離:
拖動滑塊進行驗證,c=index&a=codeDrag 接口響應返回校驗的結果,請求參數中 i 和 t 經過了加密處理,需要逆向還原出加密算法,後文會進行研究分析,callback 生成方式如下:
"fangcheck_" + (parseInt(1e4 * Math.random()) + (new Date).valueOf())
- 1e4 * Math.random():生成一個介於 0 到 10000 之間的隨機數;
- (new Date).valueOf():獲取當前的時間戳(以毫秒為單位)。
challenge 和 gt 參數是前面所説的 getslidecodeinit.api 接口響應返回,start 和 end 為滑動軌跡開始及結束的時間戳:
滑塊驗證失敗,code 有兩種狀態碼:
101 ---> 參數校驗失敗
102 ---> 缺口識別錯誤
滑塊驗證成功,code 為 100:
驗證成功之後,會響應返回 validate 參數,攜帶該參數請求 loginsendmsm.api 接口,即可成功發送短信驗證碼:
發送成功,響應返回的 message 為 Success,失敗則為 Error:
逆向分析
i 參數
先來分析下 i 參數是如何加密生成的,從驗證接口跟棧到 jigsawpc.1.0.1.js 文件中:
ctrl + f 搜索 i:,只有一個結果:
在第 204 行打下斷點,滑動滑塊即會斷住,可以看到,l 即滑動軌跡,由 x 軸、y 軸距離以及時間戳組成,後面再對軌跡進行分析,前文所講到的 start、end 在此驗證了,為滑動的開始及結束時間:
從第 203 行,跟進到 x.compress 方法中去:
可以看到,i 參數的值就是由 x.baseCompress 方法生成的,傳入的 e 參數很像是由一些值拼接而成的:
回到第 203 行,e 參數是由 function(e) {...} 方法生成的,點擊前大括號,找到該函數結束的位置,在第 301 行打下斷點,斷住後會發現,e 參數的值是先通過 join( ) 方法將 r 數組的所有元素用 !! 符分隔後連接成一個字符串,再使用 encodeURIComponent( ) 方法進行編碼後得到的:
那 r 數組是由哪些元素組成的呢?往上跟到第 296 行就會發現,r 數組中的元素如下,包括一些瀏覽器環境,最後確實校驗了,但不多:
["textLength", "HTMLLength", "documentMode", "screenLeft", "screenTop", "screenAvailLeft", "screenAvailTop", "innerWidth", "innerHeight", "outerWidth", "outerHeight", "browserLanguage", "browserLanguages", "systemLanguage", "devicePixelRatio", "colorDepth", "userAgent", "cookieEnabled", "netEnabled", "screenWidth", "screenHeight", "screenAvailWidth", "screenAvailHeight", "localStorageEnabled", "sessionStorageEnabled", "indexedDBEnabled", "CPUClass", "platform", "doNotTrack", "timezone", "canvas2DFP", "canvas3DFP", "plugins", "maxTouchPoints", "flashEnabled", "javaEnabled", "hardwareConcurrency", "jsFonts", "timestamp", "performanceTiming", "cwidth"]
下面是對數組中各環境屬性的簡單描述,可供參考:
- textLength:用於測量 HTML 元素文本內容的長度;
- HTMLLength:獲取當前文檔中 HTML 根元素的內部 HTML 內容的長度;
- documentMode:用於在 Internet Explorer 瀏覽器中確定文檔的呈現模式;
- screenLeft,screenTop:窗口左上角相對於屏幕左上角的座標;
- screenAvailLeft,screenAvailTop:可用屏幕空間左上角相對於屏幕左上角的座標;
- innerWidth,innerHeight:瀏覽器窗口的內部寬度和高度,不包括瀏覽器工具欄和滾動條;
- outerWidth,outerHeight:瀏覽器窗口的外部寬度和高度,包括瀏覽器邊框和工具欄;
- browserLanguage,browserLanguages:瀏覽器當前使用的語言或語言列表;
- systemLanguage:操作系統的默認語言;
- devicePixelRatio:設備像素比,用於在不同分辨率屏幕上進行適配;
- colorDepth:屏幕顏色深度;
- userAgent:瀏覽器的用户代理字符串,通常包含瀏覽器和操作系統信息;
- cookieEnabled:表示瀏覽器是否啓用了 Cookie;
- screenWidth,screenHeight:屏幕的寬度和高度;
- screenAvailWidth,screenAvailHeight:可用屏幕的寬度和高度;
- localStorageEnabled,sessionStorageEnabled:表示瀏覽器是否啓用了本地存儲和會話存儲;
- indexedDBEnabled:表示瀏覽器是否啓用了 IndexedDB;
- CPUClass:表示 CPU 的等級或類別;
- platform:操作系統平台信息;
- doNotTrack:表示用户是否啓用了 "不跟蹤" 功能;
- timezone:用户所在時區;
- canvas2DFP,canvas3DFP:Canvas 防指紋技術,用於保護用户隱私;
- plugins:瀏覽器安裝的插件列表;
- maxTouchPoints:設備支持的最大觸摸點數;
- flashEnabled:表示瀏覽器中是否啓用了 Flash;
- javaEnabled:表示瀏覽器中是否啓用了 Java 插件;
- hardwareConcurrency:表示設備的邏輯處理器核心數;
- jsFonts:瀏覽器已安裝的字體列表;
- timestamp:時間戳,通常用於測量性能和時間間隔;
- performanceTiming:訪問有關頁面加載和性能計時的信息。
至此 e 參數的構成方法分析完了,再回到 x.compress 方法中,也就是第 505 行,前文分析了,i 參數由 x.baseCompress 方法生成,該方法傳入了三個參數,前兩個已經分析完了,來看看第三個函數部分:
function(e) {
return x.toChart16(t(e))
}
t 方法定義在第 502 行,就是 String.fromCharCode( ),它用於將一組 Unicode 值(UTF-16 編碼)轉換成對應的字符串,每個參數都是一個表示 Unicode 值的整數。再跟進到 x.toChart16 方法中去,定義在第 628 行,直接扣下來就行了:
最後直接將 baseCompress 方法扣下來即可,i 參數就分析完了:
t 參數
生成 t 參數的方法定義在第 302 行,同樣搜 t: 就可以找到,和 i 一樣,也是幾個自執行函數,直接跟到第 392 行,打下斷點,斷住後驗證了,t 參數就是在這裏生成的:
t 參數是於一長串二進制字符串 e 中從前往後依次截取六位字符,再通過 parseInt 方法將截取到的二進制字符串轉換為整數,即索引,最後使用 charAt 方法根據索引從固定字符串 E 中取值,循環 e.length / 6 次後拼接而成的:
那一長串二進制字符串怎麼來的呢?生成 t 參數的函數是個自執行函數,傳入的參數是 l,l 定義在第 368 行,生成方法逐個跟,扣下來即可:
接着往上跟到 return 處,即第 360 行,此時傳入的 e 為鼠標軌跡,很明顯,這裏對軌跡做了處理,不再是前文所講的 x、y、t 形式,被轉換成了一個大數組:
相關轉換算法在第 180 行,即 e 參數,軌跡校驗的不是很嚴格,模擬構造即可: