今天繼續跟蹤某招標網站的反扒流程,利用 JS 的單步除錯功能,成功定位到了 ajax 傳送請求前對 URL 偷樑換柱的地方,也是一大進展。
條件斷點和堆疊定位之前可以斷定就是 ajax 請求中存在鉤子事件,導致請求 URL 引數變化的,但一直沒找到證據。偷學到一些 JS 逆向技巧之後,利用條件斷點和堆疊呼叫鏈,終於定位到了。
第一步,在 Sources 右側除錯面板中,開啟 Ajax 任意請求斷點:
利用堆疊鏈,倒推檢視呼叫方,是 ajax 的 send 方法:
這個 send 方法並沒有修改 URL 引數,所以繼續在前面 g.open 那裡打斷點,它解釋的函式最終完成了對 URL 引數的重置邏輯。
注意:open 和 send 之前呼叫的方法,是動態變動的,每一次請求後這個函式名稱也會變化,這裡是 _$zh ,後面就變成其他了套路如此深,讓我等小白只能望而卻步!
重寫 ajax 的 open前面利用條件斷點後,再單步除錯跟蹤,最終找到了 ajax 傳送之前修改 URL 路徑的方法,繼續分析它的流程。
首先,網站的 send 方法存在鉤子,在 ajax 的傳送邏輯中,open 被解釋為另一個函式【網頁每次重新整理,該函式名稱都會變化】:
第二步,繼續定位到 _$eg 函式定義的地方:
第三步,檢視它的程式碼
function _$eg() { _$6f(); var _$Rc = _$ep(arguments[1]); arguments[1] = _$Rc._$s3; this._$Wl = _$Rc._$Wl; return _$UA[_$uT[43]](this, arguments);}
當心,這裡存在障眼法,所見的 _6f() 函式並不是真實的函式,它被解釋為:
在當前 JS 片段中搜索 _$CU 函式定義:
function _$CU() { var _$lh = [65]; Array.prototype.push.apply(_$lh, arguments); return _$wV.apply(this, _$lh);}
繼續追 _$wV 是一堆很長的混淆 JS ,它並沒有影響請求引數,暫時不管它。
修改 URL 的地方後面那句 var _$Rc = _$ep(arguments[1]) 函式呼叫後,ajax 的請求的 URL 就變化了,增加了兩個額外引數:
定位到這個方法:
function _$ep(_$SZ, _$uA) { var _$G_, _$uG = null; var _$Rc = _$SZ; function _$kw(_$Mj, _$jU) { var _$Rc = []; var _$fL = ''; var _$nC = _$Fm(_$Q5()); _$Rc = _$Rc[_$uT[4]](_$jU, _$Mj, _$uA || 0, _$nC); var _$eg = _$5Z(743, 6, true, _$Rc); var _$x$ = _$EJ + _$eg; _$uG = _$hx(_$4T(_$x$), 2); return _$aW[_$uT[9]](_$fL, _$3a, _$uT[3], _$x$); } function _$fL() { try { if (typeof _$SZ !== _$uT[6]) _$SZ += ''; _$G_ = _$Vr(_$SZ); if (_$O0) { _$SZ = _$RJ(_$SZ, _$G_); } } catch (_$Rc) { return; } if (_$G_ === null || _$G_._$R9 >= 4) { _$5Z(773, 6); return; } if (_$8H(_$G_)) { _$5Z(773, 6); return; } _$SZ = _$G_._$Sy + _$G_._$lJ; var _$fL = _$Rf(_$G_); var _$nC = _$fL ? _$uT[7] + _$fL : ''; var _$eg = _$3f(_$91(_$nZ(_$G_._$$G + _$nC))); var _$x$ = 0; if (_$G_._$_B) { _$x$ |= 1; } _$SZ += _$uT[7] + _$kw(_$x$, _$eg, _$uA); if (_$fL.length > 0) { if (_$HV && _$HV <= 8) { _$SZ = _$s9(_$SZ); } if (!(_$Zu & 1024)) { _$fL = _$s9(_$fL); } _$fL = _$uT[62] + _$E6(_$fL, _$uG, 4); } _$SZ += _$fL; } function _$nC(_$Mj) { _$40(2, _$nP(5)); if (_$uG === null || _$hS(_$G_) === false) { return _$Mj; } if (typeof _$Mj === _$uT[6] || typeof _$Mj === _$uT[615] || typeof _$Mj === _$uT[77]) { _$Mj = _$E6(_$Mj, _$uG, 5); } return _$Mj; } function _$eg(_$Mj, _$jU) { if ((_$Mj === 'get' || _$Mj === _$uT[247]) && (_$yA & 1) && (_$Zu & 8192) && !(_$G_ && (_$G_._$R9 >= 5 || _$G_._$_B))) { if (_$jU === _$Xm || _$jU === null || _$jU === '') _$jU = _$uT[299]; if (_$jU === _$uT[299]) { return _$jU; } } return ''; } _$fL(); return { _$7W: _$Rc, _$s3: _$SZ, _$Wl: _$nC, _$Wm: _$eg };}
跟到這裡真的是無能為力了,這一堆不明所以的 JS ,可能就是傳說中利用凱撒密碼混淆過的,下一步的研究方向就是用 AST 語法把它們還原。
可能這個邏輯中存在時間戳資訊。
第二,直接呼叫一下 open 之前那個修改 URL 的函式,可以拿到後面的引數:
理論上逆向解釋出上面這段 JS ,用 Java 實現這個 return 語句得到修正後的 URL ,就可以解決反扒問題了。難點在於怎麼用程式碼在模擬 ajax 請求之前定位到當前頁面返回的這個 open 鉤子函式呢?人家可是動態生成的啊,還是得解密上面那段鉤子函式所在的 JS 片段。
說實話,這個可比上一篇介紹的無限 debugger 手段高明多了,就算知道能除錯,但實際上卻是一本無字天書!神吶,救救我吧!