吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4713|回复: 32
收起左侧

[Web逆向] 某验点选验证码分析

  [复制链接]
kylin1020 发表于 2024-4-3 17:03
本帖最后由 kylin1020 于 2024-4-3 17:55 编辑

某验点选验证码分析

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关.  本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除.

链接: aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tLw==

流程分析

尝试点击页面的登录会触发点选验证码, 随便点几下触发验证流程.
从 devtools 中可以分析得到整个验证码的验证流程如下:

  1. 首先访问 https://passport.bilibili.com/x/passport-login/captcha 拿到极验的 gt 和 challenge 参数, 用于后续的验证.
    1712048977504-3d661aa5-60de-4ffc-be9e-70641b238ff7.png

  2. 然后根据 gt 参数访问 https://api.geetest.com/gettype.php 获取并加载当前版本的 js 代码.
    1712048977504-3d661aa5-60de-4ffc-be9e-70641b238ff7.png

  3. 访问https://api.geetest.com/get.php 获取一些基本信息, 校验的服务器 api 地址, c, s 参数等, 可以看到访问时带了一个 w 参数, 暂时不知道生成逻辑.

1712048977504-3d661aa5-60de-4ffc-be9e-70641b238ff7.png
1712048977504-3d661aa5-60de-4ffc-be9e-70641b238ff7.png

  1. 访问 https://api.geetest.com/ajax.php 接口拿到验证码校验类型, 该接口同样有 w 参数.
    1712048977504-3d661aa5-60de-4ffc-be9e-70641b238ff7.png

  2. 再次访问 https://api.geetest.com/get.php , 这次请求带上了更多参数, 包括 w, 验证码类型, api_server 等, 返回的数据也带上了验证码图片和新的 c,s 参数.
    1712048977504-3d661aa5-60de-4ffc-be9e-70641b238ff7.png

  3. 最后请求 https://api.geetest.com/ajax.php 校验验证码, 主要参数是 gt, challenge 和 w, 得到验证结果.
    通过对这个流程分析发现主要参数就是 w, 因此要跟踪下三个 w 参数的请求, 看下各自的 w 生成逻辑是什么.

第一次 w 生成逻辑分析

通过查看请求的调用栈可以知道第一个 w 是由 fullpage.xxx.js 生成的.

在请求调用栈上打个断点, 找下 w 参数在哪里生成.

可以看到代码已经经过混淆, 利用ast简单去掉字符串替换和 unicode 编码, override content 之后继续分析.

在代码中搜索"w", 找到 5 处 w 的生成逻辑, 5 处都打下断点后重新刷新.

打下断点再次刷新后停在了其中一处断点, 可以知道第一个 w 由 i+r 组成.

1.1 r 参数分析

r 是由 t["$_CCGw"]函数得到, 往下跟这个函数的逻辑.

核心代码: new X()["encrypt"](this["$_CCHU"](e))
其中this["$_CCHU"]函数如下图:

可以知道该函数作用是生成 aes key, 如果已存在则使用已存在的 key, 所以这个 r 参数应该是保存加密的 aes key. 跟一下 encrypt 函数看下是什么加密算法.

从一些关键词判断应该是 RSA 算法, 返回 16 进制字符串, 从上下文代码中可以找到设置 RSA 公钥的函数 SetPublic, 在该函数下断点跟一下即可知道公钥 e 和 t.

1.2 i 参数分析

根据上文的代码截图可以知道 i 的来源.
关键代码:

o = $_BFo()["encrypt1"](de["stringify"](t["$_EJV"]), t["$_CCHU"]()),
i = p["$_HEt"](o)

o 参数生成
t["$_EJV"]是一个 Object, 保存了一些验证码的信息.

t["$_CCHU"]()从上面的 i 参数知道是一个 aes key.

然后分析下 encrypt1 函数的逻辑, 简单查看代码逻辑后基本断定是 aes 加密算法而且从各种函数关键词来看基本不会是魔改 aes.
主动调用该函数确认是标准的 aes, 并且采用 cbc 模式/pkcs7 填充, iv 默认是 0000000000000000.

完成 aes 加密之后对每个 32 位的数字做如下转换

其作用是将 32 位的数字转为四个 8 位的数字
i 参数生成

i 由p["$_HEt"]函数输入 o 经过一系列操作之后由 res+end 组成, 其中$_HCK函数有多处将三个 8bit 数字换算成 24bit 数字然后转换为四个字符的逻辑, 似乎是 base64 算法变体.
t 函数是取指定 t 二进制位上对应的值组合成新的数字.

$_GJI函数作用是取对应数字的字符, 取不到则用"."代替. $_GAp="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()",
与标准 base64 需要的字符串差异在最后两个字符由"+/"变为了"()".

$_GCK=7274496(二进制: 11011110000000000000000), $_GDu=9483264(100100001011010000000000)
$\_GEk=19220(100101100010100),$_GFY=235(11101011)
实现标准 base64 需要的四个数字应该是:
0xfc0000(111111000000000000000000),
0x3f000(111111000000000000),
0xfc0(111111000000),
0x3f(111111),
跟标准 base64 实现有点不同.

1.3 总结分析

r 参数保存 aes 密钥, 采用 RSA 加密后取 16 进制, i 是 aes 加密数据后采用 base64 编码的字符串.

第二次 w 生成逻辑分析

第二个 w 同样是由fullpage.xxx.js生成的.

n["w"] = t["$_CEAN"]


跟踪下 t["$_CEAN"]的生成, 可以发现同样是采用 aes 加密, 不过加密的数据不同, 这个 r 参数当中有很多未知的参数, 似乎是一些环境检测的参数.

{"lang":"zh-cn","type":"fullpage","tt":"M6(*((1Sj((sM((","light":-1,"s":"c7c3e21112fe4f741921cb3e4ff9f7cb","h":"321f9af1e098233dbd03f250fd2b5e21","hh":"39bd9cad9e425c3a8f51610fd506e3b3","hi":"09eb21b3ae9542a9bc1e8b63b3d9a467","vip_order":-1,"ct":-1,"ep":{"v":"9.1.9-r8k4eq","te":false,"$_BBp":false,"ven":"Google Inc. (NVIDIA)","ren":"ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 (0x00002488) Direct3D11 vs_5_0 ps_5_0, D3D11)","fp":null,"lp":null,"em":{"ph":0,"cp":0,"ek":"11","wd":1,"nt":0,"si":0,"sc":0},"tm":{"a":1711849377534,"b":1711849377617,"c":1711849377617,"d":0,"e":0,"f":1711849377539,"g":1711849377539,"h":1711849377539,"i":1711849377539,"j":1711849377539,"k":0,"l":1711849377547,"m":1711849377614,"n":1711849377615,"o":1711849377620,"p":1711849377751,"q":1711849377751,"r":1711849377752,"s":1711849378121,"t":1711849378121,"u":1711849378121},"dnf":"dnf","by":2},"passtime":8258,"rp":"d2d182b3ce6cf55f590e9ec11c9b1635","captcha_token":"549902629","otpj":"jm4jwcx7"}

搜一下其中关键词例如"hh"找到代码逻辑位置.

2.1 s 参数分析


关键代码H(p["$_HD_"](t)), 其中 p["$HD"]根据上文分析已知是 base64 变体算法, t 是一个字符串, H 函数是标准 md5 算法.

t 这个字符串来自$_BICT函数生成, $_BICT函数用于收集鼠标操作事件并采用 base64 编码, base64 字符映射为"()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~".
收集到的鼠标事件信息会进行一系列转换成二进制字符串后进行 base64 编码, 核心代码如下:

for (var t = [], n = [], r = [], o = [], i = 0, s = e["length"]; i < s; i += 1) {
   var a = e[i],
       _ = a["length"];
   t["push"](a[0]), n["push"](2 === _ ? a[1] : a[2]), 3 === _ && (r["push"](a[1][0]), o["push"](a[1][1]));
}
var c = f(t) + d(n, !1) + d(r, !0) + d(o, !0),
   l = c["length"];

其中 t 是鼠标事件数组, 例如: ["move", "move", "down", "move", "up"], 鼠标事件包括: move/down/up/scroll/focus/blur/unload.
n 是鼠标事件发生的毫秒级时间戳数组(例如: [1711952404794, 1711952404794, 1711952404798, 1711952404798, 1711952404798]).
如果传入该函数的 e 参数每个元素是一个长度 3 的数组, 即每个元素只记录了[事件类型, [x 坐标, y 坐标], 毫秒级时间戳]信息,例如: "move", [123, 456], 1711952404794], ["down", [123, 456], 1711952404798, 则 r 和 o 参数分别是记录 x 和 y 坐标的数组.

2.2 h 参数分析

 n = i["$_BJDB"]["$_BICT"]();
 ["h", H(p["$_HD_"](n))];

i["$_BJDB"]["$_BICT"]函数作用是检测一个 Object 对象(该对象为空 Object)指定的属性是否存在, 存在则返回该属性的值, 不存在则返回-1, 由此组成一个数组并用"magic data"字符串 joi 拼接这个数组得到. 检测的属性列表如下:

["textLength","HTMLLength","documentMode","A","ARTICLE","ASIDE","AUDIO","BASE","BUTTON","CANVAS","CODE","IFRAME","IMG","INPUT","LABEL","LINK","NAV","OBJECT","OL","PICTURE","PRE","SECTION","SELECT","SOURCE","SPAN","STYLE","TABLE","TEXTAREA","VIDEO","screenLeft","screenTop","screenAvailLeft","screenAvailTop","innerWidth","innerHeight","outerWidth","outerHeight","browserLanguage","browserLanguages","systemLanguage","devicePixelRatio","colorDepth","userAgent","cookieEnabled","netEnabled","screenWidth","screenHeight","screenAvailWidth","screenAvailHeight","localStorageEnabled","sessionStorageEnabled","indexedDBEnabled","CPUClass","platform","doNotTrack","timezone","canvas2DFP","canvas3DFP","plugins","maxTouchPoints","flashEnabled","javaEnabled","hardwareConcurrency","jsFonts","timestamp","performanceTiming","internalip","mediaDevices","DIV","P","UL","LI","SCRIPT","touchEvent"]

2.3 tt 参数分析

e = i["$_CAAG"]["$_BIBg"]();

tt 参数作用是根据服务器返回的 c 和 s 参数截取鼠标事件 base64 编码后的字符串, 可能是用于校验.
c 是一个纯数字字符串, 每两个字符代表一个 16 进制数字; s 是一个数组, 取其中下标 0, 2, 4 三个数字.

2.4 hh 参数分析

["hh", H(n)]

hh 参数是 n 的 md5 值, 跟 h 参数区别是 h 参数是 base64 编码 n 之后取 md5 值.

2.5 hi 参数分析

var e = t["$_BJDB"]["$_BIBg"]();
t["$_CCFY"] = e;

["hi", H(i["$_CCFY"])]

t["$_BJDB"]["$_BIBg"]函数的作用与 2.2 h 参数分析中的 n 变量的生成相似, 只是组成的数组用"!!"字符串 join 拼接得到.

2.6 ep 参数分析

["ep", i["$_CEDy"]() || -1]


ep 参数应该是一些环境检测的汇总数据, 各属性可以在代码中看出来, 汇总如下:

属性名 作用
v 版本号, 当前版本为 9.1.9-r8k4eq
te 是否支持 touchEvent
$_BBp 是否支持 mouseEvent 事件
ven WEBGL_debug_renderer_info 扩展的 UNMASKED_VENDOR_WEBGL
ren WEBGL_debug_renderer_info 扩展的 UNMASKED_RENDERER_WEBGL
fp null, 可能是记录第一个鼠标事件
lp null, 可能是记录最后一个鼠标事件
em 用于检测运行环境中的各种属性, 一般 1 为表示有该属性, 0 表示没有. 其中 ph 表示phantom 是否在 window 中; cp 表示 callPhantom 是否在 window 属性中; ek 表示遍历一个 TypeError 对象的属性列表, 检测的属性列表为["line","column","lineNumber","columnNumber","fileName","message","number","description","sourceURL","stack"], 并用一个二进制数表示, 存在则为 1, 否则为 0, 例如 0000010001, 然后将该二进制数用 16 进制字符串表示, 例如"11"; wd 表示 webdriver 在 window 属性中且 webdriver 为 true, nt 表示__nightmare 是否在 window 属性中; si 表示_webdriverscriptfn 是否在 document 属性中; sc 表示$cdc_asdjflasutopfhvcZLmcfl是否在 document 属性中
tm 记录 window.performance.timing 对象的各属性, a: navigationStart, b: unloadEventStart, c: unloadEventEnd, d: redirectStart, e: redirectEnd, f: fetchStart, g: domainLookupStart, h: domainLookupEnd, i: connectStart, j: connectEnd, k: secureConnectionStart, l: requestStart, m: responseStart, n: responseEnd, o: domLoading, p: domInteractive, q: domContentLoadedEventStart,r: domContentLoadedEventEnd, s: domComplete, t: loadEventStart, u: loadEventEnd

2.7 captcha_token 参数分析


captcha_token 是一个校验值, 分别对 n 函数, o 函数和 e 变量计算 djb hash 得到, 其中 n 函数是计算 captcha_token 所处的函数, o 函数是计算 djb hash 的函数, e 是一个固定值, 如果发现程序进入到开始计算 djb hash 的过程中执行时间大于 100ms 则将 e 变更为"qwe".

2.8 passtime 参数分析

s = $_Gt() - rt;

["passtime", s || -1]

passtime 记录加载 js 的时间到现在的毫秒数.

2.9 rp 参数分析

["rp", H(o["gt"] + o["challenge"] + s)]]

rp 参数是 gt + challenge + passtime 的 md5 值

第三次 w 参数分析

第三个 w 由 click.xxx.js 生成, 利用ast简单去掉字符串替换和 unicode 编码后在全文中搜索"w"关键词, 找到一处 w 的生成, 随后打断点.

查看代码可以知道w由p+u组成, 这个加密模式与上文的w生成很相似, 基本模式都是: aes加密数据后base64 + rsa加密aes key得到.

3.1 u参数分析

var u = n["$_CAAz"]();




通过对上文fullpage.xxx.js的分析, 可以很快判断出此处的u也是rsa加密aes key得到hex字符串, rsa公钥也可以通过全局搜索迅速得到.

3.2 p参数分析

h = X["encrypt"](ae["stringify"](o), n["$_CABL"]());
p = w["$_EFO"](h);

X["encrypt"]是aes加密算法, n["$_CABL"]()是aes key. p用于存储加密后的o对象信息, o对象信息示例如下:

{"lang":"zh-cn","passtime":711,"a":"2942_8572,5487_9028","pic":"/captcha_v3/batch/v3/66355/2024-04-02T13/word/bd8c68ebff254a08be793bcff14a7479.jpg","tt":"M/d8Pj:E8(D(PjIE(UQ?O:9U)R.B,::B)O5:-A-:A*,1:::ggJ:.-A:..:Dg-)(?.NMbpE-9-_-:,O-M-N88-8M:*O4N8N7N(GPN8N0Wif06Lh)b.qqb.ij:UC-K-:*:/)-)UCM9n:-)/),c(BAb)6-M-8-0T)*c/:1K)D3),c(:-U-7M:-9?b9j)G)Y(,()9(((b51BBB,bb5,55i/)M)(NKY-2Vd-cE?q2d.:EEj9(-7b9cL11MB--Io2OMN-)DBOE,)(?d).GbU/(6GgM9i1-3JE-(Ln((p(((*(Aj((qc9()b,(((2BB9(,b(,((nb5,18(@-P0))Y-0b9qH)(@-N,VGUB)*),6R.M92*:g/H@OHcUT30ng0:jATC1?/)(U--BhAN*)(95?5E-*M9(E/(-iMEW-7*(0j(()p((((((","ep":{"ca":[{"x":684,"y":404,"t":1,"dt":777},{"x":713,"y":471,"t":1,"dt":192},{"x":880,"y":527,"t":3,"dt":454},{"x":716,"y":442,"t":1,"dt":348016},{"x":794,"y":456,"t":1,"dt":367},{"x":881,"y":512,"t":3,"dt":343}],"v":"3.1.0","$_FB":false,"me":true,"tm":{"a":1712036876180,"b":1712036876251,"c":1712036876251,"d":0,"e":0,"f":1712036876181,"g":1712036876181,"h":1712036876181,"i":1712036876181,"j":1712036876181,"k":0,"l":1712036876189,"m":1712036876248,"n":1712036876317,"o":1712036876253,"p":1712036876348,"q":1712036876348,"r":1712036876349,"s":1712036876681,"t":1712036876681,"u":1712036876681}},"h9s9":"1816378497","rp":"839fed64c9f55bd9ecc4faffdebb9a29"}

其中有多个字段需要继续深入分析下来源, 全文搜索"tt"字符串, 找到o对象各种属性的代码生成位置, 打上断点重新开始.

3.2.1 o["a"]参数分析

o["a"]是传进来的参数e, 往回跟一下调用栈.

可以知道, o["a"]是由函数n["$_CBIQ"]["$_FAE"]()生成的, 在该函数位置打断点分析.

n["$_CBIQ"]["$_FAE"]()作用是遍历this["$_FC_"]_JIT 属性, 将每个元素的$_CEGt$_CEHV属性拼接, 得到的数组join字符,得到一个组合字符串.
全文搜一下关键词$_CEGt$_CEHV找到设置这两个属性的代码位置分析下.



通过对        $_BFGo函数上下文进行分析, 可以知道$_CEGt$_CEHV分别表示鼠标点击的相对x(相对于验证框的位置)和相对y坐标, 其中$_CEGt是Math.round(x相对坐标 100)得到, $_CEHV是Math.round(y相对坐标 100)得到.
所以可以知道o["a"]是记录点击坐标的字符串, 例如"4182_5734,6759_7365".

3.2.2 o["tt"]参数分析

o["tt"]参数与2.3的tt参数生成算法一致, 此时记录的是整个验证过程的鼠标操作事件, 包括move/down/up.

3.2.3 o["ep"]参数分析
"ep": n["$_BJJj"]()


ep是一个object对象, 记录了多个值, 其中ca和tm参数要分析下. ca是鼠标点击事件的坐标和耗时的数组, 这个数组最后一个元素是点击确认按钮.

tm参数与2.6 ep参数分析中的tm参数一致, 都是记录 window.performance.timing 对象的各属性, a: navigationStart, b: unloadEventStart, c: unloadEventEnd, d: redirectStart, e: redirectEnd, f: fetchStart, g: domainLookupStart, h: domainLookupEnd, i: connectStart, j: connectEnd, k: secureConnectionStart, l: requestStart, m: responseStart, n: responseEnd, o: domLoading, p: domInteractive, q: domContentLoadedEventStart,r: domContentLoadedEventEnd, s: domComplete, t: loadEventStart, u: loadEventEnd.

3.2.4 o["h9s9"]参数分析

全文搜索发现没有h9s9关键词, 但是一定会在某个时间点给o附上h9s9属性, 直接在o定义完后加个proxy, 在设置h9s9时debugger:

o = new Proxy(o, {
    set: function (data, key, value) {
        if (key && key === "h9s9") {
            debugger;
        }
        data[key] = value
        return true
    }
});

继续执行则停在了设置h9s9的位置上, 往回看调用栈找h9s9的逻辑代码.


可以知道h9s9在变量_中就已经被设置好, 继续分析_的生成逻辑.

在变量_定义处打下断点, 逐步调试, 可以知道h9s9在经历window["_gct"](_)函数调用后被设置, 继续跟踪window["_gct"](_)函数发现js文件已经跳转到gct.xxx.js, 去除掉字符串函数和unicode编码后overwrite content, 进入_gct这个函数打断点分析.


发现会走到StJC函数, 并且在其中一个步骤设置了h9s9.



查看下Rbfk函数的代码逻辑, 是一个djb hash算法, 所以这个h9s9参数就是给StJCRbfk函数的代码签名, 如果这两个函数的代码被变更则h9s9会不同. h9s9参数跟2.7 captcha_token 参数分析的captcha_token参数作用类似.

3.2.5 o["rp"]参数分析


rp参数跟上文2.9 rp 参数分析提到的rp参数很类似, 可以明显看到四个魔术, 也是md5算法, 所以rp是r["gt"] + r["challenge"] + o["passtime"]组合的md5值.

免费评分

参与人数 12威望 +2 吾爱币 +112 热心值 +10 收起 理由
ravi + 1 + 1 谢谢@Thanks!
trust1234 + 1 + 1 谢谢@Thanks!
wuweiwuwei + 1 + 1 我很赞同!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
windylove + 1 谢谢@Thanks!
mufu + 1 + 1 谢谢@Thanks!
eureka0225 + 1 + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
liupin924 + 1 谢谢@Thanks!
涛之雨 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
BonnieRan + 1 + 1 谢谢@Thanks!
二十瞬 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

luguoyixiazi 发表于 2024-10-31 22:24
本帖最后由 luguoyixiazi 于 2024-10-31 22:38 编辑

佬,三代九宫格验证时传参是啥?以及能套用文字点选吗?
我抓了一下文字点选的包,但是不确定三代九宫格咋整
三代九宫格貌似用的也是click,请求的api传参我都用的文字点选的,最后到ajax验证的时候不知道该传啥参,
我用https://github.com/ravizhan/geetest-v3-click-crack改了一下point的位置试九宫格但是没过,项目本身文字点选能过
这个项目里面point是
“”“
result_list = model.siamese(small_img, big_img)
print(f"文字配对完成,用时: {time.time() - tt}")
point_list = []
# print(result_list)
for i in result_list:
    left = str(round((i[0] + 30) / 333 * 10000))
    top = str(round((i[1] + 30) / 333 * 10000))
    point_list.append(f"{left}_{top}")
”“”
然后我的i转换成了正确图片区域内随机一个点,三代九宫格图片的尺寸是344*384,
还试了直接point list里面放col_row和row_col,都不行,
模型跑的是对的,就是参数不清楚
 楼主| kylin1020 发表于 2024-4-19 17:00
adjzhang 发表于 2024-4-19 16:41
某验的参数大差不差都是这个参数,真要扣得花挺久时间的

某验的验证码没有做过多的混淆,控制流也很弱的,简单去除字符串函数即可,控制流也是能明显看出来执行步骤,然后每个参数搜一下关键词看一下逻辑能快速知道是什么算法。
hal1314 发表于 2024-4-3 18:29
二十瞬 发表于 2024-4-3 22:16
谢谢你的分享
BonnieRan 发表于 2024-4-3 23:35
每个参数基本都有分析,应该是站内某验参数分析最详细的一篇帖子~
阿姨来点汁 发表于 2024-4-4 00:13
谢谢分享受教了
yst453 发表于 2024-4-4 18:47
学习了,谢谢!
酷狗音乐 发表于 2024-4-4 19:30
谢谢你的分享
liupin924 发表于 2024-4-5 11:58
谢谢分享
wanjianxin 发表于 2024-4-5 21:06
优秀优秀
J14N 发表于 2024-4-5 22:52
学习了谢谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-22 10:21

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表