python实现:XXX的官网课程刷课实现-JS逆向

最近呢,我敬爱的导师也因为课程学习,忙的头皮发麻。身为三好学生的我,必然是要献上一份绵薄之力。

这里匿名一下的好,因为本文重在分享技术,不针对任何个体或单位。

一、先做一下基本的部署:

分析这个课程进度提交的请求方式、请求参数、参数是否加密、响应信息等···

这里我们分析了表单参数(其中vid参数对应不同的课时,这参数写的一点都不标准。vid的参数后文会说),同时观察一下响应数据:

显然是返回的视频进度信息。

也就是咱们只要仿照相同的参数发送给服务器就能得到响应的返回,要想返回的进度为100,就修改相对应的请求参数即可。此时请求参数中最大的难点就是加密参数的破解。

二、此时调用js栈堆运行代码观察这个pid的加密参数是怎么生成的:

好家伙,进入堆栈就被这个扎眼的cv闪到了。这还犹豫啥,这里面大概率就是加密参数逻辑的书写位置。

进来之后搜索咱们的pid,看看怎么生成的:

找了一圈,找到了这两个,很显然加"BAIJIAYUN_"前缀这个就是咱们要找的。

分析这行代码:

我们发现中间的Math.uuid()方法应该是后面的32位参数,接下来寻找Math.uuid()方法是怎么生成的:

找到啦~~~

三、开始解密

首先,i() 函数生成的是一个随机的十六进制字符串片段 (65536 * (1 + Math.random()) | 0).toString(16).substring(1),这实际上会生成一个 3 到 4 个字符长的十六进制字符串(因为 Math.random() 生成的是 [0, 1) 范围内的浮点数,乘以 65536 后转换为十六进制并去掉第一个字符,结果长度取决于随机数的大小)

然后就是这个强硬拼合为16进制的UUID 标准格式(8-4-4-4-12)

同时用.toLocaleUpperCase()将所有的小写字母转为了大写字母

考虑到后面是直接将_去掉了。所以咱们也就不加下划线了。

直接用python生成Math.uuid()的功能:

四、验证

这就OK了,接下来咱们带入验证函数进行验证,看能否得到响应

哎,我这里直接把一个视频最后一秒提交了,然后提示计时超时,看来还是要一步一步来。因为这边对视频进度的判定方式是,每隔20秒提交一次,并返回进度。看来咱们要获取每个视频的时长,然后每隔20秒的参数发送一个请求,这样应该就可以了。
接下来要讲如何获得每个视频的长度,就要先获得每个视频的不同点在哪

通过对比两个视频表单的参数,得到:myClassCourseVideoId的参数就是每个视频的id

五、寻找课程id

接下来咱们找找这个参数在哪记录,首当其冲的就是html页面元素,看哪里是否记录

惊不惊喜?意不意外?排列整齐、格式严谨。那真是太好了

这里教一下怎么截取页面的html元素,大多数情况下,html页面的代码都在源代码中能够找到

这里我们找一下主页面框架的请求地址,得到响应数据(一般是html格式,也有可能是json格式,还有可能是json+html混合格式)这里咱们先看看

在源代码的页面搜索咱们需要的ID,很遗憾,不在源代码中。

那看来是封装到另一个接口中了,咱们去接口里面搜索

六、获取课程id接口数据

必然就是这个接口无疑了。咱们看看这个接口的请求方式····等等信息

分析这里得到,前面是url地址,后面的参数定睛一看,好像是课程的ID,应该是固定的,咱们直接复制就行

这时候,咱们就不能照搬上面的cookie了,肯定是cookie变了

对比后发现,不是cookie的问题。

通过自习观察后我发现:

上文刚才抓到的接口中给的信息有post请求表单的各项信息,也就是说,这边抓到之后,问题就将得到解决。

怎么现在开始分析这个接口:

get一、课程接口的抓取

OK,数据到手,课程接口很简单,一个get请求,没有加密。

二、接下来进行数据清洗,这里我们要用正则表达式清洗数据

def data_go(data_text):
    # 筛选数据模块
    data = re.findall('"videoRPs":(.*?)]',data_text)
    items = str(''.join(data))
    # 二次筛选抽取对应属性并存储
    obj = re.compile('.*?baiJiaYunVideoSource":"(?P<VID>.*?)",".*?"duration":"(?P<TIMES>.*?)".*?'
                     'myClassCourseVideoId":"(?P<MCCVI>.*?)","myClassCourseId":"(?P<MCCI>.*?)",".*?',re.S)
    item = obj.finditer(items)
    item_list = []
    for i in item:
        item_list.append([i.group('VID'),i.group('MCCI'),i.group('MCCVI'),i.group('TIMES')])
    return item_list

现在还差最后一步,写一个每隔20秒上传一次的循环表单:

def fun():
    items_list = get_to()
    for i in items_list:
        for j in range(0,i[3],20):
            post_to(MCCI=i[1],MCCVI=i[2],VID=i[0],j=j)
            time.sleep(.5)

这里呢,基本所有的东西就写完了

最后整理一下,直接运行。

已经能成功访问到服务器并返回数据,但是计时ID应该有问题,这个还需要继续分析

后续进行补充

更新:

上文中,请求已经成立,但是呢非法参数异常,计时ID不匹配,现在我来更新 并处理一下这个问题。

优化一(问题是非法参数异常,计时ID不匹配,所以咱们开始逐步分析,这里我一步一步回头看,找到了参数不匹配的原因,也就是第二个视频的参数出现在了第一个上面。):

这个是抽取每个课程的CCI、CCVI、times、VID的参数提取规则,此处当时正则表达式的编辑,在顺序上存在异常,故进行修改。
这样进行修改之后,得到的参数信息才是匹配的,之前匹配的内容发生了错位。故而出现参数匹配不正确的数值呢。

更改后出现了这个原因:

这个呢,是因为此时发送的请求已经被服务器记录,但是时间就没有第二次响应,故而超时。

所以呢,我通过观察,发现了这样一个规则:

观察这两个表单,是否发现不同的地方。

对!就是isCalculateclassHourFlag参数。每次刷新视频出现第一个表单就是不含此参数的表单。可见这个表单的提交就是为了开始记录时间的提交。

另外这个第一次的表单也确定了pid的值,也就是pid每次生成的值是随机的。第一次随机值会被服务器记录。因为当时我也纳闷,pid是随机值,服务器该怎么验证呢,原来通过sv去锁定了pid的值,这显然有点掩耳盗铃的意思了,哈哈哈。

现在咱们先进行头部信息的验证。发现结果:

nice,终于得到正常的响应了,接下来,咱们配置好参数,来跑一次,看结果怎么样。

这时候我们看,状态全部正常,返回参数正常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值