首页IT科技验证码诈骗是怎么把钱骗走的(【验证码逆向专栏】安某客滑块逆向)

验证码诈骗是怎么把钱骗走的(【验证码逆向专栏】安某客滑块逆向)

时间2025-06-16 23:49:10分类IT科技浏览5115
导读:声明...

声明

本文章中所有内容仅供学习交流使用             ,不用于其他任何目的                    ,不提供完整代码       ,抓包内容             、敏感网址                    、数据接口等均已做脱敏处理       ,严禁用于商业用途和非法用途                    ,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载              ,禁止任何修改后二次传播       ,擅自使用本文讲解的技术而导致的任何意外                    ,作者均不负责              ,若有侵权,请在公众号【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 json import base64 import requests from lxml import etree from loguru import logger from urllib.parse import quote_plus from Crypto.Cipher import AES from 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(fdInfo ==> {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

结果验证

整个过程比较简单                     ,验证成功。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
wordpress插件开发视频教程(最好的WordPress中文SEO插件——优化你的网站排名) 让网站内页快速收录的10个技巧(从优化内容到建立外链,提高网页收录效率)