背景
當用代碼或者postman訪問一個網站的時候,訪問他的任何地址都會返回<script src="/_guard/auto.js"></script>,但是從瀏覽器中訪問顯示的頁面是正常的,這種就是網站做了反爬蟲策略。本文就是帶大家來破解這種策略,也就是反反爬蟲。
思路
尋找關鍵參數
既然在瀏覽器中訪問沒問題,那我們就把瀏覽器的請求復制下來,看是哪些參數讓請求可以正常訪問,將curl復制到postman中,把請求頭一個個去掉,看去掉哪些請求頭會讓請求無法正常訪問
最終發現是Cookie和User-Agent一起使得請求合法,如下
- Cookie:guardret=BQgG; __51vcke__K1rw5p3uprPRftXo=21f5dde6-91d9-520b-a429-4a6e99d44523; __51vuft__K1rw5p3uprPRftXo=1720509084853; guardok=9DltyP8ERJnWJaolNInDWV03ft30EOzKt4tqyEk7ovRpu+YeNMKAWDqyyT9DwacZaxy9brXjs+8M+k2pbxhhWw==; PHPSESSID=khol0nbd4esktf48ddmecbidb6; __vtins__K1rw5p3uprPRftXo=%7B%22sid%22%3A%20%22045d7540-b7de-543b-830f-f3cb437c85bd%22%2C%20%22vd%22%3A%201%2C%20%22stt%22%3A%200%2C%20%22dr%22%3A%200%2C%20%22expires%22%3A%201721135512843%2C%20%22ct%22%3A%201721133712843%7D; __51uvsct__K1rw5p3uprPRftXo=7
- User-Agent:Mozilla/xxx
可以看到Cookie中有好幾項,我們繼續在Cookie中刪除,發現只有guardok有用,其他的都沒用,所以最終有用的請求頭如下
- Cookie:guardok=9DltyP8ERJnWJaolNInDWV03ft30EOzKt4tqyEk7ovRpu+YeNMKAWDqyyT9DwacZaxy9brXjs+8M+k2pbxhhWw==
- User-Agent:Mozilla/xxx
js混淆
這么看來關鍵的東西就是這個guardok,那我們看看這個是什么時候生成的,把瀏覽器的cookie刪除,再打開開發者模式
但是發現在開發者模式下,這個js在無限的debug,這是一個很常見的防debug的代碼,就是定時循環執行含有debugger的代碼,如果沒在開發者模式那么debug就不會生效(遇到debugger斷點不會停),但如果是在開發者模式下就會停到斷點處,并且這個方法還會不斷的自己調自己直到下一次定時時間,所以即使我們調試通過這個斷點也會立刻到這個斷點處。
由于這個代碼的存在我們不能查看network,因為會一直卡在debuger。那我們就直接用postman訪問這個js看看guardok是不是在這個js中生成的。
但是這個js返回的內容還是混淆過的,直接看是看不懂的,比如他會把 "location" 混淆成 _0x10a691(0x215, 'lIIz'),其實這個的意思是將一個初始值_0x10a691 進行位偏移,偏移后就變成了另一個值"location" ,并且這個在瀏覽器上運行也是能正常運行,只不過加大了我們的翻譯成本。
分析關鍵參數guardok生成過程
既然翻譯成本大,那我就先確認這個guardok是否和這個js有關,別翻譯了半天發現跟他沒關系,那心態就崩了。這個也好確認,在瀏覽器上訪問一次看這個guardok是什么時候生成的就行,但因為這個debbuger的問題我們不能直接在瀏覽器上訪問,所以就抓個包看看這個接口就行,比如使用Charles。
通過抓包可以看到,同一個接口訪問了兩次
- 第一次訪問,在響應頭中的cookie里返回了guard,并且返回的報文體中返回了那個js文件
- 第二次訪問,在響應頭中的cookie里返回了guardok,并且返回的報文體中返回了正常的頁面數據
可以看到第二次訪問的請求中并沒有任何地方攜帶guardok,但是在響應頭中有guardok。那么就說明第二次的請求中有參數會傳給后端,由后端生成guardok并放到Set-Cookie中,后續的請求就都攜帶了guardok。
查看第二次的請求只是在請求的cookie中多了guardret和guard這兩項。由此可以知道是根據guardret和guard去服務端換取guardok,而guard會在第一次請求的響應中返回到Set-Cookie,無需客戶端手動生成。而guardret則只可能會由第一次請求返回的那個js中生成,那我們只需在js中把生成guardret的算法找出來就行了
反js混淆
到這里也就只能對js進行反混淆了,只有知道生成guardret的算法,那一切就都通了。我試過好多反混淆工具都無法解析出實際的代碼。沒辦法只能花時間一點點的還原了。重頭戲來了,還原的方法其實并不難,相反還很簡單,就是苦力活。比如這個方法
var _0xd750ee = _0x5391;function setRet(_0x34d4ed) {var _0x10a691 = _0xd750ee, WtHInZ = {'GIeQp': function (callee, _0xf9e2d4) {return callee(_0xf9e2d4);}, 'LYVKf': 'undefined', 'fOOLQ': function (_0x396e94, _0x39a709) {return _0x396e94 - _0x39a709;}, 'FARua': function (_0x4be905, _0x42316e) {return _0x4be905 * _0x42316e;}, 'ascvk': function (callee, _0x10b8fa, _0x4313da) {return callee(_0x10b8fa, _0x4313da);}, 'wqePU': function (callee, _0x1a7786) {return callee(_0x1a7786);}, 'dYcOv': _0x10a691(0x201, '0@TB')}, _0x3a9f4b = _0x34d4ed[_0x10a691(0x1ee, '6%cq')](0x0, 0x8), time_num_plain = _0x34d4ed['substr'](0xc),_0x305bd1 = WtHInZ[_0x10a691(0x1c8, '2qE2')](parseInt, time_num_plain['substr'](0xa));typeof window === WtHInZ[_0x10a691(0x1dd, 'WPXd')] && (_0x305bd1 = 0x2);var _0x552e00 = WtHInZ[_0x10a691(0x1da, 'QiI*')](WtHInZ[_0x10a691(0x1d2, 'p7[8')](_0x305bd1, 0x2) + 0x11, 0x2),encrypted = WtHInZ[_0x10a691(0x25a, '!koh')](x, _0x552e00[_0x10a691(0x275, '6f6c')](), _0x3a9f4b),guard_encrypted = WtHInZ[_0x10a691(0x24e, 'lIIz')](b, encrypted);document[_0x10a691(0x1f7, 'hlsZ')] = WtHInZ[_0x10a691(0x1eb, 'sPw2')] + guard_encrypted, window[_0x10a691(0x215, 'lIIz')]['reload']();
}
里面的很多代碼都看不出是啥東西,不過沒關系,我們可以讓瀏覽器幫我們翻譯,首先把無限debug的代碼先去掉,改成空方法即可,如下
function debuggerProtection(counter) {}
然后在一個文本里加入script標簽, <script type="text/javascript"> </script>,再把修改后的js代碼復制到標簽中間,另存為.html文件。雙擊該html文件再使用開發者工具即可。
然后我們就一步步的用瀏覽器debug即可,比如 WtHInZ[_0x10a691(0x1d2, 'p7[8')](_0x305bd1, 0x2)?
1.文本翻譯
首先翻譯 _0x10a691(0x1d2, 'p7[8'),因為var _0x10a691 = _0xd750ee,所以_0x10a691(0x1d2, 'p7[8')也就是_0xd750ee(0x1d2, 'p7[8'),那我們只需要在瀏覽器中把它打印出來即可,alert、debug、console打印都行,在這里我們用debug,隨便找個地方執行,如下打印個斷點查看
可以看到_0x10a691(0x1d2, 'p7[8')為"FARua"
2.文本替換?
WtHInZ[_0x10a691(0x1d2, 'p7[8')](_0x305bd1, 0x2) 就等于 WtHInZ["FARua"](_0x305bd1, 0x2)
3.方法替換?
WtHInZ是一個字典值,里面的key對應里各種方法或者文本,key為"FARua"所對應的是一個方法如下
function (_0x4be905, _0x42316e) {return _0x4be905 * _0x42316e;}
可以看出也就是一個簡單的兩個數相乘,所以WtHInZ["FARua"](_0x305bd1, 0x2)= ?_0x305bd1*0x2。
4.最終替換
到這里就完成了對WtHInZ[_0x10a691(0x1d2, 'p7[8')](_0x305bd1, 0x2)的翻譯。即WtHInZ[_0x10a691(0x1d2, 'p7[8')](_0x305bd1, 0x2) = _0x305bd1*0x2?
其中的_0x305bd1是一個變量名,由上一步計算出來的,不用管
這樣一步步把需要的代碼就還原出來了,其實里面大部分代碼是沒用的就是為了混淆我們,所以我們不用都翻譯,只要翻譯自己感覺像的那幾個方法就行。翻譯完就是這樣的
function setRet(_0x34d4ed) {var _0x10a691 = _0xd750ee, WtHInZ = {'GIeQp': function (callee, _0xf9e2d4) {return callee(_0xf9e2d4);}, 'LYVKf': 'undefined', 'fOOLQ': function (_0x396e94, _0x39a709) {return _0x396e94 - _0x39a709;}, 'FARua': function (_0x4be905, _0x42316e) {return _0x4be905 * _0x42316e;}, 'ascvk': function (callee, _0x10b8fa, _0x4313da) {return callee(_0x10b8fa, _0x4313da);}, 'wqePU': function (callee, _0x1a7786) {return callee(_0x1a7786);}, 'dYcOv': "guardret="}_0x3a9f4b = _0x34d4ed["substr"](0x0, 0x8)time_num_plain = _0x34d4ed['substr'](0xc)_0x305bd1 = parseInt(time_num_plain['substr'](0xa));var _0x552e00 = _0x305bd1 * 0x2 + 0x11 - 0x2encrypted = x(_0x552e00["toString"](), _0x3a9f4b)guard_encrypted = btoa(encrypted);document["cookie"] = "guardret=" + guard_encrypted, window['location']['reload']();
}
可以看到guardret確實是在這個js中生成的,并且生成的算法也比較簡單就是一些加減乘除加上異或操作等,生成后就可以使用guardret和guard去服務端換guardok了。由此這個破解反爬蟲策略也就完成了
完整破解實戰
下一篇文章我會實戰破解兩個這種反爬蟲策略的網站,并用java實現