吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3404|回复: 14
收起左侧

[Web逆向] 【WEB逆向】某瓣读书搜索接口

[复制链接]
cxsYinGu1427 发表于 2023-1-29 17:54

在论坛潜水了这么久,都没发过贴,发现n年前写过一篇某瓣的逆向,里面的东西挺有意思的,就搬来这里了(网站可能已经更新,懒得去看了 😂)

首先来看一下原始数据,可以看到 window.DATA 里面是一大串意义不明的乱码

直接全局搜索 window.__DATA__ 然后下断点,刷新页面

进入到 Lt.dispatch 函数内部

这里很明显,r 就是 window.__DATA__,即原始数据,e(t)(n) 意思是原始数据经过了若干个函数的处理

我们进入到函数内部,看看具体做了什么,以及怎么用python来实现相同的功能

第一个函数

首先是 (n) 函数

可以看到里面有好几处处理,我们一个一个来复现

  1. a = e.from(r, "base64")

    原始数据r经过e.from函数的处理后,变成了Unit8Array的数据类型,其实就是对原始数据进行 base64 解码
    python主要使用base64标准库实现
a = list(base64.b64decode(data))


从上图可以看到两个的效果都是一样的

  1. su
s = Math.max(Math.floor((a.length - 2 * i) / 3), 0)
u = a.slice(s, s + i)

这两个就不用多说了,s是地板除(后面的0可以忽视),u是切片操作,python实现:

s = floor((len(a) - 2 * i) / 3)
u = a[s : s + i]
  1. a = e.concat([a.slice(0, s), a.slice(s + i)])
    concat是连接的意思,e.concat其实就是对两个数组进行合并操作
a = a[0:s] + a[s + i :]
  1. c = Object(o.hash)(e.concat([u, e.from(t)]))
    这一步进行了较多操作,首先是 e.concat([u, e.from(t)]),几次打断点发现 e.from(t) 都是空数组,所以这里结果只有 u数组
    然后是 Object(o.hash) 很明显这里是一个hash函数来处理 u,进入到内部看一下

    继续进入 h.default 内部

    这里就得凭经验来判断了,如果熟悉 xxhash算法(传送门),下方红框处的五个magic constants应该就会很眼熟,对照五个常数,确认是64位的xxhash算法
    xxhash算法源码

python的话,直接使用第三方的 xxhash库,seed 则是固定的 41405,两者的结果对比:

这里涉及到 byteslist 的相互转换,直接使用对应的内置函数就行
xxh64_hexdigest 只能处理字节

第二个函数

第一个函数处理就到此结束,主要是为了得到 a 和 c 两个参数,下面进入第二个函数(需要经过三次 step in & out):

这里注意到 r = Pt.crypto.decrypt(t[n], n), t[n] 对应上一步的a,n则对应c,所以我们需要进入 decrypt内部看看做了什么

这里有几处很明显的特征,如果熟悉rc4算法的朋友,应该能很快地认出来,下面是rc4的wiki

当然,如果认不出是rc4也没关系,就几十行代码,完全可以照着写实现整个过程,就是重复造轮子比较花时间

我们直接使用 CryptoARC4 模块

def crypto_rc4(text: bytes, sec_key: str):
    cipher = ARC4.new(sec_key.encode())
    cipher_text = cipher.encrypt(text)
    return cipher_text

拿一小段字节,分别进行加密测试,对比结果,成功复现原功能:

第三个函数

第二步一样,3次 step into & out 后,我们进入第三个函数内部,可以看到参数t就是经过rc4加密后的数组

再次进入函数内部

进入最后的 r函数内部

这里我们无法确定用的是哪种解析,直接拿出全部的关键词去google一下吧,出现最多的关键词是 bplist,看一下它的实现过程,是不是和豆瓣的很相似?

bplist的全称是 Binary Property Listplist 属性列表是一种ios系统中用来存储少量数据的文件格式,可以和 json、xml、binary等格式互相转换,这里有介绍

最终将 binary数组 转成 json数据

python的内置标准库已经有plistlib专门来处理plist文件,直接使用 loads 方法来加载 binary 就行

import plistlib
import struct

'''
需要引入 struct 模块,不然会报 undefined error
'''
pb_results = plistlib.loads(rc4_bytes, fmt=plistlib.FMT_BINARY)

最终的解析结果是一个数组:

代码整理

import requests
import base64
import re
import xxhash
import plistlib
import struct

from pprint import pprint
from Crypto.Cipher import ARC4
from math import floor
from plistlib import FMT_BINARY, _BinaryPlistParser, _undefined

def crypto_rc4(raw_data: bytes, sec_key: str):
    cipher = ARC4.new(sec_key.encode())
    rc4_bytes = cipher.encrypt(raw_data)
    return rc4_bytes

def judge_title(a):
    if "onclick" not in a and a != "search_subject" and a != "" and "doubanio" not in a:
        return True
    return False

def main():
    url = f"https://search.douban.com/book/subject_search?search_text=虫师&cat=1001"
    res = requests.get(url)
    data = re.search(r'window.__DATA__ = "(.+?)"', res.text, flags=re.DOTALL).group(1)

    i = 16
    a = base64.b64decode(data)
    s = floor((len(a) - 2 * i) / 3)
    u = a[s : s + i]
    raw_bytes = a[0:s] + a[s + i :]
    sec_key = xxhash.xxh64_hexdigest(u, 41405)
    # print(sec_key)
    # print(raw_text)

    rc4_bytes = crypto_rc4(raw_bytes, sec_key)
    # print(rc4_bytes)
    pb_results = plistlib.loads(rc4_bytes, fmt=FMT_BINARY)
    print("最终结果为:")
    pprint(pb_results)
    results = list(
        filter(
            lambda d: isinstance(d, dict)
            and len(d["k"]) > 10
            and len(d.keys()) == 2,
            pb_results,
        )
    )

    for d in results:
        for a in d["k"]:
            if bool(a) and isinstance(a, int) or isinstance(a, str):
                a = str(a)
                if "img" in a:
                    print(f"封面:{a}")
                elif "book.douban.com" in a:
                    print(f"豆瓣链接:{a}")
                elif a.isdigit():
                    print(f"豆瓣id:{a}")
                elif " / " in a:
                    print(f"出版信息:{a}")
                elif judge_title(a):
                    print(f"书名:{a}")
        print("cxs".center(50, "-"))

if __name__ == "__main__":
    main()

运行结果如下,我做了一下筛选和格式化输出

最后,其实m端的豆瓣接口没有加密,可以直接用 xpath提取数据,不用去逆向这么麻烦 🤣🤣🤣

免费评分

参与人数 7威望 +1 吾爱币 +28 热心值 +6 收起 理由
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
ZhiYing520 + 1 + 1 我很赞同!
李玉风我爱你 + 3 + 1 我很赞同!
lemonrains + 1 + 1 用心讨论,共获提升!
hscxdesign + 1 用心讨论,共获提升!
万事笔通 + 1 + 1 我很赞同!
woyucheng + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

李玉风我爱你 发表于 2023-2-1 21:11
本帖最后由 李玉风我爱你 于 2023-2-1 21:14 编辑

请教下 为何 我在执行时 报错

我看 rc4_bytes和网站的数据是一致的

[Python] 纯文本查看 复制代码
plistlib.loads(rc4_bytes, fmt=plistlib.FMT_BINARY)


[Python] 纯文本查看 复制代码
UnicodeDecodeError: 'ascii' codec can't decode byte 0x86 in position 0: ordinal not in range(128)
cyy5167705 发表于 2024-1-16 16:01
有抠代码的教程吗?  抠的头疼  到c = Object(o.hash)(e.concat([u, e.from(t)]))  抠不动了
正己 发表于 2023-1-29 22:20
你这cdn的图片国内访问不了哦,建议上传论坛
xixicoco 发表于 2023-1-30 01:21
大佬厉害啊,分析的很好
 楼主| cxsYinGu1427 发表于 2023-1-30 09:29
正己 发表于 2023-1-29 22:20
你这cdn的图片国内访问不了哦,建议上传论坛

嗯嗯,之前托管在github,忘记了
塞北的雪 发表于 2023-1-30 11:21
感谢原创教程,经常被这种webpack打包的js搞的头疼,各种混淆跳转加密……
attar 发表于 2023-1-30 11:37
高手,谢谢分享!
weishi9527 发表于 2023-1-30 12:46
感谢用心的分享
万事笔通 发表于 2023-1-30 14:26
讲得比较详细
Sealsclerk 发表于 2023-1-30 19:12
感谢分享,讲得很详细
DeathLYH 发表于 2023-1-31 10:56
谢谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 07:07

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表