使用Haskell破解极验验证码:详细指南

一. 观察verify请求
首先,我们需要观察verify请求的Initiator,可以看到所有请求都来自于gcaptcha4.js这个文件,所以该文件是我们此次的目标所在。

打开gcaptcha4.js文件,格式化后,我们在13339行发现,代码经过混淆处理,这时候我们有两种选择:硬刚或者使用AST还原。我选择了AST还原JS文件,然后使用Chrome上的reres插件将混淆的JS替换掉,再一步步调试。AST还原代码已放在GitHub上,reres插件的安装和使用请自行搜索。

替换成功后,我们得到以下结果:

二. 定位w参数加密位置
搜索w参数,我们可以通过以下关键词定位:

w
.w
'w'
"w"
搜索"w"后,我们找到了目标,在2527行打上断点,可以看到w的值r在2525行定义,我们也在此打上断点,刷新页面,成功断上。至此,我们已完成w参数的加密位置定位。

三. 分析w参数加密算法
简化w的值r的定义:

javascript

var r = (0, d["default"])(l["default"]["stringify"](e), a)
等价于:

javascript

var r = d.default(l.default.stringify(e), a)
分析l.default.stringify(e)的含义:
可以看出此处是对e进行JSON序列化操作。

分析d.default(l.default.stringify(e), a)的含义:
跟进d函数,在几个条件判断处打上断点,调试后得出结论:

javascript

function a(e, t) {
    var n = c["guid"](), 
        a = new _["default"]()["encrypt"](n);
        o = i["default"]["encrypt"](e, n);
    return c["arrayToHex"](o) + a;
}
我们确定参数t并未使用,参数e即w的明文。继续分析JSON序列化前e的组成:

haskell

data E = E {
  device_id :: String,
  em :: Em,
  ep :: String,
  geetest :: String,
  fq6a :: String,
  lang :: String,
  lot_number :: String,
  passtime :: Int,
  pow_msg :: String,
  pow_sign :: String,
  setLeft :: Int,
  track :: [[Int]],
  userresponse :: Double
} deriving (Show, Generic)

data Em = Em {
  cp :: Int,
  ek :: String,
  nt :: Int,
  ph :: Int,
  sc :: Int,
  si :: Int,
  wd :: Int
} deriving (Show, Generic)
四. 分析pow_msg和pow_sign
搜索pow_msg,打上断点,刷新,保留成功断上的断点:

haskell

u = n ++ "|" ++ a ++ "|" ++ s ++ "|" ++ o ++ "|" ++ t ++ "|" ++ e ++ "|" ++ r ++ "|"
p = generateGuid()
g = u ++ p
pow_msg = u ++ p
pow_sign = md5 g
我们需要分析generateGuid算法:

haskell

import System.Random (randomRIO)
import Data.List (intercalate)

generateGuid :: IO String
generateGuid = do
  parts <- mapM (const $ randomRIO (0, 65535)) [1..4]
  return $ intercalate "" $ map (`showHex` "") parts
五. 分析set_left、track、passtime、userresponse
set_left为滑块移动距离取整。
track为移动轨迹,从第二步开始,是上一步的相对移动距离(x, y, t)。
passtime为总移动时间。
userresponse为set_left / (0.8876 * 340 / 300)。
至此,我们已将w的明文分析完毕。以下是Haskell实现代码:

六. 代码实现
haskell

{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson (encode, ToJSON)
import GHC.Generics (Generic)
import System.Random (randomRIO)
import Data.Digest.Pure.MD5 (md5)
import Data.ByteString.Lazy.Char8 (pack)
import Numeric (showHex)
import Data.List (intercalate)

data E = E {
  device_id :: String,
  em :: Em,
  ep :: String,
  geetest :: String,
  fq6a :: String,
  lang :: String,
  lot_number :: String,
  passtime :: Int,
  pow_msg :: String,
  pow_sign :: String,
  setLeft :: Int,
  track :: [[Int]],
  userresponse :: Double
} deriving (Show, Generic)

data Em = Em {
  cp :: Int,
  ek :: String,
  nt :: Int,
  ph :: Int,
  sc :: Int,
  si :: Int,
  wd :: Int
} deriving (Show, Generic)

instance ToJSON E
instance ToJSON Em

generateGuid :: IO String
generateGuid = do
  parts <- mapM (const $ randomRIO (0, 65535)) [1..4]
  return $ intercalate "" $ map (`showHex` "") parts

generateWParams :: IO E
generateWParams = do
  let device_id = "A8A0"
      em = Em 0 "11" 0 0 0 0 1
      ep = "123"
      geetest = "captcha"
      fq6a = "1925502591"
      lang = "zh"
      lot_number = "7e22264d4f3e4dd8a6ffbf6e82e1122d"
      passtime = 166
      set_left = 88
      track = [[38, 18, 0], [1, 0, 33]]
      userresponse = fromIntegral set_left / (0.8876 * 340 / 300)

  guid <- generateGuid
  let u = lot_number ++ "|" ++ guid
      pow_msg = u ++ guid
      pow_sign = show (md5 (pack pow_msg))

  return $ E device_id em ep geetest fq6a lang lot_number passtime pow_msg pow_sign set_left track userresponse

main :: IO ()
main = do
  wParams <- generateWParams
  print $ encode wParams

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值