网址https://cloud.tencent.com/product/captcha
滑完之后先看数据包
滑块验证流程分析
首先看加载滑块图片的请求,响应中有滑块的图片,图片是带缺口的,这个不重要,到时候放到打码平台上可以识别
参数看下
这里aid的参数是固定的,主要是ua这个参数,而且这个响应的sess字段会作为后面请求验证滑块的数据包的参数
并且验证滑块的请求参数中的[{"elem_id":1,"type":"DynAnswerType_POS","data":"374,54"}]里的data是缺口的坐标,这里的y左边54也是在请求滑块图片的响应包中
获取滑块图片参数逆向
ua其实就是user-agent做了个base64,很容易猜出来
滑块验证参数逆向
通过initiator找堆栈,找参数的过程很容易,最终定位到如下函数
关键参数在这里
1 | c = { |
pow_answer参数
该参数是在这一堆代码里生成的
1 | u = this.workLoadData, d = u.workloadAns, l = u.workloadDuration, p = u.workloadNonce; |
对c的赋值操作在
1 | (c.pow_answer = null !== d && d.length > 0 ? "".concat(p).concat(d) : d, |
注意下这里的逻辑,针对 c.pow_answer = null !== d && d.length > 0 来说,是先执行 null !== d 得到一个布尔值,然后判断&& d.length > 0再得到一个布尔值,然后判断?里面的条件赋值,最后把赋值结果给c.pow_answer,这个解析的顺序涉及到js里面的运算符优先级
现在所需要的就是上面的p和d是那里来的了,这两个变量最终都指向了this.workLoadData
此时p的值和d的值分别如下
这里首先考虑去搜索p和d的值,看是不是前面数据包返回的,如果是,可以节省很多工作量
然后去全局搜workLoadData,找到的地方不多,在能找到的地方打下断点,着重看下workloadAns属性的赋值
往下执行,可以发现workloadAns的赋值在r.workLoadData.workloadAns = "".concat(t.ans)这里
现在就是要看t是哪里来的,往上跟堆栈,t是这里的d传进来的,d最终是该函数的e参数经过操作得来的c = e.data, u = c.type, d = c.data
再往上,跟到一个PostMessage里面,在这里断下来,这个PostMessage是js的Web Worker机制,这里简单说一下什么是Web Worker,下面是一个demo
1 | const worker = new Worker("encrypt-worker.js"); |
主线程使用 postMessage(...) 给 Worker 发送消息
Worker内部代码
1 | onmessage = function(e) { |
Worker 监听 onmessage,收到的 e 就是一个 MessageEvent,里面的 .data 属性就是 postMessage(...) 发送的对象。
再回到目标代码,此时e.data的数据如下
1 | { |
要的是data字段,也就是PostMessage里面的{data:n}的n,n是这里PostMessage函数的参数,跟到PostMessage的上层
可以看到n就是这里面这一堆生成的
1 | r(r({}, (0, |
再往上跟,可以看到这里面的data: e是需要的
e是.run方法的参数,可以在这里下断点
然后断到这里,查看上层函数,发现断在这里的时候的上层就是workerLoader.run方法
通过调试发现的现象就是,经过了这个.run方法之后,后面的ans就有值了,另外这里在临时打包的js的上层函数也打了断点,为啥要在这里打断点呢?我考虑的是既然临时这里断不下来,那就在上层函数那里断点看看
继续往上看临时打包的js的上层函数,也就是上图函数的上层函数
可以发现临时的临时的js就是这里的导出函数被执行之后才生成的,生成临时这里可以当成题外话
现在重点看run方法里面是怎么生成ans的值的,重新请求滑块,看一下断点的执行流程
首先走到了refreshWorkload方法里
然后是加载临时的那段js
然后调用run方法
最后发现生成值的地方在之前PostMessage的上层也就是这里,这里其实不好debugger下来,因为是临时生成的js代码,这里是通过先断在生成临时js代码的上层之后再搜索字符串,然后再debugger(或者通过.run方法里面也有生成参数的getWorkloadResult方法,这里不再赘述了,那个地方更不好debugger下来)
1 | r(r({}, (0, |
此时t.data里面还没有ans,执行完上面那一串代码之后就生成了ans
所以关键代码就在a.getWorkloadResult函数和参数c这里,c的值如下,这俩值都是之前请求拿到的
1 | { |
现在就是要扣这个函数的代码了,把目标函数单独扣下来,剩下的就是缺什么就从js里copy什么就行了
1 | // 上面还有一些代码,边运行边补就行了 |
最终的pow_answer参数就是把prefix和ans拼接起来就行了
sess参数分析
前面响应里有
eks参数分析
和collect一样的,环境都一样
collect参数分析
collect参数是o.getTdcData生成的,跟到这个函数里面
开始的a({ft: (0,i["default"])()})执行结果是undefined,执行一下可以发现最终生成参数的值是通过window.TDC.getData(!0)生成的,找到这个函数所在的js
其实这个js的url也是前面的请求返回的,这个很重要,js代码要动态生成,不同的js加密的结果也会不同
这里需要注意的一点是需要先进a({ft: (0,i["default"])()})里面,因为虽然这个函数返回的是undefined,但是该函数内部却是设置了一个后面加密所需的变量
所以在本地扣代码的时候,也需要先调用window.TDC.setData(t)然后再调用加密函数,t的值是固定值
1 | { |
这里其实js代码是收集了鼠标轨迹的,但是没有校验轨迹
tlg参数
collect参数的长度
pow_calc_time参数
和pow_answer参数一起生成的




























