一. 观察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