乐趣区

关于javascript:验证码逆向专栏安某客滑块逆向

申明本文章中所有内容仅供学习交换应用,不用于其余任何目标,不提供残缺代码,抓包内容、敏感网址、数据接口等均已做脱敏解决,严禁用于商业用途和非法用处,否则由此产生的所有结果均与作者无关!本文章未经许可禁止转载,禁止任何批改后二次流传,擅自应用本文解说的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K 哥爬虫】分割作者立刻删除!逆向指标指标:安某客滑动验证码逆向剖析主页:aHR0cHM6Ly93d3cuYW5qdWtlLmNvbS9jYXB0Y2hhLXZlcmlmeS8/Y2FsbGJhY2s9c2hpZWxkJmZyb209YW50aXNwYW0=

抓包剖析首页申请,有个初始化函数,其中有个 sessionId 后续会用到。

而后有个 getInfoTp 的申请,Form Data 里有个 dInfo 是加密参数,返回值里 info 也是加密的,蕴含了图片信息,返回值 responseId 在后续的申请也会用到。

滑动之后,有个 checkInfoTp 申请,Form Data 里有个 data 是加密参数,蕴含了轨迹信息,返回值 message 能够看到是否校验胜利。

整体流程就是:申请首页获取 sessionId,申请 getInfoTp 获取图片信息和 responseId,申请 checkInfoTp 校验是否胜利,两头波及到 dInfo 和 data 两个加密参数,以及 getInfoTp 返回失去的 info 的解密。dInfo 生成先来看 getInfoTp 申请的 dInfo 参数,间接搜寻可定位,刷新断下,大抵就能够看出是 AES 加密,传入了 sessionId 和一个 _taN() 函数的返回值:

_taN() 函数是一些 URL,UA 之类的信息,能够写死:

往里跟就能够看到 AES 算法了:

这里简简单单扣一下,JavaScript 代码如下:/ ==================================# @Time   : 2021-12-14# @Author : 微信公众号:K 哥爬虫# @FileName: ajk.js# @Software: PyCharm# ================================== /​​var CryptoJS = require(‘crypto-js’)​function AESEncrypt(_cRV, 2undefinedp) {_2undefinedp = _2undefinedp.split(“”).reduce(function(_PUi, _JrX, _JP9) {return _JP9 % 2 == 0 ? _PUi + “” : _PUi + _JrX;}, “”);    _2undefinedp = CryptoJS.enc.Utf8.parse(_2undefinedp);    _cRV = “string” == typeof _cRV ? _cRV : JSON.stringify(_cRV);    _cRV = CryptoJS.AES.encrypt(_cRV, _2undefinedp, {        iv: _2undefinedp,        mode: CryptoJS.mode.CBC,        padding: CryptoJS.pad.Pkcs7});    return encodeURIComponent(_cRV.toString())}​function u() {    return {        “sdkv”: “3.0.1”,        “busurl”: “https://www. 脱敏解决.com/captcha-verify/?callback=shield&from=antispam”,        “useragent”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36”,        “clienttype”: “1”   }}​function getDInfo(sessionId){return AESEncrypt(u(), sessionId)}​// 测试样例 var sessionId = “a8b339ec0c26459598786fee1cce8dc2″console.log(getDInfo(sessionId)) 这段逻辑也能够用 Python 来实现,要害代码如下(脱敏解决,不能间接运行):# ==================================# —— coding: utf-8 —–# @Time   : 2021-12-14# @Author : 微信公众号:K 哥爬虫# @FileName: ajk.py# @Software: PyCharm# ==================================​​import jsonimport base64import requestsfrom lxml import etreefrom loguru import loggerfrom urllib.parse import quote_plusfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpad​​class AESAlgorithm:    @staticmethod    def encrypt(aes_key_iv, text):        “”” 对明文进行加密 “””        cipher = AES.new(key=bytes(aes_key_iv, encoding=’utf-8′), mode=AES.MODE_CBC, iv=bytes(aes_key_iv, encoding=’utf-8′))        result = base64.b64encode(cipher.encrypt(pad(text.encode(‘utf-8’), 16))).decode(‘utf-8′)        result = quote_plus(result)        return result​    @staticmethod    def decrypt(aes_key_iv, text):        “”” 对密文进行解密 “””        cipher = AES.new(key=bytes(aes_key_iv, encoding=’utf-8′), mode=AES.MODE_CBC, iv=bytes(aes_key_iv, encoding=’utf-8’))        result = unpad(cipher.decrypt(base64.b64decode(text)), 16).decode(‘utf-8’)        return result​​class AJKSlide:    def init__(self, index_url, user_agent):        self.aes = AESAlgorithm()        self.index_url = index_url        self.user_agent = user_agent        self.headers = {“user-agent”: self.user_agent}​    def get_session_id(self):        “”” 获取 sessionId “””        response = requests.get(url=self.index_url, headers=self.headers).text        session_id = etree.HTML(response).xpath(“//input[@name=’sessionId’]/@value”)[0]        logger.info(f”sessionId ==> {session_id}”)        return session_id​    @staticmethod    def get_aes_key_iv(session_id):        “”” 设置 AES key 和 iv”””        aes_key_iv = ”        for index, value in enumerate(session_id):            if index % 2 != 0:                aes_key_iv += value        logger.info(f” 解决 sessionId 获取 aes key iv ==> {aes_key_iv}”)        return aes_key_iv​    def get_d_info(self, aes_key_iv):        “”” 获取 dInfo”””        sdk_info = {“sdkv”: “3.0.1”,            “busurl”: self.index_url,            “useragent”: self.user_agent,            “clienttype”: 1}        d_info = self.aes.encrypt(aes_key_iv, json.dumps(sdk_info))        logger.info(f’dInfo ==> {d_info}’)        return d_info​    def run(self, session_id=None):        if not session_id:            session_id = self.get_session_id()        aes_key_iv = self.get_aes_key_iv(session_id)        self.get_d_info(aes_key_iv)​​if __name == ‘__main__’:    UA = “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36”    index_url = “https://www. 脱敏解决.com/captcha-verify/?callback=shield&from=antispam”    ajk_slide = AJKSlide(index_url_, UA)    ajk_slide.run()getInfoTp 解密 getInfoTp 这个接口返回的 info 的值是加密的,后面咱们曾经晓得用到了 AES 加密算法,这里能够间接猜想也是用的的 AES 来解密的,找到 AESDecrypt 这个办法,下个断点,刷新发现断下之后传入了两个参数,第一个正是 info 的内容,第二个则是 sessionId。

解密后果能够看到滑块的图片地址等信息:

data 生成接下来就是 checkInfoTp 提交验证了,要搞清楚提交的 data 是什么货色,同样搜寻打断点,如下图所示 _5DD 就是 data 值,传过来的。

往上跟栈,能够看到 _Ug0 外面有个 track 参数,这显著就是轨迹了,同样最初的后果通过了 AES 加密。

再往上跟,能够看到 _Ug0 由三个参数组成,x 是程度滑动的间隔,track 是轨迹,p 是定值。

轨迹解决轨迹生成前,得先辨认缺口失去要滑动的间隔,形式有很多,比方 OpenCV、开源的 ddddocr,或者间接打码平台都行,这里惟一要留神的一点就是图片是有缩放的,原始尺寸 480 × 270 px 渲染后的尺寸 280 × 158 px,比例大略是 1:0.5833333333333333,能够先将图片进行缩放后再辨认,也能够先辨认间隔后再将间隔进行缩放。

轨迹的解决,该站点校验并不太严格,所以能够本人写一下,对于滑块的轨迹解决,次要有缩放法、本地轨迹库、依据一些函数来生成轨迹,如缓动函数、贝塞尔曲线等,K 哥当前再独自写一篇文章来介绍,本例中能够应用缩放法,先采集一条失常的,手动滑进去的轨迹,而后依据辨认出的理论间隔和样本轨迹中的间隔相比,失去一个比值,而后将样本中的 x 值和工夫值都做一个对应的缩放,生成新的轨迹,次要代码如下:def generate_track(distance):    “”” 生成轨迹,样本间隔为 126″””    ratio = distance / 126    new_track = “”    base_track = “29,11,0|29,11,11|29,11,26|33,11,56|34,11,66|36,11,67|39,11,76|41,11,83|43,11,86|46,11,92|49,11,98|50,11,102|52,11,106|53,11,111|55,11,116|57,11,118|59,11,123|60,11,126|62,11,132|64,12,134|65,12,138|66,12,142|68,12,148|69,12,151|70,13,155|71,13,158|72,13,164|74,13,166|75,13,170|76,14,174|77,14,180|79,14,182|81,14,186|82,14,196|84,14,198|86,14,207|87,15,212|89,15,219|90,15,223|92,15,230|93,15,234|94,15,239|95,15,243|98,15,246|100,15,250|102,15,260|105,15,262|106,15,266|108,15,270|109,16,276|111,16,278|113,16,283|115,16,286|117,16,291|118,16,294|119,16,298|121,16,302|123,16,309|124,16,311|125,16,315|126,16,319|129,16,324|130,16,327|131,16,331|132,16,334|132,16,388|132,16,522|133,16,566|134,16,574|135,16,575|136,16,594|137,16,620|138,16,625|139,16,652|140,16,657|141,17,676|141,18,680|142,18,684|143,18,688|144,18,716|145,18,724|146,18,796|147,19,828|148,19,860|149,19,888|149,19,890|150,19,916|151,20,932|152,20,936|152,20,1021|153,20,1150|154,20,1152|155,20,1236|155,20,1388|155,20,1522|155,20,1717|”    base_track = base_track.split(“|”)[:-1]    for track in base_track:        t = track.split(“,”)        new_track += str(int(int(t[0]) ratio)) + “,” + str(t[1]) + “,” + str(int(int(t[2]) ratio)) + “|”        logger.info(f”new_track ==> {new_track}”)        return new_track 后果验证整个过程比较简单,验证胜利。

退出移动版