<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何使用 JS 破解輕量級(jí)滑塊驗(yàn)證碼

          共 3322字,需瀏覽 7分鐘

           ·

          2022-01-14 18:45

          今天在這篇文章里給大家介紹一下怎么使用 JS 破解滑塊驗(yàn)證碼。

          文章末尾有源碼鏈接,需要的朋友可以自取,不過(guò)拿走之前記得三連哇!

          思路講解

          操作瀏覽器打開頁(yè)面

          滑塊驗(yàn)證碼是滑動(dòng)驗(yàn)證碼的一種,生成流程至少包含三步:

          1. 根據(jù)用戶標(biāo)識(shí),從后臺(tái)獲得驗(yàn)證碼圖片

          2. 監(jiān)聽鼠標(biāo)事件并回傳后臺(tái)

          3. 后臺(tái)判斷事件的真?zhèn)?,回傳?yàn)證結(jié)果

          無(wú)論生成機(jī)制的細(xì)節(jié)如何,滑塊終究是要展示在頁(yè)面上的。直接用 HTTP Request 把頁(yè)面 HTML 請(qǐng)求下來(lái)肯定不行,這里我們需要使用 Puppeteer 打開網(wǎng)頁(yè)進(jìn)行渲染。測(cè)試頁(yè)面就以 react-slider-vertify 的官網(wǎng)為例。

          Puppeteer 是一個(gè)通過(guò) DevTools 協(xié)議來(lái)控制瀏覽器行為的庫(kù),只需編寫不多的代碼,就可以操作真實(shí)的瀏覽器搞定諸如爬蟲、自動(dòng)化測(cè)試、網(wǎng)頁(yè)性能分析、瀏覽器擴(kuò)展測(cè)試等功能。使用起來(lái)比較方便,文檔齊全。根據(jù)文檔,打開頁(yè)面前需要啟動(dòng)一個(gè)瀏覽器實(shí)例,然后調(diào)用 newPage 方法創(chuàng)建一個(gè)新頁(yè)面。核心代碼如下。

          const?puppeteer?=?require('puppeteer')

          puppeteer.launch().then(async?browser?=>?{
          ??const?page?=?await?browser.newPage()
          ??await?page.goto('http://h5.dooring.cn/slider-vertify/vertify')
          })

          正常瀏覽網(wǎng)頁(yè)時(shí),很少會(huì)碰到滑塊驗(yàn)證,因?yàn)樗穆窂椒浅iL(zhǎng),會(huì)需要浪費(fèi)用戶好一會(huì)兒時(shí)間。雖然文案上給你顯示“恭喜 0.9s 打敗了 99% 的用戶”,但從前置腳本請(qǐng)求,到加載圖片,用戶滑動(dòng)滑塊,到回傳驗(yàn)證...前前后后的步驟加起來(lái)可能花了你 9s 不止。

          本質(zhì)上它是防御性的手段,一般當(dāng)服務(wù)器限流,或者服務(wù)器已經(jīng)懷疑你是爬蟲的時(shí)候,才會(huì)讓它跳出來(lái)要求做進(jìn)一步驗(yàn)證。所以一般來(lái)說(shuō),爬蟲代碼可以默認(rèn)忽略滑塊驗(yàn)證的,不過(guò)本文代碼我們就假定默認(rèn)驗(yàn)證碼一定存在吧。

          接下來(lái),分析一下用戶行為。解決滑塊驗(yàn)證,無(wú)非就是先判斷一下缺口的位置,然后移動(dòng)鼠標(biāo)。這里進(jìn)一步可以細(xì)分為鼠標(biāo)的點(diǎn)擊事件和鼠標(biāo)移動(dòng)事件兩種。代碼邏輯大致如下。

          1. 等待驗(yàn)證碼圖片加載完畢

          2. 移動(dòng)鼠標(biāo)到滑塊位置

          3. 按下鼠標(biāo)

          4. 移動(dòng)鼠標(biāo)到缺口位置

          5. 松開鼠標(biāo)

          6. 等待結(jié)果返回

          啊,流程我都知道,可問(wèn)題就在,怎么判斷要把鼠標(biāo)移動(dòng)到哪里?服務(wù)器端返回的是一張帶缺口的圖片,缺口位置指定是不通過(guò)接口傳遞的。

          可能還有些同學(xué)會(huì)問(wèn),怎么移動(dòng)鼠標(biāo)呢?如果我一次性就把滑塊給移到指定位置了,那服務(wù)器不是會(huì)立馬把我標(biāo)記為爬蟲嘛... 這兩個(gè)問(wèn)題我逐一解答。

          判斷缺口位置

          要分析缺口的位置,我們必須先知道這個(gè)缺口是怎么畫上去的。打開控制臺(tái)初步檢查可以發(fā)現(xiàn),頁(yè)面從服務(wù)器先拿到了一張完整的圖片,然后缺口位置是通過(guò) JS 隨機(jī)生成的。啊這...這...這是因?yàn)槲覀冇玫臏y(cè)試頁(yè)面是文檔頁(yè),大家不必在意這些安全方面的小細(xì)節(jié)。

          接著剛才的思路繼續(xù),既然缺口是從原圖中挖的一個(gè)洞,那么我們只需要識(shí)別一下圖片中洞的位置就好了。比較簡(jiǎn)單的方案是使用第三方的圖像識(shí)別技術(shù)(或相關(guān)技術(shù)),把圖片上傳至第三方,就直接拿到缺口位置的相對(duì)坐標(biāo)。下圖給大家展示一下阿里云的圖像分割的效果alimagic。

          如果想做一個(gè)效果穩(wěn)定一點(diǎn)的驗(yàn)證碼破解工具,我建議大家還是用自己的模型,或者自己寫算法。一來(lái)是第三方要的錢錢不少哇~ 再就是這種圖像識(shí)別并不是專門為驗(yàn)證碼訓(xùn)練的,所以放到爬蟲中還不太成熟,一旦背景圖片復(fù)雜,識(shí)別率下降得老快了。

          以下介紹一種新思路。反正我們已經(jīng)有一張完整的圖片了,那就只要不斷地滑動(dòng)滑塊,把結(jié)果和原圖比對(duì)就行。理論上,只要滑倒差不多“那個(gè)點(diǎn)”,肉眼看起來(lái)不會(huì)有很大的違和感時(shí),就搞定了。比如下面這張圖。

          截圖嘛可以直接用 Pupeteer 的截圖功能,它提供了對(duì)應(yīng)的 API,可以精準(zhǔn)的截出特定元素。

          至于圖片比對(duì),其實(shí)就是給圖片初步處理后,兩張圖一個(gè)像素一個(gè)像素的去比較。兩個(gè)像素如果顏色差異超過(guò)閾值,就認(rèn)為這是兩個(gè)不相同的像素。簡(jiǎn)便起見(jiàn),我們直接用開源庫(kù) rembrandt,它會(huì)給我們返回兩張圖片間的差異。

          最后是滑動(dòng)滑塊,既然要模擬人肉操作,那么操作 CSS,用絕對(duì)定位,把滑塊一個(gè)像素一個(gè)像素的向右移;每移動(dòng)一次,把圖像比對(duì)的結(jié)果記錄下來(lái)。

          以上三個(gè)流程的核心代碼非常簡(jiǎn)單,只需要以下幾行:

          while?(left?<=?maxOffset)?{

          ??/*?使用?CSS?left?屬性控制懸浮的滑塊的偏移量?*/

          ??await?page.evaluate(async?($sliderFloat,?left)?=>?{
          ????$sliderFloat.setAttribute('style',?`left:?${left}px`)
          ??},?$sliderFloat,?left)

          ??/*?截圖并和原圖進(jìn)行比對(duì),把結(jié)果存到?results?數(shù)組里?*/

          ??const?$panel?=?await?page.$('#Vertify-demo-4?.canvasArea')
          ??const?panelImgBase64?=?await?$panel.screenshot({
          ????type:?'jpeg'
          ??})
          ??const?compareRes?=?await?rembrandt({
          ????imageA:?panelImgBase64,
          ????imageB:?rawImage
          ??})
          ??results.push({
          ????left,
          ????diff:?compareRes.differences
          ??})

          ??left?+=?1
          }

          最后,把 results 扔到里面展示一下(這里給個(gè) ECharts 折線圖示例網(wǎng)址),不出意外能得到這樣一張圖表??吹侥莻€(gè)尖尖的“V”型山谷了嘛,呼哈哈哈,答案很明顯,當(dāng)我們把滑塊從左往右移動(dòng)時(shí),滑塊約接近缺口,那截出來(lái)的圖片就越像原圖,它兩之間像素差異越??;一直往右移動(dòng),滑塊會(huì)逐漸遠(yuǎn)離缺口,截出來(lái)的圖片和原圖相比像素差異又逐漸開始增大。

          我們只需要把差異最小的那個(gè)點(diǎn)找到,然后滑動(dòng)滑塊到對(duì)應(yīng)的 left 偏移量就闊以了。

          題外話,為什么最大的差異在 3000 左右呢?我們簡(jiǎn)單估算一下。

          滑塊的大小為 45*45,再加上外面的圓形,約摸占了 2100 像素;也就是說(shuō)缺口加滑塊,理論上最大會(huì)有 4200 個(gè)像素和原圖不同。不過(guò)滑塊可能和遮住的地方像素有重合,假設(shè)重合了 350 像素,再加上我們的最低點(diǎn)的圖片差異都有 351,減去這些誤差,得 3499。嗚呼,3499 約等于 3000,估算成功(手動(dòng)狗頭)。

          速度優(yōu)化技巧

          不過(guò)這還沒(méi)完,你要是把代碼跑起來(lái)就會(huì)發(fā)現(xiàn),臥草,太慢了這玩意兒!正常人劃一下驗(yàn)證碼頂多兩秒鐘的事兒,我們一幀一幀截圖得花個(gè) 40s 的時(shí)間才能截完圖算出山谷谷底的值來(lái)。

          這里提供幾種思路優(yōu)化效率:

          1. 把元素縮小,復(fù)制多份,平鋪開來(lái)展示;這樣只要截一次,然后再裁剪、比對(duì)就好。

          2. 放大步長(zhǎng),比方說(shuō)先每次平移 15px,找到局部最優(yōu)解,然后在局部最優(yōu)解附近再回到平移 1px 的方案找最優(yōu)解。

          3. 因?yàn)閳D片比對(duì)的結(jié)果類似“V”字,“V”字右半邊其實(shí)是可以不用再計(jì)算的。

          使用 1+2+3 我覺(jué)得可以在 3s 內(nèi)搞定最優(yōu)解,不過(guò)代碼復(fù)雜度會(huì)變得很高,文中簡(jiǎn)單起見(jiàn)暫只實(shí)現(xiàn)一下方案 2。

          首先是每次移動(dòng) 15px 找局部最優(yōu)解。

          //?圖片缺口是不會(huì)給挖在初始附近的,
          //?所以?left?從?45?像素開始計(jì)算可以節(jié)約不少計(jì)算量,
          let?left?=?45;
          const?max15Offset?=?265;
          const?res15px?=?[];
          while?(left?<=?max15Offset)?{
          ??await?setLeft(left);
          ??const?compareRes?=?await?compare();
          ??res15px.push({
          ????left,
          ????diff:?compareRes.differences,
          ??});
          ??left?+=?15;
          }

          然后再嘗試每次移動(dòng) 2px 找最優(yōu)解,搜尋的范圍是 15px 步長(zhǎng)最優(yōu)解的 left 偏移量的左右共 20 個(gè)像素。

          const?min15pxDiff?=?Math.min(...res15px.map((x)?=>?x.diff));
          const?min15pxLeft?=?res15px.find((x)?=>?x.diff?===?min15pxDiff).left;

          left?=?min15pxLeft?-?12;
          const?max2Offset?=?min15pxLeft?+?8;
          const?res2px?=?[];
          while?(left?<=?max2Offset)?{
          ??await?setLeft(left);
          ??const?compareRes?=?await?compare();
          ??res2px.push({
          ????left,
          ????diff:?compareRes.differences,
          ??});
          ??left?+=?2;
          }

          此時(shí)得到的解可以約等于最優(yōu)解了。當(dāng)然,如果你覺(jué)得不穩(wěn)的話,還可以使用 1px 步長(zhǎng)去找。

          估算一下,原先需要截 245 次圖片,現(xiàn)在直接降到 1/10,23 次。不過(guò),也別太高興,因?yàn)闇y(cè)試發(fā)現(xiàn)只做優(yōu)化 2,解驗(yàn)證碼的時(shí)候還是要 7s...

          操作鼠標(biāo)滑滑塊

          缺口位置都搞定了,那移鼠標(biāo)滑滑塊兒還不簡(jiǎn)單嘛~

          Puppeteer 已經(jīng)提供了鼠標(biāo)相關(guān)的接口,一共四個(gè):mouse.click、mouse.down、mouse.move、mouse.up,分別是點(diǎn)擊、按下、移動(dòng)和松開。使用 mouse.move 可以直接把鼠標(biāo)位置移動(dòng)到一個(gè)特定的坐標(biāo)上。

          假設(shè)我們現(xiàn)在從坐標(biāo)(100,100)花大約 1s 把鼠標(biāo)移動(dòng)到 (200,200),可以使用循環(huán)實(shí)現(xiàn)。

          const?now?=?{
          ??x:?100,
          ??y:?100
          }
          const?target?=?{
          ??time:?1000,
          ??x:?200,
          ??y:?200,?
          }
          const?steps?=?10
          const?step?=?{
          ??x:?Math.floor((target.x?-?now.x)?/?steps),
          ??y:?Math.floor((target.y?-?now.y)?/?steps),
          ??time:?target.time?/?steps
          }
          while?(now.x???await?sleep(step.time)
          ??now.x?+=?step.x
          ??now.y?+=?step.y
          ??await?page.mouse.move(now)
          }

          鼠標(biāo)軌跡優(yōu)化

          害,要是打游戲的時(shí)候也像這段代碼一樣,我想我的手點(diǎn)到哪兒它就點(diǎn)到哪兒就好了~

          機(jī)器是不會(huì)手抖的,這段代碼和真實(shí)世界的滑動(dòng)效果相差太遠(yuǎn)了!我們看一張我用手滑的效果,尤其是要仔細(xì)觀察滑動(dòng)過(guò)程中鼠標(biāo)的位置。

          • 鼠標(biāo) Y 軸位置總是在變

          • 鼠標(biāo) X 軸位置會(huì)滑過(guò)頭(別笑,你肯定也經(jīng)常劃過(guò)頭)

          這里做一波小優(yōu)化,把這兩個(gè)細(xì)節(jié)整合進(jìn)去。

          //?獲得一個(gè)隨機(jī)的偏移量
          const?getRandOffset?=?(randNegative?=?true,?max?=?3)?=>?{
          ??const?negative?=?randNeagtive
          ??????(Math.random()?????:?1
          ??return?Math.floor(Math.random()?*?max)?*?negative
          }

          //?先滑過(guò)頭十幾像素,然后再花?100?毫秒的時(shí)間往回滑到正確位置
          const?points?=?[
          ??{
          ????time:?1000,
          ????x:?200?+?getRandOffset(false,?15),
          ????y:?200?+?getRandOffset(false,?15),
          ????steps:?10
          ??},
          ??{
          ????time:?100,
          ????x:?200,
          ????y:?200,
          ????steps:?3
          ??}
          ]

          //?注意這里用?for?await?循環(huán)把?points?串起來(lái)執(zhí)行
          for?await?(const?target?of?points)?{
          ??const?step?=?{
          ????x:?Math.floor((target.x?-?now.x)?/?target.steps),
          ????y:?Math.floor((target.y?-?now.y)?/?target.steps),
          ????time:?target.time?/?target.steps,
          ??}
          ??let?gap
          ??while?(gap?=?Math.abs((target.x?-?now.x)),?gap?>?0)?{
          ????await?sleep(step.time)
          ????//?最后一步就直接滑動(dòng)到位,不需要隨機(jī)數(shù)了
          ????const?inOneStep?=?Math.abs(target.x?-?now.x)?<=?Math.abs(step.x);
          ????if?(inOneStep)?{
          ??????now.x?=?target.x;
          ??????now.y?=?target.y;
          ????}?else?{
          ??????now.x?+=?step.x?+?getRandOffset();
          ??????now.y?+=?step.y?+?getRandOffset();
          ????}
          ????moveMouseTo(now)
          ??}
          }

          如何移動(dòng)鼠標(biāo)到這里就解決了,如果要考慮加速度、用戶習(xí)慣等因素,代碼會(huì)復(fù)雜許多,暫時(shí)就不深入討論啦,有興趣的同學(xué)可以自己研究。

          最終效果見(jiàn)下圖。

          源碼地址在此:CrackTheShield
          https://github.com/Lionad-Morotar/crack-the-shield/tree/master/tasks/dooring-slider

          作者:仿生獅子

          https://juejin.cn/post/7009333291140513799

          - EOF -


          瀏覽 127
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  20120av天堂 | 强伦轩一区二区三区四区 | 一级黄片99日日 | 天天干夜夜操www | 亚洲第一色 |