猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2

js混淆,动态cookie2

  • 一、前言
  • 二、加密逻辑初探
  • 三、加密逻辑深入分析
  • 四、代码实现
    • 4.1、ast解混淆的一个坑
    • 4.2、完整实现过程
  • 五、参考文献

一、前言

一转眼又有快两个星期没更博客了,主要是没想到第九题真的这么难,硬是让我死磕了快半个月,最终在参考其他大神的解题分享,才完全搞明白为啥自己始终算不对加密参数,还是吃了没文化的亏啊。总体来说这题坑不少,但也让我学到了不少新知识,死磕的过程中多少还有点收获。

二、加密逻辑初探

先看题目要求,这里是要求要找出所有的评论数量,按惯例还是先看看页面请求api的返回结果。
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第1张图片
f12兴冲冲的打开了开发者模式,结果一上来就遇到一道坎,这里的udc.js文件内设置了无限debugger,第一波反扒措施,意味着不可能按以前那种方法随便调试了,肯定要想办法把这里过掉。
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第2张图片
直接调试不行,我决定先用fiddler抓个包,看看具体请求是什么情况。删除已有缓存和cookies后,重新发起请求,在fd里观察一下:

第一次请求的是这个url:

http://match.yuanrenxue.com/match/9

响应头里返回了一个cookie,即sessionid:
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第3张图片
响应体里是混淆后的代码,目测混淆加密逻辑应该是在这里生成
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第4张图片
之后呢,又请求了这个udc.js文件,也就是在我们浏览器调试时,有无限debugger的那段代码:
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第5张图片
在上面两个请求完成之后,重新请求了我们第一次访问的url:

http://match.yuanrenxue.com/match/9

只不过这次请求已经带上了cookie,其中一个是初次请求时返回的sessionid,一个是m这段字符串,很显然m应该是我们要找的关键值
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第6张图片
再完成了上面这一系列请求操作以后,最终才请求了数据接口页,返回了包含有评论数量的json数据。
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第7张图片
总的来看,这题的加密逻辑就放在初次请求页面的响应体里,且分为了两部分,一部分混淆代码是在udc.js文件内,一部分是直接放在源码里,通过这两部分代码完成cookiem字段的生成,最后通过location.reload()实现页面的重载。但由于第二次请求时请求头里已经携带了cookie,所以这次得到了该页面的真实响应结果。
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第8张图片

三、加密逻辑深入分析

由于网站设置了无限debugger来组织调试,所以这里我尝试在使用猿人学ob混淆专解工具解混淆后,使用reres插件来用本地js替换网站请求中的udc.js文件。

一万多行的混淆代码在解混淆后,被精简到了两千多行,不得不说学习下ast还是很有必要的。
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第9张图片在注释掉了包含debugger的这段代码之后,打开reres,开启开发者模式,重新请求网页,这次果然没有再被udc里的第一个无限debugger阻碍。

结果发现后面那段直接写在源代码里的混淆代码里也有无限debugger设置,而且是用constructor的形式生成,过掉这段debugger的方式是在运行到这段代码之前在浏览器console控制台输入下面这行代码,将构造函数置空,这样debugger设置就失效了:

Function.prototype.constructor=function(){}

在这里插入图片描述
到此总算解决了不能顺利调试的问题,于是开始搜索加密关键词,在下面这端代码里找到了包含documentdecrypt等关键词,看着很可疑,于是在这里打上断点试试。
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第10张图片
这里需要注意的是,在请求初始页面时,除了udc.js的内容不变以外,每次服务器返回的第二段混淆代码都是有略微区别的,也就是数字2标示的这部分代码。
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第11张图片

不过代码的含义是不变的,可以通过document关键字来定位,实际上在这里我们已经可以看到document这里就是cookie最终赋值的地方

猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第12张图片

经过分析,这行代码的未经混淆的实际内容如下(每次加载具体变量名可能不一样),核心是三段字符串相加,而res就是加密的关键,在上面的for循环内明显可以看出,res是由decrypt函数对一段时间戳处理后得到的结果。

document["cookie"] = "m=" + (_0x326619 - 1)["toString"]() + res ;

顺着调用栈,找一下decrypt函数的位置,发现原来这个函数就在udc.js内,现在整个加密逻辑就更清晰了。两部分混淆代码中,前面这段负责具体加密实现,后面那段就负责给cookie赋值。
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第13张图片
最后总结一下,这道题的整个加密分为两部分,udc里有一部分加密逻辑,这部分负责对时间戳进行加密;后面直接写在源码里的混淆代码里也有一部分逻辑,主要是控制加密函数循环次数以及为cookie赋值。

四、代码实现

4.1、ast解混淆的一个坑

在将两部分混淆代码都用猿人学ob解混淆专解工具处理以后,就是具体的补环境,在本地调试代码的环节了,这个过程中有几个在常规调试时需要注意的地方:

一是window、navigator、document等全局变量要事先定义好,并且删除代码里本身就有的多余的相关定义;

二是有些代码是只在nodejs环境下才会生效的,比如global全局变量在浏览器端是没有的,但是网站会把这作为反爬虫手段,利用条件判断或者异常捕捉等手段,让它在本地调试时生效,从而把全局变量赋值为空等操作,达到影响代码运行结果的目的。

另一个非常规问题是我苦想了一星期,硬是没找到原因的问题。按道理,在将两部分代码解混淆,并合并以后就可以顺利生成m的结果,但实际上使用同样的时间戳作为参数,浏览器的结果跟我们自己生成的却完全不一样。

比如某一次请求中,时间戳字符串为"1624783099",循环次数为4
猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2_第14张图片其最终加密后的m字符串结果是:

m=4VyTSsUbyttmHH4Sib0MO0RtkCD2kXPeSKtTwT4kmaS33LhCGr5PKNaQJSQydLjAUenSQ5k8r%2FSAmz9QNXiznmpydmN4g8xVtqW8S3EuiOdZ4GhDYbkTrnJyPOrXyfaNbbjmEIT%2FfakD%2FvFJ7tGhL%2BD8NgZ%2BqMrBz26tg3Xe4YrC5kUAaKFqRsh1gKMkjXKpkldoJ8Q%2B%2B8RQgpiYLEv6%2FkN3Xl%2FSNJQFNPSFuqAMv9pxqSDwFd8JBN3PVQnWJp3wlF3Zi63Bf8jKoWDD06KKGagAIkL4Aq7QZ43dy8mQkcGPxIlqdSRpQs93rg8STvs%2BrVDDHGLJiMmxv7cqIomH3Lw%3D%3Dr

同样的参数,在本地nodejs环境下运行的结果确是:

m=4X9WyY0y65NV6BJ7CQjPUbCX9jZLXznIXxxXXiKJUYqeMktiAC3yi4jaJtGQt%2BKcruwMWbHlFRThejUY3YHtulS6I9SeT8abXlVx6Pwy9QO6n6HqJcTvDWOuKdQFrLESKq6sDPxabCl430U6cZmCr0noV9eSePtypi13J1Itcm48FYqLoQs9VFKCS2EBkXqycNF3tYvkDqdSJpm3zs52EldSyY8t426P86ClL7AYzn%2F3FQJnBiRfjgA8AfiVheMpi9gZrfE0umDeaTF%2Bo0icgg5OpkoPpcqUmAuN%2BgYP24JGq%2BTLOfUPlwiro%2FvqBaYgK76SCv9tT%2BzHDaZBOD%2BMPxQ%3D%3Dr

对于这个问题,期初我并没觉得是我这里加密函数有问题,因为我在浏览器端用reres替换udc.js进行调试时,和我在本地运行时测试的加密结果是一样的。直到我了解到原来通过ast解混淆的代码跟原始的混淆代码其语义并不总是一样的,且对代码进行格式化有时候也会对源代码造成修改,而我在浏览器端调试时使用的也是解混淆后的代码,这样本地和线上运行得到的其实都是错误的结果,所以与正常加密结果不一致。

这里之所以本地加密结果跟实际结果不一样,就是因为解混淆的过程中替换了一个关键变量。

udc.js中有这样一段代码,原始混淆后的内容是这样的:

if (_0x26b7fb[_0x56ae('0x754', '\x4c\x53\x44\x79')](null, _0x28fced)) {
        _0x28fced = [];
        var _0x234805 = void (_0x198bd8 = 0x0);
        var _0xde5242 = new Uint32Array(0x100);
        if (window['\x63\x72\x79\x70\x74\x6f'] && window[_0x56ae('0x755', '\x55\x56\x44\x74')][_0x56ae('0x756', '\x28\x65\x6c\x28')]) {} else {
            global = new Array();
            window = new Array();
        }
    }

经过解混淆后的结果是这样的

  if (null == _0x28fced) {
    _0x28fced = [];
    var _0x234805 = undefined;

    var _0xde5242 = new Uint32Array(256);

    if (window["crypto"] && window["crypto"]["getRandomValues"]) {} else {
      global = new Array();
      window = new Array();
    }
  }

这里有一行对变量_0x234805赋值进行修改的操作,把其值变成了undefined,虽然这个变量值本身确实是未定义,但是void函数的内部变量_0x198bd8并不是局部变量,其在后面的其他代码中也被引用了。而在这里将这个变量的赋值语句给删除以后,就导致后面这个变量参与的相关运算中其结果就变了。

解混淆前:

var _0x234805 = void (_0x198bd8 = 0x0);

解混淆后:

var _0x234805 = undefined;

所以这段赋值操作不能被直接替换为undifined,或者即使替换了,也要在上面加上_0x198bd8 = 0x0这个语句。这个大坑确实如果不懂ast,真是想破头都难发现竟然会是因为解混淆导致函数出bug,所以还是非常有必要了解下相关知识的。

4.2、完整实现过程

js代码部分:在这里

python代码部分:

# -*- coding: utf-8 -*-

import requests
import subprocess
import re

HEADERS = {
    'Connection': 'keep-alive',
    'Cookie': '',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'User-Agent': 'yuanrenxue.project',
    'X-Requested-With': 'XMLHttpRequest',
    'Referer': 'http://match.yuanrenxue.com/match/9',
    'Accept-Language': 'zh-CN,zh;q=0.9',
}

# extract session id

session = requests.session()
session.headers = HEADERS
response = session.get("http://match.yuanrenxue.com/match/9")  # 响应头里返回session id
session_id = response.headers['Set-Cookie'].split(';')[0]

'''
循环的几种形式
for(var m=0x1;f[$b('\x30\x78\x38\x30','\x72\x40\x31\x59')+'\x69\x75'](m,2);m++){
for(var m=0x1;f[$b('\x30\x78\x31\x30\x35','\x38\x61\x70\x78')+'\x42\x4f'](m,5);m++)
for(var m=0x1;m<=3;m++)
'''
#提取加密循环次数
try:
    iter_num = re.findall("for\(var m=0x1;f\[.*?\]\(m,(.*?)\);m", response.text, re.S)[0]
except:
    iter_num = re.findall("for\(var m=0x1;m<=(.*?);m", response.text, re.S)[0]

#提取时间戳
time_stamp=re.findall("decrypt,'(.*?)'\).*?72",response.text,re.S)[0]

#执行js,获得加密后的m
p = subprocess.Popen(['node', './9_动态cookie2.js', time_stamp,iter_num], stdout=subprocess.PIPE)
m = p.stdout.read().replace('\n', '')
print("time_stamp:",time_stamp,"\nm:",m)

cookie = ';'.join([session_id,m])

#设置cookie
HEADERS['Cookie'] = cookie
session.headers = HEADERS

#按访问逻辑请求数据api
a=session.get('http://match.yuanrenxue.com/match/9')
b=session.get('http://match.yuanrenxue.com/api/loginInfo')
for i in range(1,6):
    page=str(i)
    params = (
        ('page', page),
    )
    res = session.get('http://match.yuanrenxue.com/api/match/9', params=params).json()
    print(res)


运行结果:

time_stamp: 1624800758 
m: m=3WWGFqTU82saWA4%2BCN2RR%2FTMUIe%2BRTejLsy%2BcYH1gc5YBul4IauMtjEbjh2PbOi1UFtfcg1hSt3Hd4cdV0aJjhdfvXFaKwLB%2B6I41MVGveI40UrDlWaBF6M%2BWuCH%2ByJAvHsNCznAve6UaOSjBfRwrLLz3wKox5GfEVGpdqVP4evWkRCuKxJrwrCz3ez%2FvhzDEAmSYgLT5%2FdfQfx7gSSIjXFhrYgdOjuMiXRs7ckxtec1cc2YRE%2FmjQEY%2Brhqwm4QpKWLDJsKEd7hLBIZN7ebIcq7Rp11CttsxuiCSMnruxcqyGArHLyEFjZkTfMM2IRpzEoaJ1zGGKdQK5gxM9W%2Fq%2BQ%3D%3Dr
{'status': '1', 'state': 'success', 'data': [{'value': 7175}, {'value': 6135}, {'value': 1244}, {'value': 4419}, {'value': 8053}, {'value': 3509}, {'value': 1571}, {'value': 1744}, {'value': 6825}, {'value': 8924}]}
{'status': '1', 'state': 'success', 'data': [{'value': 6580}, {'value': 4161}, {'value': 4642}, {'value': 8679}, {'value': 9897}, {'value': 9703}, {'value': 2608}, {'value': 4306}, {'value': 3764}, {'value': 8781}]}
{'status': '1', 'state': 'success', 'data': [{'value': 8616}, {'value': 9281}, {'value': 4560}, {'value': 8019}, {'value': 2863}, {'value': 8817}, {'value': 4347}, {'value': 1654}, {'value': 4294}, {'value': 4708}]}
{'status': '1', 'state': 'success', 'data': [{'value': 897}, {'value': 2104}, {'value': 6344}, {'value': 470}, {'value': 1795}, {'value': 9387}, {'value': 929}, {'value': 3357}, {'value': 2335}, {'value': 5244}]}
{'status': '1', 'state': 'success', 'data': [{'value': 4603}, {'value': 8619}, {'value': 2315}, {'value': 2331}, {'value': 11}, {'value': 9469}, {'value': 3932}, {'value': 7862}, {'value': 570}, {'value': 2547}]}

五、参考文献

1、猿人学-JS逆向-爬虫攻防大赛题目详解合集
2、某网站Web端爬虫攻防大赛题目交流

你可能感兴趣的:(爬虫,Python,python,js,爬虫,javascript)