js逆向分析---analysis参数分析

原本需要爬取七麦数据,该网站(https://api.qimai.cn/rank/index?analysis=ezB5Qnt3fkVqXnpcOWBfQgV%2FDENqYQ0NcBMfEVZdXktRW1JVSHATAQIJVwQGAVcNBg8BcBMB&brand=free&device=iphone&country=cn&genre=5000)的analysis参数是js加密之后的,需要对原代码进行逆向分析才能解密爬取。

js逆向分析的学习,参考B站关于评论爬取的视频(https://www.bilibili.com/video/BV1Mf4y1s7ds?p=42),七麦网的分析,参考博客https://blog.csdn.net/weixin_43582101/article/details/122456609
这里以music评论为例记录下js逆向分析的流程。

一,接口分析:

1,首先F12 → Network → Fetch/XHR 找到评论对应的Request URL: (https://music.163.com/weapi/comment/resource/comments/get?csrf_token=) (如图,通过Preview找到内容为评论的请求),且请求方式为POST请求:
js逆向分析---analysis参数分析_第1张图片

2,新版谷歌点击Payload(旧版谷歌在Headers下滑到最底部),查看Form Data,得到请求时的参数,可以看到params 和 encSecKey 两个参数都是加密参数,那么本次分析的重点就是分析出这两个参数是怎么加密的,之后解密即可正常请求得到数据。
js逆向分析---analysis参数分析_第2张图片

二,逆向分析
3,点击Initiator,查看调用的栈,点击最上方的栈进入:
js逆向分析---analysis参数分析_第3张图片

4,点击左下方{}或者在上方弹窗选择【Pretty-print】使得代码规范显示,更方便查看,之后看到是一个send()方法进行的该请求,在该方法处打断点:
js逆向分析---analysis参数分析_第4张图片
js逆向分析---analysis参数分析_第5张图片

5,打断点之后刷新网页,重新请求,网页在断点处暂停(Paused in debugger),查看右边的Local处reque下的数据,可以看到url并不是评论对应的url(get?csrf_token=):
js逆向分析---analysis参数分析_第6张图片

6,点击F8或者直接点击蓝色按钮,放开,查看url,如果不是继续点击调试,直到url是评论的链接(当前时间点击两次调试就得到目的url):data对应的无序代码即是加密部分
js逆向分析---analysis参数分析_第7张图片

7,点击右方的Call Stack,点击b7g.blm8e栈进入,美观查看,可以看到是点d7e这个参数,再在右边查看data,是加密状态:
js逆向分析---analysis参数分析_第8张图片

8,再在Call Stack往下找,直到传参的data不再是加密状态:data: “rid=R_SO_4_1325905146&threadId=R_SO_4_1325905146&pageNo=1&pageSize=20&cursor=-1&offset=0&orderType=1”
js逆向分析---analysis参数分析_第9张图片

9,所以回到上一个堆栈,去打断点,调试查看加密方式:

js逆向分析---analysis参数分析_第10张图片

10,在i7b参数处设置断点(同时取消之前的send()方法的断点),刷新网页,查看Y8Q对应的url链接,如果不是评论的链接,则点击F8或者调试按钮继续调试,直到右边Y8Q对应的url链接是评论链接:
js逆向分析---analysis参数分析_第11张图片

11,然后多加几个断点,点击下一步按钮或者F10,一步步往下走,这里过程可能比较漫长,一步步调试,直到看到方法内的bVj7c被重新赋值成encText和encSeckey,在右侧显示为两个加密参数,而data则由bVj7c的两个参数组合而成:
data =( params=encText) & (encSeckey =encSeckey)
js逆向分析---analysis参数分析_第12张图片
js逆向分析---analysis参数分析_第13张图片

12,所以可以判断,加密过程在这两行代码内:

var bVj7c = window.asrsea(JSON.stringify(i7b), bsR1x(["流泪", "强"]), bsR1x(Xp4t.md), bsR1x(["爱心", "女孩", "惊恐", "大笑"]));
e7d.data = j7c.cq8i({
    params: bVj7c.encText,
    encSecKey: bVj7c.encSecKey
})

三,逆向解密
1,请求方式是POST,找到data的真实参数,即i7b:

data = {    #即 i7b
    "csrf_token": "",
    "cursor": "-1",
    "offset": "0",
    "orderType": "1",
    "pageNo": "1",
    "pageSize": "20",
    "rid": "R_SO_4_1325905146",
    "threadId": "R_SO_4_1325905146"
    }

2,处理加密过程:找到window.asrsea()方法,在代码里面直接全局搜索,除window.asrsea(JSON.stringify(i7b), bsR1x([“流泪”, “强”]), bsR1x(Xp4t.md), bsR1x([“爱心”, “女孩”, “惊恐”, “大笑”]));外,仅剩window.asrsea = d,可以判断加密处理过程主要在以下代码段:
js逆向分析---analysis参数分析_第14张图片

3,分析d方法(即window.asrsea()方法)的4个传参:d即为data,需要继续分析;e,f,g,点击console,输入都是定值:

# d = window.asrsea(JSON.stringify(i7b), bsR1x(["流泪", "强"]), bsR1x(Xp4t.md), bsR1x(["爱心", "女孩", "惊恐", "大笑"]));
function d(d, e, f, g) {   # d:JSON.stringify(i7b),i7b即data; 
    var h = {}
      , i = a(16);     # i在后面分析
    return h.encText = b(d, g),
    h.encText = b(h.encText, i),
    h.encSecKey = c(i, e, f),
    h
}
# d:JSON.stringify(i7b),i7b即data; 
# e:控制台输入bsR1x(["流泪", "强"])得到:'010001'
# f:console输入bsR1x(Xp4t.md)得到很长的定值:'00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
# g:console输入bsR1x(["爱心", "女孩", "惊恐", "大笑"])得到:'0CoJUm6Qyw8W8jud'

js逆向分析---analysis参数分析_第15张图片

4,继续分析d方法里面的h 和 i:

"""
function a(a) {   # a=16  ,返回随机的16位字符串
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)    # 循环16次
            e = Math.random() * b.length,    # 随机数,假设位2.234
            e = Math.floor(e),    # 取整 ,2.234取整之后为2
            c += b.charAt(e);     # 取字符串中的xxx位置,字符串中2位置的值为c
        return c
    }
    function b(a, b) {
        var c = CryptoJS.enc.Utf8.parse(b)        # b是密钥
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")    # 这里告诉了偏移量iv的值
          , e = CryptoJS.enc.Utf8.parse(a)        # e是数据
          , f = CryptoJS.AES.encrypt(e, c, {          # c 加密的密钥
            iv: d,                                          # iv 偏移量
            mode: CryptoJS.mode.CBC       # 用CBC模式加密
        });
        return f.toString()       # 加密完之后还要转换成字符串
    }
    function c(a, b, c) {
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    function d(d, e, f, g) {   # d:JSON.stringify(i7b),i7b即data; e:'010001' f:很长的定值
        var h = {}
          , i = a(16);   # 通过a方法的分析,i就是一个16位的随机数
        return h.encText = b(d, g),         # 改写为h.encText = b(d, g),
        h.encText = b(h.encText, i),  # h.encText返回的就是params
        h.encSecKey = c(i, e, f),     # h.encSecKey得到的就是encSecKey     e和f是定制,i是随机的,所以c方法主要根据i来得到值,c的值和i的值对应,假设i取一个定值,c的值是固定的
        h                                  # 改写为 return h
    }
"""

5,在d方法内的h.encText = b(h.encText, i),处设置断点,刷新网页,查看i有值时,对应的encSecKey 和 encText值分别是多少:
js逆向分析---analysis参数分析_第16张图片

6,从而得到i= "rXGtWFn56Kk6oJCU"时,c的定值为:“0028176d4c322cd27302597aadf221ba1f94557f63bdaaf20735c30823e0c4beafcf0e4ee0f56f0779f91aeab9ca31e88863c30e3f6d33319b46836d34188de62e0002f586bc14ee338b8d6a6ecc383c9c2f139dcc2bb15b7e5839992727ff54d0ec534ab6c61238e3ed773d29b8ec1acc3b242764a0d095b7d21158c92c8ebf”,即encSecKey的值。

# 再次加密:数据+g => 第一次加密+i => b=params
h.encText = b(d, g),         # g是密钥
h.encText = b(h.encText, i),  # i也是b方法中的密钥

7,继续编写解密代码:

from Crypto.Cipher import AES
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
data = {    
    "csrf_token": "",
    "cursor": "-1",
    "offset": "0",
    "orderType": "1",
    "pageNo": "1",
    "pageSize": "20",
    "rid": "R_SO_4_1325905146",
    "threadId": "R_SO_4_1325905146"
    }
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
i = 'rXGtWFn56Kk6oJCU'

def get_encSecKey():
    return "0028176d4c322cd27302597aadf221ba1f94557f63bdaaf20735c30823e0c4beafcf0e4ee0f56f0779f91aeab9ca31e88863c30e3f6d33319b46836d34188de62e0002f586bc14ee338b8d6a6ecc383c9c2f139dcc2bb15b7e5839992727ff54d0ec534ab6c61238e3ed773d29b8ec1acc3b242764a0d095b7d21158c92c8ebf"
    
def get_params(data):    #默认这里收到的是字符串
    first = enc_params(data,g)
    secend = enc_params(first,i)
    return second

def enc_params(data,key):
    aes = AES.new(key=key.encode("utf-8"),IV=iv.encode("utf-8"),mode=AES.MODE_CBC)  # 创建加密器
    bs = aes.encrypt(data.encode("utf-8"))   # 加密,加密的内容的长度必须是16的倍数
    return str(b64encode(bs),"utf-8")     # 转换成字符串返回

完整代码:

import json
import requests
from Crypto.Cipher import AES   # pip install pycryptodome
from base64 import b64encode

url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
# 请求方式是POST
data = {    #即 i7b
    "csrf_token": "",
    "cursor": "-1",
    "offset": "0",
    "orderType": "1",
    "pageNo": "1",
    "pageSize": "20",
    "rid": "R_SO_4_1325905146",
    "threadId": "R_SO_4_1325905146"
}
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
e = "010001"
i = "gjsKgpBKWhQGJVYJ"   # encSecKey的值和i的值是对应的,将i的值固定死之后,随之得到encSecKey的值

def get_encSecKey():
    return "34e795a0c95ca5c4513555b806fa5041bd85a9e3a90aaaa2bbf41d4dc4c649cb1baa12bf3d17a0463701341f15477723f3d27a71f9d7ea5deeb69cd7c6d75777f5e1f0b06e7eda8bef996ea92acd172792fc1caab619d369ab7b4b2411b588a10f331d646af12c9ab44a0d3d331c6d8807be66cd5d00948a49c6c437d88687b1"

def get_params(data):       # 默认这里收到的是字符串
    first = enc_params(data,g)
    second = enc_params(first,i)
    return second

def to_16(data):
    pad = 16 - len(data) % 16
    data += chr(pad) * pad
    return data

def enc_params(data,key):   # 加密过程
    iv = "0102030405060708"
    data = to_16(data)
    aes = AES.new(key=key.encode("utf-8"),IV=iv.encode("utf-8"),mode=AES.MODE_CBC)  # 创建加密器
    bs = aes.encrypt(data.encode("utf-8"))   # 加密,加密的内容的长度必须是16的倍数
    return str(b64encode(bs),"utf-8")  # 转换成字符串返回

# 处理加密过程
# d = window.asrsea(JSON.stringify(i7b), bsR1x(["流泪", "强"]), bsR1x(Xp4t.md), bsR1x(["爱心", "女孩", "惊恐", "大笑"]));
"""
function a(a) {
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)
            e = Math.random() * b.length,
            e = Math.floor(e),
            c += b.charAt(e);
        return c
    }
    function b(a, b) {
        var c = CryptoJS.enc.Utf8.parse(b)        # b是密钥
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")    # 这里告诉了偏移量iv的值
          , e = CryptoJS.enc.Utf8.parse(a)        # e是数据
          , f = CryptoJS.AES.encrypt(e, c, {          # c 加密的密钥
            iv: d,                                          # iv 偏移量
            mode: CryptoJS.mode.CBC       # CBC模式
        });
        return f.toString()       # 加密完之后还要转换成字符串
    }
    function c(a, b, c) {
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    function d(d, e, f, g) {   # d:JSON.stringify(i7b),i7b即data; e:'010001' f:很长的定值
        var h = {}
          , i = a(16);
        return h.encText = b(d, g),
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),
        h
    }
"""
resp = requests.post(url,data={
    "params":get_params(json.dumps(data)),
    "encSecKey":get_encSecKey()
})
print(resp.text)
print(resp.status_code)

转载链接:https://juejin.cn/post/7098590553884852237

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