【验证码逆向专栏】最新某度旋转验证码 v2 逆向分析

声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!
前言
七月底有爬友反馈,某度旋转验证码 v2 的加密算法更新了。对各个站点测试后发现,站长后台的算法及请求参数更新了,因为近期也发过某度系列验证码的文章,为了避免失效部分对粉丝的误导,现再次对相关算法进行逆向分析。
逆向目标
- 目标:某度旋转验证码 v2,fs 参数逆向分析
- 网站:aHR0cHM6Ly96aXl1YW4uYmFpZHUuY29tL2xpbmtzdWJtaXQvdXJs
- 加密算法:AES、MD5、SHA1、SHA256、SHA512、SHA3

抓包分析
init 接口有四个请求参数,ver 为固定值 1,_ 为 13 位时间戳,refer 为该网站的 url,ak 是固定值,相当于盾 ID,不同网站不一样,很多厂商的验证码也都会有这么一个参数:

init 接口响应返回的参数中 as、tk 在后续请求中会用到:

填写链接地址,点击提交,即会弹出旋转验证码,此时抓包到 style 接口, 请求参数中,tk 参数是 init 接口响应返回的, _ 是时间戳,其他的都是定值:

style 接口响应返回的参数中,path 即旋转验证码的底图,backstr 是生成 fs 参数值的关键部分:

转动验证码后,会抓包到 log 接口,请求参数中,ak 为盾 ID,tk、as 均是之前的接口响应返回的,fs 参数需要逆向分析,由轨迹、旋转角度、as、backstr 参数等加密生成。最新的 v2 较之前多了一个浏览器指纹参数 fuid,不同浏览器的值不一致,相同浏览器的值短时间内不会改变:
# "Edge": "FOCoIC3q5fKa8fgJnwzbE67EJ49BGJeplOzf+4l4EOvDuu2RXBRv6R3A1AZMa49I27C0gDDLrJyxcIIeAeEhD8JYsoLTpBiaCXhLqvzbzmvy3SeAW17tKgNq/Xx+RgOdb8TWCFe62MVrDTY6lMf2GrfqL8c87KLF2qFER3obJGnCfjdYr2J6wEsox+bQtrTEGEimjy3MrXEpSuItnI4KDyOhCNLIvGcJ9TrqWJqhR97vnz96e18U/ntNdoDIMLzy/6P9rOWdIYWmTQAeeG69438PcpN++VzDmPtrURexo5YYWpVFkRs9k5n0AC3djzGRuXr1+yVZXtGGofFzxBmdr9HtaANtMMPysO2XXACLNUNkLLWJN9fLc3OAWce48tpeQQ2ufd7knx4Oo6OM0PpOVDwQcezbGX85VEmymh7f7M5kIyVl+w5yn2AY4BmLrEWEsyw9SzzW8eHQ5zYIUjiw9hXi7OMMoCL+ptOvZYbHZs0R5qLHmmDCW1M8MMX5yyJF0BV1dQvKslKnAJwZu4XCbsXKn3UXosU1U30/poiN2VeXkBPeo8+Xj/4BIoC2I7WZ6zkFa/Uwd5SvC91kvff2a/Z4OwyTQNM7ES9HmRhChdWg0SJ2xEs1aiXAit16RiTlf82esJH+X/j52G7R3ErwQeJT3QoDv64R2702+8NbGIjf1ZOfxhUCpmJqV4jeHSaHRmnKgJZsK91XhhrdJKXdsbt3phIOpxGLupULr2K+v1DNdId8/HuE0776+tTpUl7shVCeM/XWrdkhru42pifhiujnDhIblsLt8grnj5/GRqcD6ZPAXqJW3lLc0/ub9jXgvXK/EczRgKl+7/tTBkPTCrUVtajA0luHLQOrVsXuN1v0/PR3i09SuFzZJkJBKE3M6rYvPttK9NQiBxhxYWDhX82uQu2XK8+8oU3gxCIaJwsQmX/It0kaZ45PZHFqtD40uOX0sXuThvUin4N4RSI2G9d7jPkj5hbBFquQKM4S+tDJ34jmplOTrqqKT7PPVfrdgd4OkK13pEy86BsJ8M0gKXgtivUgM8Bjl1m/pkg0SuDyntWLdrmMxcZYvgySvSSwQ2Qtm8EkKHIMyR/XgfHnpX5vadGpRMro2qaE8u+x8w1gJHIRKib2u6Q1JtQiZE1Rde/vRx8xKfg6uYR37n0BvfgJE5+KbeuwCyAvJRGUA2fpt0VClIfV0m2PRG7bvH00OODKY6cFi7NgWAK6Jc1G4Ugkfp7W8I0ZYwNpTTxVoxIIBF37aBhyiPWPAOeYXBqA"
# Chrome: "FOCoIC3q5fKa8fgJnwzbE67EJ49BGJeplOzf+4l4EOvDuu2RXBRv6R3A1AZMa49IZbsw3/U3NYEqD0LjhKzgMn8fIES5OyXlgwN5I+F8wHowpWWfXkQJw8/9AsO5Q2VOvnc2JlHGIlGS8Vq2z4OA80lVLon08EG3PPxkVZGm39fDi2exK9NDrZB+tNLX6ISxE5PzBgXpCOJ6oP9F1B0OBWaCMD/m01n8FhdDNCvP8EO5cetU79+pgL+ECRdtN6V4VElGJE0mxV4+4Zq4Jf/Xe/q8CkoTNf7Ti1glGYmN32UM9dg0uX+VzET/mmTRe4Dt+MuVHSzsI/bKCjPbpaOqfM8UsxDJUG9hyrGZ8QHa1kC04aTxkkTxI275dv3+ijS1zkWOdjFiy1eD/0R8HcRWYp2smk9EmXBkIAHL4H0gC9lQtdjey37/kyl4JA9Fp4zjuVO0arsD8MrGy1divU++B1KdawGqXpnbOcHZ3CctNGrpgmswaScc6DNWb34jFj0X3tdRE0uuHuqiYa5BClFS2V0TCorKi4CobgR419xWaX8IKLJiaNNLOShWdZdlQO2DXXVxcinzKHqUvWTYx45jsiUVlY78AHQGol6CJLQQ8Q797MShlazvdSwPXgJP5z0uMJp9L+3x/Y2GGhW5sit55sFuMXafALTYf69FCUw5+nVIRs150a4+KK+tA0Eu7Itiu3dM2pflKYWwPE6SDZznyejQ08vd+HpXRB/zhfSUcIYlT5gFEiMIA6SXZCo/XT7vC8D3gHdN+yr46XdVol/WkjFQof0JQH/Vhjj5C1xcAyNxq/VVBT01vdKk6zo6c08e84FEVMLd0m3XWtjFOYu7wRI7lldw2pSxyGnWvA4aiYWcWvvKNJtqB8wHqc5RPr9KRzhbxJnTM5K1vTx4xT/1ZUR3pU7nQKZo/4kP9XycIr/Jg3XMRSnqCBUJlagKAFPt2HF0LdsSk4WWcldb97Ar584nVGbSjPXEUVH0VgbUEm+dADzPoLP+NPMYOyhwgfADiqWaXyKT4UNESYXsPBkdGk6mLCaNSEQsDN1G2677Se3qjzDcyXBnEmHEFptRbmyJzKJ73veHPqfFYtsHO9jH0XnhYk8zKdRuqQ7dnuNIDwxm3UCPo22uFI0ZcgPvQm01s+8jYiMEFJDVra9jWyWTdMpMuhT3p2yYLf70CvUwIkw="
过一段时间,fuid 的值是会变化的:

fuid 在 fingerprint.js 文件中生成,由 user-agent、canvas、plugins 等浏览器属性构成,可以逐个跟栈分析,F 方法是对 URI 进行编码,即 encodeURIComponent(),U 方法为 AES 加密,加密模式为 ECB,填充方式为 PKCS7,key 为固定值:
- ECB:Electronic Code Book(电子码本模式),是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文;
- PKCS7:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为需要填充的字节长度值。

相关环境:
{
"userAgent": "Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20WOW64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F86.0.4240.198%20Safari%2F537.36",
"canvas": "2a5a8f1d4173bc025dab2d50816ba134",
"language": "zh-CN",
"colorDepth": "24",
"deviceMemory": "8",
"hardwareConcurrency": "8",
"screenResolution": "1920%2C1080",
"availableScreenResolution": "1032%2C1920",
"timezoneOffset": "-480",
"timezone": "",
"sessionStorage": "true",
"localStorage": "true",
"indexedDb": "true",
"addBehavior": "false",
"openDatabase": "true",
"cpuClass": "",
"platform": "Win32",
"plugins": "null",
"webgl": "1af17d7a73799a2516da34f3b37bb363",
"webglVendorAndRenderer": "Google%20Inc.~ANGLE%20(NVIDIA%20NVS%205400M%20Direct3D11%20vs_5_0%20ps_5_0)",
"adBlock": "false",
"hasLiedLanguages": "false",
"hasLiedResolution": "false",
"hasLiedOs": "false",
"hasLiedBrowser": "false",
"touchSupport": "0%2Cfalse%2Cfalse",
"fonts": "42",
"audio": "null"
}
验证码未转正,响应返回的 op 值为 3:

验证码转正,则响应返回的 op 值为 1:

urlsubmit 接口的请求参数 ds、tk 是 log 接口响应返回的,url 为提交的链接地址:

以下是目前遇到过的链接提交之后的几种结果:
- 提交成功 --->
- 验证码信息有误 --->
- 未输入提交链接 --->
- 未添加 cookies ---> not allowed
逆向分析
log 接口有一个加密参数 fs,通过跟栈会发现,和之前一样,fs 参数的加密位置在 mkd_v2.js 这个文件里面,跟进去之后,直接 ctrl + f 搜索 fs = ,有两个结果,这里就是生成 fs 参数的关键位置:

先分析第一个 n.fs:
n.fs = (0, f.Li)(JSON.stringify(this.rzData), this.secondHandle)
JSON.stringify(this.rzData) 是将 this.rzData 转换成字符串形式,在第 12871 行打下断点,this.rzData 由 backstr 及鼠标轨迹等参数构成,backstr 参数是 style 接口响应返回的,ac_c 是旋转比例,现在的底图是 AI 生成的,和之前的风景图不一样了,识别模型最好重新训练,轨迹和旧版不一样,需要模拟构造不能直接固定,且校验相对严格,轨迹中有个参数很重要:

向上跟栈,即可定位到计算 ac_c 的位置:
Number((this.distance / (e - 52)).toFixed(2))

this.distance 是滑动的距离,e 为定值 290,实际上就是滑动条能够滑动的最大长度:

this.secondHandle 主要包含 as 参数,该参数是 init 接口响应返回的:

JSON.stringify(this.rzData)、this.secondHandle 作为参数传递到 f.Li 函数中,进行加密处理之后生成了第一个 fs 值:

鼠标选中 f.Li 后跟进去:

encrypt(key, word, !0) 即关键加密函数:

先来分析下传入的三个参数,!0 为 true,word 参数即 JSON.stringify(this.rzData),key 参数定义在第 13312 行,旧版 v2 的 fs 参数是 AES 加密,key 为 "as + appsapi2",新版的 key 经过 getNewKey(params.as) 方法做了进一步的处理,params.as 是 init 接口响应返回的,跟进到 getNewKey 函数中去:

t 即 as,e 为 t + "appsapi2",r 取了 e 的最后一位字符,下面是一段 switch...case 语句,根据 r 的值选择相应的加密算法,截取加密后的字符串的前 16 位,即是 key 值,目前主要碰到过三种加密算法,MD5、SHA3-256 以及 SHA3-512,以下通过 JavaScript 对其进行复现:
const CryptoJS = require('crypto-js');
function getNewKey(as){
/**
* encryptedStr(SHA3-256) ---> f25f1614appsapi2
* encryptedValue ---> 49d3a9685870cc30f63330b8136c7adfdb8859c6b538308992a1c9a456db2e59
*
* encryptedValue(MD5) ---> 5e4ebc8cappsapi2
* encryptedValue ---> c30b8b5289e46489598de382a658cc7f
*/
var encryptedStr = as + "appsapi2";
var r = as.substr(as.length - 1, 1);
switch (true) {
case ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'a', 'b', 'c', 'd', 'e', 'f', 'g'].includes(r):
encryptedValue = CryptoJS.MD5(encryptedStr).toString();
break;
case ['H', 'I', 'J', 'K', 'L', 'M', 'N', 'h', 'i', 'j', 'k', 'l', 'm', 'n'].includes(r):
encryptedValue = CryptoJS.SHA1(encryptedStr).toString(CryptoJS.enc.Hex);
break;
case ['O', 'P', 'Q', 'R', 'S', 'T', 'o', 'p', 'q', 'r', 's', 't'].includes(r):
encryptedValue = CryptoJS.SHA256(encryptedStr).toString(CryptoJS.enc.Hex);
break;
case ['U', 'V', 'W', 'X', 'Y', 'Z', 'u', 'v', 'w', 'x', 'y', 'z'].includes(r):
encryptedValue = CryptoJS.SHA512(encryptedStr).toString(CryptoJS.enc.Hex);
break;
case ['0', '1', '2', '3', '4'].includes(r):
encryptedValue = CryptoJS.SHA3(encryptedStr, { outputLength: 256 }).toString(CryptoJS.enc.Hex);
break;
case ['5', '6', '7', '8', '9'].includes(r):
encryptedValue = CryptoJS.SHA3(encryptedStr, { outputLength: 512 }).toString(CryptoJS.enc.Hex);
break;
default:
encryptedValue = e;
}
var key = encryptedValue.slice(0, 16);
return key;
}
var as = "7d7f8765";
var key = getNewKey(as);
console.log('key:', key);
SHA3-512 结果校验一致:

至此 key 参数分析完了,回到第 13315 行,跟进到加密函数 encrypt 中,和旧版 v2 一样,是 AES 加密,不同点在于:1. 旧版的 key = as + "appsapi2",新版 key 的值为 as + "appsapi2" 加密后取前 16 位字符;2. 旧版的填充方式 padding 为 Pkcs7,新版的为 ZeroPadding:
- PKCS7:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为需要填充的字节长度值;
- ZeroPadding:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为 0 。

接下来分析第二个 n.fs,也就是最终的 fs 参数的值,同样是经过 f.Li 函数加密生成,不过传入的两个参数发生了改变,common_en 的值即第一个 n.fs 的值,backstr 以及 as 是前面接口响应返回的值,key 即上文所述的 AES 算法加密的密钥值:
n.fs = (0, f.Li)(JSON.stringify(
{
common_en: n.fs,
backstr: this.cfg.backstr
}), {
key: this.newKey,
as: this.cfg.as,
method: "aes-ecb"
})
在第 12873 行打下断点,此时第一个 n.fs 参数的值生成了:

在第 12882 行打下断点,此时生成了第二个 n.fs 参数的值,即最终的 fs 参数的值,对比验证一下:

结果验证

【验证码逆向专栏】最新某度旋转验证码 v2 逆向分析的更多相关文章
- python 验证码识别示例(二) 复杂验证码识别
在这篇博文中手把手教你如何去分割验证,然后进行识别. 一:下载验证码 验证码分析,图片上有折线,验证码有数字,有英文字母大小写,分类的时候需要更多的样本,验证码的字母是彩色的,图片上有雪花等噪点,因 ...
- css3 向上淡入 小图标翻转 360度旋转
代码 <!DOCTYPE HTML> <html> <style type="text/css"> div { border: 1px soli ...
- Python 爬虫入门(四)—— 验证码上篇(主要讲述验证码验证流程,不含破解验证码)
本篇主要讲述验证码的验证流程,包括如何验证码的实现.如何获取验证码.识别验证码(这篇是人来识别,机器识别放在下篇).发送验证码.同样以一个例子来说明.目标网址 http://icp.alexa.cn/ ...
- python 验证码识别示例(五) 简单验证码识别
今天介绍一个简单验证的识别. 主要是标准的格式,没有扭曲和变现.就用 pytesseract 去识别一下. 验证码地址:http://wscx.gjxfj.gov.cn/zfp/webroot/xfs ...
- 爬虫(十二):图形验证码的识别、滑动验证码的识别(B站滑动验证码)
1. 验证码识别 随着爬虫的发展,越来越多的网站开始采用各种各样的措施来反爬虫,其中一个措施便是使用验证码.随着技术的发展,验证码也越来越花里胡哨的了.最开始就是几个数字随机组成的图像验证码,后来加入 ...
- 逆向中静态分析工具——IDA初学者笔记之字符串分析
逆向中静态分析工具——IDA初学者笔记之字符串分析 程序中往往包含很多字符串资源,这些资源存在于PE文件的rdata段,使用IDA反编译后,可以查找到这些字符串, 逆向破解程序通常需要一个突破点,而这 ...
- Anroid逆向学习从编写so到静动态调试分析arm的一次总结
Anroid逆向学习从编写so到静动态调试分析arm的一次总结 一.前言 最近跟着教我兄弟学逆向这篇教程学习Android逆向,在第七课后作业反复折腾了好几天,正好在折腾的时候对前面的学习总结一波,动 ...
- python语言验证码识别,以后不用老输入验证码了。
1.Python 3.6 安装包 1.要加环境变量 2.pip安装PIL库 3.pip安装pytesseract模块 2.tesseract-ocr-setup-4.00.00dev.exe -- ...
- ASP.NET Core 使用 Google 验证码(reCAPTCHA v3)代替传统验证码
写在前面 友情提示: Google reCAPTCHA(v3下同) 的使用不需要"梯子",但申请账号的时候需要! Google reCAPTCHA 的使用不需要"梯子&q ...
- Dede织梦验证码不显示,织梦后台登陆验证码不显示解决方法
关于"织梦验证码不显示"的解决方法 "织梦验证码无法显示出来"的问题分析? 1.之前显示正常,但是换了服务器后就不能够正常显示:(这种通常是网站程序经过迁移后所 ...
随机推荐
- Hadoop中mapreduce作业日志是如何生成的
摘要:本篇博客介绍了hadoop中mapreduce类型的作业日志是如何生成的.主要介绍日志生成的几个关键过程,不涉及过多细节性的内容. 本文分享自华为云社区<hadoop中mapreduce作 ...
- 让数据大白于天下:GCC插件实现代码分析和安全审计
摘要: 如何利用GCC的插件功能,辅助安全分析人员实现对程序的安全审计.漏洞检测.安全加固等自动化处理能力,提升分析效率和精准度. 本文分享自华为云社区<利用GCC插件实现代码分析和安全审计&g ...
- 最新的iOS应用上架App Store详细流程解析
最新的iOS应用上架App Store详细流程解析 2023已经过了2/3的时间,由于现在苹果签名市场的价格不断的上升,现在很多的开发商一直在想着如何进行上架一些自己的产品,下面小编来给大家梳理一下上 ...
- 利用 Solon-web 框架写一个 Hello World
Solon 项目的开源地址: https://gitee.com/noear/solon 最近看了不少别人写的各种框架的 Hello world 示例,有些看起来,真的很复杂. 今天,我们用号称简单到 ...
- 创建一个简单的Docker镜像
1. 创建 Dockerfile 文件.index.html测试页面 [root@localhost docker]# vi Dockerfile FROM nginx:1.17.6 #基于 ngin ...
- DNS--智能地址解析(view视图)
域名:xinenhui.com DNS服务器:192.168.198.128 DNS1:192.168.198.129 DNS2:192.168.198.146 1 简介 使客户端就近访问DNS服务器 ...
- Codeforces Round #730 (Div. 2) A~D题个人题解
比赛链接:Here 1543A. Exciting Bets math, 给定两个数字 \(a,b (a,b \le 1e18)\) 和以下两种操作: \(a + 1,b+1\) \(a-1,b-1\ ...
- webgl创建一个点
- 报错:for..in loops iterate over the entire prototype chain, which is virtually never what you want.
for..in loops iterate over the entire prototype chain, which is virtually never what you want. 意思是使用 ...
- 实现不限层级的Element的NavMenu
做管理后台开发的时候,需要用到Element的NavMenu组件,于是乎,翻开文档,大致是这样实现的. <el-menu> <el-menu-item index="1&q ...