当前位置: 首页 > news >正文

某航后缀混淆逆向与顶像风控分析

文章目录

  • 1. 写在前面
  • 2. 接口分析
  • 3. 加密分析
  • 4. 风控分析

【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章

作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!

先赞后看、已成习惯~

1. 写在前面

   先之前分析了一下Web端的加密参数跟它的设备指纹风控策略。接下来在再分析一下分析的其他端(APPM)本期我们先继续来看看它在M端的接口加密防护以及风控强度

一般很多厂商它们外采第三方的风控防护产品的话一般不管是什么端大部分都会选择用同一家的!但是它这个明显是没有用一家的,Web用的是某东的然后M端的话用的是顶像

另外它的一个整体防护强度M端是做的比Web要好的很多(至少它的那个行为验证码好像是无法进行绕过的


分析网站

bS5haXJjaGluYS5jb20uY24=

2. 接口分析

它这个端一样不需要登录,直接去查询的接口搜索看看发包情况。请求头里面没有其他明显疑似加密的参数,Cookie的话看起来是有一些风控相关的参数(毕竟走的是非登录的游客浏览模式),然后请求参数有一个后缀参数加密,与Web端不相同的是这个参数不是请求参数的加密(是各种风控的信息加一些固定参数生成的)如下所示:

在这里插入图片描述

在第一次请求这个数据接口的时候,是需要过行为验证码的,接口返回信息如下所示:

{"msg":"成功","level":"REVIEW","risky":true}

意思就是告诉我们触发了人工介入的风控等级需要处理,这并不影响我们先对后缀参数加密先展开分析

在这里插入图片描述

上面我们把它的整个发包请求的参数转为Python代码可以看到,除了FECU这个参数是加密的外,还有它提交的data参数内有一个udidtk这个参数看起来也不像是固定的(会不会是沿用了Web的流程什么接口请求下发的)先不管它!另外就是checkToken这个参数,它是过了顶像的滑块给的,携带后方可正常获取到数据

3. 加密分析

这边跟Web端一样直接关键词参数全局搜索是无法找到有效信息的,还是通过XHR断点及跟栈分析,直接找到一个OB混淆的JS文件。遇到这种采取的关键步骤可以先尝试反混淆一下,AST算是目前处理混淆、函数变量重命名、多层级的level、控制流扁平化...主流有效的也是比较有优势的方法

这里我们也可以先使用工具来大致的反混淆一下,作用不大但不至于没用。可以看到JS代码中明显存在的一些检测项(浏览器环境、自动化还有调试检测)当然也是可以获取到一些跟加密相关的关键信息。比如MD5AES的加密。如下所示:

在这里插入图片描述
在这里插入图片描述

它上图的这个MD5调用了很多次,加密的对象有环境信息、也有一些动态固定的盐或字符。然后做完多次的MD5就会得到很多段字符串然后加了时间戳做了一个拼接得到了一个120多位的长串,然后对这个120+的字符串进行最后的一个AES加密。如下所示:

在这里插入图片描述
在这里插入图片描述

参数最终的长度是194位,根据上面的分析来总结一下它这个参数的计算加密流程,如下所示:

  • 对一个162位长度的字符串()做了MD5(这个长串几乎每天都会随这个JS动态更新)
  • 16位的数字盐进行了MD5(如上动态)
  • 32位的动态字符串进行MD5(动态)
  • 上面串起来以后末尾追加一个一个数字(动态)
  • 13位的一个时间戳
  • 动态混淆JS生成的5位字符串

上面所有通过动态加密生成出来的值拼接成上面调试看到的120多位参与最终AES加密的长字符串,AES加密生成出来的字符串是只有192位的,最终的194位是因为在做完AES以后在字符串的头尾部再次各插入了一个字符得到最终的加密值

这里来说一下最后那个动态JS混淆代码生成的5位字符是怎么来的,生成位置如下所示:

在这里插入图片描述

function a0_0x3ac913() {var a0_0x4ee28f = {_0x55f800: 0x3dd,_0xd300b0: 0x3ce,_0xbb5872: 0x35c,_0x25e711: 0x21e,_0x5835c1: 0x1af,_0x27bcc8: 0x2df,_0x35310c: 0x3dd,_0x389de6: 0x3ab,_0x2c9188: 0x58f,_0x3665e1: 0x328,_0x25f5b9: 0x329,_0x318c38: 0x21e,_0x59f07d: 0x329,_0x56c922: 0x3f7}, _0x76df49 = a0_0x5e6a13, _0x415cf6 = {};_0x415cf6[_0x76df49(a0_0x4ee28f._0x55f800)] = _0x76df49(a0_0x4ee28f._0xd300b0),_0x415cf6[_0x76df49(a0_0x4ee28f._0xbb5872)] = function(_0x2ea17a, _0xde9bd7) {return _0x2ea17a * _0xde9bd7;},_0x415cf6[_0x76df49(a0_0x4ee28f._0x25e711)] = function(_0x2abe69, _0x1f0d5e) {return _0x2abe69 + _0x1f0d5e;},_0x415cf6[_0x76df49(a0_0x4ee28f._0x5835c1)] = _0x76df49(a0_0x4ee28f._0x27bcc8);var _0x484dd3 = _0x415cf6;try {var _0x54fa41 = 0x5, _0x213abd = _0x484dd3[_0x76df49(a0_0x4ee28f._0x35310c)], _0xcf844 = _0x213abd[_0x76df49(a0_0x4ee28f._0x389de6)], _0x8eaac = '';for (var _0x3bac47 = 0x0; _0x8eaac[_0x76df49(a0_0x4ee28f._0x389de6)] < _0x54fa41; _0x3bac47++) {var _0xfcf239 = Math[_0x76df49(a0_0x4ee28f._0x2c9188)](_0x484dd3[_0x76df49(a0_0x4ee28f._0xbb5872)](Math[_0x76df49(a0_0x4ee28f._0x3665e1)](), _0xcf844));if (!_0x213abd[_0x76df49(a0_0x4ee28f._0x25f5b9)]('')[_0xfcf239])continue;_0x8eaac = _0x484dd3[_0x76df49(a0_0x4ee28f._0x318c38)](_0x8eaac, _0x213abd[_0x76df49(a0_0x4ee28f._0x59f07d)]('')[_0xfcf239]);}return _0x8eaac;} catch (_0x4e07db) {return console[_0x76df49(a0_0x4ee28f._0x56c922)](_0x484dd3[_0x76df49(a0_0x4ee28f._0x5835c1)], _0x4e07db),'';}
}

来稍微看一下上面的混淆代码做了什么操作,内部根据上面分析,直接对分析出来的加密流程进行代码还原,a0_0x5e6a13是一个解码的函数,解混淆后可以看到里面包含了62个字符!循环了5次来索引生成5位字符,通过Math.floor(Math.random() * 62) 生成0-61之间的随机数,从62位字符中选取字符,具体代码如下所示:

function a0_0x4ca3(_0x333150, _0x2c8f46) {var _0x343f67 = a0_0x13be();return a0_0x4ca3 = function(_0x4126d9, _0xba068e) {_0x4126d9 = _0x4126d9 - 0x181;var _0x13be7 = _0x343f67[_0x4126d9];if (a0_0x4ca3['JsCqYZ'] === undefined) {var _0x4ca322 = function(_0x3806ad) {var _0x258353 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x35ed5b = '', _0x3b8797 = '', _0x49fd92 = _0x35ed5b + _0x4ca322;for (var _0xd393bf = 0x0, _0x1cec02, _0x4bf15c, _0x5a98b3 = 0x0; _0x4bf15c = _0x3806ad['charAt'](_0x5a98b3++); ~_0x4bf15c && (_0x1cec02 = _0xd393bf % 0x4 ? _0x1cec02 * 0x40 + _0x4bf15c : _0x4bf15c,_0xd393bf++ % 0x4) ? _0x35ed5b += _0x49fd92['charCodeAt'](_0x5a98b3 + 0xa) - 0xa !== 0x0 ? String['fromCharCode'](0xff & _0x1cec02 >> (-0x2 * _0xd393bf & 0x6)) : _0xd393bf : 0x0) {_0x4bf15c = _0x258353['indexOf'](_0x4bf15c);}for (var _0x5f2dc5 = 0x0, _0xd9179c = _0x35ed5b['length']; _0x5f2dc5 < _0xd9179c; _0x5f2dc5++) {_0x3b8797 += '%' + ('00' + _0x35ed5b['charCodeAt'](_0x5f2dc5)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x3b8797);};a0_0x4ca3['dMwUfl'] = _0x4ca322,_0x333150 = arguments,a0_0x4ca3['JsCqYZ'] = !![];}var _0x5877e2 = _0x343f67[0x0], _0x3fe196 = _0x4126d9 + _0x5877e2, _0x5dca38 = _0x333150[_0x3fe196];if (!_0x5dca38) {var _0xb0f2b1 = function(_0x565615) {this['yRUkPV'] = _0x565615,this['aAtqKx'] = [0x1, 0x0, 0x0],this['mmhcrU'] = function() {return 'newState';},this['HZoHkF'] = '\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['BEzgTG'] = '[\x27|\x22].+[\x27|\x22];?\x20*}';};_0xb0f2b1['prototype']['KzabiP'] = function() {var _0xc855bd = new RegExp(this['HZoHkF'] + this['BEzgTG']), _0x3186d8 = _0xc855bd['test'](this['mmhcrU']['toString']()) ? --this['aAtqKx'][0x1] : --this['aAtqKx'][0x0];return this['Zwynsy'](_0x3186d8);},_0xb0f2b1['prototype']['Zwynsy'] = function(_0x448299) {if (!Boolean(~_0x448299))return _0x448299;return this['iTzDTy'](this['yRUkPV']);},_0xb0f2b1['prototype']['iTzDTy'] = function(_0x43ac0b) {for (var _0x445e27 = 0x0, _0x3f4543 = this['aAtqKx']['length']; _0x445e27 < _0x3f4543; _0x445e27++) {this['aAtqKx']['push'](Math['round'](Math['random']())),_0x3f4543 = this['aAtqKx']['length'];}return _0x43ac0b(this['aAtqKx'][0x0]);},new _0xb0f2b1(a0_0x4ca3)['KzabiP'](),_0x13be7 = a0_0x4ca3['dMwUfl'](_0x13be7),_0x333150[_0x3fe196] = _0x13be7;} else_0x13be7 = _0x5dca38;return _0x13be7;},a0_0x4ca3(_0x333150, _0x2c8f46);
}

可以看到上面的解码JS内有一个方法a0_0x13be,里面包含了大量的字符串。这种一般都算是整个混淆体系里面的核心数据,可能会包括(加解密的密钥函数变量名

并通过定义了一个functionreturn _0x649c32重新定义为直接返回字符串数组的函数来隐藏原始函数防止静态分析或者工具直接获取到字符串的内容,如下所示:

function a0_0x13be() {var _0x649c32 = ['zMLSBa', 'ANDLCM0', ..., 'yw4T']; // 包含数百个字符串的数组a0_0x13be = function() {return _0x649c32;};return a0_0x13be();
}

至此,上述分析可以还原出后缀参数的加密。然后接着看一下data参数内的udidtk参数是怎么来的,因为它是动态的不管它校不校验(这种参数最好分析一下JS跟接口层面还原动态获取或生成流程)有的平台一般的就会通过某些动态风控参来埋点

在这里插入图片描述

通过上图查询的发包流程大致是这样:先获取udidtk参数–>第一次请求–>验证码拦截–>过验证码–>第二次请求–>拿到数据

所以udidtk在请求数据接口之前之前携带FUCE加密参数请求udid/c.do这个接口拿到就行。最后我们根据上面分析梳理的流程封装加密算法跟请求示例,来请求验证一下,如下所示:

在这里插入图片描述
在这里插入图片描述

可以看到查询请求失败了,出现了行为验证码的风控,跟我们页面访问发包接口出现了一样的情况。它这个M端不管页面还是接口请求访问都是要过这个顶像验证码的,下面我们来分析一下验证码,对接上去看看是否可以正常请求成功

4. 风控分析

它的这个验证码采用的还是顶像的多套组合,要是想完全解决多套验证的方案,需要花费比较多的时候来逆向验证码协议,目前看是3套(滑块还原图标点选单旋转)现在好多平台都搞了多套这种机制,从防护角度来说确实有一定的效果(对于爬虫方来说会很累,比较好的防护厂商的方案都是更新很频繁的

在这里插入图片描述

这里作者为了验证让文章更加完整,花了点时间就搞一个滑块还原来验证上面的流程!因为这个貌似频率比较高,如果要搞几种的话我都不想写了(喜欢没有强度的工作),过掉滑块验证以后拿到CheckToken参数就可以成功。最终测试一下整个查询航班信息的流程包括过顶像行为验证的结果,如下所示:

在这里插入图片描述

相关文章:

  • 循环流化床锅炉关键技术设计与优化路径
  • 【第四十七周】HippoRAG 2 复现与分析(一):环境部署与代码分析
  • 医疗数理范式化:从范式迁移到认知革命的深度解析
  • 怎样在PyQt5中使用信号与槽机制?
  • 开始使用 Elastic AI Assistant for Observability 和 Amazon Bedrock
  • 六月一日python-AI代码
  • Python UV 环境下的 PyKDL 运动学库安装
  • 定时任务:springboot集成xxl-job-core(一)
  • 14.Wifi模组(ESP8266)
  • 过滤攻击-隐私保护
  • 设计模式-行为型模式-模版方法模式
  • 【课堂笔记】生成对抗网络 Generative Adversarial Network(GAN)
  • 图像处理篇---face_recognition库实现人脸检测
  • Vue3+SpringBoot全栈开发:从零实现增删改查与分页功能
  • 字节golang后端二面
  • 用dayjs解析时间戳,我被提了bug
  • 在IIS上无法使用PUT等请求
  • 基于机器学习的心脏病预测模型构建与可解释性分析
  • 西瓜书第十章——聚类
  • buuctf-web
  • 怎么和网站建设公司签合同/宁波seo网络优化公司
  • 网站存在原理/宁波专业seo服务
  • 网站开发展示/网站生成
  • dedecms做多语言的网站/seo外链推广员
  • 企业网站制作 厦门/合肥网站seo费用
  • 电商网站建设哪个好/最佳磁力链ciliba