<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>安全研究 on 逍遥笔记</title><link>https://blog.7qiang.com/categories/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/</link><description>Recent content in 安全研究 on 逍遥笔记</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>逍遥笔记</copyright><lastBuildDate>Wed, 22 Jul 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://blog.7qiang.com/categories/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/index.xml" rel="self" type="application/rss+xml"/><item><title>Google Play 内购 CC Max 漏洞深度拆解 — 一个 Bundle.putInt 就能白嫖 $250/月的 Claude Max</title><link>https://blog.7qiang.com/posts/google-play-ccmax-proration-vulnerability-2026/</link><pubDate>Wed, 22 Jul 2026 10:00:00 +0800</pubDate><guid>https://blog.7qiang.com/posts/google-play-ccmax-proration-vulnerability-2026/</guid><description>&lt;img src="https://blog.7qiang.com/blog-cover-cyber-mask.gif" alt="Featured image of post Google Play 内购 CC Max 漏洞深度拆解 — 一个 Bundle.putInt 就能白嫖 $250/月的 Claude Max" /&gt;&lt;section class="xiaoyao-contact-card" aria-label="联系逍遥"&gt;
 &lt;img class="xiaoyao-contact-card__qr" src="https://blog.7qiang.com/contact-qr.png" width="690" height="690" loading="lazy" decoding="async" alt="逍遥微信二维码"&gt;
 &lt;div class="xiaoyao-contact-card__content"&gt;
 &lt;h3 class="xiaoyao-contact-card__title"&gt;联系逍遥&lt;/h3&gt;
 &lt;p class="xiaoyao-contact-card__description"&gt;扫码添加微信，或点击下方按钮复制联系方式。&lt;/p&gt;
 &lt;div class="xiaoyao-contact-buttons" data-contact-buttons&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--wechat" type="button" data-copy-contact="plus6566" data-copy-label="微信号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;微信&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;plus6566&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--qq" type="button" data-copy-contact="826215906" data-copy-label="QQ 号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;QQ&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;826215906&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="xiaoyao-contact-feedback" role="status" aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;

 &lt;/div&gt;
&lt;/section&gt;

&lt;p&gt;进微信群请联系博主，各位觉得文章对你有帮助的话可否打赏一些呀~&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;⚠️ 免责声明&lt;/strong&gt;：本文仅供安全研究与技术讨论。本文描述的漏洞&lt;strong&gt;已于 2026 年 7 月被修复&lt;/strong&gt;。任何利用类似手段对正式服务进行未授权操作属违法行为，后果自负。&lt;/p&gt;
&lt;p&gt;部分思路来自 &lt;a class="link" href="https://cctest.ai" target="_blank" rel="noopener"
 &gt;cctest.ai&lt;/a&gt; 作者，在此致谢。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="0x00-背景20-的-pro250-的-max中间差了一个-hook"&gt;0x00 背景：$20 的 Pro，$250 的 Max，中间差了一个 hook
&lt;/h2&gt;&lt;p&gt;Claude 的 Android 客户端走 Google Play 内购（Google Play Billing Library）处理订阅。价格梯度大概是这样：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;方案&lt;/th&gt;
 &lt;th&gt;月费&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pro&lt;/td&gt;
 &lt;td&gt;$20&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Max 5x&lt;/td&gt;
 &lt;td&gt;$125&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Max 20x&lt;/td&gt;
 &lt;td&gt;$250&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;正常流程：你是 Pro 用户，想升级到 Max，Google Play 会按剩余天数计算差价，立刻从你的信用卡上扣。&lt;/p&gt;
&lt;p&gt;但如果我告诉你，有个参数决定了 Google Play 对这笔&amp;quot;升级&amp;quot;收不收钱呢？&lt;/p&gt;
&lt;p&gt;而且这个参数，就是 Android Bundle 里一个普普通通的 &lt;code&gt;putInt&lt;/code&gt; 调用？&lt;/p&gt;
&lt;h2 id="0x01-prorationmodegoogle-play-里最被低估的攻击面"&gt;0x01 prorationMode：Google Play 里最被低估的攻击面
&lt;/h2&gt;&lt;p&gt;Google Play Billing Library 在处理订阅升/降级时，有个参数叫 &lt;code&gt;prorationMode&lt;/code&gt;（比例分配模式），定义在 &lt;code&gt;BillingFlowParams.SubscriptionUpdateParams&lt;/code&gt; 里。它决定了用户切换订阅方案时的扣费逻辑：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;值&lt;/th&gt;
 &lt;th&gt;常量名&lt;/th&gt;
 &lt;th&gt;行为&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY&lt;/td&gt;
 &lt;td&gt;未知&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;WITH_TIME_PRORATION&lt;/td&gt;
 &lt;td&gt;按剩余时间折算差价，&lt;strong&gt;立刻扣费&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;CHARGE_PRORATED_PRICE&lt;/td&gt;
 &lt;td&gt;按比例收取差价&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;WITHOUT_PRORATION&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;立刻升级，本周期不收费，下个周期按新价扣&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;CHARGE_FULL_PRICE&lt;/td&gt;
 &lt;td&gt;立刻全额扣费&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;DEFERRED&lt;/td&gt;
 &lt;td&gt;延迟到下个周期才生效&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;重点关注 &lt;code&gt;3 — WITHOUT_PRORATION&lt;/code&gt;：Google 官方文档里写得很清楚，这个模式的设计意图是&amp;quot;让用户立刻获得高级功能，但推迟扣费到下个计费周期&amp;quot;。本来是给某些业务场景用的（比如试用期升级），但如果被滥用……&lt;/p&gt;
&lt;p&gt;Claude 的客户端代码里，升级订阅时用的是模式 &lt;code&gt;1&lt;/code&gt;（WITH_TIME_PRORATION），也就是正常的按比例扣差价。它通过 &lt;code&gt;Bundle.putInt(&amp;quot;prorationMode&amp;quot;, 1)&lt;/code&gt; 把这个值传给 Google Play 的计费 Activity。&lt;/p&gt;
&lt;p&gt;这个 &lt;code&gt;Bundle.putInt&lt;/code&gt; 调用发生在客户端侧。客户端侧的东西，你懂的。&lt;/p&gt;
&lt;h2 id="0x02-攻击链frida-一条龙"&gt;0x02 攻击链：Frida 一条龙
&lt;/h2&gt;&lt;p&gt;整个攻击链其实非常简洁，所有操作都在模拟器上完成。用到的工具：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MuMu 模拟器&lt;/strong&gt;（x86_64，因为要跑 Frida server 的 x86 版本）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KernelSU&lt;/strong&gt;（MuMu 自带，用来获取 root）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frida 17.16.0&lt;/strong&gt;（动态插桩框架）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特定版本的 Claude APK&lt;/strong&gt;（v1.260330.27，在 Uptodown 可以下到历史版本）&lt;/li&gt;
&lt;li&gt;一个能扣 $20 的美区 Google 账号&lt;/li&gt;
&lt;li&gt;家宽 IP（避免 Google 风控拦截）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;步骤拆解：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：搭环境&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MuMu 模拟器装好后，用它自带的 Google 安装器装上 Google 套件 + Play 商店。打开 KernelSU Manager 给 Shell 授权 root。这步没啥技术含量，MuMu 对 Google 服务兼容性比 LDPlayer 好不少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：准备 Claude APK&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里有个前置问题——你不能直接从 Play 商店装 Claude。&lt;/p&gt;
&lt;p&gt;现在的 Android 应用普遍使用 App Bundle（AAB）格式发布，Play 商店下发的是 split APK：base.apk 加一堆按设备拆分的 config splits（ABI、密度、语言）。x86_64 模拟器拿到的 splits 跟 ARM 真机不一样，而且 split APK 不能直接 &lt;code&gt;adb install&lt;/code&gt; 侧载。&lt;/p&gt;
&lt;p&gt;所以需要一个&lt;strong&gt;经过修补、移除了 split 限制的完整 APK&lt;/strong&gt;。实测可用的版本是 &lt;code&gt;v1.260330.27&lt;/code&gt;，从 Uptodown 等第三方市场可以找到历史版本的完整包。关键词：“已修补 split 限制”——就是把 App Bundle 重新打包成单个 APK，绕过 Play 商店的分发限制。&lt;/p&gt;
&lt;p&gt;还有一个连锁问题：修补过的 APK 签名跟 Google Play 原版不一致（相当于 debug 签名），&lt;strong&gt;Google OAuth 登录会直接拒绝&lt;/strong&gt;——点 “Continue with Google” 没反应或者报错。解决办法很简单：用邮箱登录（Continue with email），输入 Claude 账号的邮箱和密码，完全不走 OAuth。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：部署 Frida&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb push frida-server-17.16.0-android-x86_64 /data/local/tmp/frida-server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell chmod &lt;span class="m"&gt;755&lt;/span&gt; /data/local/tmp/frida-server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell su -c &lt;span class="s2"&gt;&amp;#34;setenforce 0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell su -c &lt;span class="s2"&gt;&amp;#34;/data/local/tmp/frida-server -D &amp;amp;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MuMu 有个坑：它的 ADB 端口不是标准 5555，而是 5557（多开的话 5559、5561 递增）。另外 MuMu 用 houdini 做 ARM 翻译，所以 Frida 必须用 &lt;strong&gt;attach 模式&lt;/strong&gt;，不能用 spawn 模式（&lt;code&gt;-f&lt;/code&gt; 参数），否则 houdini 翻译层直接 SIGSEGV 崩掉。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第四步：注入 Hook&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核心的 hook 脚本其实就干了一件事——拦截 &lt;code&gt;Bundle.putInt&lt;/code&gt;，把 key 为 &lt;code&gt;prorationMode&lt;/code&gt; 的值从 1 改成 3：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;Bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;android.os.Bundle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putInt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;java.lang.String&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;int&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;prorationMode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[*] 拦截 prorationMode: &amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34; → 3 [WITHOUT_PRORATION]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 就这一行，价值 $230/月
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意启动方式——必须先手动打开 Claude，再用 &lt;code&gt;-H&lt;/code&gt; 连接（而不是 &lt;code&gt;-U&lt;/code&gt;），因为 MuMu 的 USB 层跟标准 Android 不一样。而且 Frida 的进程名参数必须用&lt;strong&gt;显示名 &lt;code&gt;Claude&lt;/code&gt;&lt;/strong&gt;，不能用包名 &lt;code&gt;com.anthropic.claude&lt;/code&gt;——MuMu 上 &lt;code&gt;-H&lt;/code&gt; 模式下 Frida 按包名是找不到进程的，只认 app_process 里注册的显示名：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb forward tcp:27042 tcp:27042
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;frida -H 127.0.0.1:27042 Claude -l hook-replacement-mode.js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 注意：用 com.anthropic.claude 会报 &amp;#34;unable to find process&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里还有个细节：脚本额外拦截了 &lt;code&gt;SentryNdk.loadNativeLibraries&lt;/code&gt; 和 &lt;code&gt;SentryAndroidNdk&lt;/code&gt; 的初始化方法，因为 Sentry 的 native crash 上报模块在 houdini 翻译层下也会崩。不拦截的话 App 直接闪退。&lt;/p&gt;
&lt;p&gt;除了核心的 prorationMode 拦截，脚本还 hook 了 &lt;code&gt;PurchaseReceipt&lt;/code&gt; 的构造函数，打印 &lt;code&gt;purchase_token&lt;/code&gt; 和 &lt;code&gt;organization_id&lt;/code&gt;。这个 org_id 是 Anthropic 后端用来标识组织/用户的唯一 ID（格式类似 &lt;code&gt;6a085918-8383-44e5-9538-c4c81c6e122b&lt;/code&gt;），在规模化操作时用于追踪哪个 Claude 账号绑定了哪笔 Google Play 订阅——一个 Google 账号只能给一个 Claude 账号开通订阅，org_id 就是这个映射关系的 key。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第五步：走一遍正常支付&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;hook 注入成功后（控制台会显示 &lt;code&gt;Frida Hook 注入成功!&lt;/code&gt;，App 里也会弹 Toast），正常操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用邮箱登录 Claude 账号（不能用 Google OAuth，原因见上）&lt;/li&gt;
&lt;li&gt;在模拟器里绑定支付卡（需要能扣 $20 的真实/虚拟信用卡）&lt;/li&gt;
&lt;li&gt;通过 Google Play 订阅 Pro（$20/月）—— 这笔钱是真扣的&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;第六步：触发升级&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pro 订阅成功后，直接在 App 里点&amp;quot;升级到 Max&amp;quot;。这时候 hook 就发挥作用了：&lt;/p&gt;
&lt;p&gt;控制台输出：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[*] 拦截 prorationMode: 1 [WITH_TIME_PRORATION] → 3 [WITHOUT_PRORATION]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同时 &lt;code&gt;PurchaseReceipt&lt;/code&gt; 的 hook 会打印出新订阅的 org_id，确认升级已经走通了 Anthropic 后端。&lt;/p&gt;
&lt;p&gt;Google Play 的计费页面会显示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;产品：Claude Max&lt;/li&gt;
&lt;li&gt;价格：$250.00/month（日区显示 JP¥42,500/month）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开始日期：2026年8月20日&lt;/strong&gt;（注意，是下个月！）&lt;/li&gt;
&lt;li&gt;“我们将于 2026年8月20日向您收取第一笔费用”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看到了吗？因为 prorationMode 被改成了 &lt;code&gt;WITHOUT_PRORATION&lt;/code&gt;，Google Play 认为&amp;quot;当前不收费，下个计费周期再扣&amp;quot;。点击订阅，立刻升级到 Max，卡上不会多扣一分钱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第七步：善后&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;升级成功后，去 &lt;a class="link" href="https://payments.google.com" target="_blank" rel="noopener"
 &gt;payments.google.com&lt;/a&gt; 把这个订阅的支付方式换成余额钱包（Google Play 余额一般是 0）或者一张空的虚拟卡，然后解绑原来的支付卡。&lt;/p&gt;
&lt;p&gt;下个月续费的时候，余额不够或者虚拟卡扣费失败，订阅自然过期。但这一个月的 Max 你已经用上了。&lt;/p&gt;
&lt;p&gt;$20 的成本，享受 $250 的服务。利润率 1150%。&lt;/p&gt;
&lt;p&gt;有一个规模化的硬约束：&lt;strong&gt;一个 Google 账号只能给一个 Claude 账号开通订阅&lt;/strong&gt;。想批量撸就得不断囤 Google 号——这也是为什么这条灰产链的瓶颈不在技术，而在号源。&lt;/p&gt;
&lt;h2 id="0x03-为什么这个-bug-能成立"&gt;0x03 为什么这个 bug 能成立
&lt;/h2&gt;&lt;p&gt;从技术层面分析，这个漏洞能成立需要几个条件同时满足：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. prorationMode 由客户端决定&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是最根本的问题。Google Play Billing Library 的设计是把 &lt;code&gt;prorationMode&lt;/code&gt; 作为客户端参数传递给 Google Play 的计费系统。Google Play 本身不会校验&amp;quot;这个 App 有没有资格用 WITHOUT_PRORATION 模式&amp;quot;——它无条件信任客户端传来的值。&lt;/p&gt;
&lt;p&gt;这是一个经典的&lt;strong&gt;信任边界错误&lt;/strong&gt;：把安全关键参数的决定权放在了不可信的客户端侧。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 服务端验证缺位&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude 的后端在收到 Google Play 的订阅回调时，主要验证的是&amp;quot;这个 purchase token 是否有效&amp;quot;和&amp;quot;订阅是否 active&amp;quot;。它不会去检查 &lt;code&gt;prorationMode&lt;/code&gt; 是什么——因为这个信息在服务端回调里根本就没有。Google Play 的 &lt;code&gt;purchases.subscriptions&lt;/code&gt; API 返回的数据里不包含当初用的是哪个 proration mode。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 历史版本 APK 的可用性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;新版 Claude 可能已经做了混淆或者改变了参数传递方式，但旧版本在 Uptodown 等第三方市场永远可以下到。只要 Google Play 的 Billing API 版本还兼容，旧 APK 照样能完成订阅操作。&lt;/p&gt;
&lt;h2 id="0x04-不用-hook-的路子速刷"&gt;0x04 不用 hook 的路子：速刷
&lt;/h2&gt;&lt;p&gt;值得一提的是，prorationMode hook 并不是唯一的利用方式。圈子里还流传一种叫&amp;quot;速刷&amp;quot;的打法——不需要 Frida，不需要 hook，纯靠旧版本客户端的某些行为差异来打时间差。&lt;/p&gt;
&lt;p&gt;具体原理不在本文展开（毕竟还没被修），但核心思路类似：利用 Google Play Billing Library 旧版本在处理订阅变更时的窗口期，在扣费确认到达之前完成订阅切换。跟 prorationMode hook 相比，速刷的门槛更低（不需要 root、不需要 Frida），但成功率也更低，而且强依赖特定的历史版本 APK。&lt;/p&gt;
&lt;p&gt;这两种方法本质上攻击的是同一个面：&lt;strong&gt;Google Play 对客户端行为的过度信任&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="0x05-修复状态与后续"&gt;0x05 修复状态与后续
&lt;/h2&gt;&lt;p&gt;截至 2026 年 7 月，prorationMode hook 这个特定的攻击路径已被修复。从修复思路上猜测，可能的措施包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;客户端侧&lt;/strong&gt;：新版 Claude APK 不再使用 Bundle 明文传递 prorationMode，可能改用了加密或者服务端控制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务端侧&lt;/strong&gt;：在处理升级回调时增加了额外验证（比如检查 &lt;code&gt;linkedPurchaseToken&lt;/code&gt; 的订阅金额是否合理）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Play 侧&lt;/strong&gt;：Google 可能限制了 WITHOUT_PRORATION 模式的使用场景（需要应用显式在 Google Play Console 配置）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但说实话，&lt;strong&gt;Google Play Billing 的这个攻击面远没有被堵死&lt;/strong&gt;。&lt;code&gt;prorationMode&lt;/code&gt; 只是其中一个参数，&lt;code&gt;BillingFlowParams&lt;/code&gt; 里还有其他可操控的字段。只要&amp;quot;客户端构建支付参数 → 传给 Google Play → Google Play 无条件执行&amp;quot;这个模型不变，同类问题就会持续出现。&lt;/p&gt;
&lt;p&gt;据了解，目前仍有针对其他参数或其他 App 的类似攻击手法在流通。Google Play 内购系统的安全模型，说白了就是建立在&amp;quot;客户端可信&amp;quot;这个脆弱假设上的。&lt;/p&gt;
&lt;h2 id="0x06-技术启示"&gt;0x06 技术启示
&lt;/h2&gt;&lt;p&gt;如果你是开发者，从这个漏洞可以学到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;永远不要信任客户端传来的支付参数&lt;/strong&gt;。即使是通过 Google Play 这种&amp;quot;可信&amp;quot;中间人，底层的 &lt;code&gt;BillingFlowParams&lt;/code&gt; 仍然是客户端构造的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务端必须独立验证订阅状态&lt;/strong&gt;。不要只检查 token 有效性，还要验证订阅的实际扣费金额（通过 &lt;code&gt;purchases.subscriptions&lt;/code&gt; 的 &lt;code&gt;priceAmountMicros&lt;/code&gt; 字段）与预期是否一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pin APK 版本&lt;/strong&gt;。如果你依赖客户端逻辑做安全控制，服务端应该检查客户端版本号，拒绝过旧版本的请求。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你是安全研究者，这个 case 是一个很好的&amp;quot;API 信任边界&amp;quot;研究样本。Google Play Billing Library 把自己定位成&amp;quot;安全的支付中间件&amp;quot;，但它实际上信任了太多来自客户端的输入。类似 Frida hook &lt;code&gt;Bundle.putInt&lt;/code&gt; 这种低成本的攻击手段，在整个 Android 内购生态里适用面很广。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;本文记录的是一个已修复的安全问题，旨在帮助开发者理解 Google Play 内购系统的安全边界。&lt;/p&gt;
&lt;p&gt;如果你对 Android 安全、支付系统攻防感兴趣，欢迎关注博客，后续会持续更新更多分析。&lt;/p&gt;

 &lt;/blockquote&gt;</description></item><item><title>GPT Plus 订阅转移全解析 - 从 Google Play Token 到一键开通的完整技术链路</title><link>https://blog.7qiang.com/posts/gpt-plus-google-play-subscription-transfer/</link><pubDate>Fri, 17 Jul 2026 16:00:00 +0800</pubDate><guid>https://blog.7qiang.com/posts/gpt-plus-google-play-subscription-transfer/</guid><description>&lt;img src="https://blog.7qiang.com/blog-cover-cyber-mask.gif" alt="Featured image of post GPT Plus 订阅转移全解析 - 从 Google Play Token 到一键开通的完整技术链路" /&gt;&lt;section class="xiaoyao-contact-card" aria-label="联系逍遥"&gt;
 &lt;img class="xiaoyao-contact-card__qr" src="https://blog.7qiang.com/contact-qr.png" width="690" height="690" loading="lazy" decoding="async" alt="逍遥微信二维码"&gt;
 &lt;div class="xiaoyao-contact-card__content"&gt;
 &lt;h3 class="xiaoyao-contact-card__title"&gt;联系逍遥&lt;/h3&gt;
 &lt;p class="xiaoyao-contact-card__description"&gt;扫码添加微信，或点击下方按钮复制联系方式。&lt;/p&gt;
 &lt;div class="xiaoyao-contact-buttons" data-contact-buttons&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--wechat" type="button" data-copy-contact="plus6566" data-copy-label="微信号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;微信&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;plus6566&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--qq" type="button" data-copy-contact="826215906" data-copy-label="QQ 号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;QQ&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;826215906&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="xiaoyao-contact-feedback" role="status" aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;

 &lt;/div&gt;
&lt;/section&gt;

&lt;p&gt;进微信群请联系博主，各位觉得文章对你有帮助的话可否打赏一些呀~&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="前言"&gt;前言
&lt;/h2&gt;&lt;p&gt;之前写了两篇关于 GPT Plus 的漏洞分析：一篇是 &lt;a class="link" href="https://blog.7qiang.com/posts/gpt-plus-receipt-vulnerability-2026" target="_blank" rel="noopener"
 &gt;iOS 收据复用&lt;/a&gt;，一篇是 &lt;a class="link" href="https://blog.7qiang.com/posts/gpt-plus-exploit-revenuecat-vulnerability" target="_blank" rel="noopener"
 &gt;Google Play offerToken 替换实现 0 元订阅&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;今天这篇不一样——我们不搞 0 元白嫖，也不搞收据复用。今天讲的是一个更优雅的思路：&lt;strong&gt;订阅转移&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;简单说就是：A 账号在 Google Play 上正经花钱买了 Plus，但通过技术手段，&lt;strong&gt;把这个订阅&amp;quot;转&amp;quot;给 B 账号&lt;/strong&gt;。B 账号什么都没付，直接变 Plus。&lt;/p&gt;
&lt;p&gt;听起来玄乎？其实原理简单得令人发指。往下看。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;免责声明&lt;/strong&gt;：本文仅供安全研究与技术交流。未经授权的行为可能违反相关法律法规，请勿用于非法用途。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一核心原理为什么能转移"&gt;一、核心原理：为什么能&amp;quot;转移&amp;quot;？
&lt;/h2&gt;&lt;h3 id="chatgpt-android-的订阅架构"&gt;ChatGPT Android 的订阅架构
&lt;/h3&gt;&lt;p&gt;很多人以为 GPT 的订阅是 OpenAI 自己处理的。&lt;strong&gt;不是。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Android 端的订阅走的是这条链路：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Google Play 支付（扣钱）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 生成 purchase token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ChatGPT App 拿到 token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 发送到 RevenueCat
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RevenueCat 验证 token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 通知 OpenAI
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;OpenAI 后端开通 Plus
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;中间有个关键角色：&lt;strong&gt;RevenueCat&lt;/strong&gt;。它是一个第三方订阅管理平台，充当 Google Play 和 OpenAI 之间的&amp;quot;中间人&amp;quot;。&lt;/p&gt;
&lt;h3 id="漏洞在哪"&gt;漏洞在哪？
&lt;/h3&gt;&lt;p&gt;RevenueCat 的核心 API 是 &lt;code&gt;POST /v1/receipts&lt;/code&gt;，请求体长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;fetch_token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;Google Play 的购买凭证&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;app_user_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;OpenAI 的账户 ID&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;product_ids&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;oai.chatgpt.plus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意这两个参数：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;参数&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;th&gt;来源&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;fetch_token&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;购买凭证，证明&amp;quot;有人付了钱&amp;quot;&lt;/td&gt;
 &lt;td&gt;Google Play 支付后返回&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;app_user_id&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;账户 ID，决定&amp;quot;给谁开通&amp;quot;&lt;/td&gt;
 &lt;td&gt;OpenAI 的 account_id&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;这两个参数是完全独立的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Google Play 只管&amp;quot;收钱并出具凭证&amp;quot;——它不关心这个凭证最终给谁用。RevenueCat 只管&amp;quot;验证凭证并绑定到指定用户&amp;quot;——它不检查 &lt;code&gt;app_user_id&lt;/code&gt; 是否就是付钱的那个人。&lt;/p&gt;
&lt;p&gt;打个比方：你在肯德基买了个汉堡，小票给了你朋友，你朋友拿小票去柜台，柜员一扫码——嗯，小票是真的，给。至于小票是不是你朋友买的？柜员根本不看。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;所以：用 A 的 token + B 的 account_id = 给 B 开 Plus。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就是&amp;quot;订阅转移&amp;quot;的全部原理。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二关键标识符一览"&gt;二、关键标识符一览
&lt;/h2&gt;&lt;p&gt;在动手之前，先搞清楚几个关键 ID：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;标识符&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;th&gt;长啥样&lt;/th&gt;
 &lt;th&gt;怎么拿&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;fetch_token&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Google Play 购买凭证&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;iekllednimgnlmo...&lt;/code&gt;（很长一串）&lt;/td&gt;
 &lt;td&gt;支付后拦截/ADB 提取&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;app_user_id&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;OpenAI 账户 ID&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;f211fe99-83d9-4c48-b016-ee08984a592a&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;调 accounts/check API&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;RevenueCat API Key&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;RC 公钥（固定值）&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;goog_DPguJtknNxbQBStStwhWGRsghUw&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;抓包可得，下面直接给你&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;product_id&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;订阅产品标识（固定值）&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;oai.chatgpt.plus&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;固定的&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="三获取-purchase-token两种姿势"&gt;三、获取 purchase token（两种姿势）
&lt;/h2&gt;&lt;p&gt;Token 是核心。拿到 token，后面的事就水到渠成了。这里提供两种获取方式，难度由低到高。&lt;/p&gt;
&lt;h3 id="方式一mitm-代理拦截推荐无需-root"&gt;方式一：MITM 代理拦截（推荐，无需 Root）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt;：在 Android 设备和 RevenueCat 服务器之间架一层代理，支付完成后 ChatGPT App 会把 token 发给 RevenueCat，我们在中间截下来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;你需要准备&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Android 设备（真机或模拟器，需装 Google Play 服务）&lt;/li&gt;
&lt;li&gt;已登录的 Google 账号（需有支付方式或免费试用资格）&lt;/li&gt;
&lt;li&gt;ChatGPT App&lt;/li&gt;
&lt;li&gt;MITM 代理工具（推荐 Reqable / mitmproxy / Charles）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;步骤&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;：设备连接代理，安装并信任 MITM 证书。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;：在代理工具中设置拦截规则：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;URL 匹配: api.revenuecat.com/v1/receipts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;请求方法: POST
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;动作: 拦截请求（Block）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;：打开 ChatGPT App，用一个&lt;strong&gt;临时账号&lt;/strong&gt;登录（不是目标账号！）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;：进入订阅页面 → 选择 Plus → 完成 Google Play 支付。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;：支付完成后，代理工具会拦截到 &lt;code&gt;POST /v1/receipts&lt;/code&gt; 请求。从请求体中提取 &lt;code&gt;fetch_token&lt;/code&gt; 字段，保存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 6&lt;/strong&gt;：&lt;strong&gt;阻断该请求&lt;/strong&gt;，不让它发到 RevenueCat。&lt;/p&gt;
&lt;p&gt;为什么要阻断？因为如果让请求正常到达 RevenueCat，token 就会被&amp;quot;消费&amp;quot;掉，绑定到临时账号上，就没法转移了。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;时间窗口&lt;/strong&gt;：token 拦截后有 &lt;strong&gt;72 小时&lt;/strong&gt;有效期。超时未被 Acknowledge，Google Play 会自动退款。所以别拖太久。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;如果你用 mitmproxy，这里有个现成的拦截脚本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# mitm_intercept.py — 自动拦截并保存 token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mitmproxy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;TOKEN_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tokens.jsonl&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RevenueCatInterceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPFlow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pretty_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/v1/receipts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fetch_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fetch_token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fetch_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;fetch_token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fetch_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;original_user_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;app_user_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;captured_at&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;T%H:%M:%SZ&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;used&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKEN_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[+] Token captured: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fetch_token&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 返回伪造响应，阻断真实请求&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;subscriber&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;entitlements&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}}}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[-] Parse error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;addons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;RevenueCatInterceptor&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 启动: mitmproxy -s mitm_intercept.py -p 8888&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;跑起来之后，每次支付完成，token 就自动保存到 &lt;code&gt;tokens.jsonl&lt;/code&gt;，一条一行，干净利落。&lt;/p&gt;
&lt;h3 id="方式二adb-直读-google-play-数据库需要-root"&gt;方式二：ADB 直读 Google Play 数据库（需要 Root）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt;：Google Play 的购买记录存在本地 SQLite 数据库 &lt;code&gt;library.db&lt;/code&gt; 里。Root 设备可以直接把数据库拉出来，用 SQL 查 token。&lt;/p&gt;
&lt;p&gt;这个方法的好处是&lt;strong&gt;不需要实时拦截&lt;/strong&gt;——支付完成后随时都能提取。坏处是需要 Root。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完整提取脚本&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;read_purchase_token.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;通过 ADB 从 Google Play library.db 读取 ChatGPT 购买 Token
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;OUTPUT_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;purchases.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DB_SRC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/data/data/com.android.vending/databases/library.db&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DB_SDCARD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/sdcard/.tmp_read_token.db&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DEVICE_SERIAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 留空自动选第一台设备&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;adb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;adb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;DEVICE_SERIAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEVICE_SERIAL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;adb not found&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_token&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Step 1: 用 root 权限把数据库复制到 sdcard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;shell&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;su -c &amp;#39;cp &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DB_SRC&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DB_SDCARD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; chmod 644 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DB_SDCARD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; echo DONE&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DONE&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;复制失败，请确认设备已 Root&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Step 2: pull 到本地&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkstemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.db&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;adb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pull&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DB_SDCARD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Step 3: SQLite 查询&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 自动识别表结构&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;PRAGMA table_info(ownership)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 找 JSON 数据列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;json_candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;inapp_purchase_data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;purchase_data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;json_col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;json_candidates&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;doc_col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;doc_id&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;doc_id&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 查 ChatGPT 相关购买记录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; SELECT account, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;doc_col&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json_col&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; FROM ownership
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; WHERE (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;doc_col&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; LIKE &amp;#39;%openai%&amp;#39; OR &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;doc_col&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; LIKE &amp;#39;%chatgpt%&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; AND &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json_col&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; IS NOT NULL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; ORDER BY rowid DESC
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Step 4: 解析并保存 token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unknown&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;raw_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;purchaseToken&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;purchaseToken&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;orderId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt; %H:%M:%S&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;邮箱: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Order: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Token: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;已保存 -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;共保存 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 条&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;adb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;shell&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rm -f &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DB_SDCARD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;read_token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;这段代码做了什么？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一步步拆解：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;su -c 'cp ...'&lt;/code&gt;&lt;/strong&gt; — Google Play 的数据库在 &lt;code&gt;/data/data/com.android.vending/&lt;/code&gt; 下，普通权限读不了，必须用 &lt;code&gt;su&lt;/code&gt;（Root）复制到 sdcard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;adb pull&lt;/code&gt;&lt;/strong&gt; — 把数据库从手机拉到电脑上&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLite 查询&lt;/strong&gt; — &lt;code&gt;ownership&lt;/code&gt; 表存了所有购买记录，用 &lt;code&gt;LIKE '%chatgpt%'&lt;/code&gt; 过滤出 ChatGPT 相关的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON 解析&lt;/strong&gt; — 购买数据是 JSON 格式存的，里面有 &lt;code&gt;purchaseToken&lt;/code&gt; 和 &lt;code&gt;orderId&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;数据库的 &lt;code&gt;ownership&lt;/code&gt; 表结构大概长这样：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;列名&lt;/th&gt;
 &lt;th&gt;类型&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;account&lt;/td&gt;
 &lt;td&gt;TEXT&lt;/td&gt;
 &lt;td&gt;Google 账号邮箱&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;doc_id&lt;/td&gt;
 &lt;td&gt;TEXT&lt;/td&gt;
 &lt;td&gt;应用包名+产品ID&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;inapp_purchase_data&lt;/td&gt;
 &lt;td&gt;TEXT&lt;/td&gt;
 &lt;td&gt;JSON 格式的购买数据&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;JSON 里面关键字段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;orderId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GPA.3305-3747-1234-56789&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;packageName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;com.openai.chatgpt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;productId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;oai.chatgpt.plus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;purchaseTime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1720000000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;purchaseState&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;purchaseToken&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;iekllednimgnlmo...（很长一串）&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;quantity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;acknowledged&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;autoRenewing&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;purchaseToken&lt;/code&gt;&lt;/strong&gt; 就是我们要的东西。拿到它，就能&amp;quot;转移&amp;quot;订阅。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四获取目标账号的-account_id"&gt;四、获取目标账号的 account_id
&lt;/h2&gt;&lt;p&gt;Token 到手了，接下来要知道&amp;quot;转给谁&amp;quot;——也就是目标 GPT 账号的 &lt;code&gt;account_id&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;API 调用&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GET https://android.chat.openai.com/backend-api/accounts/check/v4-2023-04-27
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Authorization: Bearer &amp;lt;目标账号的 JWT Token&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;响应里有个 &lt;code&gt;account_id&lt;/code&gt; 字段，UUID 格式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;account_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;f211fe99-83d9-4c48-b016-ee08984a592a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;target@example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;怎么拿 JWT Token？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最简单的方式：浏览器登录 ChatGPT → 访问 &lt;code&gt;https://chatgpt.com/api/auth/session&lt;/code&gt; → 响应里有 &lt;code&gt;accessToken&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;或者直接让用户给你 Session JSON，你从里面解析 &lt;code&gt;account.id&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五执行转移"&gt;五、执行转移
&lt;/h2&gt;&lt;p&gt;Token 有了，account_id 有了，最后一步：调 RevenueCat API，把 token 绑到目标账号上。&lt;/p&gt;
&lt;h3 id="完整转移脚本"&gt;完整转移脚本
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://api.revenuecat.com/v1/receipts&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;User-Agent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Dalvik/2.1.0 (Linux; U; Android 16; Pixel 9 Build/BP4A.260205.001)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-Platform&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;android&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-Platform-Flavor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;native&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-Platform-Version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;36&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-Version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9.22.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-Client-Bundle-ID&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;com.openai.chatgpt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-Observer-Mode-Enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-Custom-Entitlements-Computation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-Storefront&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;US&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bearer goog_DPguJtknNxbQBStStwhWGRsghUw&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;X-RevenueCat-ETag&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;fetch_token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;你拿到的 purchase token&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;product_ids&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;oai.chatgpt.plus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;platform_product_ids&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;product_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;oai.chatgpt.plus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;app_user_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;目标账号的 account_id&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;is_restore&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;observer_mode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;purchase_completed_by&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;revenuecat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;initiation_source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unsynced_active_purchases&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sdk_originated&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;payload_version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subscriber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;subscriber&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;entitlements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;entitlements&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;subscriptions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;chatgpt_plus&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entitlements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entitlements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;chatgpt_plus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;充值成功！&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; 账号 ID : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;original_app_user_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; 到期时间 : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;expires_date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; 产品标识 : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product_identifier&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;price&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; 价格 : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;currency&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; 订阅类型 : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;period_type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; 是否沙盒 : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;是&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_sandbox&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;否&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;未找到 chatgpt_plus 权益，可能失败了&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ensure_ascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;请求失败: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="逐行拆解"&gt;逐行拆解
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Headers 部分&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;User-Agent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Dalvik/2.1.0 (Linux; U; Android 16; Pixel 9 Build/...)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;伪装成 Android 设备的 ChatGPT App。RevenueCat 会检查 User-Agent，用浏览器的 UA 会被拒。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bearer goog_DPguJtknNxbQBStStwhWGRsghUw&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;这不是用户的 token&lt;/strong&gt;，这是 RevenueCat 的 &lt;strong&gt;公共 API Key&lt;/strong&gt;。每个使用 RevenueCat 的 App 都有一个，固定不变的。ChatGPT Android 的公钥就是这个。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Body 部分&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;fetch_token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;purchase token&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 第三步拿到的购买凭证&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;app_user_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;account_id&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 第四步拿到的目标账号 ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;is_restore&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt; &lt;span class="c1"&gt;# 告诉 RC 这是&amp;#34;恢复购买&amp;#34;，不是新购买&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;initiation_source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unsynced_active_purchases&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 来源标记&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;is_restore: True&lt;/code&gt; 是个关键细节——它告诉 RevenueCat “这不是一次新购买，而是恢复一个已有的购买”。这样 RC 不会创建新的订阅记录，而是把已有的 token 绑定到 &lt;code&gt;app_user_id&lt;/code&gt; 指定的账号上。&lt;/p&gt;
&lt;h3 id="成功响应长啥样"&gt;成功响应长啥样？
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;subscriber&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;entitlements&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;chatgpt_plus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;expires_date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-08-17T07:13:32Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;product_identifier&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;oai.chatgpt.plus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;purchase_date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-07-17T07:13:32Z&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;subscriptions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;oai.chatgpt.plus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;period_type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;trial&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;price&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;amount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;currency&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;JPY&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;store&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;play_store&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;expires_date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-08-17T07:13:32Z&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;management_url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://play.google.com/store/account/subscriptions?...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看到 &lt;code&gt;entitlements&lt;/code&gt; 里有 &lt;code&gt;chatgpt_plus&lt;/code&gt;，就说明&lt;strong&gt;转移成功&lt;/strong&gt;了。&lt;code&gt;expires_date&lt;/code&gt; 是到期时间。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六完整流程图"&gt;六、完整流程图
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌─────────────────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Phase 1: Token 获取 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 方式A: MITM 拦截 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Android + ChatGPT + Reqable/mitmproxy │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 完成 Google Play 支付 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 拦截 POST /v1/receipts → 提取 fetch_token │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 阻断请求 → 保存 token (72h 有效) │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 方式B: ADB 直读 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Root 设备 + ADB │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 读取 library.db → ownership 表 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 解析 JSON → 提取 purchaseToken │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└───────────────────────┬─────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌─────────────────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Phase 2: Token 使用 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 获取目标账号 account_id │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ GET /backend-api/accounts/check │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 或从 Session JSON 解析 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ↓ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ POST /v1/receipts │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ fetch_token = 保存的 token │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ app_user_id = 目标 account_id │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Authorization = RC 公钥 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ↓ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ RevenueCat 验证 → 通知 OpenAI → Plus 开通 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─────────────────────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="七规模化从手动到自动"&gt;七、规模化：从手动到自动
&lt;/h2&gt;&lt;p&gt;手动操作一两个账号没问题，但如果要做成服务呢？这就涉及到工程化了。&lt;/p&gt;
&lt;h3 id="架构设计"&gt;架构设计
&lt;/h3&gt;&lt;p&gt;一个完整的自动化系统大概长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌──────────┐ ┌────────────┐ ┌────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Google │ │ 拦截层 │ │ Token │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 账号池 │────▶│ mitmproxy │────▶│ 队列 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ + ADB │ │ (FIFO) │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└──────────┘ └────────────┘ └─────┬──────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌──────────┐ ┌────────────┐ ┌────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 目标 │ │ 充值 API │ │ CDK │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 账号 │◀────│ 自动提交 │◀────│ 卡密系统 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ RC 调用 │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└──────────┘ └────────────┘ └────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;核心组件&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;组件&lt;/th&gt;
 &lt;th&gt;职责&lt;/th&gt;
 &lt;th&gt;技术栈&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Google 账号池&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;存储可用的 Google 账号&lt;/td&gt;
 &lt;td&gt;数据库&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;ADB 自动化&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;自动完成 Play 支付流程&lt;/td&gt;
 &lt;td&gt;Python + ADB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Token 队列&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;缓存未使用的 purchase token&lt;/td&gt;
 &lt;td&gt;数据库/消息队列&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;CDK 卡密系统&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;发放激活码给终端用户&lt;/td&gt;
 &lt;td&gt;Web 应用&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;充值 API&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;接收用户 Session，调用 RC 完成开通&lt;/td&gt;
 &lt;td&gt;后端服务&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="数据库设计"&gt;数据库设计
&lt;/h3&gt;&lt;p&gt;核心就三张表：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;google_credential&lt;/strong&gt; — 记录每次 Google Play 购买：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;字段&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;google_email&lt;/td&gt;
 &lt;td&gt;Google 账号&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;order_id&lt;/td&gt;
 &lt;td&gt;订单号&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;purchase_token&lt;/td&gt;
 &lt;td&gt;购买凭证&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;product_id&lt;/td&gt;
 &lt;td&gt;产品标识&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;acknowledged&lt;/td&gt;
 &lt;td&gt;是否已确认&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;status&lt;/td&gt;
 &lt;td&gt;状态（未使用/已使用/已退款）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;card_key_id&lt;/td&gt;
 &lt;td&gt;关联的 CDK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;card_key&lt;/strong&gt; — CDK 卡密：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;字段&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;card_key&lt;/td&gt;
 &lt;td&gt;16位激活码&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;product_id&lt;/td&gt;
 &lt;td&gt;对应产品&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;credential_id&lt;/td&gt;
 &lt;td&gt;关联的购买凭证&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;status&lt;/td&gt;
 &lt;td&gt;状态（待使用/已使用/已禁用）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;redeem_record&lt;/strong&gt; — 充值记录：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;字段&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;card_key&lt;/td&gt;
 &lt;td&gt;使用的激活码&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;external_account_id&lt;/td&gt;
 &lt;td&gt;目标 GPT 账号 ID&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;status&lt;/td&gt;
 &lt;td&gt;结果（成功/失败）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;expires_date&lt;/td&gt;
 &lt;td&gt;订阅到期时间&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="用户端流程"&gt;用户端流程
&lt;/h3&gt;&lt;p&gt;做成产品后，终端用户看到的是这样的流程：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用户拿到 CDK 激活码
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;打开充值页面，输入激活码
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;系统验证激活码有效性
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用户粘贴自己的 ChatGPT Session JSON
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;系统解析 Session，提取 account_id
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;系统从 Token 队列取一个 token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;调用 RevenueCat API 完成转移
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;返回充值结果（成功/失败 + 到期时间）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;用户全程不需要知道底层发生了什么。他只需要：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入激活码&lt;/li&gt;
&lt;li&gt;粘贴 Session&lt;/li&gt;
&lt;li&gt;等几秒钟&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="八风控与注意事项"&gt;八、风控与注意事项
&lt;/h2&gt;&lt;h3 id="时间约束"&gt;时间约束
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;约束&lt;/th&gt;
 &lt;th&gt;时限&lt;/th&gt;
 &lt;th&gt;后果&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Token 未 Acknowledge&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;72 小时&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Google Play 自动退款&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RC 提交后自动 Acknowledge&lt;/td&gt;
 &lt;td&gt;立即&lt;/td&gt;
 &lt;td&gt;Token 被消费，无法再用&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;所以&lt;/strong&gt;：token 拦截后必须在 3 天内使用。提交给 RevenueCat 后就立即被 Acknowledge，相当于&amp;quot;消费&amp;quot;掉了。&lt;/p&gt;
&lt;h3 id="一-token-一账号"&gt;一 token 一账号
&lt;/h3&gt;&lt;p&gt;purchase token 一旦提交给 RevenueCat 并被 Acknowledge，就标记为&amp;quot;已消费&amp;quot;。&lt;strong&gt;不能重复使用&lt;/strong&gt;。一次支付 = 一个 token = 开通一个账号。&lt;/p&gt;
&lt;p&gt;这跟之前 iOS 收据复用漏洞不同——iOS 那个是同一张收据能反复用，Android 这个不行。&lt;/p&gt;
&lt;h3 id="x-post-params-hash"&gt;X-Post-Params-Hash
&lt;/h3&gt;&lt;p&gt;抓包时你可能注意到请求头里有个 &lt;code&gt;X-Post-Params-Hash&lt;/code&gt;，格式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;app_user_id,fetch_token:sha256:&amp;lt;hash值&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个是 &lt;code&gt;app_user_id&lt;/code&gt; 和 &lt;code&gt;fetch_token&lt;/code&gt; 拼接后的 SHA256。换了 account_id 后需要重新算。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但经过实测&lt;/strong&gt;：服务端不校验这个值。随便填或者留空都不影响结果。RevenueCat 可能是为了调试保留的字段，并没有做服务端校验。&lt;/p&gt;
&lt;p&gt;同样，&lt;code&gt;X-Nonce&lt;/code&gt; 这个字段也不校验。&lt;/p&gt;
&lt;h3 id="风控分析"&gt;风控分析
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;风控点&lt;/th&gt;
 &lt;th&gt;风险&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;RevenueCat 服务端&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;低&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;只验 token 真假，不检查 app_user_id 匹配&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Google Play 退款检测&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;中&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;频繁退款/争议可能被 Google 封号&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;OpenAI 行为分析&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;低&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;OpenAI 后端只看 RC 返回的订阅状态&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Google 账号风控&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;中&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;同一账号频繁订阅/退订会被标记&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="google-账号消耗"&gt;Google 账号消耗
&lt;/h3&gt;&lt;p&gt;如果走的是&lt;strong&gt;免费试用&lt;/strong&gt;路径（&lt;code&gt;period_type: &amp;quot;trial&amp;quot;&lt;/code&gt;, &lt;code&gt;price: 0.0&lt;/code&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个 Google 账号对 &lt;code&gt;oai.chatgpt.plus&lt;/code&gt; 只有 &lt;strong&gt;1 次&lt;/strong&gt;免费试用机会&lt;/li&gt;
&lt;li&gt;试用后该账号永久标记为&amp;quot;已使用试用&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;N 个 token 需要 N 个 Google 账号&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果走的是&lt;strong&gt;付费订阅&lt;/strong&gt;路径：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Token 被拦截未 Acknowledge → 72h 后自动退款 → 理论上可重新购买&lt;/li&gt;
&lt;li&gt;但频繁操作会触发 Google 风控&lt;/li&gt;
&lt;li&gt;建议每个账号不超过 3-5 次循环&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="九与-ios-收据漏洞的对比"&gt;九、与 iOS 收据漏洞的对比
&lt;/h2&gt;&lt;p&gt;很多人会把这个和 iOS 收据复用搞混，这里做个对比：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;维度&lt;/th&gt;
 &lt;th&gt;iOS 收据复用&lt;/th&gt;
 &lt;th&gt;Android Token 转移&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;支付平台&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;App Store&lt;/td&gt;
 &lt;td&gt;Google Play&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;中间层&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;直接对接 OpenAI&lt;/td&gt;
 &lt;td&gt;经过 RevenueCat&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;核心资产&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;iOS receipt（Base64）&lt;/td&gt;
 &lt;td&gt;purchase token&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;能否复用&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;能，一张收据开无数账号&lt;/td&gt;
 &lt;td&gt;不能，一个 token 只能用一次&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;成本模型&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;一次付费 → 无限开通&lt;/td&gt;
 &lt;td&gt;一次付费 → 一次开通&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;获取难度&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;需要 iOS 设备 + 拦截&lt;/td&gt;
 &lt;td&gt;Android + MITM 或 Root&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;修复状态&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;部分修复&lt;/td&gt;
 &lt;td&gt;未修复&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Android 这个转移漏洞的成本比 iOS 高（每开一个账号都需要一个新 token），但胜在&lt;strong&gt;稳定&lt;/strong&gt;——因为它利用的是 RevenueCat 的设计缺陷，不是简单的验证遗漏，修起来牵涉面更广。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十总结"&gt;十、总结
&lt;/h2&gt;&lt;p&gt;整条攻击链路其实非常清晰：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Google Play 支付 → 拦截 purchase token → 阻断 RevenueCat 回调
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;目标账号 account_id ← Session 解析 ← 用户提交
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;POST /v1/receipts (token + account_id) → RevenueCat 验证
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; OpenAI 开通 Plus
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;漏洞本质&lt;/strong&gt;：RevenueCat 作为订阅中间层，在验证 purchase token 时不检查 &lt;code&gt;app_user_id&lt;/code&gt; 与实际付款人的对应关系。它只关心两件事：token 是不是真的、token 有没有被用过。至于&amp;quot;谁付的钱&amp;quot;和&amp;quot;给谁开通&amp;quot;是不是同一个人——它不在乎。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么 OpenAI 不修？&lt;/strong&gt; 大概率不是不能修，而是不值得。要修这个洞，要么放弃 RevenueCat 自建订阅系统（工程量巨大），要么在 RevenueCat 侧增加 app_user_id 校验（需要 RC 配合改 API）。对于一个 B 端收入占大头的公司来说，个人订阅的漏洞优先级确实不高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技术要点回顾&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;架构理解&lt;/strong&gt; — Google Play → RevenueCat → OpenAI 三层解耦&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token 获取&lt;/strong&gt; — MITM 拦截（简单）或 ADB 直读 library.db（需 Root）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;account_id&lt;/strong&gt; — 从 &lt;code&gt;/accounts/check&lt;/code&gt; API 或 Session JSON 获取&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API 调用&lt;/strong&gt; — &lt;code&gt;POST /v1/receipts&lt;/code&gt; 携带 token + account_id&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时间窗口&lt;/strong&gt; — 72 小时，过期自动退款&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后提醒：&lt;strong&gt;本文仅供技术研究和安全交流&lt;/strong&gt;。Google Play 订阅体系的安全性问题是一个值得关注的研究方向，但请不要将本文技术用于非法用途。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="参考资料"&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.revenuecat.com/" target="_blank" rel="noopener"
 &gt;RevenueCat 官方文档 - POST /v1/receipts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://developer.android.com/google/play/billing/security" target="_blank" rel="noopener"
 &gt;Google Play Billing Library 安全最佳实践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://owasp.org/www-project-mobile-top-10/" target="_blank" rel="noopener"
 &gt;OWASP Mobile Top 10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.mitmproxy.org/" target="_blank" rel="noopener"
 &gt;mitmproxy 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://developer.android.com/tools/adb" target="_blank" rel="noopener"
 &gt;Android Debug Bridge (ADB) 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;标签&lt;/strong&gt;: #ChatGPT #GPTPlus #GooglePlay #RevenueCat #订阅转移 #Android #支付安全 #技术科普&lt;/p&gt;</description></item><item><title>GPT Plus 代充漏洞揭秘 - 一张收据给无数账号开 Plus 的骚操作</title><link>https://blog.7qiang.com/posts/gpt-plus-receipt-vulnerability-2026/</link><pubDate>Thu, 16 Apr 2026 11:45:00 +0000</pubDate><guid>https://blog.7qiang.com/posts/gpt-plus-receipt-vulnerability-2026/</guid><description>&lt;img src="https://blog.7qiang.com/blog-cover-cyber-mask.gif" alt="Featured image of post GPT Plus 代充漏洞揭秘 - 一张收据给无数账号开 Plus 的骚操作" /&gt;&lt;section class="xiaoyao-contact-card" aria-label="联系逍遥"&gt;
 &lt;img class="xiaoyao-contact-card__qr" src="https://blog.7qiang.com/contact-qr.png" width="690" height="690" loading="lazy" decoding="async" alt="逍遥微信二维码"&gt;
 &lt;div class="xiaoyao-contact-card__content"&gt;
 &lt;h3 class="xiaoyao-contact-card__title"&gt;联系逍遥&lt;/h3&gt;
 &lt;p class="xiaoyao-contact-card__description"&gt;扫码添加微信，或点击下方按钮复制联系方式。&lt;/p&gt;
 &lt;div class="xiaoyao-contact-buttons" data-contact-buttons&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--wechat" type="button" data-copy-contact="plus6566" data-copy-label="微信号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;微信&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;plus6566&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--qq" type="button" data-copy-contact="826215906" data-copy-label="QQ 号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;QQ&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;826215906&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="xiaoyao-contact-feedback" role="status" aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;

 &lt;/div&gt;
&lt;/section&gt;

&lt;p&gt;进微信群请联系博主，各位觉得文章对你有帮助的话可否打赏一些呀~&lt;/p&gt;
&lt;hr&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;⚠️ 时效性提醒（2026-07 更新）&lt;/strong&gt;：本文发布于 2026 年 4 月，距今已过去 3 个月。iOS 收据复用漏洞据反馈已被 OpenAI 部分修复，&lt;strong&gt;当前可用性存疑&lt;/strong&gt;。本文保留仅供安全研究与技术原理学习参考。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="前言"&gt;前言
&lt;/h2&gt;&lt;p&gt;兄弟们，今天来扒一个超级劲爆的漏洞——&lt;strong&gt;GPT Plus 代充背后的秘密&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;你是不是经常在闲鱼上看到二三十块钱一个月的 GPT Plus 代充？是不是好奇人家咋做到的？今天我就把这个漏洞给你扒得明明白白！&lt;/p&gt;
&lt;p&gt;先说好啊，&lt;strong&gt;本文仅供技术交流&lt;/strong&gt;，任何实际操作产生的账号封禁、法律风险，自行承担！别怪我没提醒你~&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="原理openai-验票机制的重大漏洞"&gt;原理：OpenAI 验票机制的重大漏洞
&lt;/h2&gt;&lt;h3 id="正常流程是啥样的"&gt;正常流程是啥样的？
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你 iPhone 上点付款
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App Store 扣钱
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Apple 把收据扔到你手机本地
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ChatGPT App 捡起收据
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App 把收据和你当前登录的账号 token 一起打包发给 OpenAI
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;OpenAI 验一下收据真假
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;给你账号开 Plus
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看起来挺严谨对吧？&lt;strong&gt;屁！&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="漏洞在哪"&gt;漏洞在哪？
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;OpenAI 验票的时候，根本不看这张收据是你 Apple ID 买的，还是隔壁老王 Apple ID 买的！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它只看两样东西：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;收据合不合法&lt;/li&gt;
&lt;li&gt;你传过来的 ChatGPT auth token 是不是活的&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;说白了&lt;/strong&gt;：什么 Apple ID 绑定、什么账号对应关系，全！不！查！&lt;/p&gt;
&lt;p&gt;这就好比你拿着别人的购物小票去柜台领东西，柜员只看小票真假，不看你身份证…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;收据合法 + token 有效 = 任意账号变 Plus&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="漏洞利用全流程"&gt;漏洞利用全流程
&lt;/h2&gt;&lt;h3 id="第一步搞一个土耳其区-apple-id"&gt;第一步：搞一个土耳其区 Apple ID
&lt;/h3&gt;&lt;p&gt;为啥是土耳其？因为&lt;strong&gt;便宜&lt;/strong&gt;啊！&lt;/p&gt;
&lt;p&gt;土耳其区 Plus 标价 499 里拉一个月，折人民币大概&lt;strong&gt;八十五块&lt;/strong&gt;。国内定价多少？一百四五！差价自己算~&lt;/p&gt;
&lt;p&gt;往这个土区 Apple ID 里充好礼品卡，钱备足。&lt;/p&gt;
&lt;h3 id="第二步拦截收据"&gt;第二步：拦截收据
&lt;/h3&gt;&lt;p&gt;在你 iPhone 上打开 ChatGPT App，&lt;strong&gt;别登录你想开 Plus 的目标账号&lt;/strong&gt;，随便登个临时号或者干脆不登。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心操作&lt;/strong&gt;：内购付款之前，配置网络拦截——阻止 ChatGPT App 把收据发给 OpenAI 服务器。&lt;/p&gt;
&lt;p&gt;App Store 那边照样扣钱，Apple 照样把收据扔到你手机本地沙盒目录里。但因为你的拦截，这张收据没自动飞向 OpenAI，而是老老实实待在本地等你来拿。&lt;/p&gt;
&lt;h3 id="第三步导出收据"&gt;第三步：导出收据
&lt;/h3&gt;&lt;p&gt;导出收据通常需越狱设备或利用系统漏洞。实际主流玩法有三种：&lt;/p&gt;
&lt;h4 id="方法-a端点本地映射"&gt;方法 A：端点本地映射
&lt;/h4&gt;&lt;p&gt;用 DNS 劫持或本地代理，把 ChatGPT App 发往 OpenAI 的请求重定向到你自己的本地服务器。请求里本来就带着 Base64 编码的收据，到了你本地，直接保存下来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工具&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mitmproxy&lt;/li&gt;
&lt;li&gt;Charles Proxy&lt;/li&gt;
&lt;li&gt;自建 HTTPS 代理加一张自签证书&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不用越狱，门槛低得令人发指。&lt;/p&gt;
&lt;h4 id="方法-b越狱加-hook"&gt;方法 B：越狱加 Hook
&lt;/h4&gt;&lt;p&gt;越狱设备上用 Frida 或者 Flex，直接 hook StoreKit 框架，截下 &lt;code&gt;SKPaymentTransaction&lt;/code&gt; 的 &lt;code&gt;transactionReceipt&lt;/code&gt;，或者读 &lt;code&gt;appStoreReceiptURL&lt;/code&gt; 拿收据文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;粗暴直接&lt;/strong&gt;。&lt;/p&gt;
&lt;h4 id="方法-c安卓径用-xposed-hook"&gt;方法 C：安卓径用 Xposed Hook
&lt;/h4&gt;&lt;p&gt;逻辑类似，不细说了。&lt;/p&gt;
&lt;h3 id="第四步api-补单"&gt;第四步：API 补单
&lt;/h3&gt;&lt;p&gt;收据到手，直接往 OpenAI 的订阅接口发请求：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST https://chat.openai.com/backend-api/payments/subscribe
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Content-Type: application/json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Authorization: Bearer &amp;lt;目标账号的 auth token&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;receipt&amp;#34;: &amp;#34;&amp;lt;Base64 编码的 iOS 收据&amp;gt;&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;platform&amp;#34;: &amp;#34;ios&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;product_id&amp;#34;: &amp;#34;com.openai.chatgpt.plus&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;device_info&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;model&amp;#34;: &amp;#34;iPhone14,2&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;os_version&amp;#34;: &amp;#34;17.0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;timezone&amp;#34;: &amp;#34;Asia/Shanghai&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：这里的 auth token 是目标用户给你的登录令牌，权限有限，有效期短。你给他充完 Plus，他立刻改密码，你拿着这张收据去充下一个。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="骚操作来了"&gt;骚操作来了！
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;一张土耳其收据八十五块人民币成本，能给无数个账号开 Plus！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;闲鱼上那些二三十块钱一个月的 GPT Plus 代充，就是这么来的。你以为人家做慈善？人家是&lt;strong&gt;拿一张票反复上车&lt;/strong&gt;！&lt;/p&gt;
&lt;h3 id="成本计算"&gt;成本计算
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;项目&lt;/th&gt;
 &lt;th&gt;成本&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;土耳其区 Apple ID&lt;/td&gt;
 &lt;td&gt;免费（自己注册）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;单张收据成本&lt;/td&gt;
 &lt;td&gt;¥85&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;可开通账号数&lt;/td&gt;
 &lt;td&gt;∞（理论无限）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;单账号成本&lt;/td&gt;
 &lt;td&gt;趋近于 0&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;这利润空间…啧啧啧&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="为啥-openai-不修复"&gt;为啥 OpenAI 不修复？
&lt;/h2&gt;&lt;p&gt;很多人会问：这么大的漏洞，OpenAI 为啥不补？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;真相&lt;/strong&gt;：不是补不了，是&lt;strong&gt;优先级不够&lt;/strong&gt;，或者说，他们根本不在乎这点订阅收入的漏洞。&lt;/p&gt;
&lt;p&gt;毕竟大头在企业 API 那边，个人订阅那点钱，对 OpenAI 来说就是毛毛雨~&lt;/p&gt;
&lt;p&gt;而且这个漏洞还有个特点：&lt;strong&gt;需要 iOS 设备 + 网络拦截能力&lt;/strong&gt;，门槛其实不低。普通用户根本玩不转，所以影响范围有限。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="风险提示"&gt;风险提示
&lt;/h2&gt;&lt;p&gt;⚠️ &lt;strong&gt;重要提醒&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;账号风险&lt;/strong&gt;：用这种方式开通的 Plus 账号，随时可能被 OpenAI 封禁&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;法律风险&lt;/strong&gt;：在某些地区可能涉及欺诈行为&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;道德风险&lt;/strong&gt;：本质上是利用漏洞薅羊毛&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;本文仅供技术交流，请勿用于非法用途！&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="如何防范"&gt;如何防范？
&lt;/h2&gt;&lt;h3 id="对于-openai"&gt;对于 OpenAI
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;验证收据与 Apple ID 的绑定关系&lt;/li&gt;
&lt;li&gt;限制单张收据的使用次数&lt;/li&gt;
&lt;li&gt;增加设备指纹验证&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="对于用户"&gt;对于用户
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;走官方渠道购买 Plus&lt;/li&gt;
&lt;li&gt;不要贪便宜买低价代充&lt;/li&gt;
&lt;li&gt;定期检查账号状态&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;p&gt;这个漏洞的本质是&lt;strong&gt;OpenAI 在收据验证环节偷懒了&lt;/strong&gt;，只验证收据真假，不验证收据与账号的对应关系。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技术要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;土耳其区 Apple ID（便宜）&lt;/li&gt;
&lt;li&gt;网络拦截（mitmproxy/Charles）&lt;/li&gt;
&lt;li&gt;收据导出（越狱/Hook）&lt;/li&gt;
&lt;li&gt;API 补单（重复使用收据）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;最后再说一遍&lt;/strong&gt;：本文仅供技术交流，请勿用于非法用途！想稳定用 Plus，还是走官方渠道吧~&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;觉得有用？别忘了分享给更多需要的朋友！👍&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;关注我，下期继续扒更多有趣的技术漏洞~&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>