宣告
本文章中所有內容僅供學習交流,抓包內容、敏感網址、資料介面均已做脫敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關,若有侵權,請聯絡我立即刪除!
目標網站
aHR0cHM6Ly9wYXNzcG9ydC5qZC5jb20vbmV3L2xvZ2luLmFzcHg/UmV0dXJuVXJsPWh0dHBzJTNBJTJGJTJGd3d3LmpkLmNvbSUyRg==
前言
上次介紹了H5st的加密。這次來研究一下模擬登陸。即登入+滑塊
這裡先貼一張圖 下圖所示 是登入要的所有引數。至於所得所有引數 怎麼來的? 如何來的?請往下看。
有能力的同學可以加下本人星球哦
https://t.zsxq.com/17OvVkJha
本文詳細原文
https://mp.weixin.qq.com/s/D2MTec5F92GoSho0af26ww
介面分析
引數過多。這裡直接步入正題。
首先開啟一個無痕瀏覽器(這很重要)
關注一下這個slide/g.html
請求。 發現這裡只有一個 e 請求是會改變的。但是好像寫死也沒問題。
如下圖然後往後看有很多個請求。但請求都大同小異啊。這裡不賣關子了。請求是繫結的滑鼠事件。每次移動滑鼠都有請求發過來。
進棧點看看。
這裡發現d的結果已經出來 往上走。
這裡發現 C(a)
的值就是生成d
的引數。
這裡 傳參a
為
c函式如下
這裡我扣下來了。
C = function (a) {
if (void 0 == a || null == a)
return null;
var b = "{", e;
for (e in a)
b += "'" + e + "':",
b = "string" == typeof a[e] ? b + ("'" + a[e] + "'") : b + a[e],
b += ",";
b = b.substring(0, b.length - 1);
return E(b + "}")
}
E = function (a) {
this.tdmovebit = function () {
var a = 10
, b = 20
, c = 30;
++a;
a++;
a = ++a + ++b + c++ + a++;
return d + a - 76
}
;
var b = "23IL";
a = encodeURIComponent(a);
var e = ""
, c = "";
b += "<N01c7KvwZO56RSTAfghiFyzWJqVabGH4PQdopUrsCuX*xeBjkltDEmn89.-";
var d = 0;
var g = "";
do {
var h = a.charCodeAt(d);
d = this.tdmovebit(d);
var f = a.charCodeAt(d);
d = this.tdmovebit(d);
e = a.charCodeAt(d);
d = this.tdmovebit(d);
var k = h >> Math.round(((19 << 43) / 90 ^ 34) / 214);
h = (h & 3) << 4 | f >> 4;
var l = (f & 15) << 2 | e >> 6;
c = e & Math.round(((19 << 43) / 90 ^ 34) / 6) - 4;
isNaN(f) ? l = c = Math.round(((19 << 43) / 90 ^ 34) / 6) - 3 : isNaN(e) && (c = Math.round(((19 << 43) / 90 ^ 34) / 6) - 3);
g = g + b.charAt(k) + b.charAt(h) + b.charAt(l) + b.charAt(c);
k = h = l = c = h = f = e = ""
} while (d <= a.length);
return g + "/"
}
let a = {
"bizId": "passport_jd_com_login_pc",
"elementId": "loginname",
"seq": "sniffV",
"sessionId": "294576929414652485",
"special": "0",
"sp": 1,
"version": "1.0",
"val": "",
"ctime": 1709542681074
}
console.log(C(a));
拿到這個 d
值了。
這裡繼續往下 下斷點。
然後我們移動滑鼠就會有請求發過來。
發現這個地方 就是監聽滑鼠的事件。
這裡不扯這個了。繼續回頭來走這個流程
然後我們輸完密碼 發現又有個 slide/s.html
請求
然後它的返回值是fail
意思是滑動失敗
然後隨後又發了個slide/g.html
請求。
那這樣的話。我們是不是可以簡單梳理一下。
- g請求拿驗證碼
- s請求返回結果
那這個g請求中的e
是什麼?
我們找一下 第二個堆疊
那這個方法中如何獲取eid
呢
我們繼續追棧進去
繼續進這個 a = getJdEid()['eid']
好了 進去一個虛擬機器了
然後發現 這個 eid
居然在這個fcf.html 請求中
然後要訪問這個請求 需要三個加密方法。
分別為 a
, g
, d
.
嫌麻煩可以把 e
引數寫死 能不能成功我就不知道了。
fcf介面加密分析
這裡不廢話了。打斷點 開幹。
這裡不知道怎麼找。直接追棧吧。就直接從最上面的棧追。然後一步一步往上看。
如下圖所示 就是加密的地方。
這裡我們一個一個看把
z(a)引數分析
如上圖 傳遞了很多引數。其中要注意的是 fc fp jtb
這裡其實可以全拉下來補環境。但是我感覺有點麻煩了。
可以一個一個去扣下。
這裡tdencrypt
函式很好扣。
如下圖所示
這裡摳出來了。但是傳參我們還沒扣。
fc
fc是eid 測試下來可以為空;
**fp **
fp 如下圖
繼續往上追
然後我們全域性搜尋_JdJrTdRiskFpInfo
然後我們進去get這個方法
把這個方法全扣下來
然後補一下環境。
ok 這樣就出結果了
jtb
jtb就是jd_shadow__
這裡往上找 終於找到加密的地方是個自執行函式。用了什麼加密就不說了 一目瞭然
這裡給他扣下來。發現扣下來執行結果是undefined
這裡有兩種結果。第一種是異常了。第二種是加密的值就是undefined
這裡發現 加密了r值 而r一開始是個陣列,新增了一些陣列然後拼接成字串。
然後修修補補 最後修改一下 執行就行。
n(g,d)引數分析
如上圖 g 和 d 引數都被賦值給了 n。
g
n.g = td_collect.tdencrypt(u); 這裡就直接偽造u就可以了 tdencrypt上文 搞z的時候已經寫好了。
d
n.d = _JdJrRiskClientCollectData;
但是 td_collect.collect()
這個 應該是很多環境檢測。我們進去看看。
至於我為什麼篤定是環境檢測。因為上一篇h5st中collect函式也是。
如下圖 是collect() 函式。這裡我們直接滑到最低。
直接把g全弄下來構造環境即可。
至此 a
, g
, d
全部搞定。全部搞定就可以獲得eid了。
這裡扣不扣程式碼和補環境差不多 都是兩千多行。
這部分的程式碼 我放到星球裡了。
滑塊分析
s請求。
這裡我們先看 s
請求。
這裡我們需要逆向的引數是 d
其他的值 有的是透過 g
有的是可以寫死。
這裡 o
是使用者賬號 但是密碼不在這個請求裡。
這裡我們直接進 下圖這個棧 透過這個棧跟。
然後到達 submit 函式
繼續追 到 a['getCoordinate'](b)
這個地方就是生成d的地方。其他值 生成的邏輯大家也可以看看。
這裡我們發現 還是沒有發現密碼的引數是哪個?
這裡可以把 這個函式摳出來。
這個摳出來的邏輯 巨!!!簡單。不說了 如下圖所示
g請求
搞完了s 我們來繼續請求g
這裡之所以倒退 主要是其他值生成的邏輯 我們可以提前看看。就可以找到要拿哪些需要的引數了
先訪問這個請求。
這裡值得注意的是這個 challenge
是驗證碼ID 也需要提出來。
其他的引數更簡單了 提取出s需要的引數就行了。
密碼加密
但是這裡有另一個問題。
s
請求和 g
請求都搞了。但是我們一直沒找到密碼的加密引數啊
這裡重新走一遍流程。
這裡發現,我們滑鼠即使不動。在輸入密碼的時候 依然有很多請求發過去。那這個是怎麼回事?
這裡繼續在這個C(a)這個地方打斷點。並且埋點,看看整體的邏輯。
console.log(a,">>>>>",C(a));false
這裡為什麼這麼寫就不用多說了 。之前xhs寫過了。
這裡 elementId: "nloginpwd" 暴露了這是密碼加密的地方。並且傳遞密碼還是透過每次的鍵盤事件。
但是這每次傳。總不可能伺服器接受這麼多次吧。肯定還有個統一提交的地方。
登入提交
我們完整的再走一次流程。把滑塊也給滑過去。
此時發現一個請求uc/loginService
這個請求應該大機率是提交的地方。
這裡我們再走一下流程搜尋 aksParamsU
和 aksParamsB
如下圖。
aksParamsU
aksParamsB
這裡aksParamsU
和 aksParamsB
發現都是公用的encrypte
函式。
這裡我也複製下來了
function encrypte(data) {
if (checkInitStatus() && !isBlank(data)) {
try {
// const data_ = decodeURIComponent(data)
return encodeURIComponent(SummerCryptico.encryptData(publicKey, data + ""))
} catch (e) {
return data
}
} else {
return data
}
}
扣下來大概兩千多行。
這裡可以發現 除了常規加密 還帶了一個 publicKey
這裡public的生成經過斷點分析後可知由最開始初始化的init請求
提供。
這裡我們把data 複製下來分析一下。
這個引數還挺多的。
這裡往上找堆疊
(發現和上文我們複製的引數是一樣的。)
這裡繼續往這個getEntryptPwd下走
這裡在走斷點的時候發現 文中的 publickey如下所示
然後把函式扣下來加密 就是密碼的加密了。
大概隨便補一下就出來了。
總結
很好 這裡該解密的都解密的都解密出來了。
這裡簡單敘述一下流程
其實就三個請求。
- g請求拿到驗證碼
a. 但是 e呢 需要訪問fcf請求
b. fcf請求需要逆向e,g,d - s請求根據g請求返回結果
a. e引數同上
b. d引數是透過JDJRValidate.getCoordinate
加密軌跡得到。 - loginService請求 發起請求。並且整個從引數加密
a. aksParamsU 與 aksParamsB都是統一加密方式。不同的是加密的明文
b. 明文的密碼 又是一段加密程式碼。
結語
本文關於程式碼部分的加密引數。可以看下文詳解。
https://wx.zsxq.com/dweb2/article?groupId=51112885255144