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

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

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

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

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

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环境下执行看看结果。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

Akamai有tls指纹检测,所以需要用curl_cffi这个库来请求。
可以看到这里是可以直接请求拿到数据的,然后观察请求头和请求连接并没有反爬点,这时可以推测cookies存在反爬点。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

当然不可能全部cookie都是反爬点,这里可以用排除法来确定真正的反爬点。
这里试出来确定请求必须携带_abck cookie,不然请求响应码是428
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

既然知道cookie _abck是反爬点,那我们就得知道它从哪里生成的。
看cookie列表这个参数的Secure是勾上的,说明它是服务器响应返回的,这就好找了,用fiddler抓包搜一下就知道。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

打开fiddler然后清空浏览器数据,刷新页面,并搜索一下接口,拿到最新的 _abck,拿到fiddler,快捷键Ctrl F打开搜索框搜索,匹配到的包就会标为黄色,找到第一次出现的包就是了。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

拿到目标链接后,在开发者工具那搜一下,发现这个接口请求了两次,第一次是GET请求,第二次是POST请求,而第二次请求响应结果返回了需要的cookie _abck
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

这两次请求是啥关系呢:第一个GET请求返回的是JS代码,然后这些JS代码会生成得到请求体sensor_data,用来第二次POST请求。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

那这个接口链接又是在哪里得到的呢,我们继续在fiddler搜一下这个接口,发现它第一次出现在网站链接响应内容里
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

_abck

这个cookie其实在首页就有返回,包括后面的请求有些接口也会返回,但是这里它的中间值都是~-1~,而对于Akamai来说,它的中间值需要是~0~才有效。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

定位结果

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

逆向前准备工作

sensor_data生成位置

把JS接口加入xhr断点捕捉,这个能在接口发包时断住,我这里这个接口结尾是BHBSM,大家按自己的来,然后清空浏览器数据,刷新页面。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

看到没,OIY的值就是我们想要的请求体sensor_data,理论上只要逆向出OIY的生成算法就能实现破解了。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

本地替换文件

一开始我就说了Akamai同一份js文件的代码会不定时更新,所以我们需要将首页链接和JS接口GET链接保存到本地并进行替换。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

这样就替换成功了,后续刷新就不会再变更代码或者文件了
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

请求体sensor_data逆向分析

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

上文我们已经找到了请求体sensor_data的生成位置,这里是RmX,我们看下图右边,发现pUXRmX有关联,我们先创建个空白js文件,将它俩的赋值代码扣过去。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

大家看下pUX的赋值:Yx[Sm()[vd(dd)](Vd, Ym)][Fn()[ft(Y9)](cE, sO)](qtX);,看着是很复杂,但是打印一下发现其实就是某些方法的混淆。看下图,这就是Akamai另一个比较恶心的地方了,如果会AST的可以尝试去解混淆,这里就手动解混淆了
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

后面不会在这一块讲解太多,大家自己来…
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

通过上图我们需要找一下qtX的生成位置,直接在代码那搜索qtX = ,发现有五个位置,这里把五个位置全部打上断点并把代码全部扣下来,然后刷新页面,这样后面才能解混淆。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

老样子,先手动解混淆,然后补未定义的参数,如果不知道哪些未定义的可以执行一下脚本,按报错的内容补也行。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

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
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

然后看下q0,这里跟进入拿代码
q0方法本身是一个switch控制流,但是这里它不会执行很多次,只会进入LQ这一步,拿下这一步的代码即可,打上断点,执行到这里。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

q0方法解混淆后
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

搞定后我们打印一下执行结果然后跟浏览器对比一下,没问题。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

第四次赋值

qtX = hDX(qtX, xQX[0]);看下hDX方法,直接把它扣下来解混淆
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

搞定后我们打印一下执行结果然后跟浏览器对比一下,没问题。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

第五次赋值

qtX = \'\'[\'concat\'](CqX, \';\')[\'concat\'](kQX, \';\')[\'concat\'](qtX);看下CqXkQX,往上找就行
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

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

WV:直接复制js文件提供的方法,或者返回当前时间
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向
MmXZOXxBXWV有关,且它们是在qtX多次赋值操作前后
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

再看var CqX = InX(xQX);,直接拿下InX方法解混淆
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

到这里理论上已经拿到了请求体sensor-data的结果了,后面我们需要测试一下能够成功拿到数据。
爬虫逆向学习(十五):Akamai 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

请求测试

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

import reimport execjsfrom bs4 import BeautifulSoupfrom curl_cffi import requestsclass 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 3.0反爬分析与sensor-data算法逆向经验_akamai逆向

结尾

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

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

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

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