背景:由於爬 https://www.tianyancha.com/ ,需要登錄登錄認證,所以來做破解 極驗驗證
參考資料:https://www.cnblogs.com/ZQWelcomeIndex/p/8367202.html 破解騰訊空間滑塊 (注:目前圖片地址有變化,該地址代碼下載不能直接使用。可以參考 https://www.cnblogs.com/hujunmin/p/11486124.html)
Nuget: Selenium.WebDriver,Selenium.WebDriver.ChromeDriver
思路:
一:獲取原始圖片,如下圖:(圖1)
二:獲取原始圖加缺口圖疊加后的圖片
隨意拖動一次后,得到下圖(圖2):
通過JS控制CSS隱藏上圖中紅色塊后,得到原始圖加缺口圖組合后的圖,如下圖:(圖3)
三:對比前2步驟的圖片,獲取缺口位置
對比 圖1 圖3,獲得缺口在圖片的X坐標
四:減去左邊偏移量,獲得移動距離
減去 圖2 中缺口圖起點X坐標(4px)
五:根據移動距離,計算移動軌跡
極驗驗證碼后台對滑動軌跡有驗證。若是通過代碼直接勻速直接移動到指定位置,會提示:“圖片被怪物吃掉了”。所以要程序模擬認為滑動操作:
離缺口位置遠,移動速度快。
離缺口位置近,移動速度慢。
需要模擬不小心超過指定位置,然后再慢慢回頭對缺口操作。
六:根據移動軌跡拖動滑塊
調用 Actions 的 MoveByOffset,按照移動軌跡一步步移動。注意:每次移動后 Actions 要重新 new ,否則會對不上缺口。具體原因自己去找資料(重復 MoveByOffset ,每次移動是之前的累計值)
七:判斷拖動滑塊后是否驗證通過,若不通過,重試
拖動后,判讀這個按鈕是否還存在
至此,思路完畢。
注意:
原始圖
https://static.geetest.com/pictures/gt/969ffa43c/969ffa43c.webp
原始圖加缺口圖組合后的組合圖
https://static.geetest.com/pictures/gt/969ffa43c/bg/a0a1cdb4c.webp
兩個圖片是無序的,和我在瀏覽器上看到的不一致。
所以對比圖片的時候,需要將無序圖片轉成正常的圖片。
轉換思路一:
經分析極驗驗證碼是把圖片分成52塊小圖片,按照指定順序打亂后,通過css再重新排序顯示的。
知道圖片規則,我們就按照這個規則,把圖片切成52個小圖,然后排序再組合成一張有序的原始圖。
轉換思路二:
直接去瀏覽器上通過顯示隱藏不同的圖片,然后截圖對比(目前我代碼這個思路處理的)。
上代碼:
using System; using System.Collections.Generic; using System.Drawing; using System.Threading; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Remote; namespace Sniffer.VerificationCode.VerificationCodes { public class GeetestSlideVerificationCode : ISlideVerificationCode { #region 屬性 /// <summary> /// 拖動按鈕 /// </summary> private string _slidButton = "gt_slider_knob"; /// <summary> /// 原始圖層 /// </summary> private string _originalMap = "gt_fullbg"; /// <summary> /// 原始圖加缺口背景圖 /// </summary> private string _newMap = "gt_bg"; /// <summary> /// 缺口圖層 /// </summary> private string _sliceMap = "gt_slice"; /// <summary> /// 重試次數 /// </summary> private int _tryTimes = 6; /// <summary> /// 缺口圖默認偏移像素 /// </summary> private int _leftOffset = 4; private string _fullScreenPath = AppDomain.CurrentDomain.BaseDirectory + "全屏.png"; private string _originalMapPath = AppDomain.CurrentDomain.BaseDirectory + "原圖.png"; private string _newMapPath = AppDomain.CurrentDomain.BaseDirectory + "新圖.png"; #endregion public bool Pass(RemoteWebDriver remoteWebDriver) { int failTimes = 0; bool flag = false; do { //#TODO 檢查圖層是否正常彈出 //截圖 Console.WriteLine("開始截圖..."); ScreenMap(remoteWebDriver); Console.WriteLine("開始計算距離..."); //獲取缺口圖層位移距離 var distance = GetDistance(); //獲取移動軌跡 Console.WriteLine("開始獲取移動軌跡..."); var moveEntitys = GetMoveEntities(distance); //移動 Console.WriteLine("開始移動..."); Move(remoteWebDriver, moveEntitys); Console.WriteLine("休眠3秒,顯示等待提交驗證碼..."); Thread.Sleep(3000); Console.WriteLine("開始檢查認證是否通過..."); //檢查移動是否成功 flag = CheckSuccess(remoteWebDriver); if (flag) break; } while (++failTimes < _tryTimes); return flag; } #region 內部方法 protected virtual bool CheckSuccess(RemoteWebDriver remoteWebDriver) { //WebDriverWait wait = new WebDriverWait(remoteWebDriver, TimeSpan.FromSeconds(5)); //IWebElement gt_ajax_tip = null; //gt_ajax_tip = wait.Until<IWebElement>((d) => //{ // try // { // return d.FindElement(By.CssSelector(".gt_holder .gt_ajax_tip.gt_success")); // } // catch (Exception ex) // { // return null; // } //}); //if (gt_ajax_tip == null) //{ // Console.WriteLine("驗證失敗,顯示等待6秒刷新驗證碼..."); // Thread.Sleep(6000); // return false; //} //else //{ // return true; //} var gt_slider_knob = remoteWebDriver.FindElementExt(By.ClassName(_slidButton), 10); if (gt_slider_knob == null) { return true; } else { Console.WriteLine("驗證失敗,顯示等待6秒刷新驗證碼..."); Thread.Sleep(6000); return false; } } private void Move(RemoteWebDriver remoteWebDriver,List<MoveEntity> moveEntities) { var slidButton = GetSlidButtonElement(remoteWebDriver); Actions builder = new Actions(remoteWebDriver); builder.ClickAndHold(slidButton).Perform(); int offset = 0; int index = 0; foreach (var item in moveEntities) { index++; builder = new Actions(remoteWebDriver); builder.MoveByOffset(item.X, item.Y).Perform(); //Console.WriteLine("向右總共移動了:" + (offset = offset + item.X)); //if (offset != 0 && index != moveEntities.Count) // Thread.Sleep(item.MillisecondsTimeout / offset); } builder.Release().Perform(); } private List<MoveEntity> GetMoveEntities(int distance) { List<MoveEntity> moveEntities = new List<MoveEntity>(); int allOffset = 0; do { int offset = 0; double offsetPercentage = allOffset / (double)distance; if (offsetPercentage > 0.5) { if (offsetPercentage < 0.85) { offset = new Random().Next(10, 20); } else { offset = new Random().Next(2, 5); } } else { offset = new Random().Next(20, 30); } allOffset += offset; int y = (new Random().Next(0, 1) == 1 ? new Random().Next(0, 2) : 0 - new Random().Next(0, 2)); moveEntities.Add(new MoveEntity(offset,y , offset)); } while (allOffset <= distance + 5); //最后一部分移動 var moveOver = allOffset > distance; for (int j = 0; j < Math.Abs(distance - allOffset);) { int step = 3; int offset = moveOver ? -step : step; int sleep = new Random().Next(100, 200); moveEntities.Add(new MoveEntity(offset,0, sleep)); ; j = j + step; } return moveEntities; } /// <summary> /// 比較兩張圖片的像素,確定陰影圖片位置 /// </summary> /// <param name="oldBmp"></param> /// <param name="newBmp"></param> /// <returns></returns> private int GetArgb(Bitmap oldBmp, Bitmap newBmp) { //由於陰影圖片四個角存在黑點(矩形1*1) for (int i = 0; i < newBmp.Width; i++) { for (int j = 0; j < newBmp.Height; j++) { if ((i >= 0 && i <= 1) && ((j >= 0 && j <= 1) || (j >= (newBmp.Height - 2) && j <= (newBmp.Height - 1)))) { continue; } if ((i >= (newBmp.Width - 2) && i <= (newBmp.Width - 1)) && ((j >= 0 && j <= 1) || (j >= (newBmp.Height - 2) && j <= (newBmp.Height - 1)))) { continue; } //獲取該點的像素的RGB的顏色 Color oldColor = oldBmp.GetPixel(i, j); Color newColor = newBmp.GetPixel(i, j); if (Math.Abs(oldColor.R - newColor.R) > 60 || Math.Abs(oldColor.G - newColor.G) > 60 || Math.Abs(oldColor.B - newColor.B) > 60) { return i; } } } return 0; } /// <summary> /// 獲取實際圖層缺口實際距離 /// </summary> /// <returns></returns> private int GetDistance() { using (Bitmap oldBitmap = (Bitmap)Image.FromFile(_originalMapPath)) { using (Bitmap newBitmap = (Bitmap)Image.FromFile(_newMapPath)) { var distance = GetArgb(oldBitmap, newBitmap); distance = distance - _leftOffset; return distance; } } } /// <summary> /// 截圖 /// </summary> /// <param name="remoteWebDriver"></param> private void ScreenMap(RemoteWebDriver remoteWebDriver) { //顯示原始圖 ShowOriginalMap(remoteWebDriver); //全屏截圖 FullScreen(remoteWebDriver); //獲取原始圖層 var originalElement = GetOriginalElement(remoteWebDriver); //保存原始圖 CutBitmap(_fullScreenPath, _originalMapPath, originalElement); //顯示新圖層 ShowNewMap(remoteWebDriver); //全屏截圖 FullScreen(remoteWebDriver); //獲取新圖層 var newElement = GetNewMapElement(remoteWebDriver); //保存新圖 CutBitmap(_fullScreenPath, _newMapPath, newElement); //顯示缺口圖 ShowSliceMap(remoteWebDriver); } /// <summary> /// 截圖 /// </summary> /// <param name="sourcePath"></param> /// <param name="targetPath"></param> /// <param name="webElement"></param> private void CutBitmap(string sourcePath, string targetPath, IWebElement webElement) { //獲取原始圖 using (var bitmap = (Bitmap)Image.FromFile(sourcePath)) { var newBitmap = bitmap.Clone(new Rectangle(webElement.Location, webElement.Size), System.Drawing.Imaging.PixelFormat.DontCare); newBitmap.Save(targetPath); newBitmap.Dispose(); bitmap.Dispose(); } } /// <summary> /// 全屏截圖 /// </summary> /// <param name="remoteWebDriver"></param> private void FullScreen(RemoteWebDriver remoteWebDriver) { remoteWebDriver.GetScreenshot().SaveAsFile(_fullScreenPath); } /// <summary> /// 獲取原始圖層元素 /// </summary> /// <param name="remoteWebDriver"></param> /// <returns></returns> protected virtual IWebElement GetOriginalElement(RemoteWebDriver remoteWebDriver) { return remoteWebDriver.FindElementExt(By.ClassName(_originalMap), 10); } /// <summary> /// 獲取原始圖加缺口背景圖元素 /// </summary> /// <param name="remoteWebDriver"></param> /// <returns></returns> protected virtual IWebElement GetNewMapElement(RemoteWebDriver remoteWebDriver) { return remoteWebDriver.FindElementExt(By.ClassName(_newMap), 10); } /// <summary> /// 獲取缺口圖層元素 /// </summary> /// <param name="remoteWebDriver"></param> /// <returns></returns> protected virtual IWebElement GetSliceMapElement(RemoteWebDriver remoteWebDriver) { return remoteWebDriver.FindElementExt(By.ClassName(_sliceMap), 10); } /// <summary> /// 獲取拖動按鈕元素 /// </summary> /// <param name="remoteWebDriver"></param> /// <returns></returns> protected virtual IWebElement GetSlidButtonElement(RemoteWebDriver remoteWebDriver) { return remoteWebDriver.FindElementExt(By.ClassName(_slidButton), 10); } /// <summary> /// 顯示原始圖層 /// </summary> /// <param name="remoteWebDriver"></param> protected virtual bool ShowOriginalMap(RemoteWebDriver remoteWebDriver) { remoteWebDriver.ExecuteScript("$('." + _newMap + "').hide();$('." + _originalMap + "').show();$('." + _sliceMap + "').hide();"); Console.WriteLine("顯示原始圖"); Thread.Sleep(100); //#TODO 判斷JS執行后是否正確 return true; } /// <summary> /// 顯示原始圖加缺口背景之后的圖層 /// </summary> /// <param name="remoteWebDriver"></param> /// <returns></returns> protected virtual bool ShowNewMap(RemoteWebDriver remoteWebDriver) { remoteWebDriver.ExecuteScript("$('." + _newMap + "').show();$('." + _originalMap + "').hide();$('." + _sliceMap + "').hide();"); Console.WriteLine("顯示原始圖加缺口背景之后的圖層"); Thread.Sleep(100); //#TODO 判斷JS執行后是否正確 return true; } /// <summary> /// 顯示缺口圖 /// </summary> /// <param name="remoteWebDriver"></param> /// <returns></returns> protected virtual bool ShowSliceMap(RemoteWebDriver remoteWebDriver) { remoteWebDriver.ExecuteScript("$('." + _sliceMap + "').show();"); Console.WriteLine("顯示原始圖加缺口背景之后的圖層"); Thread.Sleep(100); //#TODO 判斷JS執行后是否正確 return true; } #endregion } }
public interface ISlideVerificationCode { bool Pass(RemoteWebDriver remoteWebDriver); }
using OpenQA.Selenium.Chrome; using Sniffer.VerificationCode.VerificationCodes; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Sniffer.VerificationCode.Tests { class Program { static void Main(string[] args) { ChromeDriver driver = new ChromeDriver(); driver.Navigate().GoToUrl("https://www.tianyancha.com/"); //driver.Manage().Window.Maximize();//窗口最大化,便於腳本執行 driver.Manage().Window.Size = new Size(800, 800); //Console.WriteLine("ChromeDriver 設置超時等待(隱式等待)時間設置10秒"); //設置超時等待(隱式等待)時間設置10秒 //driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10); //點擊《登錄/注冊》按鈕 driver.ExecuteScript("header.loginLink(event)"); Console.WriteLine("點擊《登錄/注冊》按鈕"); Thread.Sleep(500); //點擊 《密碼登錄》 driver.ExecuteScript("loginObj.changeCurrent(1);"); Console.WriteLine("點擊 《密碼登錄》按鈕"); Thread.Sleep(500); //輸入賬號密碼 driver.ExecuteScript("$('.contactphone').val('18620800677')"); driver.ExecuteScript("$('.contactword').val('******')"); Console.WriteLine("輸入賬號密碼"); Thread.Sleep(500); //點擊登錄按鈕 driver.ExecuteScript("loginObj.loginByPhone(event);"); Console.WriteLine("點擊《登錄》按鈕"); Thread.Sleep(1000); GeetestSlideVerificationCode slideVerificationCode = new GeetestSlideVerificationCode(); var flag = slideVerificationCode.Pass(driver); Console.WriteLine("過驗證 " + flag); } } }