声明

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

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

目标

  • 目标:数美全家桶,包括:滑块、文字点选、图标点选、语序点选、空间推理、无感验证
  • 地址:
// 官网体验地址
aHR0cHM6Ly93d3cuaXNodW1laS5jb20vdHJpYWwvY2FwdGNoYS5odG1s
// 官方隐藏地址
aHR0cHM6Ly9jYXN0YXRpYy5mZW5na29uZ2Nsb3VkLmNuL3ByL3YxLjAuNC9kZW1vLmh0bWw=
// 某红书验证页面
aHR0cHM6Ly93d3cueGlhb2hvbmdzaHUuY29tL3dlYi1sb2dpbi9jYXB0Y2hh

数美不同类型验证码核心的 JS 都是一样的,只是个别参数有微小差别,主要以滑块为例来分析,通过 JS 代码以及官方文档可以看出数美是有无感验证的,但是官网体验地址里并没有放出来,官方有一个隐藏地址,里面的 demo 是最全的,包括无感,可以去上面给出的第二个地址里查看;数美的加密参数包含了 DES 加密算法,参数名以及 DES Key 不定时会变化,本文也会分析如何利用 AST 来获取动态的参数。

抓包分析

conf 接口,获取配置,主要是获取核心的 captcha-sdk.min.js 的地址,请求参数解释:

参数 含义
organization 数美分配的公司标识,一般是每个网站唯一,写死即可
appId 应用标识,区分不同应用,数美后台可以管理
callback 回调参数
lang 语言,zh-cn 简体中文、zh-tw 繁体中文、en 英文
model 模式,slide 滑块、auto_slide 无感验证、select 文字点选、icon_select 图标点选、seq_select 语序点选、spatial_select 空间推理
sdkver 这个 sdk 版本是 captcha-sdk.min.js 内部写死的
channel 推广渠道,数美后台可以管理
captchaUuid 32位随机字符串,与业务方自身埋点数据配合,便于后续定位问题或进行数据统计
rversion captcha-sdk.min.js 版本号

返回结果重点看 captcha-sdk.min.js 文件地址,如下图所示有个 v1.0.4-171,本文中我们称 v1.0.4 为大版本,171 为小版本,小版本不定时会更新,版本号不断升高。

然后就是 register 接口,不同类型,返回的数据都大同小异,其中 bg 是背景图片,fg 是滑块,文字点选、空间推理中 order 是提示信息,klrid 三个参数后续会用到。

最后就是 fverify 验证接口,有类似下图红框中的 12 个参数,都是通过 JS 生成的,其参数名会根据 captcha-sdk.min.js 的变化而变化,其中有个最长的类似于下图的 ep 值,包含了轨迹加密。返回值里参数解释:

参数 含义
code 1100:成功;1901:QPS超限;1902:参数不合法;1903:服务失败;9101:无权限操作
riskLevel 处置建议,PASS:正常,建议直接放行;REJECT:违规,建议直接拦截

逆向分析

跟栈会发现核心逻辑在 captcha-sdk.min.js 里,这个 JS 类似于 OB 混淆(以前的文章介绍过,此处不再细说):

这里可以自己写 AST 还原一下,为了方便我们直接使用 v_jstools 解混淆:

然后替换掉原来的 captcha-sdk.min.js,如果你测试的是官网的体验页面,使用 Fiddler 替换时要注意可能有跨域问题,需要利用 Filters 功能,设置响应头 Access-Control-Allow-Origin 字段值为当前域名:

如果你没注意到这个跨域问题,可能会替换之后发现没替换成功,原因是数美的资源有四个域名,其中一个宕了便会启用另一个,你替换其中一个报错了就会自动跳转另一个,所以看起来你并没有替换成功:

PS:若替换的 JS 格式化了,那么你在网页上滑动也是校验失败的,因为 JS 里检测了格式化,将 JS 压缩成一行再替换即可,具体检测的位置后文会讲到。

captchaUuid

直接搜索关键词下断点,经过多次调试会发现第一个出现 captchaUuid 的地方是在 smcp.min.js,如下图所示:

这里的栈并不多,来回跟栈也没发现是哪里生成的,此时可以从初始位置也就是 embed.html 初始化验证码的地方开始单步跟:

单步跟进去会发现一个 getCaptchaUuid() 的方法,将此方法扣出来即可。

function generateTimeFormat() {
var e = new Date()
, t = function(n) {
return +n < 10 ? "0" + n : n.toString();
};
return ((e.getFullYear().toString() + t(e.getMonth() + 1)) + t(e.getDate()) + t(e.getHours()) + t(e.getMinutes())) + t(e.getSeconds());
} function getCaptchaUuid() {
var c = "";
var o = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
var s = o.length;
for (var a = 0; a < 18; a++) {
c += o.charAt(Math.floor(Math.random() * s));
}
return generateTimeFormat() + c;
}

12 个加密参数

直接跟栈就很容易找到,如下图所示的位置,D 就是生成的所有参数,此外,也可以通过搜索关键字 getEncryptContent 或者直接搜索参数名称来定位。

可以发现上图里就有四个加密参数,都用到了 getEncryptContent 这个加密方法,加密方法传入两个参数,一个是待加密参数,一个是 DES Key,这四个待加密参数分别为 appId 值、channel 值、lang 值和一个 getSafeParams 方法。

重点跟进 getEncryptContent 方法看看,一个控制流,挑几个重点的讲一下,第一步是获取一个 key,这个 key 是在前面设置的,后续会讲到,实际上这个 key 没啥用。

然后会有一个 isJsFormat 的格式化检测函数,正常应该是 false 的,如果你格式化了就为 true,也就会导致 f 的值为时间戳加数美的域名,这个 f 值后续是 DES 的 Key,不对的话自然怎么滑都不会通过。

然后就是 DES 加密了,这个 DES 是标准的加密算法,下图中传入的 1 和 0 表示的是加密,0 和 0 则表示解密,解密的情况也有,后续会遇到,modeECBpaddingZeroPadding,不需要 iv,可以直接扣代码,或者直接引库即可。

var CryptoJS = require("crypto-js")

function DESEncrypt(key, word) {
var key_ = CryptoJS.enc.Utf8.parse(key);
var srcs = CryptoJS.enc.Utf8.parse(word);
var encrypted = CryptoJS.DES.encrypt(srcs, key_, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
});
return encrypted.toString();
} function DESDecrypt(key, word) {
var key_ = CryptoJS.enc.Utf8.parse(key);
var decrypt = CryptoJS.DES.decrypt(word, key_, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
});
return decrypt.toString(CryptoJS.enc.Utf8);
}

这里的四个值就分析完了,还有八个值是在前面生成的,如下图所示 x 的值即为其他八个值,往前看是一个函数生成的,往里面跟即可。

跟进来是一个 getMouseAction 方法,里面先是挨个取值,后续会对这些值进行 DES 加密,下图中的 a、c 参数就是 register 接口返回的 k、l 值,s 参数是对 register 接口返回的 k 值进行解密操作:

上图中 u = this._data 里面的值,根据滑块、点选、无感模式的不同,也有所差异,以下代码中,以 baseData 来表示 this._data 的值,根据模式的不同,可分为三类,大致构成如下:

滑块(slide):

/*
track:滑动轨迹(x, y, t),distance:滑动距离,randomNum:生成两数之间的随机值,示例:
var track = [[0, -2, 0], [62, 1, 98], [73, 4, 205], [91, 3, 303], [123, -3, 397], [136, 8, 502], [160, 0, 599], [184, 0, 697], [169, 0, 797]]
var distance = 169
*/ var baseData = {}
baseData.mouseData = track
baseData.startTime = 0
baseData.endTime = track[track.length - 1][2] + randomNum(100, 500)
baseData.mouseEndX = distance
baseData.trueWidth = 300
baseData.trueHeight = 150
baseData.selectData = []
baseData.blockWidth = 40

滑块轨迹生成代码:

def get_sm_track(distance):
track_length = random.randint(4, 10)
track = [[0, -2, 0]]
m = distance % track_length
e = int(distance / track_length)
for i in range(track_length):
x = (i + 1) * e + m + random.randint(20, 40)
y = -2 + (random.randint(-1, 10))
t = (i + 1) * 100 + random.randint(-3, 5)
if i == track_length - 1:
x = distance
track.append([x, y, t])
else:
track.append([x, y, t])
logger.info("track: %s" % track)
return track

点选类(文字点选 select、图标点选 icon_select、语序点选 seq_select、空间推理 spatial_select):

/*
coordinate:点选坐标(x, y),randomNum:生成两数之间的随机值,示例:
var coordinate = [[171, 101], [88, 102], [138, 109], [225, 100]]
*/ var baseData = {}
var time_ = new Date().getTime()
coordinate.forEach(function(co) {
co[0] = co[0] / 300
co[1] = co[1] / 150
co[2] = time_
time_ += randomNum(100, 500)
})
baseData.mouseData = coordinate
baseData.startTime = time_ - randomNum(800, 20000)
baseData.endTime = coordinate[coordinate.length - 1][2]
baseData.mouseEndX = 0
baseData.trueWidth = 300
baseData.trueHeight = 150
baseData.selectData = coordinate
baseData.blockWidth = undefined

无感(auto_slide):

/*
randomNum:生成两数之间的随机值
*/ var baseData = {}
baseData.mouseData = [[0, 0, 0]]
baseData.startTime = 0
baseData.endTime = randomNum(100, 500)
baseData.mouseEndX = 260
baseData.trueWidth = 300
baseData.trueHeight = 150
baseData.selectData = []
baseData.blockWidth = 40

这些值生成完了之后,就是挨个通过 getEncryptContent 进行加密,前面已经分析过,实际上就是 DES 加密,可以看到分为点选、滑块和无感三类,其中 DES Key 也是会每隔一段时间变化的:

再往下走还有三个加密参数,待加密值是定值,然后将 s 的值(也就是前面 register 接口返回的 k 经过 DES 解密后的值赋值给了 this._data.__key)。

至此所有加密参数就搞完了。

结果验证

AST 获取动态参数

前面说了,/v1.0.4-171/captcha-sdk.min.js 文件地址,我们称 v1.0.4 为大版本,171 为小版本,小版本每隔一段时间会更新,版本号会不断升高,具体更新周期是多少?这里推荐一个方法 document.lastModified,该方法记录的是物理网页的最后修改时间,我们直接访问 JS 地址,就可以直接查看不同版本的 JS 是啥时候更新的了,多对比几个版本,发现更新间隔时间并没有太明显的规律,如下图所示:

不同版本里面的 12 个加密参数的名称和 DES 加密的 Key 都不一样,我们可以利用 AST 来动态获取这 12 个参数,经过测试,以下版本均可正常提取:

  • v1.0.4-148 ~ v1.0.4-171
  • v1.0.3-147 ~ v1.0.3-171
  • v1.0.1-147 ~ v1.0.1-171

截止本文发布,小版本 171 为最新,v1.0.4 小版本从 148 开始,v1.0.3v1.0.1147 以前没有混淆,可自行正则匹配,暂未发现其他大版本,如有遇到不能适配的,可联系我瞅瞅,完整的代码在公众号 k哥爬虫 中,有需要的可以点击下方链接。

【验证码逆向专栏】数美验证码全家桶逆向分析以及 AST 获取动态参数

PS:此 AST 代码仅实现对动态参数的提取,并非还原所有的混淆,提取出来的结果是有序、未去重的,后续按索引取就行。

【验证码逆向专栏】数美验证码全家桶逆向分析以及 AST 获取动态参数的更多相关文章

  1. Spring全家桶系列–SpringBoot之AOP详解

    //本文作者:cuifuan //本文将收录到菜单栏:<Spring全家桶>专栏中 面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关 ...

  2. Spring全家桶系列–SpringBoot渐入佳境

    //本文作者:cuifuan //本文将收录到菜单栏:<Spring全家桶>专栏中 首发地址:https://www.javazhiyin.com/20913.html 萌新:小哥,我在实 ...

  3. Vue 全家桶单元测试简要指南

    此文已由作者张汉锐授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. vue 的单元测试环境 按照目前全家桶的情况,是需要 webpack 的支持的.可以直接使用 vue-cli ...

  4. 开发工具类API调用的代码示例合集:六位图片验证码生成、四位图片验证码生成、简单验证码识别等

    以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 六位图片验证码生成:包括纯数字.小写字母.大写字母.大小写混合.数 ...

  5. 南邮 base64全家桶

    这几天不想学逆向 做做crypto(菜还瞎j2做)..... 题目: 全家桶全家桶全家桶!我怎么饿了......密文(解密前删除回车):R1pDVE1NWlhHUTNETU4yQ0dZWkRNTUpY ...

  6. 使用react全家桶制作博客后台管理系统

    前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基于react全家桶(React.React-r ...

  7. mac设计师系列 Adobe “全家桶” 15款设计软件 值得收藏!

    文章素材来源:风云社区.简书 文章收录于:风云社区 www.scoee.com,提供1700多款mac软件下载 Adobe Creative Cloud 全线产品均可开放下载(简称Adobe CC 全 ...

  8. CSS background 属性全家桶

    介绍我们都知道css的background属性是一个复合属性,可以简写成一行代码,也可以将每个属性分开来写. background 简写属性在一个声明中设置所有的背景属性.如:body{ backgr ...

  9. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  10. 【知识总结】多项式全家桶(三)(任意模数NTT)

    经过两个月的咕咕,"多项式全家桶" 系列终于迎来了第三期--(雾) 上一篇:[知识总结]多项式全家桶(二)(ln和exp) 先膜拜(伏地膜)大恐龙的博客:任意模数 NTT (在页面 ...

随机推荐

  1. Axure 辅助线--栅格化布局

    全局辅助线 在所有页面都会显示,比如主页面是框架.子页面通过[内联框架]去加载,为了子页面的元件不偏移,可以创建创建全局辅助线 页面辅助线

  2. SQL Server 项目中 SQL 脚本更新、升级方式,防止多次重复执行

    MySQL 项目中 SQL 脚本更新.升级方式,防止多次重复执行 Oracle 项目中 SQL 脚本更新方式 一套代码,多家部署时,在SQL脚本升级时,通过一个SQL文件给运维,避免出现SQL执行序顺 ...

  3. 手把手教你搭建深度学习开发环境(Tensorflow)

    前段时间在阿里云买了一台服务器,准备部署网站,近期想玩一些深度学习项目,正好拿来用.TensorFlow官网的安装仅提及Ubuntu,但我的ECS操作系统是 CentOS 7.6 64位,搭建Pyth ...

  4. 这两种完全不同的JPEG加载方式,你肯定见过!

    现如今网站所使用的的图片格式多种多样,但是有一种图片格式占到了 74% 的使用量.它就是 JPEG,即联合图像专家组.这类文件的后缀通常为 .jpg 或 .jpeg,是摄影中常见的图片类型. JPEG ...

  5. #1495:非常可乐(BFS+数论)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1495 BFS解法 题目 给三个数字 s n m s=n+m s在1到100之间 就是个倒水问题可以从第 ...

  6. JSP常见错误以及解决方案

    原作者为 RioTian@cnblogs, 本作品采用 CC 4.0 BY 进行许可,转载请注明出处. 本节我们分析一下常见的 JSP 错误信息,并给出解决方案.这些错误在实际开发中会经常遇到,所以有 ...

  7. 【每日一题】3.数学考试 (前缀和,线性DP)

    题目链接:Here 思路:区间求和问题可以想到一个常用算法.前缀和.区间 \([l,r]\) 的和可以用 \(sum_r - sum_l\) 方便求出 由于区间长度 \(k\) 已知,所以我们可以直接 ...

  8. 51 nod | 1007 正整数分组(背包DP)

    补题链接:Here 将一堆正整数分为2组,要求2组的和相差最小. 例如:1 2 3 4 5,将1 2 4分为1组,3 5分为1组,两组和相差1,是所有方案中相差最少的. 输入 第1行:一个数N,N为正 ...

  9. SpringCloud学习 系列五、创建生产者和消费者验证微服务中心 Eureka的作用

    系列导航 SpringCloud学习 系列一. 前言-为什么要学习微服务 SpringCloud学习 系列二. 简介 SpringCloud学习 系列三. 创建一个没有使用springCloud的服务 ...

  10. mixin混合

    多个组件有相同的逻辑,抽离出来 mixin并不是完美的解决方案,会有一些问题 vue3提出composition api旨在解决这些问题