爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验

news/2025/1/16 3:33:43 标签: 爬虫, 学习, python, 逆向, Akamai

此分享只用于学习用途,不作商业用途,若有冒犯,请联系处理

Akamai 3.0反爬分析与sensor-data算法逆向经验

  • Akamai
  • 开始正题前须知
  • 站点信息
  • 接口分析反爬点
    • 反爬点定位
    • _abck
    • 定位结果
  • 逆向前准备工作
    • sensor_data生成位置
    • 本地替换文件
  • 请求体sensor_data逆向分析
    • qtx五次赋值分析
      • 第一次赋值
      • 第二次赋值
      • 第三次赋值
      • 第四次赋值
      • 第五次赋值
  • 请求测试
  • 结尾

Akamai_2">Akamai

Akamai是啥就不多说了,这在爬虫圈可是有名的反爬产品。Akamai 3.0以前的产品我没有研究过,不过在扣Akamai 3.0 sensor-data算法时也看了其他博主的分享,感觉逆向流程差不多的,只不过Akamai 3.0现在每个一两天就会变换js文件,然后js文件每隔十分钟又会变换代码(不同站点规则可能不一样)

严重怀疑变换js文件后相当于变更了算法,而同一份js文件的更新则是对参数的重新混淆和打乱大数组长度与取值

开始正题前须知

我是2024年12月份开始研究的,期间一直是拿保存到本地js文件去跟流程扣算法的,然后到2025年1月份破解算法还能拿到成功数据。

这里要着重说一下:大家研究时代码肯定跟我的不一样了,所以不要抱着能一一跟学的想法,这篇博客重点是提供给大家扣算法思路和一些处理经验。

我尽可能写详细,有点难写hhh…

站点信息

  • 网站:https://www.dhl.com/cn-zh/home/tracking/tracking-supply-chain.html?submit=1&tacking-id=1232343
  • 接口:https://www.dhl.com/utapi?trackingNumber=1232343&language=zh&requesterCountryCode=CN&source=tt

接口分析反爬点

反爬点定位

复制utapi接口的发包内容,然后将其转成python requests请求格式,拿到本地python环境下执行看看结果。
在这里插入图片描述

Akamaitls指纹检测,所以需要用curl_cffi这个库来请求。
可以看到这里是可以直接请求拿到数据的,然后观察请求头和请求连接并没有反爬点,这时可以推测cookies存在反爬点。
在这里插入图片描述

当然不可能全部cookie都是反爬点,这里可以用排除法来确定真正的反爬点。
这里试出来确定请求必须携带_abck cookie,不然请求响应码是428
在这里插入图片描述
在这里插入图片描述

既然知道cookie _abck是反爬点,那我们就得知道它从哪里生成的。
看cookie列表这个参数的Secure是勾上的,说明它是服务器响应返回的,这就好找了,用fiddler抓包搜一下就知道。
在这里插入图片描述

打开fiddler然后清空浏览器数据,刷新页面,并搜索一下接口,拿到最新的 _abck,拿到fiddler,快捷键Ctrl F打开搜索框搜索,匹配到的包就会标为黄色,找到第一次出现的包就是了。
在这里插入图片描述
在这里插入图片描述

拿到目标链接后,在开发者工具那搜一下,发现这个接口请求了两次,第一次是GET请求,第二次是POST请求,而第二次请求响应结果返回了需要的cookie _abck
在这里插入图片描述
在这里插入图片描述

这两次请求是啥关系呢:第一个GET请求返回的是JS代码,然后这些JS代码会生成得到请求体sensor_data,用来第二次POST请求。
在这里插入图片描述

那这个接口链接又是在哪里得到的呢,我们继续在fiddler搜一下这个接口,发现它第一次出现在网站链接响应内容里
在这里插入图片描述
在这里插入图片描述

_abck

这个cookie其实在首页就有返回,包括后面的请求有些接口也会返回,但是这里它的中间值都是~-1~,而对于Akamai来说,它的中间值需要是~0~才有效。
在这里插入图片描述

定位结果

  • 请求首页链接拿到JS代码接口
  • 通过接口返回的JS代码生成得到请求体sensor_data
  • 使用POST请求接口,并带上请求体sensor_data,得到需要的cookie _abck
  • _abck中间内容为~0~说明cookie有效,反之cookie无效

逆向前准备工作

sensor_data生成位置

把JS接口加入xhr断点捕捉,这个能在接口发包时断住,我这里这个接口结尾是BHBSM,大家按自己的来,然后清空浏览器数据,刷新页面。
在这里插入图片描述

看到没,OIY的值就是我们想要的请求体sensor_data,理论上只要逆向OIY的生成算法就能实现破解了。
在这里插入图片描述

本地替换文件

一开始我就说了Akamai同一份js文件的代码会不定时更新,所以我们需要将首页链接和JS接口GET链接保存到本地并进行替换。
在这里插入图片描述
在这里插入图片描述

这样就替换成功了,后续刷新就不会再变更代码或者文件了
在这里插入图片描述

请求体sensor_data逆向分析

承接上文,这里我们开始分析请求体sensor_data生成算法,由于JS代码太多太乱,所以我们选择逆向推理,从结果推理过程。
再次强调,本文所演示的代码肯定跟大家实操时不一致,大家重点学历逆向思路

上文我们已经找到了请求体sensor_data的生成位置,这里是RmX,我们看下图右边,发现pUXRmX有关联,我们先创建个空白js文件,将它俩的赋值代码扣过去。
在这里插入图片描述

大家看下pUX的赋值:Yx[Sm()[vd(dd)](Vd, Ym)][Fn()[ft(Y9)](cE, sO)](qtX);,看着是很复杂,但是打印一下发现其实就是某些方法的混淆。看下图,这就是Akamai另一个比较恶心的地方了,如果会AST的可以尝试去解混淆,这里就手动解混淆了
在这里插入图片描述

后面不会在这一块讲解太多,大家自己来…
在这里插入图片描述

通过上图我们需要找一下qtX的生成位置,直接在代码那搜索qtX = ,发现有五个位置,这里把五个位置全部打上断点并把代码全部扣下来,然后刷新页面,这样后面才能解混淆。
在这里插入图片描述

老样子,先手动解混淆,然后补未定义的参数,如果不知道哪些未定义的可以执行一下脚本,按报错的内容补也行。
在这里插入图片描述

qtx五次赋值分析

题外话大家往下看会发现很多参数都是写死的,这里原因有两个:

  1. 参数属于Akamai 3.0核心部分,后续再详细讲解;
  2. 参数属于常量,是在js文件执行时就生成固定了,我们只要知道它怎么取值就行;

言归正传,对于这五个赋值我们这里选择从前往后推。

第一次赋值

var qtX = ‘’;

第二次赋值

qtX = JSON['stringify'](MUX);中的MUX先写死,复制浏览器的值就行,这是Akamai 3.0核心部分大字典,后面再细究

第三次赋值

qtX = qO(45, [qtX, xQX[1]]);
看下xQXvar xQX = h9X || bb(); -> bb();所以咱直接处理bb();就行,把它代码拿下来
bb方法需要用到cookie bm_sz
在这里插入图片描述

然后看下q0,这里跟进入拿代码
q0方法本身是一个switch控制流,但是这里它不会执行很多次,只会进入LQ这一步,拿下这一步的代码即可,打上断点,执行到这里。
在这里插入图片描述

q0方法解混淆后
在这里插入图片描述

搞定后我们打印一下执行结果然后跟浏览器对比一下,没问题。
在这里插入图片描述
在这里插入图片描述

第四次赋值

qtX = hDX(qtX, xQX[0]);看下hDX方法,直接把它扣下来解混淆
在这里插入图片描述

搞定后我们打印一下执行结果然后跟浏览器对比一下,没问题。
在这里插入图片描述
在这里插入图片描述

第五次赋值

qtX = ''['concat'](CqX, ';')['concat'](kQX, ';')['concat'](qtX);看下CqXkQX,往上找就行
在这里插入图片描述

先看看var kQX = ''['concat'](WV() - MmX, ',')['concat'](0, ',')['concat'](0, ',')['concat'](ZOX, ',')['concat'](xBX, ',')['concat'](0);

WV:直接复制js文件提供的方法,或者返回当前时间
在这里插入图片描述
MmXZOXxBXWV有关,且它们是在qtX多次赋值操作前后
在这里插入图片描述

再看var CqX = InX(xQX);,直接拿下InX方法解混淆
在这里插入图片描述

到这里理论上已经拿到了请求体sensor-data的结果了,后面我们需要测试一下能够成功拿到数据。
在这里插入图片描述

请求测试

这是根据Akamai反爬流程开发的python请求代码

python">import re

import execjs
from bs4 import BeautifulSoup
from curl_cffi import requests


class DhlAkamai:
    index_url = 'https://www.dhl.com/cn-zh/home/tracking/tracking-supply-chain.html?submit=1&tacking-id=1232343'
    data_api = 'https://www.dhl.com/utapi?trackingNumber=1232343&language=zh&requesterCountryCode=CN&source=tt'

    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update({
            'accept': '*/*',
            'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
            'content-type': 'text/plain;charset=UTF-8',
            'dnt': '1',
            'origin': 'https://www.dhl.com',
            'priority': 'u=1, i',
            'referer': 'https://www.dhl.com/cn-zh/home/tracking/tracking-supply-chain.html?submit=1&tacking-id=1232343',
            'user-agent': 'user-agent'
        })
        self.session.cookies.set('cookieDisclaimer', 'seen')

    def get_crack_url(self):
        resp = self.session.get(self.index_url)
        de_url = re.search('type="text/javascript" {2}src="(.*?)">', resp.text).group(1)
        return 'https://www.dhl.com' + de_url

    def get_js_data(self, js_url):
        self.session.get(js_url)  # 代码可以不拿,但是要得到返回的cookie

    def post_js_data(self, js_url):
        with open('crack_00.js', 'r', encoding='utf8') as js_file:
            js_text = js_file.read()
            js = execjs.compile(js_text)
            sensor_data = js.call('gen_sensor_data', self.session.cookies.get('bm_sz'))
        print('sensor_data: ', sensor_data)
        resp = self.session.post(js_url, data=sensor_data)
        return resp

    def get_data(self):
        resp = self.session.get(self.data_api)
        return resp

    def str_cookie(self):
        return '; '.join([f'{k}={v}' for k, v in dict(self.session.cookies).items()])

    def run(self):
        js_url = self.get_crack_url()
        print(js_url)
        print('index _abck: ', dict(self.session.cookies)['_abck'])
        self.get_js_data(js_url)
        print('getJs _abck: ', dict(self.session.cookies)['_abck'])
        p_resp = self.post_js_data(js_url)
        print('posJs _abck: ', dict(self.session.cookies)['_abck'])
        print(p_resp.status_code, p_resp.text)
        d_resp = self.get_data()
        print('gData _abck: ', dict(self.session.cookies)['_abck'])
        print(d_resp.status_code, d_resp.text)


if __name__ == '__main__':
    dhl_akamai = DhlAkamai()
    dhl_akamai.run()

crack_00.js文件就是扣出来的js代码文件,只不过需要封装出来一个gen_sensor_data方法。

执行后发现是可以成功拿到数据的。
在这里插入图片描述

结尾

这篇先写到这,主要是介绍了Akamai的反爬内容和破解入口,以及对请求体sensor_data逆向分析,至于核心部分写死那块我们后面再出博客讲解。

其实最后能拿到数据也是网站风控没那么严格,细心的朋友会发现代码写死了三个位置,而那三个位置其实是很重要的,但凡你随便改了一个都无法请求成功。

我研究过一段时间,虽然能扣出它们的生成算法,但是这个算法只能适用当前JS文件提供的代码(或者一部分),已更新JS文件就不适用了,归根结底我觉得是每个JS文件会生成一个大数组,这三个位置的生成都需要用到它,而每个JS文件的大数组长度和每次取值都不一定相同,也就导致无规律性了。

估计是还没研究透,后续有时间再战…


http://www.niftyadmin.cn/n/5824606.html

相关文章

在kubernetes中部署Nacos集群

在kubernetes中部署Nacos集群 1.Nacos介绍1.1 什么是Nacos1.2 主要功能1.3 应用场景 2.部署Nacos集群实践2.1 NFS动态提供Kubernetes后端存储卷2.2 Nacos集群 1.Nacos介绍 1.1 什么是Nacos Nacos(全称为"Dynamic Naming and Configuration Service"&…

环境搭建——Mysql、Redis、Rocket MQ部署

前言 在搭建分布式系统时,MySQL、Redis 和 RocketMQ 是常用的基础服务。每个服务各自的功能不同,但它们在数据存储、缓存、消息队列等方面不可或缺。如果你是初学者,别担心,本文会一步步详细教你如何在服务器上通过 Docker 部署这…

redis acl

redis acl redis 安全访问控制 官网 本文基于redis 6.2.17 版本进行测试验证 使用方式 redis 使用 acl 的配置有2种方式:使用 redis.conf 文件配置,和在 redis.conf 文件中配置 aclfile path 指定外部 aclfile 文件路径 使用 redis.conf 文件配置 …

基于R语言的现代贝叶斯统计学方法(贝叶斯参数估计、贝叶斯回归、贝叶斯计算实践过程

专题一 贝叶斯统计学的思想与概念 1.1 信念函数与概率 1.2 事件划分与贝叶斯法则 1.3 稀少事件的概率估计 1.4 可交换性 1.5 预测模型的构建 专题二 单参数模型 2.1 二项式模型与置信域 2.2 泊松模型与后验分布 2.3 指数族模型与共轭先验 专题三 蒙特卡罗逼近 3.…

Windows 正确配置android adb调试的方法

下载适用于 Windows 的 SDK Platform-Tools https://developer.android.google.cn/tools/releases/platform-tools?hlzh-cn 设置系统变量,路径为platform-tools文件夹的绝对路径 点击Path添加环境变量 %adb%打开终端输入adb shell 这就成功了!

phpstorm jetbrain 配置review code

禁用Unused CSS selector 步骤: 在 PhpStorm 中,点击顶部菜单 File > Settings(Windows/Linux)或 PhpStorm > Preferences(macOS)。在 Inspections 界面左侧的搜索框中输入 CSS。展开 CSS 部分&…

测试模型安全的一些高级手段

1. 模型后门攻击(Model Backdoor Attack) 定义: 在训练过程中或部署阶段引入后门(backdoor),攻击者通过触发特定条件让模型产生不符合预期的行为。 实现方式: 在输入中嵌入特定的触发器&…

MySQL 练习题(1)

一、单表查询 素材: 表名:worker-- 表中字段均为中文,比如 部门号 工资 职工号 参加工作 等 CREATE TABLE worker (部门号 int(11) NOT NULL,职工号 int(11) NOT NULL,工作时间 date NOT NULL,工资 float(8,2) NOT NULL,政治面貌 varchar(1…