上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

onlyfans的OB解密及DRM过校验思路(七)

[复制链接]
  • TA的每日心情
    擦汗
    2024-7-16 09:20
  • 签到天数: 192 天

    [LV.7]常住居民III

    691

    主题

    5563

    回帖

    6462

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6462

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2024-6-30 18:24:34 | 显示全部楼层 | 阅读模式

    那接下来的问题就是我们该怎么读取onlyfans的密钥了

    首先需要解密头

    直接逐步定位打到了

                  , Pe = e=>{
                    try {
                        const t = {
                            ...(0,
                            F.A)(e)
                        };
                        t["app-token"] = te;
                        const s = V.A.getters["auth/authUserId"];
                        s && (t["user-id"] = s),
                        t["x-bc"] = fe(),
                        t["x-of-rev"] = "202406261341-9a802bb7ea";
                        const {hash: r} = V.A.state.hash;
                        return r && (t["x-hash"] = r),
                        t
                    } catch (t) {
                        console.error(t)
                    }
                    return {}
                }

    t初始化是sign和time

    然后设置app-token,这里跟其他一致

    user-id,设置用户id,等价于cookie的auth_id,也基本一致

    t["x-bc"] = fe() 提取bcToken,老生常谈

    t["x-of-rev"] = "202406261341-9a802bb7ea";固定值

    const {hash: r} = V.A.state.hash; 从https://cdn2.onlyfans.com/hash/中读取

    没难度啊我靠

    x-hash: "mkVyQlWEXk/Vb0n/4iia1HdR+AeHJrzzR27MA+8="
    x-of-rev:"202406261341-9a802bb7ea"

    这两个参数要保持最新,如果旧版本的请求没补这两个或者hash过期

    会导致请求正常但是DRM的Cookies是假的

    好心机

    但是直接往认证服务器发送包依然失败

    这部分卡了我很久

    只能去参考od-drm项目
    https://github.com/sim0n00ps/OF-DRM/blob/089b7402dc1f6240993255b0492795bcd1114be8/OF DRM Video Downloader/Helpers/APIHelper.cs#L1199

    找到了

                    var resp1 = PostData(licenceURL, drmHeaders, new byte[] { 0x08, 0x04 });
                    var certDataB64 = Convert.ToBase64String(resp1);
                    var cdm = new CDMApi();
                    var challenge = cdm.GetChallenge(pssh, certDataB64, false, false);
                    var resp2 = PostData(licenceURL, drmHeaders, challenge);
                    var licenseB64 = Convert.ToBase64String(resp2);
                    cdm.ProvideLicense(licenseB64);
                    List<ContentKey> keys = cdm.GetKeys();

    可以看到先提交了一个0804,然后设置证书再处理解密部分

    观察onlyfans的抓包也确实存在两次提交

    因为我一直在搞解密部分大意了,没有闪!

    关于设置服务器证书pywidevine也有函数,我们查看源码可以找到set_service_certificate函数

            Set a Service Privacy Certificate for Privacy Mode. (optional but recommended)
    
            The Service Certificate is used to encrypt Client IDs in Licenses. This is also
            known as Privacy Mode and may be required for some services or for some devices.
            Chrome CDM requires it as of the enforcement of VMP (Verified Media Path).
    
            We reject direct DrmCertificates as they do not have signature verification and
            cannot be verified. You must provide a SignedDrmCertificate or a SignedMessage
            containing a SignedDrmCertificate.
    
            Parameters:
                session_id: Session identifier.
                certificate: SignedDrmCertificate (or SignedMessage containing one) in Base64
                    or Bytes form obtained from the Service. Some services have their own,
                    but most use the common privacy cert, (common_privacy_cert). If None, it
                    will remove the current certificate.
    
            Raises:
                InvalidSession: If the Session identifier is invalid.
                DecodeError: If the certificate could not be parsed as a SignedDrmCertificate
                    nor a SignedMessage containing a SignedDrmCertificate.
                SignatureMismatch: If the Signature of the SignedDrmCertificate does not
                    match the underlying DrmCertificate.
    
            Returns the Service Provider ID of the verified DrmCertificate if successful.
            If certificate is None, it will return the now-unset certificate's Provider ID,
            or None if no certificate was set yet.

    那我们就继续研究一下原网页的代码,看看0801哪里来的

    之前我们分析getLicense我们知道了接受消息的在

                    u.addEventListener("message", (e=>{
                        c.trigger({
                            type: "keymessage",
                            messageEvent: e
                        }),
                        "license-request" !== e.messageType && "license-renewal" !== e.messageType || a(o, e.message, d).then((e=>{
                            r(u.update(e).then((()=>{
                                c.trigger({
                                    type: "keysessionupdated",
                                    keySession: u
                                })
                            }
                            )).catch((e=>{
                                const t = {
                                    errorType: s.default.Error.EMEFailedToUpdateSessionWithReceivedLicenseKeys,
                                    keySystem: m
                                };
                                l(e, t)
                            }
                            )))
                        }
                        )).catch((e=>{
                            p(e)
                        }
                        ))
                    }

    查找文档https://www.w3.org/TR/encrypted-media/

    generateRequest
    Generates a license request based on the initData. A message of type "license-request" or "individualization-request" will always be queued if the algorithm succeeds and the promise is resolved.
    
    Parameter   Type    Nullable    Optional    Description
    initDataType    DOMString   ✘   ✘   The Initialization Data Type of the initData.
    initData    BufferSource    ✘   ✘   Initialization Data

    根据文档的提示,生成我们可以定位到

                        u.generateRequest(n, i).catch((e=>{
                            const t = {
                                errorType: s.default.Error.EMEFailedToGenerateLicenseRequest,
                                keySystem: m
                            };
                            l(e, t),
                            p("Unable to create or initialize key session")
                        }

    关于到底这两个是否有关联可以下断在u.generateRequest调用时将函数置为空函数()=>{},可以发现接收消息没有触发,证明了这两个函数没有关联

    其中n是cenc,i是字节,我们一路往上堆栈回溯可以找到

                if (i) {
                    e[i] = {
                        attributes: n
                    };
                    const r = Te(t, "cenc:pssh")[0];
                    if (r) {
                        const t = Ie(r);
                        e[i].pssh = t && p(t)
                    }
                }

    其中p是将字符串转为字节数组

            function p(e) {
                for (var t = m(e), n = new Uint8Array(t.length), i = 0; i < t.length; i++)
                    n[i] = t.charCodeAt(i);
                return n
            }

    根据调试首先传入的是wpd中较短的pssh,然后得到0804提交再获取证书

    但是问题来了

    0804到底怎么生成的?

    我研究了几天还是没有得到答案

    于是到处找人寻味

    直到在forum.videohelp.com论坛得到了larley大神的解答!

    https://forum.videohelp.com/threads/415095-How-to-simulate-the-generateRequest-function-through-python

    The long PSSH is used for Microsoft's PlayReady and then short one is for Google's Widevine (that's what you're going to want to use).
    
    The '08 04' (or CAQ= is base64) (or '\x08\x04' in python) is a fixed data value that can be sent to the same server (even same URL and 99% of the time even the same headers) from which you will receive your license.

    0804竟然是generateRequest返回的固定值!

    我在https://integration.widevine.com/diagnostics的生成widevine pssh试了几组

    都返回了0804!

    那么一切就通顺了

    首先根据较短的pssh获得0804

    然后将0804上传得到certData证书

    再设置certData证书

    然后上传pssh得到正确key

    理论建立完毕

    实践开始!

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    开心
    6 小时前
  • 签到天数: 302 天

    [LV.8]以坛为家I

    6

    主题

    29

    回帖

    216

    积分

    高级工程师

    积分
    216

    挑战者 lv2

    发表于 2024-6-30 20:06:50 | 显示全部楼层
    ggnb 继续继续
    回复

    使用道具 举报

  • TA的每日心情
    擦汗
    2024-7-16 09:20
  • 签到天数: 192 天

    [LV.7]常住居民III

    691

    主题

    5563

    回帖

    6462

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6462

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2024-6-30 20:19:35 | 显示全部楼层

    我都快心态炸了....
    快半个月了还没结束
    又卡在授权证书了
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

    发表回复

    本版积分规则

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