关于python:验证码逆向专栏数美验证码全家桶逆向分析以及-AST-获取动态参数

43次阅读

共计 5946 个字符,预计需要花费 15 分钟才能阅读完成。

申明

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

本文章未经许可禁止转载,禁止任何批改后二次流传,擅自应用本文解说的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【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 代码仅实现对动静参数的提取,并非还原所有的混同,提取进去的后果是有序、未去重的,后续按索引取就行。

正文完
 0