0


网易易盾——推理拼图验证码参数逆向分析和调用

本篇文章主要是对验证的提交参数进行逆向分析,成功演示结果在最底部

目标网址

aHR0cHM6Ly9kdW4uMTYzLmNvbS90cmlhbC9pbmZlcmVuY2U= 

抓包

提交分析

提交链接 https://c.dun.163.com/api/v3/check 

拖动提交参数

data = {referer:'https://dun.163.com/trial/inference',zoneId:'CN31',id:'07e2387ab53a4d6f930b8d9a9be71bdf',token:'9be1b7c8292f43439d10f2280abe0fbb',acToken:'undefined',data:'{"d":"","m":"2uGwkSzkkHWNvxTIpo4HjCWz\\H7N190ntn5i4jK1IP6Ls//e1d1i\\zxU5WMwMlzXQDPdnRFrQLHeov0ENzwXK/b+4o4rv0fF488wACUwfJ7zcVgyBVTM\\I2csuLKwSyeSPyJ\\+sLKhSnR+t/6zWskh2z9f7cYje21rw9IGh5mGMNZMu99hV7sYRPMPr\\S/BELzqtTVbu4TkutjGD5kqV9S2kE/b/T+j86RlcPrh/LkYhChIOt9JajayqVEyhalWROS/EfQdm9O6LI5FuMlWmCS9z9KnR1eA0XS5\\VU/6paz+gauRshkrZDnwfTC/cKuocGT4yqxi/Ar9s7SvJ1VJU\\KLrKs8gC+QfmiuUOzxvcWcH99+1j0USVSK4CkrYJm7A9YbC8lAJ\\zAeqKn0qH2PRU0dOBusTSKn8EVvxPSJuU0pudxrlMcgp33","p":"4jqSwRIFBE4WUzd9uS5\\XqmtsfS3","ext":"tFOr5AoqV5zZf61+zE8p2blNn4A/8Vkp"}',width:'320',type:'9',version:'2.19.1',cb:'5HuUbe4XK+D5Y7GrJCqQkGh9cFnj7NK1Hh1lGzd/hii2FbBMfNG/b1nFpy4Gx9ne',extraData:'',bf:0,runEnv:10,sdkVersion:undefined,callback:'__JSONP_mvnvfob_6'
} 

以下几个参数是会变动的,所以我们主要是对这几个参数进行分析

{token:'',data:'',cb:'',callback:''
} 

我们先直接拖动验证一下,看下请求发送,会发送一个check请求,再查看一下请求堆栈调用,所有的调用都在文件core.v2.19.1.min.js里面,调用方法里面,发现有一个onDragEnd的调用,我们点进去看一下

在此处下断点,然后拖动验证,拖动结束的时候,会在这里断下,查看一下参数,这里的data参数就是我们提交的data参数,这里做了一个JSON.stringify的操作

不过这里有一个需要注意的点,下面这张图,你在这里断下之后,你每次取查看的时候他都是会变的,说明里面有一些随机数在变化,为了可以看到他真实固定的值,我们需要魔改一下代码,这里直接使用Fiddler抓包替换,然后二次编辑源码

将代码下载下来,然后设置替换

我们把上面的JSON.stringify独立出来,然后我们再提交验证一下最后的结果是不在这里

到了这里我们不需要做其他的,直接拖动验证,可以看到这个值和我们提交的是一致的,首先把data里面的参数一个一个解出来

首先我们先分析一下m

 m: _0x4b1c93(_0x37bdc3['sample'](this[_0x487f4d(0x4b4)], _0x3b034c)[_0x487f4d(0xb64)](':')) 
‘_0x487f4d(0xb64)’表示“join”
_0x3b034c是一个固定值50
this[_0x487f4d(0x4b4)]是一个字符串数组,简化为this['traceData']

根据上面的信息,简化之后就是
_0x4b1c93(_0x37bdc3['sample'](this['traceData'], 50).join(':')) 


我们先把this[‘traceData’]这个数组的内容给找出来,直接搜索关键字this[‘traceData’],发现都在这这个作用域内部,这个线索算是断了,不过根据他的英文翻译,可以猜测它可能是我们需要的路径,只是已经做了加密处理

我们到源码看一眼上下文,会发现目前是在onDragEnd,拖拽结束的事件里面

那么我们根据上面的发现,在onMouseMove里面看一下,我们分别在这两个位置下断点,可以看清他到底发生了什么,当我们把鼠标移到外面的时候,会在第一个位置断下,我们把第一个断点去掉,只留下if里面那个,然后就不会了

当我们按住方块拖动的时候,刚好他就断下来了,说明这里面就是路径跟踪的处理逻辑

var _0x4c7923 = this[_0x38ffef(0x105)]['state'][_0x38ffef(0x7a4)]
var _0x1eb7c7 = _0x5054ec(_0x4c7923, [Math[_0x38ffef(0xad7)](_0x3c8f13), Math[_0x38ffef(0xad7)](_0x46347e), _0x37bdc3['now']() - this['drag'][_0x38ffef(0xba1)]] + '')
this[_0x38ffef(0x4b4)][_0x38ffef(0x2fb)](_0x1eb7c7) 

上面代码简化之后就是这个样子

var _0x4c7923 = this.$store.state.token
var _0x1eb7c7 = _0x5054ec(_0x4c7923, [Math.round(_0x3c8f13), Math.round(_0x46347e), new Date().getTime() - this['drag'][_0x38ffef(0xba1)]] + '')
this.traceData.push(_0x1eb7c7) 


知道上述信息之后,我们还要魔改一下源码,先定义一个全局变量win_traceData和win_traceDataStr

首先修改一下’onMouseMove’: function(_0x7e6f33)里面的逻辑

然后呢,在’onDragEnd’: function(_0x115c47)的时候输出一下结果,验证一下我们之前的推测

可以看到,这里的值是没有做二次处理,那这个函数就是

跳转到function _0x1767c8(_0x3261e8, _0x286191)这里面,到了这里,我们还要再修改一下源码,方便我们后面算法的调用,这里我们不去扣他的代码,不过觉得自己有能力的可以去扣一下,反正我去我去扣了之后发现,太难了

路径加密函数

我们定义一个全局变量win_0x1767c8,然后把代码修改如下

我们创建一个文件本地进行调用测试,先验证一个坐标的数据

var _0x1eb7c7 = _0x5054ec(_0x4c7923, [m1, m2, diftime] + '')
这一句代码也就可以变为
var _0x1eb7c7 = win_0x1767c8(_0x4c7923, [m1, m2, diftime] + '') 

然后将_0x4c7923,m1,m2和diftime写si一个数据,这里我们使用’onDragEnd’: function(_0x115c47)打印的结果做验证,使用以下的测试数据测试

到了这一步,我们解决了路径加密的计算规则,我们再回到’onDragEnd’: function(_0x115c47)这里,继续搞这个m值

m: _0x4b1c93(_0x37bdc3['sample'](this[_0x487f4d(0x4b4)], _0x3b034c)[_0x487f4d(0xb64)](':')) 

首先把这个sample函数导出来

指向了这个位置,可以看到它是一个函数对象

修改之后就是这样,现在_0x37bdc3已经导出来了,然后我们还有再找一个函数_0x4b1c93

m: _0x4b1c93(_0x37bdc3['sample'](this[_0x487f4d(0x4b4)], _0x3b034c)[_0x487f4d(0xb64)](':')) 


_0x4b1c93,跳转到下面位置,这里我们也有去哪聚去接收他

data的m值加密函数

修改之后就是这样

这里调用测试一下,每次调用的时候它的值都是变动的

data的p值加密函数

原函数是这样的
'p': _0x4b1c93(_0x5054ec(this[_0x487f4d(0x105)][_0x487f4d(0x272)][_0x487f4d(0x7a4)], this['exchangePos']['join'](',')))

简化一下就是下面这样
win_0x4b1c93(win_0x1767c8(token, this['exchangePos']['join'](','))) 

this[‘exchangePos’]这个参数,需要特别说明一下,他是一个数组,里面有2个值,第一个值表示的意思是开始拖动滑块的下标索引值,第二个参数是被交换的滑块索引值,并且,顺序不能颠倒,比如我将0号滑块拖动到7号,那结果就是[0,7],不能是[7,0]

写到这里,我之前写的《对无序的验证码拼图还原》,计算出最后的结果,就可以通过这种方式匹配出来,然后我们录制完所有的路径之后,去选择我们需要的路径方案进行计算就可以实现一条龙服务了。

将上面简化之后的结果进行测试

data的ext值加密函数

// ext值原始函数
'ext': _0x4b1c93(_0x5054ec(this[_0x487f4d(0x105)][_0x487f4d(0x272)][_0x487f4d(0x7a4)], this[_0x487f4d(0x8d6)] + ',' + this[_0x487f4d(0x4b4)][_0x487f4d(0xab4)]))

简化一下就是下面这样
win_0x4b1c93(win_0x1767c8(token, 1 + ',' + pathStr.length)) 

将上面简化之后的结果进行测试

cb参数加密函数

在core.v2.19.1.min_copy.js文件下搜索referer关键字,然后下断点,可以看到这个cb跟最后发送请求的是一样的

这里我们直接修改一下源码,将_0x285d22()也暴露出来

<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/35bbb15740d74cb2a10acf635bd5b83a~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image?) 这里的_0x2acb72’uuid’要说明一下,进到这个函数里面,他会有一个随机数,所以他是随机” style=“margin: auto” />

win_0x285d22() 

callback参数加密函数

最后还剩下一个callback参数

因为callback参数我们从调试开始到现在是一直没有遇到的,我们直接从发送请求的调用堆栈去找找,直接从最后往前找

var _0x541624
var _0x21d7a8
var _0x12fd7e = Math[_0x3daaf1(0x9a7)]()[_0x3daaf1(0x327)](0x24)[_0x3daaf1(0x9d6)](0x2, 0x9)
var _0x421a5c = _0x9795d6[_0x3daaf1(0x290)] || '__JSONP'
var _0x2582fe = _0x9795d6[_0x3daaf1(0x8ef)] || _0x421a5c + ('_' + _0x12fd7e) + ('_' + _0x12f742++)
var _0x46c3ff = _0x9795d6[_0x3daaf1(0x74c)] || _0x3daaf1(0x643)
var _0x5babb5 = _0x9795d6[_0x3daaf1(0x2c7)] || 0x1770
var _0x5c6aa5 = window[_0x3daaf1(0x1d9)]
var _0x4b24f9 = document['getElementsByTagName'](_0x3daaf1(0x58f))[0x0] || document[_0x3daaf1(0x2e2)] 

// 原始代码
var _0x12fd7e = Math[_0x3daaf1(0x9a7)]()[_0x3daaf1(0x327)](0x24)[_0x3daaf1(0x9d6)](0x2, 0x9)
var _0x421a5c = _0x9795d6[_0x3daaf1(0x290)] || '__JSONP'
var _0x2582fe = _0x9795d6[_0x3daaf1(0x8ef)] || _0x421a5c + ('_' + _0x12fd7e) + ('_' + _0x12f742++)

简化后就是这样
var _0x12fd7e = Math.random().toString(0x24).slice(0x2, 0x9)
var _0x421a5c = _0x9795d6['prefix'] || '__JSONP'
// var _0x2582fe = _0x9795d6['name'] || _0x421a5c + ('_' + _0x12fd7e) + ('_' + _0x12f742++)
var _0x2582fe = _0x9795d6['name'] || _0x421a5c + ('_' + _0x12fd7e) + ('_' + 1) 

参数_0x12f742是表示次数,你没发送一次只要没有成功,他就会叠加,所以这里我们直接写成固定值

直接调用测试

验证测试

到这里,我们所有需要的参数都找到了,下一步就是模拟调用进行验证,我已经录制好了所有出现的路径(一共是7+6+5+4+3+2+1 = 28条),当然你愿意的话也可以去录制7*7的路径条数,目前先手动填写需要交换的顺序,自动填写的话,需要参考我之前发的文章《对无序的验证码拼图还原》,将这个项目做成一个服务其调用,识别计算拼图结果。

进到调用堆栈,看他最后的请求

 let _0x5f84aa = "https://c.dun.163.com/api/v3/check"_0x5f84aa += (~_0x5f84aa.indexOf('?') ? '&' : '?') + _0x31e743(senObj) + '&' + 'callback' + '=' + callback_0x5f84aa = _0x5f84aa['replace']('?&', '?')var _0x4b24f9 = document['getElementsByTagName']('script')[0x0] || document['head'];_0x5f84aa['replace']('?&', '?')let _0x541624 = document.createElement('script')_0x541624['type'] = 'text/javascript'_0x541624['src'] = _0x5f84aa_0x4b24f9['parentNode']['insertBefore'](_0x541624, _0x4b24f9)_0x30c635; 

这里需要注意一个,发送的get请求如果你只模拟这一段的话,最后会因为请求链接有空格,他会给你报链接检测错误的信息, 我们直接按照他的处理方式去处理

_0x5f84aa += (~_0x5f84aa.indexOf('?') ? '&' : '?') + _0x31e743(senObj) + '&' + 'callback' + '=' + callback 

_0x31e743(senObj)这个方法他会帮你处理好请求链接的空格问题。这里不再赘述,详细的可以去看具体细节。这里直接贴上完整模拟代码

// 前端模拟提交,这里也可以使用py去提交,我是不想去折腾了,py提交的时候记得带上cookies
window[callback] = function (_0x203a88) {console.log('okokokok');
}
function _0x31e743(_0x295923) {var _0x50ff1e = []for (var _0x5b5df6 in _0x295923)_0x295923['hasOwnProperty'](_0x5b5df6) && _0x50ff1e['push'](encodeURIComponent(_0x5b5df6) + '=' + encodeURIComponent(_0x295923[_0x5b5df6]));return _0x50ff1e['join']('&');
}
function _0x30c635() {window[_0x2582fe]()
}
let _0x5f84aa = "https://c.dun.163.com/api/v3/check"
_0x5f84aa += (~_0x5f84aa.indexOf('?') ? '&' : '?') + _0x31e743(senObj) + '&' + 'callback' + '=' + callback
_0x5f84aa = _0x5f84aa['replace']('?&', '?')
var _0x4b24f9 = document['getElementsByTagName']('script')[0x0] || document['head'];
_0x5f84aa['replace']('?&', '?')
let _0x541624 = document.createElement('script')
_0x541624['type'] = 'text/javascript'
_0x541624['src'] = _0x5f84aa
_0x4b24f9['parentNode']['insertBefore'](_0x541624, _0x4b24f9)
_0x30c635; 

完整代码

最后一步,将我们之前找出来的东西封装成函数来调用,代码如下

function createUrl(obj) {// 已经录制好的路径字典let movPath = {'0,1': [{"_0x3c8f13": 1,"_0x46347e": 0,"diffTime": 77},{"_0x3c8f13": 2,"_0x46347e": 0,"diffTime": 99},{"_0x3c8f13": 3,"_0x46347e": 0,"diffTime": 113},{"_0x3c8f13": 5,"_0x46347e": 0,"diffTime": 120},{"_0x3c8f13": 7,"_0x46347e": 1,"diffTime": 129},{"_0x3c8f13": 10,"_0x46347e": 1,"diffTime": 137},{"_0x3c8f13": 14,"_0x46347e": 1,"diffTime": 145},{"_0x3c8f13": 20,"_0x46347e": 2,"diffTime": 152},.................此处省略,因为实在太长了,6000多行,具体的可以去看最后的项目地址,里面有完整代码]}function randomNum(minNum, maxNum) {switch (arguments.length) {case 1:return parseInt(Math.random() * minNum + 1, 10);break;case 2:return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);break;default:return 0;break;}}let pathKey = obj.pos[0] + ',' + obj.pos[1]let pathList = movPath[pathKey]let pathStr = []for (let i = 0; i < pathList.length; i++) {const item = pathList[i]// 调用路径加密函数// 这里对录制的路径做了随机处理let m1 = item._0x3c8f13+randomNum(1,3)let m2 = item._0x46347e+randomNum(1,3)let dTime = item.diffTime+randomNum(5,10)let str = win_0x1767c8(obj.token, [m1, m2, dTime] + '')pathStr.push(str)}// m值let m = win_0x4b1c93(win_exports['sample'](pathStr, 50)['join'](':'))// p值let p = win_0x4b1c93(win_0x1767c8(obj.token, obj.pos['join'](',')))// ext值let ext = win_0x4b1c93(win_0x1767c8(obj.token, 1 + ',' + pathStr.length))let data = JSON.stringify({ d: "", m: m, p: p, ext: ext })console.log('data=', data);// refererlet referer = location.href.replace(/\?[\s\S]*/, "").substring(0x0, 0x80)// cb值let cb = win_0x285d22()// callback值let _0x12fd7e = Math.random().toString(0x24).slice(0x2, 0x9)let _0x421a5c = '__JSONP'let callback = _0x421a5c + ('_' + _0x12fd7e) + ('_' + 1)let senObj = {referer: referer,zoneId: 'CN31',id: '07e2387ab53a4d6f930b8d9a9be71bdf',token: obj.token,acToken: undefined,data: data,width: '320',type: '9',version: '2.19.1',cb: cb,extraData: '',bf: 0,pos: obj.pos['join'](','),runEnv: 10,sdkVersion: undefined}// 模拟提交,这里可以使用py去提交,我这里是不想去折腾了,记得带上cookieswindow[callback] = function (_0x203a88) {console.log('okokokok');}function _0x31e743(_0x295923) {var _0x50ff1e = []for (var _0x5b5df6 in _0x295923)_0x295923['hasOwnProperty'](_0x5b5df6) && _0x50ff1e['push'](encodeURIComponent(_0x5b5df6) + '=' + encodeURIComponent(_0x295923[_0x5b5df6]));return _0x50ff1e['join']('&');}function _0x30c635() {window[_0x2582fe]()}let _0x5f84aa = "https://c.dun.163.com/api/v3/check"_0x5f84aa += (~_0x5f84aa.indexOf('?') ? '&' : '?') + _0x31e743(senObj) + '&' + 'callback' + '=' + callback_0x5f84aa = _0x5f84aa['replace']('?&', '?')var _0x4b24f9 = document['getElementsByTagName']('script')[0x0] || document['head'];_0x5f84aa['replace']('?&', '?')let _0x541624 = document.createElement('script')_0x541624['type'] = 'text/javascript'_0x541624['src'] = _0x5f84aa_0x4b24f9['parentNode']['insertBefore'](_0x541624, _0x4b24f9)_0x30c635;
} 

调用测试示例: 此处不作py测试,不想折腾了,道理其实是一样的

测试方法

第一步:抓包替换文件。

第二步:找到token,这里我们直接手动找一个,在get中会返回一个token

项目代码地址

项目地址在这里

演示

网络安全成长路线图

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:

# 网络安全学习方法

​ 上面介绍了技术分类和学习路线,这里来谈一下学习方法:
​ ## 视频学习

e=“margin: auto” />

网络安全成长路线图

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:

# 网络安全学习方法

​ 上面介绍了技术分类和学习路线,这里来谈一下学习方法:
​ ## 视频学习

​ 无论你是去B站或者是油管上面都有很多网络安全的相关视频可以学习,当然如果你还不知道选择那套学习,我这里也整理了一套和上述成长路线图挂钩的视频教程,完整版的视频已经上传至CSDN官方,朋友们如果需要可以点击这个链接免费领取。网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!


本文转载自: https://blog.csdn.net/A_991128a/article/details/129015532
版权归原作者 教IT的小王A 所有, 如有侵权,请联系我们删除。

“网易易盾——推理拼图验证码参数逆向分析和调用”的评论:

还没有评论