
宣告
本文章中所有內容僅供學習交流,抓包內容、敏感網址、資料介面均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此產生的一切后果均與作者無關,若有侵權,請聯系我立即洗掉!
本文章未經許可禁止轉載,禁止任何修改后二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K哥爬蟲】聯系作者立即洗掉!
逆向目標
- 目標:某驗二代滑塊驗證碼逆向分析
- 主頁:
aHR0cDovL3d3dy5qc2dzai5nb3YuY246NTg4ODgvbWluaS9uZXR3ZWIvU01MaWJyYXJ5LmpzcA== - 說明:大多數邏輯其實和三四代都一樣,相同的就簡寫了,有疑惑的地方可以看以前的文章
- 【驗證碼逆向專欄】某驗三代滑塊驗證碼逆向分析
- 【驗證碼逆向專欄】某驗四代滑塊驗證碼逆向分析
抓包情況
主頁點擊搜索就會跳出二代的驗證碼,netWebServlet.json 的請求,會回傳 challenge 和 gt,

有個 get.php 的請求,回傳了一個新的 challenge,這個請求之后的操作,都要用這個新的 challenge,不然是驗證不成功的,其他的還有驗證碼背景圖片、亂序圖片地址、c、s 等值,之前寫過三代的文章,都是類似的,這里就不一一分析了,

然后是 ajax.php 驗證是否通過,通過之后回傳一個 validate,請求里同樣是需要我們逆向的 w 引數:


然后同樣還是 netWebServlet.json 介面,帶上 get.php 請求回傳的 challenge 以及 ajax.php 回傳的 validate,請求拿到一個 name 的欄位,


后續的搜索資料,帶上這個 name 就行了:

逆向分析
搞過三、四代的都知道我們可以直接搜索 w 的 Unicode 值 \u0077 即可定位,但是二代則不是 Unicode,而是16進制的編碼,搜索 \x77 即可定位,當然按照正常流程,跟堆疊也能很容易找到加密的位置,

獲取 H7z 值
從上圖中可以知道 w 的值為 r7z + H7z,先看 H7z,

跟進這個方法,來到一大串控制流,這里還是推薦用 AST 還原一下,后續可能有一些回圈啥的,硬跟的話容易出錯,當然直接全部扣一把梭也是可以的,H7z 的核心其實就是 RSA 加密隨機字串,三代四代都有,這里就不細講了,

獲取 r7z 值
然后就是 r7z,主要由以下兩句代碼生成:

q7z = n0B[M9r.R8z(699)](h7B[M9r.C8z(105)](Y7z), V7z[M9r.R8z(818)]())
r7z = p7B[M9r.R8z(260)](q7z)
可以看到其中有個變數 Y7z 參與了計算,先來看看他是怎么來的,直接搜索即可定位,可以發現同樣是16進制的編碼,由五個值組成:userresponse、passtime、imgload、aa、ep

獲取 userresponse 值
挨個分析,首先是 userresponse,將滑動距離和 challenge 的值傳入一個方法,得到一個 9 位字串:

上圖中 g7z 就是滑動距離,搜索可以看到定義的地方,尺子量一下對比一下,和滑動的距離是一致的:


然后再來看看那個方法,跟進去之后也是一大串 switch-case 控制流:

還原一下代碼如下:
function getUserResponse(L0z, o0z) {
for (var j0z = o0z.slice(32), c0z = [], X0z = 0; X0z < j0z.length; X0z++){
var K0z = j0z.charCodeAt(X0z);
c0z[X0z] = K0z > 57 ? K0z - 87 : K0z - 48;
}
j0z = 36 * c0z[0] + c0z[1];
var k0z = Math.round(L0z) + j0z;
o0z = o0z.slice(0, 32);
var n0z, f0z = [[], [], [], [], []], Q0z = {}, N0z = 0;
X0z = 0;
for (var i0z = o0z.length; i0z > X0z; X0z++){
n0z = o0z.charAt(X0z), Q0z[n0z] || (Q0z[n0z] = 1, f0z[N0z].push(n0z), N0z++, N0z = 5 == N0z ? 0 : N0z);
}
var y0z, v0z = k0z, B0z = 4, x0z = "", I0z = [1, 2, 5, 10, 50];
while ( v0z > 0) {
v0z - I0z[B0z] >= 0 ? (y0z = parseInt(Math.random() * f0z[B0z].length, 10),
x0z += f0z[B0z][y0z], v0z -= I0z[B0z]) : (f0z.splice(B0z, 1),
I0z.splice(B0z, 1), B0z -= 1);
}
return x0z;
}
獲取 passtime 值
passtime 不用考慮是怎么通過函式獲取的,含義就是滑動完成所花費的時間,直接取軌跡的最后一個值即可,這個也和三四代是一樣的,獲取陳述句為:var passtime = track[track.length - 1][2],如下圖所示,軌跡的最后一個值時間為 871,passtime 的值同樣也為 871,


獲取 imgload 值
imgload 也沒啥特別的,從字面意思猜測應該是圖片加載耗時,實測直接寫死即可,或者整個隨機值就行,
獲取 aa 值
aa 的值就是 F7z,如下圖所示:

搜索 F7z,定位到下圖所示的地方,向一個方法中傳入了一個時間戳:

跟進去同樣是 switch-case 控制流,需要注意的是下圖中 c7B[M9r.R8z(781)](M9r.R8z(764), K1z) 的值其實就是軌跡,

這段控制流還原一下就變成這樣了:
function getF7z(track){
var o5r = 6;
for (var N1z, X1z = s6z(track), f1z = [], B1z = [], o1z = [], t1z = 0, j1z = X1z.length; t1z < j1z; t1z++){
if (o5r * (o5r + 1) % 2 + 8) {
N1z = u6z(X1z[t1z]),
N1z ? B1z.push(N1z) : (f1z.push(O6z(X1z[t1z][0])),
B1z.push(O6z(X1z[t1z][1]))),
o1z.push(O6z(X1z[t1z][2]));
o5r = o5r >= 17705 ? o5r / 3 : o5r * 3;
}
}
return f1z.join("") + "!!" + B1z.join("") + "!!" + o1z.join("");
}
function s6z(F6z){
for (var Y6z, g6z, a6z, E6z = [], D6z = 0, P6z = [], J6z = 0, l6z = F6z.length - 1; J6z < l6z; J6z++) {
Y6z = Math.round(F6z[J6z + 1][0] - F6z[J6z][0]),
g6z = Math.round(F6z[J6z + 1][1] - F6z[J6z][1]),
a6z = Math.round(F6z[J6z + 1][2] - F6z[J6z][2]),
P6z.push([Y6z, g6z, a6z]),
0 == Y6z && 0 == g6z && 0 == a6z || (0 == Y6z && 0 == g6z ? D6z += a6z : (E6z.push([Y6z, g6z, a6z + D6z]), D6z = 0));
}
return 0 !== D6z && E6z.push([Y6z, g6z, D6z]), E6z;
}
function O6z(r6z){
var d6z = "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqr"
, m6z = d6z.length
, Z6z = ""
, H6z = Math.abs(r6z)
, W6z = parseInt(H6z / m6z);
W6z >= m6z && (W6z = m6z - 1), W6z && (Z6z = d6z.charAt(W6z)), H6z %= m6z;
var q6z = "";
return r6z < 0 && (q6z += "!"), Z6z && (q6z += "$"), q6z + Z6z + d6z.charAt(H6z);
}
function u6z(R6z){
for (var z6z = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]], h6z = 0, C6z = z6z.length; h6z < C6z; h6z++){
if (R6z[0] == z6z[h6z][0] && R6z[1] == z6z[h6z][1]){
return "stuvwxyz~"[h6z]
}
}
return 0;
}
以上只是 F7z 第一次生成的地方,后面還有二次處理,如下圖所示:

同樣跟進去,三個傳入的引數分別是第一次生成的 F7z、get.php 請求回傳的 c 和 s 引數,

同樣是一段控制流,還原后如下:
function getF7z2(Q1z, v1z, T1z){
var i1z, x1z = 0, c1z = Q1z, y1z = v1z[0], k1z = v1z[2], L1z = v1z[4];
while (1){
if (i1z = T1z.substr(x1z, 2)){
x1z += 2;
var n1z = parseInt(i1z, 16)
, M1z = String.fromCharCode(n1z)
, I1z = (y1z * n1z * n1z + k1z * n1z + L1z) % Q1z.length;
c1z = c1z.substr(0, I1z) + M1z + c1z.substr(I1z);
}else {
return c1z
}
}
return Q1z
}
至此 aa 引數分析完畢!
獲取 ep 值
ep 的值就是一個版本號,此處是 {'v': '6.0.9'},寫死即可,

獲取 rp 值
自此 Y7z 的第一步生成就分析完畢了,注意接下來還有一步,向 Y7z 里新增了一個 rp 引數:

這個值的組成看起來很長,實際上是將 gt、challenge 前 32 位以及 passtime 相加經過 MD5 加密后得到的,
Y7z["rp"] = md5(gt + challenge.slice(0, 32) + passtime)

上圖中 I0B 就是 MD5 方法,跟進去其實是可以看到很多 MD5 特征的,如下圖所示:

自此 Y7z 的值就搞定了,然后接著前面的看,也就是 q7z 的值,同樣和三四代一樣的,encrypt 是 AES 加密,Y7z 經過 JSON.stringify() 處理為字串作為待加密物件,后面是 16 為隨機字串作為 AES 的 Key,注意這里的隨機字串應該和獲取 H7z 值時的隨機字串一致,不然是驗證不成功的,

然后下一步就是獲取 r7z 的值,將上一步得到的 q7z 經過一個方法進行處理,跟進方法,又是和三四代一樣的,熟悉的 res + end,如下圖所示:

直接扣代碼,或者直接使用三代的代碼即可:
function $_GJF(e) {
var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()";
return e < 0 || e >= t["length"] ? "." : t["charAt"](e);
}
function $_HBO(e, t) {
return e >> t & 1;
}
function $_HCX(e, o) {
var i = this;
o || (o = i);
for (var t = function(e, t) {
for (var n = 0, r = 24 - 1; 0 <= r; r -= 1)
1 === $_HBO(t, r) && (n = (n << 1) + $_HBO(e, r));
return n;
}, n = "", r = "", s = e.length, a = 0; a < s; a += 3) {
var c;
if (a + 2 < s)
c = (e[a] << 16) + (e[a + 1] << 8) + e[a + 2],
n += $_GJF(t(c, 7274496)) + $_GJF(t(c, 9483264)) + $_GJF(t(c, 19220)) + $_GJF(t(c, 235));
else {
var _ = s % 3;
2 == _ ? (c = (e[a] << 16) + (e[a + 1] << 8),
n += $_GJF(t(c, 7274496)) + $_GJF(t(c, 9483264)) + $_GJF(t(c, 19220)),
r = ".") : 1 == _ && (c = e[a] << 16,
n += $_GJF(t(c, 7274496)) + $_GJF(t(c, 9483264)),
r = "." + ".");
}
}
return {
"res": n,
"end": r
};
}
獲取 w 值
自此 w 的就已經出來了,r7z + H7z 即為 w 的值,

結果驗證
測驗過掉驗證碼抓取資料成功:


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/541814.html
標籤:其他
上一篇:別催了,別催了,這篇文章我一次性把Shell的內容說完
下一篇:C++學習筆記 (2)
