js逆向02-某站qv_id和w_rid参数


前言

该文章主要是上一次逆向某站的那个cookie的延续,逆向那个数据包里面除去cookie以外另外的关键参数
目标网站:aHR0cHM6Ly9zZWFyY2guYmlsaWJpbGkuY29tL3VwdXNlcj9rZXl3b3JkPWthZmthJmZyb21fc291cmNlPXdlYnRvcF9zZWFyY2gmc3BtX2lkX2Zyb209MzMzLjEwMDcmc2VhcmNoX3NvdXJjZT0z

声明

本网站仅用于学习,请勿对网站造成任何危害


上一篇文章主要介绍了如何获取那个数据包里面cookie里面的关键参数的生成,本节我们先来分析一下该数据包要能够发包成功还需要提交上面关键参数:

首先观察该包的请求头,可以看到并没有寻常请求头以外的参数:
在这里插入图片描述

数据包请求头里面常见的值

下面我列举出一些常用的headers的值,大家在进行数据包分析的时候可以参考一下:

请求头名称作用示例值
User-Agent标识客户端的类型,包括浏览器、操作系统等信息。Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36
Accept指定客户端可以接受的响应内容类型(如 HTML、JSON、XML 等)。text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language指定客户端希望接收的文档语言。en-US,en;q=0.5
Accept-Encoding指定客户端可以接受的编码方式(如 gzip、deflate 等)。gzip, deflate
Host指定请求的主机名和端口号,用于虚拟主机的解析。www.example.com
Referer表示当前请求是从哪个页面链接过来的,用于统计分析或防止盗链。https://www.example.com/page1.html
Content-Type指定请求体的媒体类型,如表单数据、JSON、XML 等。application/json
application/x-www-form-urlencoded
Content-Length指定请求体的长度(以字节为单位)。123
Connection指定连接的类型,如 keep-alive(保持连接)或 close(关闭连接)。keep-alive
Cache-Control指定请求和响应的缓存机制,如 no-cacheno-storemax-age 等。no-cache
If-Modified-Since用于条件请求,告诉服务器只在资源自上次请求后被修改的情况下才返回响应。Wed, 21 Oct 2015 07:28:00 GMT
Origin在跨域请求中,指定发起请求的源(协议、域名、端口)。https://www.example.com
Cookie存储客户端的会话信息,用于跟踪用户状态。sessionid=12345; username=johndoe

可以看到请求头里面都是常见的值,那么这里请求头就不做过多分析了

然后我们就可以去看看负载里面是否有被加密的值:

分析请求负载里面的参数:

在这里插入图片描述

可以看到负载里面的值很多,不过不要怕,虽然值很多,但是看起来也就两个加密参数,分别是qv_id和w_rid

求qv_id值

然后我们就可以用全局搜索大法先去搜索一下qv_id这个值,如果搜索不到或者下的断点没有断下来再去跟栈

搜索可以发现全局就只有两个地方,直接把他断下来:
在这里插入图片描述
在这里插入图片描述
让他跑起来断下来:
在这里插入图片描述
好吧,可以发现并不是我们觉得可能性较高的地方,不过也是断下来了,可以看到这个qv_id就是由le.value得到的,那么就可以去找一下这个le哪来的

在这里插入图片描述
在这里插入图片描述
可以看到他实际上就是由C.qvId得到的,那么我们接着去跟C这个值的由来
往上跟发现:C = lo()
在这里插入图片描述
但是Io()的打印结果发现明显不是目标加密值(并且跟栈并没有得到比较合适的结果),对于这种情况我一般有两种处理方案

  1. 全局搜索C.qvId,看看他在哪里被赋值(不推荐,因为网站给你字符串加密一下就废了)
  2. 第二种方案,可以在附近看看C = 看看他在哪里给赋值的,再去跟一下那个变量

这里我先采取第一种方案全局搜索一下

在这里插入图片描述
可以发现可以找到,并且还找到了这上面用到的le.value,果断在这里下断点看看是否能够断下来
在这里插入图片描述
断下来后去执行这个函数可以发现qv_id果然就是在这里生成的了,并且看起来是随机的,跟进去把这个函数扣下来
在这里插入图片描述
在这里插入图片描述
在本地运行,缺啥补啥:
在这里插入图片描述
再运行:
在这里插入图片描述
发现缺少了zp,去浏览器里面看看是什么,发现是0-9,并且经过反复确认发现是写死的字符串。
就这样同理把这些缺少的值都给补上:

在这里插入图片描述

在这里插入图片描述
再去补上这个08

在这里插入图片描述
最终可以运行出来结果:
在这里插入图片描述
补好的可以运行的js代码:

function Jv(e) {
    return typeof e == "string"
}
function fr(e, t) {
    var n = typeof e == "number";
    return t ? n : n && !Number.isNaN(e) && Number.isFinite(e)
}
function O8(e, t) {
    var n = Jv(e);
    n && (e = e.split("")),
    Xl(e) || (e = []);
    for (var r = e.slice(), a = -1; ++a < r.length; )
        for (var o = a, i = function() {
            var l = r[a]
              , u = r[o]
              , c = Xl(t)
              , f = !c && (t ? l == u : l === u) || c && t.some(function(d) {
                return l && u && !rg(l[d]) && l[d] === u[d]
            });
            f && r.splice(o--, 1)
        }; ++o < r.length; )
            i();
    return n ? r.join("") : r
}
function Xl(e) {
    return e instanceof Array
}
function T8(e, t, n) {
    var r = Math.random();
    if (!fr(e))
        return r;
    E8(t) && (n = t);
    var a;
    return !fr(t) || t === e ? a = r * e : a = r * (t - e) + e,
    n ? a : Math.floor(a)
}
function E8(e) {
    return typeof e == "boolean"
}
zp = '0123456789'
Zv = 'abcdefghijklmnopqrstuvwxyz'
Xp = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
Xw = '~`!@#$%^&*()-_+=[]{};:"\',<.>/?'
function hee(e, t) {
    Jv(e) && (t = e),
    fr(e) || (e = 12);
    var n = "";
    !t || !Jv(t) ? n = zp + Xp : t === "[*]" ? n = zp + Zv + Xp + Xw : t.match(/0-9|a-z|A-Z|\[s\]/) ? (/0-9/.test(t) && (n += zp),
    /a-z/.test(t) && (n += Zv),
    /A-Z/.test(t) && (n += Xp),
    /\[s\]/.test(t) && (n += Xw),
    n += O8(t.replace(/0-9|a-z|A-Z|\[s\]/g, ""))) : n = t;
    for (var r = "", a = -1; ++a < e; )
        r += n[T8(n.length)];
    return r
}

求w_rid值

求这个值我们直接去全局搜索看看是否能够搜得到:
在这里插入图片描述
发现有很多地方都有这个值,并且wts这个值也在这里
那么我们都在搜索到的值下个断点看看是否能够断下来
在这里插入图片描述
可以看到能够断下来,并且可以看到加密结果是32位长度的,那么我们就可以怀疑是md5加密的,哪一个在线工具测试一下:
在这里插入图片描述
在这里插入图片描述
可以,和在线网站的md5加密结果对得上,秒了,然后看看这个明文是由什么组成的:
在这里插入图片描述
在这里插入图片描述
可以看到c就是由l这个对象以&拼接得到的字符串,然后l就是我们的别的参数的负载罢了
然后我们去追踪一下a的由来
在这里插入图片描述
发现也是由两个字符串拼接的加密结果,而且也是32位的,这里我就怀疑是md5加密的,但是去在线网站测试了一下发现并不是,那么可能就是他自写的算法,我们把他扣下来:
在这里插入图片描述
发现就是一个很简单的自写加密,在本地扣下来就好了
然后我们就需要去跟一下明文的由来:
n:‘7cd084941338484aae1ad9425b84077c’
r:‘4932caff0ff746eab6f01bf08b70ac45’

在这里插入图片描述
KU函数代码:

function kU(e) {
    var i;
    if (e.useAssignKey)
        return {
            imgKey: e.wbiImgKey,
            subKey: e.wbiSubKey
        };
    const t = ((i = OU(SU)) == null ? void 0 : i.split("-")) || []
      , n = t[0]
      , r = t[1]
      , a = n ? iA(n) : e.wbiImgKey
      , o = r ? iA(r) : e.wbiSubKey;
    return {
        imgKey: a,
        subKey: o
    }
}

这段代码的作用是从输入对象 e 中提取或生成两个密钥值 imgKeysubKey,用于后续的加密或验证操作。如果 e.useAssignKeytrue,则直接返回 e.wbiImgKeye.wbiSubKey 作为密钥对;否则,它会尝试从 OU(SU) 获取一个由 - 分隔的字符串数组,如果成功获取到有效数组,则将数组的第一个和第二个值分别经过 iA 函数处理后赋值给 imgKeysubKey,若获取失败或数组无效,则直接使用 e.wbiImgKeye.wbiSubKey 作为默认值。

所以在此我们有两个选择,一个就是用他浏览器里面存在的两个值,不过这两个值可能会过期,另一个选择就是去看看他两个key的生成是哪来的

这里经过我的观察发现这两个值就是这个包之前的一个前置包的返回值,具体可以看看我上一篇的内容,我在这里直接把返回结果列出:
在这里插入图片描述
就是这两个值,到目前为止,所有的明文值都已经找到了对于这个部分我们可以选择把他js代码扣出来使用即可
在这里插入图片描述
在这里对于md5加密算法(也就是xU这个函数)我们可以在js里面调用cryptojs库去实现,也可以直接用python的库去实现。

在这里我列出py代码,读者朋友们可以自行去尝试,有问题的话也可以列在下面:

import requests
import math
import time
import hmac
import hashlib
import binascii
import random
from urllib.parse import quote
import re

def format_string(text):
    """将字符串中的每个字符ASCII码减1"""
    return ''.join(chr(ord(char) - 1) for char in text)


def get_data1():
    """生成请求所需的认证数据"""
    timestamp = int(time.time())
    secret_key = "YhxToH[2q"
    formatted_key = format_string(secret_key)

    # 创建 HMAC-SHA256 签名
    signature = hmac.new(
        formatted_key.encode(),
        f"ts{timestamp}".encode(),
        hashlib.sha256
    )
    hex_signature = binascii.hexlify(signature.digest()).decode()

    return {
        "key_id": "ec02",
        "hexsign": hex_signature,
        "context[ts]": timestamp,
        "csrf": ""
    }


# 判断是否为字符串
def Jv(e):
    return isinstance(e, str)

# 判断是否为有限数字
def fr(e, t=False):
    n = isinstance(e, (int, float))
    if t:
        return n
    return n and not math.isnan(e) and math.isfinite(e)

# 去重函数
def O8(e, t=None):
    n = Jv(e)
    if n:
        e = list(e)
    if not Xl(e):
        e = []
    r = e[:]
    a = -1
    while a < len(r) - 1:
        a += 1
        o = a
        while o < len(r):
            l = r[a]
            u = r[o]
            c = Xl(t)
            f = not c and (l == u if t else l == u) or \
                c and any(l and u and not rg(l[d]) and l[d] == u[d] for d in t)
            if f:
                r.pop(o)
            else:
                o += 1
    return ''.join(r) if n else r

# 判断是否为数组
def Xl(e):
    return isinstance(e, list)

# 判断是否为布尔类型
def E8(e):
    return isinstance(e, bool)

# 随机数生成函数
def T8(e, t=None, n=False):
    r = random.random()
    if not fr(e):
        return r
    if E8(t):
        n = t
    a = r * e if t is None or t == e else r * (t - e) + e
    return a if n else math.floor(a)

# 随机字符串生成函数
def hee(e, t=None):
    if Jv(e):
        t = e
    if not fr(e):
        e = 12
    zp = '0123456789'
    Zv = 'abcdefghijklmnopqrstuvwxyz'
    Xp = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    Xw = '~`!@#$%^&*()-_+=[]{};:"\',<.>/?'
    n = ""
    if not t or not Jv(t):
        n = zp + Xp
    elif t == "[*]":
        n = zp + Zv + Xp + Xw
    elif re.search(r'[0-9a-zA-Z\[s\]]', t):
        if re.search(r'[0-9]', t):
            n += zp
        if re.search(r'[a-z]', t):
            n += Zv
        if re.search(r'[A-Z]', t):
            n += Xp
        if re.search(r'\[s\]', t):
            n += Xw
        n += O8(re.sub(r'[0-9a-zA-Z\[s\]]', '', t))
    else:
        n = t
    r = ''.join(random.choice(n) for _ in range(e))
    return r


def extract_wbi_keys(data):
    """提取wbi密钥"""
    return {
        'imgKey': data['wbiImgKey'],
        'subKey': data['wbiSubKey']
    }


def mix_wbi_keys(text):
    """对密钥进行混淆处理"""
    mix_table = [
        46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35,
        27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13,
        37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4,
        22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52
    ]

    result = [text[index] for index in mix_table if index < len(text)]
    return ''.join(result)[:32]


def get_w_rid(params, verify_data):
    """生成w_rid签名"""
    keys = extract_wbi_keys(verify_data)
    img_key, sub_key = keys['imgKey'], keys['subKey']

    mixed_key = mix_wbi_keys(img_key + sub_key)
    current_time = int(time.time())

    # 合并参数并添加时间戳
    params_with_wts = params.copy()
    params_with_wts['wts'] = current_time

    # 按键排序并构建查询字符串
    sorted_params = sorted(params_with_wts.items())
    query_params = []

    for key, value in sorted_params:
        if value is not None:
            if isinstance(value, str):
                # 移除特殊字符 !'()*
                value = value.replace('!', '').replace('\'', '').replace('(', '').replace(')', '').replace('*', '')
            query_params.append(f"{quote(str(key))}={quote(str(value))}")

    query_string = '&'.join(query_params)

    # 计算MD5签名
    md5_string = query_string + mixed_key
    w_rid = hashlib.md5(md5_string.encode()).hexdigest()

    return {
        'w_rid': w_rid,
        'wts': str(current_time)
    }


def main():
    """主函数,执行B站API请求流程"""
    # 第一步:获取认证数据
    auth_data = get_data1()

    headers = {
        "accept": "*/*",
        "accept-language": "zh-CN,zh;q=0.9",
        "cache-control": "no-cache",
        "origin": "https://search.bilibili.com",
        "referer": "https://search.bilibili.com/upuser?keyword=mac&from_source=webtop_search&spm_id_from=333.1007&search_source=3",
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
    }

    # 第二步:获取票据
    ticket_url = "https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket"
    ticket_response = requests.post(ticket_url, headers=headers, params=auth_data)
    ticket_data = ticket_response.json()

    print("票据响应:", ticket_data)

    # 第三步:设置cookies
    cookies = {
        'b_nut': str(math.floor(time.time())),
        'bili_ticket': ticket_data['data']['ticket'],
        'bili_ticket_expires': str(ticket_data['data']['created_at'] + ticket_data['data']['ttl']),
    }

    # 第四步:提取wbi密钥
    wbi_keys = {
        "wbiImgKey": ticket_data['data']['nav']['img'].split('/')[-1].split('.')[0],
        "wbiSubKey": ticket_data['data']['nav']['sub'].split('/')[-1].split('.')[0]
    }
    print("WBI密钥:", wbi_keys)

    # 第五步:构建搜索请求
    search_params = {
        "category_id": "",
        "search_type": "bili_user",
        "ad_resource": "5646",
        "__refresh__": 'true',
        "_extra": "",
        "context": "",
        "page": 1,
        "page_size": 36,
        "order": "",
        "pubtime_begin_s": 0,
        "pubtime_end_s": 0,
        "duration": "",
        "from_source": "",
        "from_spmid": "333.337",
        "platform": "pc",
        "highlight": 1,
        "single_column": 0,
        "keyword": "王者荣耀",
        "qv_id": hee(32,),
        "source_tag": 3,
        "gaia_vtoken": "",
        "order_sort": 0,
        "user_type": 0,
        "dynamic_offset": 0,
        "web_location": 1430654
    }

    # 第六步:生成w_rid签名
    w_rid_result = get_w_rid(search_params, wbi_keys)
    search_params['w_rid'] = w_rid_result['w_rid']
    search_params['wts'] = w_rid_result['wts']

    # 第七步:发送搜索请求
    search_url = 'https://api.bilibili.com/x/web-interface/wbi/search/type'
    search_response = requests.get(
        search_url,
        params=search_params,
        cookies=cookies,
        headers=headers,
    )

    author_mid = []

    for item in search_response.json()['data']['result']:
        author_mid.append(item['mid'])

    print('该页所有的博主 id 为:',author_mid)

if __name__ == "__main__":
    main()

总结

本文成功还原了关键参数 qv_idw_rid 的生成逻辑,并且给出了python代码实现,不过在这里推荐各位如果想要提高自身水平的话还是去网站里面多试试吧,先把js代码扣下来,用js代码实现加密,然后在py里面可以用execjs去调用js代码去拿到加密后的值去进行请求。在熟练了之后可以转变为python代码。

最后,如果这篇文章有说的不对的地方也希望各位大佬多多指教。本菜也希望能够从各位大佬那里得到更多宝贵的意见和建议,共同进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值