【验证码逆向专栏】百某网数字九宫格验证码逆向分析

00

声明

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

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

目标

目标:百 X 网数字九宫格验证码逆向分析

网址:aHR0cHM6Ly9iZWlqaW5nLmJhaXhpbmcuY29tL296L3M5dmVyaWZ5X2h0bWw=

01

抓包分析

本例中的验证码不是很难,但网站埋了点儿坑,容易出现识别正确、参数也正确,但仍然请求不成功的情况。访问主页响应码为 307,接着请求了一个 bf.js 和两个 s.webp 的图片,然后又跳转到首页出现验证码。如果你没有以上步骤,请求主页直接就是 200 出现验证码,则需要清除 cookie 后再访问,因为第一次 307 到请求 bf.js 再到两次 s.webp 都是在设置 cookie。

第一次请求主页,response headers 会设置一个名为 _trackId__city 的 cookie,如下图所示:

02

然后带着这两个 cookie 请求了一个 bf.js,这个 js 用于后续两个 s.webp 请求参数的加密,这里注意,第一个坑,虽然可以直接调试 js 扣算法下来后面直接用就行了,但是这个 js 必须得请求一遍,不然后面请求主页的时候一直是 307。

03

然后请求第一个 s.webp,get 请求,有三个参数:cfsf,明显是加密得来的,同时请求的 cookie 也多了三个值:c0fc276cce08ba22dcc1fc276cce08ba22dcbxf,如下图所示:

04

05

然后请求第二个 s.webp,和第一个类似,get 请求也有三个参数:cfsf,cookie 和第一个一样,但第二次请求返回了一个名为 sbxf 的新 cookie,其值和 bxfc1fc276cce08ba22dc 其实是一样的,如下图所示:

06

然后带上 __trackId__cityc0fc276cce08ba22dcc1fc276cce08ba22dcbxfsbxf 这六个 cookie 再次访问主页,就是验证码页面了,返回的 html 里有个新的 js,很长一串,如下图所示:

07

08

然后观察这个 js,里面包含了验证码图片的 URL,以及需要点击的数字,如下图所示:

09

10

点击验证后,会给 verify_url 发一个 get 请求,请求参数主要有一个 data,即点击坐标(这个坐标也有讲究,有可能你的值是对的,但有时候也不成功,这个后文再细说),cookie 和前面的请求一样,如果验证成功,会返回 ret 为 0,且有一个 code 供后续请求使用,如下图所示:

11

12

获取 cookie

这里再注意一点,所有的请求,header 只需要 RefererUser-Agent 就行了,不要乱加,比如多了个 Host 也有可能导致后续请求不成功。

想要拿验证码,得先搞定 cookie,总体流程如下:

  1. 请求首页 s9verify_html 获取 __trackId__city,主要是 __trackId__city 要不要都行;
  2. 请求 bf.js,这一步不干啥,但必须得请求,不然 cookie 不能用;
  3. 第一次请求 s.webp,cookie 里多了 c0fc276cce08ba22dcc1fc276cce08ba22dcbxf,均为 js 生成;
  4. 第二次请求 s.webp,返回的 cookie 里多了 sbxf,其值和 bxf 一样,这一步可以理解为激活 cookie,使其有效。

前两步倒没有啥,第 3、4 步都有加密参数 sf,观察这两个 s.webp 都是 fetch 请求,所以我们直接一个 fetch 断点,断下后可以看到 cb 就是我们需要的两个参数:

13

14

观察 bf.js 是一个小小的类似 OB 混淆,可以 AST 解一下混淆,但这个逻辑不是很复杂,所以直接硬看也行,关键语句 cb = c3['s'](c7, c8),c7 是定值一个字符串 fc276cce08ba22dc,c8 也是定值表示颜色的字符串 rgba(255, 0, 0, 255)

15

主要是 c3['s']() 这个方法,跟进去,首先会取一下 c0fc276cce08ba22dcc1fc276cce08ba22dcbxf 三个值,如果有的话,直接返回,如果没有的话,会生成新的,生成方法主要是 c6 这个函数,如下图所示:

16

继续跟到 c6 方法里,首先对字符串 rgba(255, 0, 0, 255) 做了一个操作,生成了一张图片的 base64 字符串:

17

这里其实很明显是 canvas 绘图的一些操作,跟到 c7 看看确实是这样的:

18

这里对于我们扣算法来说,其实就不需要管了,因为同一台设备的同一个浏览器,按照相同的规则绘制的图片,base64 值是一样的,所以我们直接忽略 c7 这个方法,直接把生成的 base64 值拿来用就行了。

然后又将 base64 值进行了一个 c3["hash"]() 的操作,根据最终的值,或者跟到方法里去看,很容易发现这个其实就是个 MD5 的操作:

19

20

接着往下看,八个字符串为一组,将 md5 值分为四组,然后四组之间用 0 或者 1 连接,拼接成新的 35 位字符串,拼接的是 0 还是 1,取决于中间的三目语句,判断是否为 true,支持情况下都是 true,所以扣算法的话根本就没必要再跟进去看是怎么判断的,直接用 1 拼接就完事儿了。然后将固定的字符串 fc276cce08ba22dc 和这 35 位字符串拼接起来再一次 MD5,就得到了参数 s 的值,而参数 f 的值则是这个 35 位字符串。

21

第一个 s.webp fetch 操作就完成了,接着是第二个 s.webp,就在第一个 fetch 附近,如下图的 ce 就是第二次的 s、f 参数的值:

22

这里生成的方法大致是一样的,首先 cd 是一个新的图片的 base64 值,这个值是第一次 s.webp 请求成功返回的,先把这个新的 base64 MD5 加密一下,生成一个新的字符串,相当于替换了第一次请求固定的字符串 fc276cce08ba22dc,后续的流程和第一次都一样了:

23

这两次生成 s 和 f 的值的流程可以精简成以下 js 实现:

MD5 = require("md5")var baseImg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAADcCAYAAAAbWs+BAAAAAXNSR0IArs4c6QAAIABJREFUeF7tnQm..."function getParams(c8) {var cb = MD5(baseImg), cc = cb.substring(0, 8), cd = cb.substring(8, 16), ce = cb.substring(16, 24), cf = cb.substring(24, 32), cg = cc + 1 + cd + 1 + ce + 1 + cf;return {"s": MD5(c8 + cg),"f": cg};
}function getFirstParams() {return getParams("fc276cce08ba22dc")
}function getSecondParams(img) {return getParams(MD5(img))
}console.log(getFirstParams())
console.log(getSecondParams("data:image/png;base64,dqyixSOIJuJN0IRG288itylhqNFFXVqL"))

然后这个 cookie 值,你可以去 Hook 一下看看,但实际上观察一下就可以发现 c0fc276cce08ba22dc 就是第一次 s.webp 请求的 s 参数,c1fc276cce08ba22dcbxf 就是第一次 s.webp 请求的 f 参数,所以直接拿来用就行了。

获取验证码

带上前面生成的正确的 cookie,再次请求主页,响应码为 200,然后在返回的 html 里可以看到有个超长的 js 地址,这个 js 直接把 .js 替换成 .jpg 就是验证码地址,替换成 .valid 就是验证结果的地址,这个 js 返回的内容里面就包含了要点击的数字。

24

25

26

获取点击坐标

最终提交的坐标是长这样的:

27

由于这个图片是九宫格的样式,一般的识别都是一排,所以这里可以将九宫格裁剪后重新排列一下(当然自己会搞深度学习的话可以单独给这种九宫格训练一下,就不用重新裁剪排列了),重新排列前后对比如下:

28

这一步的利用 Python 的 PIL 库很容易实现:

from PIL import Image# 打开九宫格验证码
captcha = Image.open("captcha.jpg")# 将图片等分成三份,每份长宽为 150px 和 50px
part1 = captcha.crop((0, 0, 150, 50))
part2 = captcha.crop((0, 50, 150, 100))
part3 = captcha.crop((0, 100, 150, 150))
part1.save("part1.jpg")
part2.save("part2.jpg")
part3.save("part3.jpg")# 创建新的图片,长宽为 450px 和 50px
new_captcha = Image.new("RGB", (450, 50))# 将三份图片按顺序拼接到新的图片上
new_captcha.paste(part1, (0, 0))
new_captcha.paste(part2, (150, 0))
new_captcha.paste(part3, (300, 0))# 保存新的图片
new_captcha.save("captcha_new.jpg")

这样处理后,怎样得到对应的坐标呢?以上图为例,假设我们需要点击 question = [1, 8, 3, 6],我们识别 captcha_new.jpg 结果为 recognition_result = "172958643",生成最后的坐标流程如下:

import randomquestion = [1, 8, 3, 6]           # 要点击的数字
recognition_result = "172958643"  # captcha_new.jpg 识别的结果mapping_table = {"0": f"{str(random.randint(15, 35))},{str(random.randint(15, 35))}|","1": f"{str(random.randint(65, 85))},{str(random.randint(15, 35))}|","2": f"{str(random.randint(115, 135))},{str(random.randint(15, 35))}|","3": f"{str(random.randint(15, 35))},{str(random.randint(65, 85))}|","4": f"{str(random.randint(65, 85))},{str(random.randint(65, 85))}|","5": f"{str(random.randint(115, 135))},{str(random.randint(65, 85))}|","6": f"{str(random.randint(15, 35))},{str(random.randint(115, 135))}|","7": f"{str(random.randint(65, 85))},{str(random.randint(115, 135))}|","8": f"{str(random.randint(115, 135))},{str(random.randint(115, 135))}|",
}answer = ""
for q in question:for r in recognition_result:if q == int(r):answer += mapping_table[str(recognition_result.index(r))]print(answer)

每一个数字的图片大小是 50x50,如果我要点击上图中的数字 1,那么我的 x、y 坐标范围就应该为 [0~50, 0~50],如果我要点击上图中的数字 8,那么我的 x、y 坐标范围就应该为 [100~150, 50~100]

但是进过多次测试,点击区域要靠正中心一点,成功率才高,所以坐标范围前后各增加、减少了 15。对应数字 1 的坐标范围就应该是 [15~35, 15~35],数字 8 的坐标范围就应该是 [115~135, 65~85]

这里为了简便,直接定义了一个映射表 mapping_table,如果我点击数字 8,那么 captcha_new.jpg 识别结果 172958643 中,8 的位置是 5,对应 mapping_table["5"],也就是 random.randint(115, 135), str(random.randint(65, 85)

结果验证

29

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/144794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue中ref的用法

vue中ref的用法 在项目中使用ref时有时候直接取值,有时候返回的却是一个数组,不知其中缘由,后查了一下ref用法,所以总结一下. 1.绑定在dom元素上时&#xff0c;用起来与id差不多&#xff0c;通过this.$refs来调用: <div id"passCarEchart" ref"passCarEch…

[PyTorch][chapter 63][强化学习-QLearning]

前言&#xff1a; 这里结合走迷宫的例子,重点学习一下QLearning迭代更新算法 0,1,2,3,4 是房间&#xff0c;之间绿色的是代表可以走过去。 5为出口 可以用下图表示 目录&#xff1a; 策略评估 策略改进 迭代算法 走迷宫实现Python 一 策略评估 强化学习最终是为了…

构建Docker基础镜像(ubuntu20.04+python3.9.10+pytorch-gpu-cuda11.8)

文章目录 一、前置条件1.创建 ubuntu 镜像源文件【sources.list】2.下载 python 安装包【Python-3.9.10.tgz】 二、构建方法1.构建目录2.创建DockerFile3.打包镜像 一、前置条件 1.创建 ubuntu 镜像源文件【sources.list】 内容如下 deb http://mirrors.aliyun.com/ubuntu/ …

SQL编写规范【干货】

编写本文档的目的是保证在开发过程中产出高效、格式统一、易阅读、易维护的SQL代码。 1 编写目 2 SQL书写规范 3 SQL编写原则 获取所有软件开发资料&#xff1a;点我获取

【Java 进阶篇】JQuery 遍历:发现元素的魔法之旅

欢迎来到 JQuery 的奇妙世界&#xff0c;一个充满活力和灵感的地方。在这个世界里&#xff0c;我们将一起探讨 JQuery 的遍历功能&#xff0c;这是一个让你轻松发现和操作网页元素的神奇工具。无需太多前端经验&#xff0c;只要有一颗探险的心&#xff0c;你就能在 JQuery 遍历…

智能家居小程序

1、设备 系统板&#xff1a;STM32F103C8T6 温湿度传感器&#xff1a;DHT11 光照度传感器&#xff1a; BH1750 WIFI模块&#xff1a;ESP8266-015 蜂鸣器&#xff1a; 立创EDC画板子&#xff0c;然后微立创下单

【数据结构】希尔排序(最小增量排序)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;数据结构 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有帮助…

java springboot在当前测试类中添加临时属性 不影响application和其他范围

目前 我们的属性基本都写在 application.yml 里面了 但是 如果 我们只是想做一下临时变量的测试 有没有办法实现呢&#xff1f; 显然是有的 这里 我们还是先在application.yml中去写一个 test属性 下面加个prop 然后 我们尝试在测试类中 获取一下这个属性 直接用 Value 读取…

C语言——求1/1-1/2+1/3-......+1/99-1/100的值

#include<stdio.h> int main() {int i 1;double sum 0;int flage 1;for(i 1;i < 100; i){sumflage*1.0/i;flage -flage; //正负号}printf("%lf\n",sum);return 0; }

场景图形管理-多视图多窗口渲染示例(4)

多视图多窗口渲染示例的代码如程序清单8-6所示 // 多视图多窗口渲染示例 void compositeViewer_8_6(const string &strDataFolder) {// 创建一个CompositeViewer对象osg::ref_ptr<osgViewer::CompositeViewer> viewer new osgViewer::CompositeViewer();// 创建两个…

Outlook关闭过去事件的提醒

Outlook关闭过去事件的提醒 故障现象 最近Outlook中推出的新功能让我们可以选择自动关闭过去事件的提醒。 目前这个功能暂时只向当月通道的Office 365 订阅者发布。 这些用户升级到1810版本后&#xff0c;可以在不想收到已发生事件提醒的时候通过下面的步骤自动忽略过去事件…

day22_mysql

今日内容 零、 复习昨日 一、MySQL 一、约束 1.1 约束 是什么? 约束,即限制,就是通过设置约束,可以限制对数据表数据的插入,删除,更新 怎么做? 约束设置的语法,大部分是 create table 表名( 字段 数据类型(长度) 约束, 字段 数据类型(长度) 约束 );1.1 数据类型 其实数据类型…

在Spring Boot中使用进程内缓存和Cache注解

在Spring Boot中使用内缓存的时候需要预先知道什么是内缓存&#xff0c;使用内缓存的好处。 什么是内缓存 内缓存&#xff08;也称为进程内缓存或本地缓存&#xff09;是指将数据存储在应用程序的内存中&#xff0c;以便在需要时快速访问和检索数据&#xff0c;而无需每次都从…

Jmeter控制RPS

一、前言 ​ RPS (Request Per Second)一般用来衡量服务端的吞吐量&#xff0c;相比于并发模式&#xff0c;更适合用来摸底服务端的性能。我们可以通过使用 JMeter 的常数吞吐量定时器来限制每个线程的RPS。对于RPS&#xff0c;我们可以把他理解为我们的TPS&#xff0c;我们就不…

解决Github上的README无法显示图片

首先感谢博主的思路&#xff1a;思路 最近写了点东西提交到git 发现本地能查看md里的图片用的相对路径&#xff0c;提交到github就看不见&#xff0c;并且发现不只是我自己的仓库看不见&#xff0c;其他人的我也看不见。那就有问题了 解决&#xff1a;正常使用相对路径&…

使用Tauri开发桌面应用

本文是对视频 Tauri入门教程[1]的学习与记录 Tauri官网[2] 对 node版本有要求 创建项目及目录介绍: 项目的目录结构如下 可以安装推荐的插件 执行npm run tauri build出错,根据 https://github.com/tauri-apps/tauri/issues/7430 执行 yarn add -D tauri-apps/cli && y…

js的File对象,Blob和file相互转换

示例 <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta name"viewport" content"widthdevice-width, initial-scale1" /><title>js的File对象&#xff0c;Blob和file相互转换</title><…

Android BitmapFactory.decodeResource读取原始图片装载成原始宽高Bitmap,Kotlin

Android BitmapFactory.decodeResource读取原始图片装载成原始宽高Bitmap&#xff0c;Kotlin fun getOriginalBitmap(resId: Int): Bitmap {val options BitmapFactory.Options()options.inJustDecodeBounds true //只解析原始图片的宽高&#xff0c;不decode原始文件装载到内…

t-product的matlab实现

t-product是一个比较好的概念&#xff0c;相对应于矩阵中的乘法。 定义如下 这里的 circ(A),MatVec(b) 的定义分别如下 这么定义的原因是为了映射到FFT域里面去&#xff0c;简化计算。 上面的一段摘录说明&#xff1a;直接按照定义来计算&#xff0c;会耗费大量的计算资源。因…