在本文中,我们将介绍如何使用Haskell来逆向工程gcaptcha4.js文件,找到并破解w参数的加密算法。整个过程包括观察verify请求,定位加密位置,分析加密算法,并最终还原w参数的明文。
一. 观察verify请求
首先,我们需要观察verify请求的发起者。通过网络请求分析工具,我们可以看到所有verify请求都来自于gcaptcha4.js文件。因此,这个文件成为我们分析的重点。
我们使用Haskell的http-conduit库来捕获和分析这些请求。
haskell
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
main :: IO ()
main = do
response <- httpLBS "https://example.com/verify"
let responseBody = getResponseBody response
putStrLn $ "The response body is: " ++ show responseBody
通过分析响应内容,可以发现所有的verify请求都指向了gcaptcha4.js文件。
二. 定位w参数加密位置
为了找到w参数的加密位置,我们需要解析gcaptcha4.js文件。我们可以下载并格式化这个文件,然后使用正则表达式搜索关键词:w、.w、'w'或"w"。
haskell
import Text.Regex.Posix
import System.IO
searchFile :: FilePath -> IO ()
searchFile filePath = do
content <- readFile filePath
let regex = "[.\\'\"]w[.\\'\"]" :: String
let matches = getAllTextMatches (content =~ regex :: AllTextMatches [] String)
mapM_ putStrLn matches
main :: IO ()
main = searchFile "gcaptcha4.js"
通过搜索"w",我们找到了相关代码。在第2527行,我们发现了w的值r在第2525行被定义。
三. 分析w参数加密算法
接下来,我们需要简化代码中w的定义。假设我们已经提取了相关的JavaScript代码,我们可以使用Haskell来模拟这个过程。
haskell
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (encode, object, (.=))
import Data.ByteString.Lazy.Char8 as B
import Crypto.Hash.MD5 (hash)
import Data.ByteString.Base16 (encodeBase16)
toJSON :: [(String, String)] -> ByteString
toJSON = encode . object . map (\(k, v) -> k .= v)
md5 :: ByteString -> ByteString
md5 = encodeBase16 . hash
main :: IO ()
main = do
let e = [("device_id", "A8A0"),
("em", "{\"cp\":0,\"ek\":\"11\",\"nt\":0,\"ph\":0,\"sc\":0,\"si\":0,\"wd\":1}"),
("ep", "123"),
("geetest", "captcha"),
("fq6a", "1925502591"),
("lang", "zh"),
("lot_number", "7e22264d4f3e4dd8a6ffbf6e82e1122d"),
("passtime", "166"),
("pow_msg", "1|0|md5|2022-03-25T14:23:36.364152+08:00|24f56dc13c40dc4a02fd0318567caef5|7e22264d4f3e4dd8a6ffbf6e82e1122d||29f07cebf938aa4e"),
("pow_sign", "2b47a3a9425dd19dd5abf902c8bb0763"),
("setLeft", "88"),
("track", "[[38,18,0],[1,0,33]]"),
("userresponse", "87.47978686742837")]
let a = "your_key"
let r = md5 (toJSON e <> a)
B.putStrLn r
4.1 分析pow_msg和pow_sign
通过搜索pow_msg,我们找到相关代码并解析如下:
haskell
import System.Random (randomIO)
import Data.Time.Clock (getCurrentTime)
generateGUID :: IO String
generateGUID = do
g1 <- randomIO :: IO Int
g2 <- randomIO :: IO Int
g3 <- randomIO :: IO Int
g4 <- randomIO :: IO Int
return $ concatMap (flip showHex "") [g1, g2, g3, g4]
md5String :: String -> String
md5String = B.unpack . encodeBase16 . hash . B.pack
main :: IO ()
main = do
let n = "example_n"
let a = "example_a"
let s = "example_s"
let o = "example_o"
let t = "example_t"
let e = "example_e"
let r = "example_r"
let u = n ++ "|" ++ a ++ "|" ++ s ++ "|" ++ o ++ "|" ++ t ++ "|" ++ e ++ "|" ++ r ++ "|"
p <- generateGUID
let g = u ++ p
let pow_msg = u ++ p
let pow_sign = md5String g
putStrLn $ "pow_msg: " ++ pow_msg
putStrLn $ "pow_sign: " ++ pow_sign
4.2 分析set_left、track、passtime、userresponse
set_left:滑块移动距离的整数值
track:移动轨迹,从第二步开始,是相对上一步的相对移动距离(x, y, t)
passtime:总移动时间
userresponse:计算公式为set_left / (0.8876 * 340 / 300)
haskell
setLeft :: Int
setLeft = 88
track :: [[Int]]
track = [[38, 18, 0], [1, 0, 33]]
passtime :: Int
passtime = 166
userResponse :: Double
userResponse = fromIntegral setLeft / (0.8876 * 340 / 300)
main :: IO ()
main = do
putStrLn $ "set_left: " ++ show setLeft
putStrLn $ "track: " ++ show track
putStrLn $ "passtime: " ++ show passtime
putStrLn $ "userresponse: " ++ show userResponse