【驗證碼逆向專欄】某驗深知 V2 業務風控逆向分析

来源:https://www.cnblogs.com/ikdl/archive/2023/04/13/17315308.html
-Advertisement-
Play Games

聲明 本文章中所有內容僅供學習交流使用,不用於其他任何目的,不提供完整代碼,抓包內容、敏感網址、數據介面等均已做脫敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關! 本文章未經許可禁止轉載,禁止任何修改後二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權, ...


聲明

本文章中所有內容僅供學習交流使用,不用於其他任何目的,不提供完整代碼,抓包內容、敏感網址、數據介面等均已做脫敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關!

本文章未經許可禁止轉載,禁止任何修改後二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K哥爬蟲】聯繫作者立即刪除!

逆向目標

01

  • 目標:某驗深知 V2 業務風控逆向分析
  • 主頁:aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby9kay12Mi5odG1s

深知簡介

某驗深知通過無感採集客戶端數據,對用戶的環境、標識、行為操作等進行智能化分析,結合業務場景有效識別有潛在風險的用戶。整個識別過程不幹擾用戶,不打斷業務既有流程。完整通訊流程如下:

02

抓包分析

訪問首頁,會引入一個 v2.sense.js,後面接了個 id,需要將其提取出來,後續有用到,當然一般情況下,同一個業務這個 id 應該是一樣的,直接複製下來寫死也行。

03

接著有個 gettype 的請求,這裡主要返回一些資源路徑,其中有個 gct.xxx.js,這個 JS 名稱每隔一段時間就會變化,這個 JS 會生成一個鍵值對,例如 {'xnbw': '1158444372'},JS 變化,這個鍵值對也會變化,這個鍵值對參與了後面加密參數的生成,在某驗系列產品中都有這個東西,少量測試將其固定發現也可以通過驗證,盲猜大量請求或者某些校驗嚴格的網站可能有影響,建議還是動態去請求這個 JS 來獲取最新的鍵值對,這個後文具體再說。

04

然後是 judge 的請求,這個請求頁面一載入就完成了,不需要手動點擊請求,其中 Query String Parameters 里有個 app_id 就是我們前面提到的 idRequest Payload 就是一串超長的字元串,這個也是我們需要逆向的參數。該請求如果驗證成功,會返回一個 session_id

05

06

然後就是業務介面了,本例中業務介面是 verify-dk-v2,也就是一個登錄介面,帶上前面 judge 介面返回的 session_id 即可請求成功。

07

08

逆向分析

由於我們逆向的參數 Request Payload 沒有鍵名導致不能直接搜索關鍵字,所以只能跟棧或者下個 XHR 斷點,跟棧可以在 sense.2.3.0.js 第 6144 行找到一個 e + h[AUJ_(1173)],這個就是正確的 Request Payload 值。

09

上圖中其實核心代碼就四行,後文也是圍繞這四行代碼來分析的:

var h = o[AUJ_(1156)]()
  , e = CoUE[ymDv(24)](NFeB)
  , l = EbF_[ymDv(409)](e, h[ymDv(1194)])
  , e = DWYi[ymDv(1137)](l)

獲取 h 值

先來看 h 的值,由一個方法生成一個對象,對象裡面分別是 aeskeyrsa,每次也都是隨機變化的。

10

繼續跟到這個方法里,重點在於 e 和 t 的值,最後返回的就是 {aeskey: e, rsa: t}

11

先看這個 e 的值,也就是 RwyT() 方法,搞過某驗其他產品的就知道這裡是 16 位隨機值。

12

然後 t 的值,和某驗其他系列產品一樣,用到了 RSA 加密演算法,這裡圖中 BPqG() 就是 RSA 演算法,t 的值就是 RSA 加密後的結果,扣的時候註意找到演算法開頭的地方,將整個 BPqG() 方法扣下來即可。

13

14

獲取 e 值

接下來是 e 的值,e = CoUE[ymDv(24)](NFeB),很明顯是將 NFeB 的值進行了處理,NFeB 是個對象,裡面有一些 dataid 等信息,如下圖所示:

15

所以我們得先找一下 NFeB 這個值是怎麼來的,直接搜索發現只有四個地方,在第 6109 行就是定義的地方,挨個看,首先有個 s 參數,將 id 傳入到一個函數進行處理,函數沒啥特別的,直接扣就行,通常經過處理後,s 的值為空,即 s=""

16

再來看有個 u 值,由一個方法生成了一大串包含很多感嘆號的字元串,本案例實際測試中,直接將這個值置空也行,可能其他校驗嚴格或者大批量請求的情況下,說不定也會校驗的,所以我們最好也跟進去找一下生成邏輯。

17

跟進這個方法,裡面是一些瀏覽器環境的值,比如屏幕高寬、canvas、ua、瀏覽器插件、時間、時區、語言等等,基本上都能寫死,後續會將這些值以 !! 相連接最終生成 u 的值。

18

然後繼續看,接下來是 c 值,是一個對象,值為 {"key":0,"value":[]},我這裡直接寫死了。

19

再往下就是 NFeB 了:

20

Unicode 轉換一下,簡單解一下混淆,就長下麵這樣:

NFeB = {
    "id": a["id"],
    "page_id": a["page_id"],
    "lang": a["lang"] || AUJ_(31),
    "data": {
        "insights": u || null,
        "track_key": c["value"] ? c["key"] : null,
        "track": c["value"] || null,
        "ep": o["KZrg"](i),
        "eco": window["GEERANDOMTOKEN"] || "",
        "ww3": ""
    }
};

id 不用說,page_id 就是個時間戳,lang 中文就是 zh-cninsights 是前面得到的 u 值,track_keytrackc 的鍵和值,epi 傳入了一個函數進行處理,i 是固定的字元串 client,這個 KZrg 方法可以跟進去看看,裡面其實有很多都是定值,唯一需要註意的是 t["tm"] 這個值,和某驗其他系列一樣,是 window.performance.timing 的值,自己獲取一下時間戳隨機加減偽造一下就行了。

21

然後就是 eco 的值,取的 window.GEERANDOMTOKEN,列印一下 window,除了有這個 token 以外,還可以看到 localStoresession 裡面也有這個值。

22

23

由於某驗的 JS 都是混淆後的,不太好定位這個值生成的地方,所以拿出我們的 Hook 大法,先清除一下緩存,不然的話是 Hook 不到值的,Hook 代碼如下:

(function() {
	var token = "";
    Object.defineProperty(window, 'GEERANDOMTOKEN', {
		set: function(val) {
            console.log('GEERANDOMTOKEN->', val);
            debugger;
            token = val;
            return val;
		},
		get: function()
		{
			return token;
		}
    });
})();

24

斷下後往前跟棧,window[o] = to 就是 GEERANDOMTOKENt 就是我們想要的值。

25

往上就可以找到 t 的生成方法,核心就是生成一個 32 位的隨機字元串,然後加上時間戳,再進行 MD5 加密得到最終值,生成位置以及實現的代碼如下:

26

var MD5 = require("md5")


function getToken(){
    var t = MD5(function(e) {
        for (var t = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"], n = "", r = 0; r < e; r++)
            n += t[parseInt(61 * Math.random(), 10)];
        return n;
    }(32) + new Date().getTime());
    return t;
}

當你把以上這些參數都搞完了,你可能認為都齊了,其實不然,後面接著還有一句 Yvwp(NFeB, r),將 r 的值增加到了 NFeB 里,這個 r 的值類似於 {olbo: "1588069361"},這個鍵值對都是每隔一段時間會變的,這個在某驗系列其他文章里也提過。

27

進一步分析,這個 r 是傳進來的,所以往上跟棧,有個 r[psPG(1183)]() 方法就生成了這個對象:

28

繼續跟到這個方法里去,首先定義了 e 這個對象,然後賦值 e = {ep: "test data", lang: "zh"},然後經過 window[tYlM(1126)]() 方法處理後,e 裡面就新增了 {olbo: "1588069361"},後續將 ep 和 lang 兩個值刪除後返回。

29

所以我們繼續跟進 window[tYlM(1126)]() 方法,會跳轉到 gct.xxxx.js 里,這個 JS 就是我們開頭講過的,他的名稱會每隔一段時間變化,內容也會變,所以導致生成的鍵值對也會變化,繼續跟,有個 t[e] = xxx 的語句,其中 e 和等號右邊的值,就是我們需要的鍵值對。

30

這個鍵值對在我們本地也可以動態獲取,只需要請求正確的 JS 文件,將要調用的方法全局導出就行了,以下給一個我的處理方法示例(註意裡面請求 url 已經脫敏處理,所以不可直接運行,自行抓包補上):

import re
import time
import json
import execjs
import requests
from loguru import logger


headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
}


def get_gct():
    url = "https://dkapi.脫敏處理.com/deepknow/v2/gettype"
    params = {
        "callback": "脫敏處理_" + str(int(time.time() * 1000))
    }
    response = requests.get(url, headers=headers, params=params).text
    response = json.loads(re.findall(r"geetest_\d+\((.*?)\)", response)[0])
    # gettype 介面返回的 gct.xxx.js 的地址
    gct_path = "https://static.脫敏處理.com" + response["gct_path"]
    logger.info("gct_path: %s" % gct_path)
    gct_js = requests.get(gct_path, headers=headers).text
    # 正則匹配需要調用的方法名稱
    function_name = re.findall(r"\)\)\{return (.*?)\(", gct_js)[0]
    # 查找需要插入全局導出代碼的位置
    break_position = gct_js.find("return function(t){")
    # window.gct 全局導出方法
    gct_js_new = gct_js[:break_position] + "window.gct=" + function_name + ";" + gct_js[break_position:]
    # 添加自定義方法調用 window.gct 獲取鍵值對
    gct_js_new = "window = global;" + gct_js_new + """
    function getGct(){
        var e = {"lang": "zh", "ep": "test data"};
        window.gct(e);
        delete e["lang"];
        delete e["ep"];
        return e;
    }"""
    gct = execjs.compile(gct_js_new).call("getGct")
    logger.info("gct: %s" % gct)
    return gct

到這裡我們 NFeB 就生成完畢了,回到 e 的值,這裡其實就是把 NFeB 轉成字元串,直接 JSON.stringify() 即可。

31

獲取 l 值

l 的值比較簡單,就是將前面生成的 h["aeskey"] 作為 key,e 作為待加密字元串,經過 AES 加密後即可得到 l 的值。

32

本地復現如下(有些變數名稱不一樣無影響,我是直接復用的某驗其他產品的方法):

var CryptoJS = require("crypto-js")


function aesEncrypt(e, i) {
    var key = CryptoJS.enc.Utf8.parse(i),
    iv = CryptoJS.enc.Utf8.parse("0000000000000000"),
    srcs = CryptoJS.enc.Utf8.parse(e),
    encrypted = CryptoJS.AES.encrypt(srcs, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    for (var r = encrypted, o = r.ciphertext.words, i = r.ciphertext.sigBytes, s = [], a = 0; a < i; a++) {
        var c = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
        s.push(c);
    }
    return s;
}

進一步處理 l

最後一步 e = DWYi[ymDv(1137)](l),將 l 的值經過了 tc_t 這個方法進行處理,就會得到最終 Request Payload 的一部分。

33

跟進這個 tc_t 方法,又是熟悉的 return e["res"] + e["end"],同樣和某驗其他產品一樣的。

34

跟到處理 e 的這個方法里,最後返回的是 {"res": a, "end": s},沒啥特別的,直接扣即可,這裡註意和某驗其他產品里的方法有些小區別,裡面有些常量的值是不一樣的,最開始我直接復用了其他產品的方法,發現結果是錯的。

35

自此整個流程分析完畢,最終 e + h[AUJ_(1173)] 的值與 Request Payload 的值一致。

36

37

結果驗證

38


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 經過了幾天的奮戰,終於把微信服務號的模板消息給寫完了。後端其實沒花多少時間,因為之前已經有同學提過pull request了,我在這基礎之上簡單優化下就完事了,主要的時間都是花在前端上,對前端頁面和參數的適配比較麻煩。 消息推送平臺🔥推送下發【郵件】【簡訊】【微信服務號】【微信小程式】【企業微信】 ...
  • 1、概念:docker是一個開源的應用容器引擎,docker可以讓開發者打包他們的應用以及依賴環境包到一個輕量級、可移值的容器中。然後發佈到任何流行的linux機器上。 安裝過程: 1、yum包更新到最新 yum update 2、安裝需要的軟體包 yum install -y yum-utils ...
  • 長字元串起因 項目裡面有一長串的加密字元串(最長的萬多個字元),需要拼接作為參數發送給第三方。 如果我們使用 枚舉 定義的話,idea 編譯的時候就會出現編譯報錯 Error: java:常量字元串過長 解決想法 網上還有一個說法,說是編譯器問題,修改 idea 工具的編譯為 eclipse 即可。 ...
  • 說下場景,我的程式在多線程場景下一個迴圈體中處理業務數據,其中需要調用一個外部http介面去獲取一些數據,程式總會在在本地執行一段時間後會拋出Address already in use: no further information錯誤。 這是大量併發場景下出現的問題,經過查閱原因是OkHttp的 ...
  • 文件操作 文件讀寫 語法:open(file, mode, encoding) 參數:file —— 文件所在位置(相對路徑、絕對路徑) mode —— 操作文件的模式 encoding —— 文件的編碼格式 相對路徑:基於目前的路徑獲取 絕對路徑:一個完整的路徑 操作文件的模式:r-讀 w-寫 a ...
  • 前言 做了個python的小項目,需要打包為桌面端的exe使用,結果一打包,體積直接上百兆了,研究了下,使用虛擬環境打出的包會更乾凈小巧。 安裝anaconda(用作python的虛擬環境管理工具) 安裝:https://repo.anaconda.com/archive/Anaconda3-202 ...
  • 判斷閏年 初始版本 year = input('請輸入一個年份:') while not year.isdigit(): year = input("抱歉,您的輸入有誤,請輸入一個整數:") year = int(year) if year % 400 == 0: print(year, "是閏年! ...
  • 在工作中,我們會將重要的文檔進行加密,並且設置用戶的訪問許可權,其他外部人員均無法打開,只有獲取該許可權的用戶才有資格打開文檔。此外,限制用戶的使用許可權,極大程度上阻止了那些有意要篡改、拷貝其中內容的人,提高文檔的安全性。與此同時,文檔加密的另一大作用是為了防止丟失,因為可能存在員工出差或離職時,將文檔 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...