【爬虫JS逆向实战】某点数据参数“k”加密逆向

0 阅读8分钟

这是一次非常有意思的JS逆向,从不知所以到拨云见日的感觉真的很爽,或许这就是我们热爱这项技术的理由。-- 笔者


✍ 前言

特别声明,本文所提供的逆向思路及代码仅供学习参考使用,请勿使用 爬虫脚本 对网站进行 高频率 以及 高并发 数据抓取操作,若对网站造成损失的,后果自负!!!

📧 网址

aHR0cHM6Ly93d3cuZGlhbmRpYW4uY29tL25ld3M=

**

📝 正文

步骤一: 加密位置寻找

依旧 网络 查看api请求,这篇文章的主人公闪亮登场

嗯,简单,却耐人寻味,看来 关键字搜索大法 不管用了,堆栈 走起,点开 启动器

直接点第一个,然后点击 翻页 断在这个位置

没结果?往下找 堆栈 即可

这不就找到了,但是该程序存在 异步 调用

别慌,直接点到 异步 栈里面去,打上断点

点击 翻页 触发之后,点入 n 的第二个方法中,如果你要问为什么不点其它的方法,你尽管试试,定叫你大败而归

进入这个方法之后,打上 断点 ,接着之前的断点就可以取消了,避免我们调试麻烦

接着重新点击 翻页 ,会停到我们断住的位置,查看 作用域 ,看 k 值有没有生成

里面只有翻页信息,看来这一步 k 值还未生成,别急,下一步看看

还没有生成,再下一步?思路打开,盆油!既然 k 最终会出现在 params 中,那么是不是有可能在这段函数中会有类似于添加值的代码段,像 params.k 这样的形式,往下找找看,也花不了多少时间

这不就找到了疑似 k 值添加的代码段,都打上断点,以免漏掉

第一个不是,看第二个

柳暗花明又一村,找到了!!!

步骤二: 代码分析

重要的是 o 值的生成,往上看很容易就能找到

参数 我们可以先固定,等下再去逆向,先点进 Object(D.a) 这个方法中去看看

我们先看这个函数的 返回值 是什么

目标明确,这是我们需要分析的,先看参数 A

嗯,奇奇怪怪的字符串,它的 初始位置 如下

好家伙,有够长的,看不懂的听我分析:A 接收的是一个 自执行 函数返回的结果,这段代码中的自执行函数是这样的格式

function(content, n, e) { 主要执行的函数 }(参数1, 参数2, 参数3);

也就是说我们首先需要明确 参数1参数2参数3 这三个参数是什么,于是我把代码折叠,如下

这样就看的比较清晰,传递的三个参数分别为:自执行 函数,Object(v.c)(s, c, l)h ,三个参数以 逗号 分开

其实有意思的一点是,参数 自执行 函数的参数中又传入了一个 自执行 函数,套娃了属于是。

function(content, n, e) { 主要执行的函数 }(function(s, n, path, e) { 主要执行的函数 }(
  function(n, e) {}(e, r),
  参数4,
  参数5,
  参数6
), 参数2, 参数3);

先把断点断在第一个 自执行 函数的代码段中,至少我们先搞清楚,它究竟要传入什么样的值

知道了传递的值大概的 格式 之后,我们就知道第二个 自执行 函数需要生成什么样的的值才算达标,接着再看到第二个 自执行 函数的代码段

嗯,对传递过来的参数作一个拼接处理,那重要的就是传过来的是什么值,依旧 断点 触发

主要不清楚的是 s 和,其它的参数其实都不需要分析,因为 n 是时间戳,pathe 是我们固定的值,于是一样的,不清楚的我们就把断点打上看结果就行

  • 首先看传递的参数

  • n 是翻页信息, e 也是固定值,主要看 r 的值

  • 可以看出,r 就是将 n 以列表的形式返回,知道了主要的代码逻辑之后,我们其实在后续的扣代码环节中将如下代码改写就行了,返回的值一模一样

  • 你可以测试一下,传入 {"business":1,"genre_id":"","sub_genre":"","word":"","page":4,"pagesize":10} ,看生成的结果

好,content 的值逆向分析完毕,现在就是 n

n 的主要生成位置主要是在 第二个参数

参数我们之前说了暂时固定

主要先分析 Object(v.c) 这个函数,打上断点之后,点进去

一步一步来,这个 n.from 我们不知道是什么,可看它生成的值,有种熟悉的感觉

兄弟们,AI 是个好东西

// Node.js 环境下直接运行
const buf = Buffer.from("89b133d52caef302", "utf8");

// 输出和你看到的结构完全一致的对象
const result = {
  type: "Buffer",
  data: Array.from(buf) // 把 Buffer 转成普通数组
};

console.log(result);

做个测试,重新拿一个值生成 Buffer 数组,对比一下

一致!n.from 的问题解决,接下来是 Object(r.createDecipheriv)

其实最终结果是一个 对象

我们暂且看后面的代码,因为对象中有许多方法,逆向起来很麻烦,看后面用到对象中的某个方法就逆向哪个会比较简单,可以看到,会用到对象中的 update 方法和 final 方法

先看 update 方法,进到函数里面,r.from 我们前面已经有现成的,所以不用管

主要是这个 this._updata 方法分析出来先,由于后续操作过于 繁杂 ,再次我只讲一些 扣代码 的注意点

  • 首先 data 的生成我在前面已经介绍过了,直接用封装的代码即可,后续如果遇到类似的代码可直接替换

  • 在补 _update 方法的时候,下图标记的 for 循环需要改掉,不然程序会陷入 无限循环

  • 遇到问题,多问 AI ,一些常规的 Buffer 方法,我都是通过 AI 解决的
  • 这里提供我的 JS代码 结构

  • 生成结果

不过这里生成的值有个 坑点 ,在控制台看上去好像没什么问题,不过在我使用 python 脚本执行这段 JS 代码将这个字符串保存在文件中时,看我发现了什么

后面竟然加了一串乱起八糟的东西,查一查这是什么

那么在后续替换过程中需要注意将这些值删掉,也很简单,观察字符串,生成的总是长度为 10 的字符串,用 python 的切片操作就可以了

如果你做任何操作将这种值代入代码中运行的话,请求结果会一直显示如下图的内容

这也算是一种网站的 反爬 手段吧,逆向的时候要细心。

至此,contentn 值逆向出来了,接下来就是主函数

看了一下,其实也不需要逆向,我直接运行就出了结果

步骤三: 参数获取

接下来就是参数问题了,还记得之前的函数需要传入的参数吗,其实这些参数是需要变化的

这里就有意思了,我们随便拿一个值

然后 全局搜索

我们只要请求这个网址,它返回的信息中就携带了 skl 的信息。

至于如何将这些数据提取出来,那就有各种各样的方法了,本人推荐 正则 ,是真的好用。

这些都搞定之后,还有个生成最终值的 n.from 方法,根据测试,直接将 n 改为 Buffer 即可,效果是一样的

步骤四: 编写代码

一切完毕,编写请求代码

import re
import json
import execjs
import requests

# 获取 n 值
def get_n():
    with open('./4.js', 'r', encoding='utf-8') as f:
        js_code = f.read()
    u = re.findall(r',u:(.*?),activeNav', requests.get('https://www.diandian.com/news').text)[0].replace(',t:b,d:b}', '}').replace('s', '"s"').replace('k', '"k"').replace('l', '"l"')
    u_ = json.loads(u)
    n = execjs.compile(js_code).call('get_n', u_['s'], u_['k'], u_['l'])
    return n

# 获取 k 值
def get_k(page, word):
    if page == 1:
        py = '1110' + word
    elif page > 1:
        py = '110' + str(page) + word
    with open('./A.js', 'r', encoding='utf-8') as f:
        js_code = f.read()
    js_code_1 = js_code.replace('test1223', get_n()[0:10]) # 替换 n 值
    k = execjs.compile(js_code_1).call("get_k", py)
    return k

def get_result(page, word):
    cookies = {} # 替换自己的cookie

    headers = {
        'Accept': 'application/json, text/plain, */*',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Origin': 'https://www.diandian.com',
        'Pragma': 'no-cache',
        'Referer': 'https://www.diandian.com/',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'same-site',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
        'language': 'zh',
        'sec-ch-ua': '"Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
    }

    params = {
        'business': '1',
        'genre_id': '',
        'sub_genre': '',
        'word': word,
        'page': str(page),
        'pagesize': '10',
        'k': get_k(page, word),
    }

    response = requests.get('https://api.diandian.com/pc/app/v1/info/search', params=params, cookies=cookies, headers=headers)
    print(response.json())
    return response.json()

if __name__ == '__main__':
    page = 1 # 页码
    word = '' # 关键词
    get_result(page, word)

请求成功!


更多有趣内容,可关注wx公众号“小恰学逆向”,分享一些爬虫JS逆向技术以及有趣的工具。(●´ω`●)ゞ