Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[新增/更新请求] 更新漫画的ImageToken GetImageIndex ComicDetail接口 #1168

Open
1 task done
btjawa opened this issue Jan 1, 2025 · 30 comments
Open
1 task done
Labels
算法/Algorithm 与API相关的算法及代码示例 漫画/Manga 接口:漫画 更新/Update 更新过时内容

Comments

@btjawa
Copy link

btjawa commented Jan 1, 2025

提交前请确认

  • 我已检索仓库中文档,不包含所提及内容,或所提及内容在本仓库中存在错误,且 Issues、Pull Requests 中无相关提交

API 来源

Web 端(含 h5)

API 类型

REST

API 地址

https://manga.bilibili.com/twirp/comic.v1.Comic/ImageTokenhttps://manga.bilibili.com/twirp/comic.v1.Comic/GetImageIndex
https://manga.bilibili.com/twirp/comic.v1.Comic/ComicDetail

详情描述

这三个接口的URL参数已变为

参数名 类型 内容 必要性 备注
device str pc 必要 pc/web方式需要该参数,否则返回ep_id platform restricted
platform str web 必要 GetImageIndex接口需要该参数,否则返回的url无法给ImageToken接口使用
nov num 25 非必要 ???
ultra_sign str ??? 必要 关键参数

ImageToken接口的正文参数有以下变动:

  • urls 数组内的url变为相对路径
  • 新增了一个 m1 参数

逆向发现 m1 参数可以由下方的算法生成(JS/TS)

async function getM1AndKey() {
    const keyPair = await crypto.subtle.generateKey({
        name: 'ECDH',
        namedCurve: 'P-256'
    }, true, ['deriveKey', 'deriveBits']);
    const raw = await crypto.subtle.exportKey('raw', keyPair.publicKey);
    const jwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
    const jwkString = JSON.stringify(jwk);
    return { m1: btoa(jwkString), key: btoa(String.fromCharCode.apply(null, new Uint8Array(raw) as any)) };
}

实际测试接口发现请求需要的 m1 参数为上方算法返回的 key 而非 m1
算法中返回的 m1 目前不知道需要在哪里使用(见下一条Comment)
以及发现请求时的 m1 写死,urls 不同的情况下也可以正常获得数据

ImageToken的返回数据也有所变动:

{
	"code": 0,
	"msg": "",
	"data": [
		{
			"url": "",
			"token": "",
			"complete_url": "xxx",
			"hit_encrpyt": true
		}
	]
}

最终的完整url直接被放置在了一个新的 complete_url 键内,不再单独存放url与token

@btjawa btjawa changed the title [新增/更新请求] 更新漫画的ImageTokenGetImageIndex接口的请求参数 [新增/更新请求] 更新漫画的ImageToken GetImageIndex ComicDetail接口的请求参数 Jan 2, 2025
@btjawa btjawa changed the title [新增/更新请求] 更新漫画的ImageToken GetImageIndex ComicDetail接口的请求参数 [新增/更新请求] 更新漫画的ImageToken GetImageIndex ComicDetail接口 Jan 2, 2025
@btjawa
Copy link
Author

btjawa commented Jan 3, 2025

ImageToken返回的 complete_url 有些可以直接作为图片下载(image/jepg),有些则不能(text/plain
对于那些不能直接下载的图片,疑似需要在请求 complete_url获得 arrayBuffer后使用 getM1AndKey 算法返回的 m1key 进行进一步解密获得真实图片

@z0z0r4 z0z0r4 added 漫画/Manga 接口:漫画 新增/Add 添加或修改新的内容 更新/Update 更新过时内容 算法/Algorithm 与API相关的算法及代码示例 and removed 新增/Add 添加或修改新的内容 labels Jan 7, 2025
@btjawa
Copy link
Author

btjawa commented Jan 24, 2025

刚刚发现 ComicDetail 的URL参数新增了一个 ultra_sign 参数
如果不携带会返回

{
  "code": 99,
  "msg": "业务异常,请稍后再试。"
}

@btjawa
Copy link
Author

btjawa commented Jan 24, 2025

ultra_sign 疑似来自于 https://s1.hdslb.com/bfs/manga-static/manga-pc/6732b1bf426cfc634293.wasmGenReqSign 函数

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Jan 27, 2025

该 pr 有待进一步扩展内容?

@btjawa
Copy link
Author

btjawa commented Jan 27, 2025

我想这应该是最后一个需要进一步逆向的参数了
这个参数应该是半个月内才被加的
后续其他接口有新增内容的话我可以新开一条issue

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Jan 27, 2025

我想这应该是最后一个需要进一步逆向的参数了
这个参数应该是半个月内才被加的
后续其他接口有新增内容的话我可以新开一条issue

抱歉瞎了,我以为这是 issue

@Nemo2011
Copy link
Contributor

喜报:https://manga.bilibili.com/classify 页对应 api https://manga.bilibili.com/twirp/comic.v1.Comic/ClassPage 也出现了 ultra_sign 参数。

@btjawa
Copy link
Author

btjawa commented Jan 27, 2025

ultra_sign 相关代码:

// From https://s1.hdslb.com/bfs/manga-static/manga-pc/static/js/bili.929f6b576d.js
        const a = async (e, t, a) => {
            await async function() {
                if (!r)
                    try {
                        const e = new Go
                          , t = new URL(n("4h2B"),n.b)
                          , i = await fetch(t)
                          , a = await i.arrayBuffer();
                        o = await WebAssembly.compile(a),
                        r = await WebAssembly.instantiate(o, e.importObject),
                        e.run(r)
                    } catch (e) {
                        throw console.error("Failed to initialize WASM:", e),
                        e
                    }
            }();
            const c = i();
            if (void 0 === c.genReqSign)
                throw new Error("WASM function not available");
            const u = a || Date.now();
            if (13 !== u.toString().length)
                throw new Error("Timestamp must be a 13-digit number");
            const s = c.genReqSign(e, t, u);
            if (s.error)
                throw new Error(s.error);
            return s.sign
        }

其中传入genReqSign的三个参数依次是
URL参数(例:device=pc&platform=web&nov=25
正文参数(例:{"style_id":-1,"area_id":-1,"is_finish":-1,"order":0,"special_tag":0,"page_num":1,"page_size":18,"is_free":-1},是将Object使用 JSON.stringify 处理过的字符串)
时间戳(例:1737974553630

new URL(n("4h2B"),n.b)https://s1.hdslb.com/bfs/manga-static/manga-pc/6732b1bf426cfc634293.wasm

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Jan 29, 2025

ultra_sign 疑似来自于 s1.hdslb.com/bfs/manga-static/manga-pc/6732b1bf426cfc634293.wasmGenReqSign 函数

ultra_sign 可以提供一份样例吗?对应三个参数的(

@btjawa
Copy link
Author

btjawa commented Jan 29, 2025

ultra_sign 疑似来自于 s1.hdslb.com/bfs/manga-static/manga-pc/6732b1bf426cfc634293.wasmGenReqSign 函数

ultra_sign 可以提供一份样例吗?对应三个参数的(

刚刚发现他们居然把这个函数注册到 window

Image

除了时间戳不一样,其他两个参数都是我上一条comment的示例参数

结果:

{
    "error": null,
    "sign": "QkYG69s3Ysh6sfXh4cKbDXR4YKwKQashhRLka9aQ41shs6Re"
}

顺便补充一下,new URL(n("4h2B"),n.b) 中的 n.b 是页面的url地址,例:https://manga.bilibili.com/detail/mc26574?from=manga_homepage

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Jan 29, 2025

刚刚逆了一下,这 wasm python 肯定得浏览器模拟才能导入了用了...有 gojs

@cxw620
Copy link
Contributor

cxw620 commented Jan 29, 2025

刚刚逆了一下,这 wasm python 肯定得浏览器模拟才能导入了用了...有 gojs

反正 wasm 就是胶水, 没必要逆, 直接拿来用就得了, KPI 产物估计是, 美名其曰前端防破解() 比 js 加混淆还好, js 混淆还得一顿找, 这种直接给 wasm 了

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Jan 29, 2025

刚刚逆了一下,这 wasm python 肯定得浏览器模拟才能导入了用了...有 gojs

反正 wasm 就是胶水, 没必要逆, 直接拿来用就得了, KPI 产物估计是, 美名其曰前端防破解() 比 js 加混淆还好, js 混淆还得一顿找, 这种直接给 wasm 了

我的意思是 bilibili-api-python 那边就只能开摆了…GG

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Jan 29, 2025

刚刚逆了一下,这 wasm python 肯定得浏览器模拟才能导入了用了...有 gojs

位操作+md5

@cxw620
Copy link
Contributor

cxw620 commented Jan 29, 2025

刚刚逆了一下,这 wasm python 肯定得浏览器模拟才能导入了用了...有 gojs

反正 wasm 就是胶水, 没必要逆, 直接拿来用就得了, KPI 产物估计是, 美名其曰前端防破解() 比 js 加混淆还好, js 混淆还得一顿找, 这种直接给 wasm 了

我的意思是 bilibili-api-python 那边就只能开摆了…GG

那没办法, 而且拿 Go 弄的, tmd 一个 wasm 没啥功能就逆天 2 MB, 逆向都看大半天(丢给 DeepSeek 看了)

@My-Responsitories
Copy link

app端同样有这个参数, 不出所料的在libapp.so(由flutter编译), 而且不知道用了什么操作, blutter也解不了函数名

@cxw620
Copy link
Contributor

cxw620 commented Jan 29, 2025

app端同样有这个参数, 不出所料的在libapp.so(由flutter编译), 而且不知道用了什么操作, blutter也解不了函数名

那就麻烦了.

大胆猜测是 MD5 一些字段, (X)RC4 加密 MD5 的结果(), Base64 编码 RC4 加密结果, 再用时间戳处理(如果时间戳不是混着在 MD5 的步骤的话, 大概率 xor), 服务端就反着来就行.

现在关键是找 RC4 的密钥, 以及分析 main_injectTsIntoSign 的操作

Image

显然有 salt.

看来得动用 Frida 大法了, 麻烦得很就是(虽然比 WASM 简单), 我就不掺和了()

@btjawa
Copy link
Author

btjawa commented Jan 29, 2025

给各位提供一个nodejs调用样例

// https://github.com/golang/go/blob/master/lib/wasm/wasm_exec.js
import { Go } from './wasm_exec.js';
import fs from 'fs';

function getArrayBuffer(path) {
    const buffer = fs.readFileSync(path);
    return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
}

async function genReqSign(query, body) {
    const go = new Go();
    // https://s1.hdslb.com/bfs/manga-static/manga-pc/6732b1bf426cfc634293.wasm
    const buffer = getArrayBuffer("./manga.wasm");
    const result = await WebAssembly.compile(buffer);
    const instance = await WebAssembly.instantiate(result, go.importObject);
    go.run(instance);
    if (void 0 === globalThis.genReqSign) {
        throw new Error("WASM function not available");
    }
    const signature = globalThis.genReqSign(
        query.toString(),
        JSON.stringify(body),
        Date.now()
    );
    return signature.sign;
}

genReqSign(
    new URLSearchParams({
        "device": "pc",
        "platform": "web",
        "nov": 25,
    }),
    {"style_id":-1,"area_id":-1,"is_finish":-1,"order":0,"special_tag":0,"page_num":1,"page_size":18,"is_free":-1}
);

@Nemo2011
Copy link
Contributor

话说能不能逆向 bilibili 漫画 uwp 分析

@cxw620
Copy link
Contributor

cxw620 commented Jan 29, 2025

话说能不能逆向 bilibili 漫画 uwp 分析

还在更新么, 如果能看的话就可以

@Nemo2011
Copy link
Contributor

Nemo2011 commented Jan 29, 2025

不知道。不知道。

希望都是。

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Jan 29, 2025

刚刚逆了一下,这 wasm python 肯定得浏览器模拟才能导入了用了...有 gojs

反正 wasm 就是胶水, 没必要逆, 直接拿来用就得了, KPI 产物估计是, 美名其曰前端防破解() 比 js 加混淆还好, js 混淆还得一顿找, 这种直接给 wasm 了

我的意思是 bilibili-api-python 那边就只能开摆了…GG

那没办法, 而且拿 Go 弄的, tmd 一个 wasm 没啥功能就逆天 2 MB, 逆向都看大半天(丢给 DeepSeek 看了)

我给deepseek直接卡死了=。=

@cxw620
Copy link
Contributor

cxw620 commented Jan 29, 2025

WASM 逆向的话还是得 debug 看内存, 静态分析只能看出来大致在干什么. 但是 Firefox 一下断点就卡死, 绷不住了...

这个 ultra_sign 确实很有难度啊()

@cxw620
Copy link
Contributor

cxw620 commented Jan 29, 2025

Image

阿哲, 挑衅是吧()

Image

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Jan 29, 2025

Image

阿哲, 挑衅是吧()

Image

什么鬼

@btjawa
Copy link
Author

btjawa commented Jan 30, 2025

ImageToken返回的 complete_url 有些可以直接作为图片下载(image/jepg),有些则不能(text/plain) 对于那些不能直接下载的图片,疑似需要在请求 complete_url获得 arrayBuffer后使用 getM1AndKey 算法返回的 m1key 进行进一步解密获得真实图片

抓了几次包,发现前几天的那次更新除了加了 ultra_sign 以外,还把所有的图片都加密了

@Nemo2011
Copy link
Contributor

ImageToken返回的 complete_url 有些可以直接作为图片下载(image/jepg),有些则不能(text/plain) 对于那些不能直接下载的图片,疑似需要在请求 complete_url获得 arrayBuffer后使用 getM1AndKey 算法返回的 m1key 进行进一步解密获得真实图片

抓了几次包,发现前几天的那次更新除了加了 ultra_sign 以外,还把所有的图片都加密了

我昨天试出了几次没加密的情况,ImageToken 返回 hit_encryptfalse,后面再试全部加密。

@yan12125
Copy link

yan12125 commented Feb 7, 2025

给各位提供一个nodejs调用样例

@btjawa Thank you very much for this great work! I would like to adapt and integrate it into RSSHub via DIYgod/RSSHub#18300. Are you okay with that?

@btjawa
Copy link
Author

btjawa commented Feb 7, 2025

给各位提供一个nodejs调用样例

@btjawa Thank you very much for this great work! I would like to adapt and integrate it into RSSHub via DIYgod/RSSHub#18300. Are you okay with that?

@yan12125 Sure! I'm totally fine with that.
just a reminder: don't forget to add export const Go = globalThis.Go in wasm-exec.js to export the Go class.

yan12125 added a commit to yan12125/RSSHub that referenced this issue Feb 8, 2025
@yan12125
Copy link

yan12125 commented Feb 8, 2025

@yan12125 Sure! I'm totally fine with that. just a reminder: don't forget to add export const Go = globalThis.Go in wasm-exec.js to export the Go class.

Cool, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
算法/Algorithm 与API相关的算法及代码示例 漫画/Manga 接口:漫画 更新/Update 更新过时内容
Projects
None yet
Development

No branches or pull requests

6 participants