这是一次非常有意思的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 是时间戳,path 和 e 是我们固定的值,于是一样的,不清楚的我们就把断点打上看结果就行
- 首先看传递的参数
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 的切片操作就可以了
如果你做任何操作将这种值代入代码中运行的话,请求结果会一直显示如下图的内容
这也算是一种网站的 反爬 手段吧,逆向的时候要细心。
至此,content 和 n 值逆向出来了,接下来就是主函数
看了一下,其实也不需要逆向,我直接运行就出了结果
步骤三: 参数获取
接下来就是参数问题了,还记得之前的函数需要传入的参数吗,其实这些参数是需要变化的
这里就有意思了,我们随便拿一个值
然后 全局搜索
我们只要请求这个网址,它返回的信息中就携带了 s ,k ,l 的信息。
至于如何将这些数据提取出来,那就有各种各样的方法了,本人推荐 正则 ,是真的好用。
这些都搞定之后,还有个生成最终值的 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逆向技术以及有趣的工具。(●´ω`●)ゞ