某航后缀混淆逆向与顶像风控分析
文章目录
- 1. 写在前面
- 2. 接口分析
- 3. 加密分析
- 4. 风控分析
【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
先赞后看、已成习惯~
1. 写在前面
先之前分析了一下Web
端的加密参数跟它的设备指纹风控策略。接下来在再分析一下分析的其他端(APP
跟M
)本期我们先继续来看看它在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
代码中明显存在的一些检测项(浏览器环境、自动化还有调试检测
)当然也是可以获取到一些跟加密相关的关键信息。比如MD5
跟AES
的加密。如下所示:
它上图的这个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
,里面包含了大量的字符串。这种一般都算是整个混淆体系里面的核心数据,可能会包括(加解密的密钥
、函数变量名
)
并通过定义了一个function
来return _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
参数就可以成功。最终测试一下整个查询航班信息的流程包括过顶像
行为验证的结果,如下所示:
相关文章:

某航后缀混淆逆向与顶像风控分析
文章目录 1. 写在前面2. 接口分析3. 加密分析4. 风控分析 【🏠作者主页】:吴秋霖 【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致…...

[Protobuf]常见数据类型以及使用注意事项
[Protobuf]常见数据类型以及使用注意事项 水墨不写bug 文章目录 一、基本数据类型1、字段2、字段的修饰规则 二、自定义数据类型1、message类型2、enum类型3、Any类型4、oneof类型5、map类型 三、小工具1.hexdump2.decode 四、注意事项 一、基本数据类型 protobuf 支持多种基础…...
【C/C++】面试基础题目收集
C 软件开发面试中常见的刷题题目通常可分为以下几大类:数据结构与算法、系统编程、面向对象设计、C 语言特性、并发编程等。 🧠 一、数据结构与算法(力扣/牛客经典题) 掌握 STL 和底层结构实现能力: 📌 数…...

模拟实现线程池(线程数目为定值)和定时器
前言 昨天学习关于定时器的相关知识。今天花时间去模拟实现了一个定时器,同时也去模拟实现了一个线程池(线程数目为定值)。我感觉我收获了很多,对于线程的理解加深了。跟大家分享一下~ 线程池和定时器(这个是主要)的实现 代码 线程池 import java.ut…...

数据结构之队列实验
引言 在计算机科学中,进制转换是基础但重要的操作。例如将一个十进制数转换为二进制或八进制表示时,我们通常使用“短除法”——即不断用目标进制去除当前数,记录余数,直到商为0为止。 这种方法得到的是低位先产生的结果&#x…...
Java求职者面试题详解:计算机网络、操作系统、设计模式与数据结构
Java求职者面试题详解:计算机网络、操作系统、设计模式与数据结构 第一轮:基础概念问题 1. 请解释什么是HTTP协议? HTTP(HyperText Transfer Protocol)是一种用于传输超文本的协议,它定义了客户端和服务…...
每日八股文6.1
每日八股-6.1 Go1.Sync.map的底层实现2.结构体的tag如何获取?3.Go实现单例模式(使用sync.Once)4.Go实现单例模式(不使用sync.Once)5.make和new的区别6.Go项目引用包为什么用_以及包的init()函数7.如何判断一个结构体是…...

【Ubuntu】摸鱼技巧之虚拟机环境复制
前言 提示:所有的操作都需要关闭虚拟机 如何快速在其它电脑布置,linux环境,如果我们有一个环境直接拷贝就有时间摸鱼呀。 1.直接复制简单粗暴 不做赘述,如果不会复制,那么请右击鼠标压缩复制 2.克隆虚拟机 2.1 …...

室内VR全景助力房产营销及装修
在当今的地产行业,VR全景已成为不可或缺的应用工具。从地产直播到楼市VR地图,从效果图到水电家装施工记录,整个地产行业的上下游生态中,云VR全景的身影无处不在。本文将探讨VR全景在房产营销及装修领域的应用,并介绍众…...

jenkins集成gitlab实现自动构建
jenkins集成gitlab实现自动构建 前面我们已经部署了Jenkins和gitlab,本文介绍将二者结合使用 项目源码上传至gitee提供公网访问:https://gitee.com/ye-xiao-tian/my-webapp 1、创建一个群组和项目 2、添加ssh密钥 #生成密钥 [rootgitlab ~]# ssh-keyge…...
【C语言练习】070. 编写代码处理C语言中的异常情况
070. 编写代码处理C语言中的异常情况 070. 编写代码处理C语言中的异常情况C语言异常处理的基本思路返回值检查示例errno使用示例setjmp/longjmp示例最佳实践建议1. 使用返回值检查错误2. 使用全局变量记录错误状态3. 使用回调函数或信号处理程序4. 使用`setjmp`和`longjmp`示例…...
Java基本数据类型、抽象类和接口、枚举、时间类、String类全面介绍
JAVA基本数据类型知识总结 基本数据类型(Primitive Types) 类型占用字节默认值范围示例byte10-128 ~ 127byte a 100;short20-32,768 ~ 32,767short b 2000;int40-2 ~ 2-1int c 100000;long80L-2⁶ ~ 2⁶-1long d 10000000000L;float40.0f~7位小数f…...

Spring Boot微服务架构(八):开发之初就引入APM工具监控
使用 APM(Application Performance Management)工具监控 Spring Boot 应用,可以帮助开发者实时追踪性能瓶颈、分析调用链路、监控资源使用情况,并快速定位故障。以下是详细的步骤和常用工具的选择指南: 一、常用 A…...

大规模真实场景 WiFi 感知基准数据集
一段话总结 本文提出CSI-Bench,首个大规模真实场景WiFi感知基准数据集,覆盖26个室内环境、35名用户、16种商用设备,包含461小时有效数据,支持跌倒检测、呼吸监测、定位、运动源识别等单任务及用户身份、活动、 proximity联合标注的多任务学习。通过标准化评估协议和基线模…...

Python实现HPSO-TVAC优化算法优化支持向量机SVC分类模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在当今数据驱动的时代,支持向量机(SVM)作为一种经典的机器学习算法,…...

ck-editor5的研究 (3):初步使用 CKEditor5 的事件系统和API
前言 在上一篇文章中—— ck-editor5的研究(2):对 CKEditor5 进行设计,并封装成一个可用的 vue 组件 ,我已经把 CKEditor5 封装成了一个通用vue组件,并且成功在nuxt中运行,并具备一定的通用性&…...
使用ReactNative加载HarmonyOS Svga动画
这是一款使用ReactNative 加载HarmonyOS Svga动画的播放器插件 三端Svga动画统一使用点击这里 版本:v1.1.5 react-native-ohos-svgaplayer [!TIP] Github 地址 安装与使用 npm npm install react-native-ohos-svgaplayer yarn yarn add react-native-ohos-svgaplayer下面…...

WPS快速排版
论文包括(按顺序):封面(含题目)、摘 要、关键词、Abstract(英文摘要)、Keywords、目录、正文、参考文献、在读期间发表的学术论文及研究成果,致 谢 题目(黑小一加粗&…...

Java实现命令行图书管理系统(附完整源码)
一、项目概述 本文将介绍如何使用Java实现一个基于命令行的图书管理系统。系统支持管理员和普通用户两种角色,提供图书的增删改查、借阅归还等功能。项目采用面向对象设计原则,代码结构清晰,适合Java初学者学习。 二、系统功能架构 graph T…...
使用Docker-NVIDIA-GPU开发配置:解决 Docker NVIDIA 运行时错误方法
问题描述 运行 Docker 命令时,系统提示 docker: Error response from daemon: unknown or invalid runtime name: nvidia,表明 Docker 无法识别 NVIDIA 运行时。这一错误通常出现在使用 --runtime=nvidia 和 --gpus 参数时,意味着 NVIDIA 容器运行时未正确安装或配置。NVID…...
如何更好的理解云计算和云原生?
本文介绍什么是云计算、什么是云原生、怎么理解云相关概念,如有问题,欢迎指正。 一、云计算 定义:云计算是通过互联网(即“云”)按需提供计算资源(如服务器、存储、数据库、网络、软件等)的服…...

【数据结构】顺序表和链表详解(上)
前言:上期我们介绍了算法的复杂度,知道的算法的重要性同时也了解到了评判一个算法的好与坏就去看他的复杂度(主要看时间复杂度),这一期我们就从顺序表和链表开始讲起。 文章目录 一,顺序表1,线性表2,顺序表…...

唯创WT2606B TFT显示灵动方案,重构电子锁人机互动界面,赋能智能门锁全场景交互!
在智能家居的浪潮中,门锁搭载显示屏已成为行业创新的焦点。据行业数据显示,2023年全球智能门锁出货量中,搭载显示屏的型号占比已突破40%,且年复合增长率达25%。而2024年国内智能门锁销量突破2200万套,预计2025年市场规…...
WPF的UI交互基石:数据绑定基础
数据绑定基础 1 Binding的Path属性2 ElementName绑定3 DataContext的作用4 绑定模式(Binding Mode)5 实用技巧集合1. 默认值处理2. 设计时数据3. 绑定验证4. 多级路径监控 6 常见错误排查 数据绑定是WPF的核心特性之一,它实现了界面ÿ…...

智能穿戴新标杆:SD NAND (贴片式SD卡)与 SOC 如何定义 AI 眼镜未来技术路径
目录 一、SD NAND:智能眼镜的“记忆中枢”突破空间限制的存储革命性能与可靠性的双重保障 二、SOC芯片:AI眼镜的“智慧大脑”从性能到能效的全面跃升多模态交互的底层支撑 三、SD NANDSOC:11>2的协同效应数据流水线的高效协同端侧…...
TCP/IP四层模型
TCP/IP四层模型 TCP/IP四层模型将网络通信分为四个层次: 1. 网络接口层:负责计算机与网络硬件间的数据传输,在物理网络上发送/接收数据帧(如以太网、Wi-Fi协议)。 2. 互联网层(网络层)&…...
深入浅出Nacos:微服务架构中的服务发现与配置管理利器
在当今的软件开发领域,随着微服务架构的普及,如何有效地进行服务治理和服务配置管理成为了开发者面临的重要挑战之一。阿里巴巴开源的 Nacos(Dynamic Naming and Configuration Service)应运而生,旨在帮助开发者更轻松地构建云原生应用。本文将详细介绍 Nacos 的核心功能、…...

node_modules包下载不下来
如果项目里面的package-lock.json有resolved ,就指向了包的下载来源,如果这个网址挂了,那npm i 就会一直卡着。而且,在终端去修改 npm的镜像是没有用的 解决办法是:把项目里面的 lock文件 .npmrc都删了 然后重新下载就可以了...

yolo个人深入理解
卷积层的理解,通过云端服务器训练模型,模型构建的重要性,针对极低像素的处理,模型训练召回率提高技巧,卷积层2,4,8,16,32的小模型与大模型的理解 一.关于backbone,neck,head深入理解 1,backbone的主要组成部分是sppf和conv,这是backbone的核心,其中yolov5和yolov8…...
Go语言中的布尔类型详解
布尔类型是Go语言中最基本的数据类型之一,用于表示逻辑值。下面详细介绍Go语言中的布尔类型。 1. 基本概念 Go语言中的布尔类型用关键字bool表示,它只有两个预定义的常量值: true // 真 false // 假 2. 声明布尔变量 var b1 bool …...