2024-12月akamai_2.0-sensor-data之cookie反爬分析详细教程(上)

目录

一、网址及目标数据

文章原文

1、网站: https://www.dhl.com/cn-zh/home/tracking/tracking-supply-chain.html?submit=1\&tacking-id=1232343

2、目标:爬取这个接口的数据 ++https://www.dhl.com/++ ++utapi?++ ++trackingNumber=1232343&language=zh&requesterCountryCode=CN&source=tt++,即如下图片内容的展示

3、直接模拟请求这个接口,响应状态码403,有反爬,目标让这个接口请求状态码响应返回200,且拿到响应文本内容

二、分析接口反爬点(akamai执行过程)

1、utapi的请求方式与请求链接:get请求,参数为固定值,无反爬加密值

2、请求头:无疑似反爬加密值

3、请求头cookie:有个_abck的cookie可能是反爬参数,包括其它ak_bmsc、bm_sz、bm_sv的cookie

4、_abck的cookie:由WDsB接口链接响应头set-cookie返回

5、_abck的cookie:详细看下WDsB接口 , 发现是POST请求,请求参数有个加密值sensor_data

6、ak_bmsc、bm_sz的cookie,由首页请求链接响应返回

这两个cookie直接get请求首页响应返回的, 同时它还返回了个_abck的cookie,但和我们目标utapi接口的cookie并不是同一个

python 复制代码
from curl_cffi import requests

url = "https://www.dhl.com/cn-zh/home/tracking/tracking-supply-chain.html?submit=1&tacking-id=1232343"

response = requests.get(url, impersonate="chrome124", timeout=10)
res_cookies = dict(response.cookies)
print(response, res_cookies)

7、bm_sv的cookie,由下面这个json链接返回

我们测试下如下代码,发现这3个ak_bmsc、bm_sz、bm_sv的cookie值我们都拿到了

python 复制代码
from curl_cffi import requests


url = "https://www.dhl.com/cn-zh/home/tracking/tracking-supply-chain.html?submit=1&tacking-id=1232343"
response = requests.get(url, impersonate="chrome124", timeout=10)
res_cookies = dict(response.cookies)
print(response, res_cookies)

url = "https://www.dhl.com/global/dhl/news-alerts.gnf.json"
response = requests.get(url, impersonate="chrome124", cookies=res_cookies, timeout=10)
res_cookies = dict(response.cookies)
print(response, res_cookies)

8、虽然前面两个接口请求也拿到了_abck的cookie,但是带入cookie请求utapi接口请求,并不能响应状态码200,且在步骤4和步骤5我们分析得到,是由WDsB的接口返回的_abck的cookie才有用

9、同时我们发现WDsB这个接口请求了2次,第一次是GET请求,第二次是POST请求,

10、且第一次get请求的WDsB接口返回的内容是js内容,

11、第二次Post请求的WDsB接口返回的内容是success:true,这个似乎是验证cookie : _abck是否正确的,所以我们接下来的目标也是拿到WDsB响应是success后的cookie: _abck

12、既然WDsB的第一次内容是js内容,我们需要找下它的js链接是哪里来的

13、我们在首次首页请求的响应文本里面找到了WDsB接口js链接的由来

14、从首页请求取js链接的url,我们多次运行发现js链接url是动态变化的

python 复制代码
from curl_cffi import requests
import re


url = "https://www.dhl.com/cn-zh/home/tracking/tracking-supply-chain.html?submit=1&tacking-id=1232343"
response = requests.get(url, impersonate="chrome124", timeout=10)
res_cookies = dict(response.cookies)
print(response, res_cookies)
js_url = 'https://www.dhl.com' + re.search('type="text/javascript"  src="(.*?)">', response.text).group(1)
print(js_url)

15、同时我们在网页上也能发现无法再次找到WDsB的接口链接,而是换成了其它的,也就是说js链接动态变化,同时可以自行观察的js文件内容混淆的也是动态变化的,不变的是算法加密逻辑

16、为了后面方便分析同一份js代码,我们需要保存一份首页的html文件,然后用overrides或者fiddler替换html的方法,保证始终分析的是同一份js代码,方便扣代码算法逻辑 ,这里直接鼠标右击首页接口Override content

17、由于步骤16我们固定了首页html,所以接下来我们的js请求链接也会同时固定,不再动态变化,从步骤5我们知道是post请求的那个WDsB链接能返回我们需要的_abck的cookie,所以要成功模拟那个接口请求,那么就要分析这个接口需要的请求参数sensor_data

18、通过xhr断点定位sensor_data的生成位置,如图我们打上xhr断点,清缓存后,刷新网页,看到了zfT的参数包含sensor_data

三、逆向分析参数sensor_data

1、定位sensor_data的生成位置可以通过xhr断点拦截js链接 , 如图我们定位到了sensor_data的生成即zfT

2、而zfT是由FIT拼接而成 ,这里的FIT就是sensor_data

3、搜索FIT=发现有12个匹配到,可疑位置打上断点清缓存再次刷新网页

4、第一次断住的位置是,FIT=0,这是初始赋值的地方

5、同时FIT最后生成的位置在这里,也就是从初始到这里的中间流程就是FIT的生成流程

python 复制代码
FIT = jr(jr(jr(jr(jr(jr(jr(jr(jr(jr(MxT, wxT), ZfT[AA()[Lc(Dq)].apply(null, [Vt, Br(Uc), x8])]), wxT), O2T[zB]), wxT), O2T[Uc]), wxT), Vg), wxT), FIT);

6、怎么扣FIT生成值,要么倒着扣,要么正着扣,继续调试,会发现第二次断点断在了这里,简单分析了下,M2T是60位数组,然后和其它几个似乎是拼接起来的

四、扣js算法代码加密详细步骤

1、开始扣js代码,我们先逆着扣js代码,如图手动解混淆还原如下,当前我们差FIT的由来

python 复制代码
function get_sensor_data(){
    // var zfT = (EM(typeof BA()[zd(HA)], 'undefined') ? BA()[zd(RO)].apply(null, [tB, lA]) : BA()[zd(mV)](f9T, tg))[S1()[MA(ld)](nh, Q0)](FIT, AA()[Lc(fD)](wp, FA, wB));
    // (true ? '{"sensor_data":"': BA()[zd(mV)](f9T, tg))['concat'](FIT, '"}');
    var zfT = '{"sensor_data":"' ['concat'](FIT, '"}');
    return zfT
}

2、由目录三的步骤5我们知道FIT最后的生成位置,手动解混淆还原如下,其中wxT是分号,现在我们还差O2T、Vg、FIT的由来

python 复制代码
//  FIT = jr(jr(jr(jr(jr(jr(jr(jr(jr(jr(MxT, wxT), ZfT[AA()[Lc(Dq)].apply(null, [Vt, Br(Uc), x8])]), wxT), O2T[zB]), wxT), O2T[Uc]), wxT), Vg), wxT), FIT);
// jr(jr(jr(jr(jr(jr(jr(jr(jr(jr(2, wxT), 0), wxT), O2T[zB]), wxT), O2T[Uc]), wxT), Vg), wxT), FIT)
// "2" + ';' + 0 + ';' + O2T[0] + ';' + O2T[1] + ';' + Vg + ';' + FIT;
python 复制代码
function get_sensor_data(){
    FIT = "2" + ';' + 0 + ';' + O2T[0] + ';' + O2T[1] + ';' + Vg + ';' + FIT;
    var zfT = '{"sensor_data":"' ['concat'](FIT, '"}');
    return zfT
}
get_sensor_data()

3、全局搜索O2T = , 搜索定位到如下var O2T = fTT || g4() ,其中fTT是undefined ,说明O2T = 是g4()函数,进入到g4()函数

4、发现g4()函数如下,手动解混淆换下如下,其中有些常量我们这里写死了固定值,后续可能还需要多次比对验证常量是固定值;同时这里我们发现它取了document.cookie['bm_sz'],所以我们改成传参的形式

python 复制代码
function g4(bm_sz) {
    var gJ = 8888888,
        qp = 7777777; // 写死固定值
    var J5 = [gJ, qp];
    var GQ = bm_sz
    var Aq = Xm['decodeURIComponent'](GQ)["split"]('~');
    if (Aq["length"] >= 4) {
        var kq = Xm["parseInt"](Aq[2], 10);
        var m0 = Xm["parseInt"](Aq[3], 10);
        kq = Xm['isNaN'](kq) ? gJ : kq;
        m0 = Xm["isNaN"](m0) ? qp : m0;
        J5 = [kq, m0];
    }
    var nG;
    return nG = J5, nG;
}

5、验证下g4()函数传入相同的bm_sz,出参是否相同,出参相同,g4()函数无误

6、现在还差Vg和FIT的由来

python 复制代码
function get_sensor_data(bm_sz){
    var O2T = g4(bm_sz);
    FIT = "2" + ';' + 0 + ';' + O2T[0] + ';' + O2T[1] + ';' + Vg + ';' + FIT;
    var zfT = '{"sensor_data":"' ['concat'](FIT, '"}');
    return zfT
}
var bm_sz = '5FED1A3F67960E11A9FA43C5BE5B3468~YAAQnuBb2taNz2qSAQAATkERlBmfh1YIrwFk3EYaAGiyZsj3sgNaTicXx28L7Ys8mj7OSUlSg+qSvsCJLZszM3bkYX6sSVSoaYDdRACRU8OCIZ2oyKI1GZ30I6XfsfbGhfEwGzBQCozr0pJf9mAGqOcvlNdw2M8forrU4ScI/PWYsMh1UrBxrUpQSHvqEQ+bZTKibFCNCuWX0J2ePdW30WZiDvPgCASbHBypI5Yvkb1Bj82h66RHPStFogRHtNFSI8s5tOtzR/p2OpTSWVwWIACO0kKpXpI+Xz7rK2N4ivZxqC0F49DSIkK7djh3q6p3/rHcuxNVeH9l7ReOiX06fq5kHEfvrQPPPQBKANsxeY+4colUWJgbb8JPjxYeAA6EH4ChjkSl1/EPvOtkhqMxl9vTpDCPaBTKsx//EPkte57rnzqT8Zt7BtLDN7Rk47rou/jSO81mY25eHnA=~4337970~427345'
console.log(get_sensor_data())

7、全局搜索Vg = , 搜索定位到如下,手动解混淆还原后,发现v8() - czT在动态变化,这是因为v8()是当前时间戳,czT也是时间戳,它是一个时间差值; Vg值正常大概是'26,0,0,2,10,0' , 这里计算的全是函数执行的时间差值,有几个0的我也直接写死了(但是但是如果后面过不掉的话,我们需要和源代码保持一致的逻辑,把这些时间差的逻辑再加回来)

python 复制代码
// var Vg = BA()[zd(sr)].apply(null, [C2, gV])[S1()[MA(ld)](nh, MsT)](cR(v8(), czT), S1()[MA(v6)](Zq, Gd))[S1()[MA(ld)].apply(null, [nh, MsT])](J2T, S1()[MA(v6)](Zq, Gd))[S1()[MA(ld)](nh, MsT)](YfT, S1()[MA(v6)](Zq, Gd))[S1()[MA(ld)](nh, MsT)](tNT, S1()[MA(v6)](Zq, Gd))[S1()[MA(ld)](nh, MsT)](kXT, S1()[MA(v6)](Zq, Gd))[S1()[MA(ld)](nh, MsT)](U3);
// var Vg = ""["concat"](cR(v8(), czT), ",")["concat"](J2T, ",")["concat"](YfT, ",")["concat"](tNT, ",")["concat"](kXT, ",")["concat"](U3);
//  var Vg = "" ["concat"](v8() - czT, ",")["concat"](0, ",")["concat"](0, ",")["concat"](tNT, ",")["concat"](kXT, ",")["concat"](0);

J2T差值是0

YfT差值是0

U3差值是0

8、现在还差czT、tNT、kXT、FIT的由来

python 复制代码
function v8() {
    if (Xm["Date"]["now"] && typeof Xm["Date"]["now"]() === 'number') {
        return Xm["Date"]["now"]();
    } else {
        return +new(Xm["Date"])();
    }
}

function get_sensor_data(bm_sz){
    var O2T = g4(bm_sz);

    var Vg = "" ["concat"](v8() - czT, ",")["concat"](0, ",")["concat"](0, ",")["concat"](tNT, ",")["concat"](kXT, ",")["concat"](0);
    FIT = "2" + ';' + 0 + ';' + O2T[0] + ';' + O2T[1] + ';' + Vg + ';' + FIT;
    var zfT = '{"sensor_data":"' ['concat'](FIT, '"}');
    return zfT
}
var bm_sz = '5FED1A3F67960E11A9FA43C5BE5B3468~YAAQnuBb2taNz2qSAQAATkERlBmfh1YIrwFk3EYaAGiyZsj3sgNaTicXx28L7Ys8mj7OSUlSg+qSvsCJLZszM3bkYX6sSVSoaYDdRACRU8OCIZ2oyKI1GZ30I6XfsfbGhfEwGzBQCozr0pJf9mAGqOcvlNdw2M8forrU4ScI/PWYsMh1UrBxrUpQSHvqEQ+bZTKibFCNCuWX0J2ePdW30WZiDvPgCASbHBypI5Yvkb1Bj82h66RHPStFogRHtNFSI8s5tOtzR/p2OpTSWVwWIACO0kKpXpI+Xz7rK2N4ivZxqC0F49DSIkK7djh3q6p3/rHcuxNVeH9l7ReOiX06fq5kHEfvrQPPPQBKANsxeY+4colUWJgbb8JPjxYeAA6EH4ChjkSl1/EPvOtkhqMxl9vTpDCPaBTKsx//EPkte57rnzqT8Zt7BtLDN7Rk47rou/jSO81mY25eHnA=~4337970~427345'
console.log(get_sensor_data())

9、全局搜索发现tNT、kXT分别是函数FIT = Sw(AZ, [FIT, O2T[Uc]]);执行的时间差值 FIT = Vk(FIT, O2T[kV[rh]]);的时间差值

python 复制代码
    // 函数时间差
    var tNT = v8();
    FIT = Sw(AZ, [FIT, O2T[1]]);
    tNT = v8() - tNT;
    var kXT = v8();
    FIT = Vk(FIT, O2T[0]);
    kXT = v8() - kXT;

10、网页上调试执行时间差[tNT,kXT]大致是[1, 4]

11、所以[tNT,kXT]大致是[1, 4]这里的值我又暂时写死了

12、同理,czT也是初始的时间戳,然后执行了一大串逻辑函数后到了Vg计算函数运行时间差,多次调试发现这个v8() - czT时间差值差不多是25~30之间

13、所以这里我们也给了个随机值,这样步骤8的czT、tNT、kXT、FIT的由来,只剩FIT的由来不清楚了

14、我们继续研究,进入Vk函数

15、Vk函数手动解混淆还原如下,右侧截图里面有成var zl = (qS >> 8) & 65535;

16、其中 Vk里面还有些变量Kq 、IW需要补充上

python 复制代码
var Kq = new (Xm[S1()[MA(sv)].apply(null, [ht, JG])])(lM);
var Kq = new (Xm['Array'])(127)
var IW = "";

17、传入相同的参数,检验Vk函数输出结果一致,Vk函数无误 ,可以发现Vk函数是将明文加密的一个流程

18、继续进入到Sw函数里面

19、手动解混淆还原如下,传入相同的参数,验证结果输出一致,Sw52无误

20、继续往上研究,手动解混淆如下

python 复制代码
FIT = jr(jr(jr(jr(jr(LzT, lIT), HPT), B2T(E4(FIT), Qt)), HPT), FIT);
FIT = LzT + lIT + HPT + (E4(FIT)^24) + HPT + FIT;

FIT = jr(jr(jr(jr(nmT, HPT), nmT), HPT), FIT);
FIT = "2" + HPT + "2" + HPT + FIT;

21、现在还差LzT 、lIT 、E4、 HPT 、FIT的由来

22、LzT搜索定位如下,这里的扣代码算法逻辑和前面步骤一样,手动解混淆还原验证,如下2张图

23、lIT还原如下,其中这里写死了个值,后面如果换版本需要留意是否一样, 留意变化

24、E4函数如下

25、HPT先定义了个字符串,然后HPT = zg(M2T, 2, false);

26、我们还发现,zg这个函数传入的M2T参数,它是一个60位的数组,包含了一些环境信息,这里我们先写死M2T,后面逐个研究由来

27、手动解混淆zg函数还原如下,传入相同的参数,验证结果输出不一致,因为函数本身里面有很多随机数,所以这个问题不大,后面如果过不了风控,我们再回来对比研究

28、补完上面的内容后,这个时候我们已经能生成sensor_data了,如下

29、其中在扣js代码中,我们写死了一些值,后面如果过不了接口,还得回来反复核对,现在我们还剩M2T这个60位数组的研究, 到目前为止,整个加密算法200多行,重点是60位数组的生成逻辑

30、M2T在这里开始生成

31、我们把上面的代码整理下,得到下面的内容,然后逐步分析M2T的各个数组的值的来源

32、从60位数组里面再次还原下,发现近一半的是固定变量可以直接写死,剩下的需要研究的变量的由来有29个,我们需要一一研究由来,大多数是环境检测ua、屏幕尺寸、页面的input信息、请求页面地址/canvas/wbgl/字体指纹/陀螺仪/事件/鼠标轨迹/函数运行时间差

33、把要研究的变量单独拧出来,发现其它值似乎一直是固定的,有5个特别长的在动态变化,我们先研究这5个看看能不能出结果

相关推荐
数据小爬虫@3 小时前
利用Python爬虫快速获取商品历史价格信息
开发语言·爬虫·python
小白学大数据4 小时前
如何使用Selenium处理JavaScript动态加载的内容?
大数据·javascript·爬虫·selenium·测试工具
qq_375872695 小时前
15爬虫:下载器中间件
爬虫
数据小小爬虫9 小时前
如何利用Python爬虫获取商品历史价格信息
开发语言·爬虫·python
黑色叉腰丶大魔王9 小时前
《基于 Python 的网页爬虫详细教程》
开发语言·爬虫·python
laity179 小时前
爬取小说案例-BeautifulSoup教学篇
爬虫·python
lovelin+v1750304096611 小时前
智能电商:API接口如何驱动自动化与智能化转型
大数据·人工智能·爬虫·python
FBI780980459413 小时前
API接口在电商行业中的创新应用与趋势
运维·网络·人工智能·爬虫·python
数据小爬虫@14 小时前
Python爬虫抓取数据,有哪些常见的问题?
开发语言·爬虫·python
漫无目的行走的月亮15 小时前
基于Python Scrapy的豆瓣Top250电影爬虫程序
爬虫·python·scrapy