申明

本文章中所有内容仅供学习交换应用,不用于其余任何目标,不提供残缺代码,抓包内容、敏感网址、数据接口等均已做脱敏解决,严禁用于商业用途和非法用处,否则由此产生的所有结果均与作者无关!

本文章未经许可禁止转载,禁止任何批改后二次流传,擅自应用本文解说的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【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推广渠道,数美后盾能够治理
captchaUuid32位随机字符串,与业务方本身埋点数据配合,便于后续定位问题或进行数据统计
rversioncaptcha-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 值,蕴含了轨迹加密。返回值里参数解释:

参数含意
code1100:胜利;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 = trackbaseData.startTime = 0baseData.endTime = track[track.length - 1][2] + randomNum(100, 500)baseData.mouseEndX = distancebaseData.trueWidth = 300baseData.trueHeight = 150baseData.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 = coordinatebaseData.startTime = time_ - randomNum(800, 20000)baseData.endTime = coordinate[coordinate.length - 1][2]baseData.mouseEndX = 0baseData.trueWidth = 300baseData.trueHeight = 150baseData.selectData = coordinatebaseData.blockWidth = undefined

无感(auto_slide):

/*randomNum:生成两数之间的随机值*/var baseData = {}baseData.mouseData = [[0, 0, 0]]baseData.startTime = 0baseData.endTime = randomNum(100, 500)baseData.mouseEndX = 260baseData.trueWidth = 300baseData.trueHeight = 150baseData.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 代码仅实现对动静参数的提取,并非还原所有的混同,提取进去的后果是有序、未去重的,后续按索引取就行。