跳到正文
Hacker News· yewenjie·· 23 小时前AI 评分22

为绕开健身房 App 的社交信息流,我用 GLM 5.3 和 DeepSeek v4.1 Flash 逆向出签到二维码

My gym forced a social media down my throat. So I reverse-engineered its app

AI 导读

一名用户因不满健身房 App 强制展示社交信息流,用 GLM 5.3 和 DeepSeek v4.1 Flash 辅助逆向其 Flutter 应用,提取密钥后自建 SvelteKit 网页版,可在本地生成签到二维码并通过闸机。

正文 · AI 翻译

我最近加入了一家新健身房。这家健身房相当不错,设备好,管理好,品牌形象也好。

还有一个应用。

它不是那种随便糊弄、靠氛围编程拼出来的烂应用。作为产品来说,它其实相当不错。UI/UX 很流畅,设计语言经过深思熟虑且保持一致。我只在里面发现了一个 bug。

不过对我来说,它唯一的用途就是在闸机处出示二维码来签到和签退。

然而,问题来了。应用的主页是一个社交信息流。你签退之后,它会提示你发布一条本次锻炼的动态。主页信息流里全是人们的健身房自拍。每次我打开这个应用,都被迫看到一些本质上像 Tinder 的东西。

我朋友认为这是个天才的商业模式。按她的说法,健身的人真的很想炫耀自己的身体,但把这种内容发到普通社交媒体上会遭人非议。所以这家健身房公司确实找到了很好的产品市场契合点。

我觉得她说得对。

但是,对于我不掌控的社交媒体和信息流,我的态度非常强硬。任何给我展示我无法关闭、排序或过滤的信息流的应用——我都视之为针对我的心理战。

我已经超过 12 年没用过社交媒体了。我非常坚决地屏蔽广告、屏蔽 YouTube 评论、Shorts 和推荐视频,以及净化 YouTube 标题和缩略图。对于我不得不用东西,比如 LinkedIn1,我会用浏览器扩展把新闻信息流彻底屏蔽掉。

所以我把它当成了针对我个人的事。

我想看看能不能把二维码的逻辑提取出来,而不带上应用里那些多余的累赘。

于是我开始挖掘。我很快发现,代码每 15 秒变化一次。那个界面上也禁止截图,这与 Android 的 FLAG_SECURE 一致。我怀疑这是某种 TOTP。生成是在服务端进行的吗?

我做了最简单的事:关掉 Wi-Fi。应用立刻告诉我,在不重新联网的情况下,这些代码还会在 N 天内保持有效2。

好,所以它可以在本地生成这些代码。

我用了 GLM 5.3 和 DeepSeek v4.1 Flash。在我的使用过程中,GLM 消耗的 token 成本大约是后者的 10 到 20 倍,却没有取得多大进展。DeepSeek 似乎推进得快得多。

需要说明的是,为了保持健身房和我自己的匿名,我省略了一些细节。我不想丢掉我的健身房会员资格。我其实挺喜欢那家健身房的。

这个应用是用 Flutter 写的,大部分逻辑被编译成了 Dart 原生代码。我们用 blutter 来检查这部分,用 jadx 检查 Android 侧的代码,用 Android 的 SDK 工具检查安装包。后来,我们用 OpenSSL 和 WebCrypto 来验证加密,用 zbar 独立解码二维码。

我时不时地给 DeepSeek 一些提示,主要靠直觉,因为我以前从没做过这种事。有时它似乎会迷失在一个方向上,而我觉得实现方式肯定比那更简单。只要告诉它我的猜测,或者说我认为一定有更简单的办法,就能帮它转向正确的方向。

我们很快弄明白了,二维码是一个 URL,包含四样东西:编码后的账户标识、加密的过期时间戳、一个随机 nonce,以及一个校验和。

过期时间是用 AES-128-CTR 加密的。校验和是用另一个独立的密钥计算的。所以我们需要这两个密钥来获取我的账户信息。

我最初的想法是让 DeepSeek 驱动一个 Android 模拟器,登录并提取密钥。但我很快意识到,我从朋友那里借来的那台用了十年的笔记本电脑,根本无法以任何合理的速度运行 Android 软件模拟。

DeepSeek 建议使用一台已 root 的手机。这时我想起我手边正好有一台能用的已 root 的 Android 手机。

我把它启动起来,通过无线 ADB 连接,然后让 DeepSeek 去调查。结果发现,这个应用会记录我们关心的那些调用的请求和响应负载。密钥材料就放在手机上一个纯文本日志文件里。

这个项目有一半靠的是 ripgrep。

提取出密钥后,我们构建了一个简单的 SvelteKit Web 应用来生成二维码。

过程中有几个错误的假设。过期时间戳需要用毫秒而不是秒。编码是 base64url,而不是标准 base64,并且保留了填充。

我们还有一个错误的 AES 实现,它自己的往返测试能通过。它加密一些内容,再解密,得到了原始输入。但两个方向共享同样的错误,所以这并不能证明它就是 AES。

与 WebCrypto 对比后暴露了差异。OpenSSL 和已知测试向量证实了这一点。浏览器应用使用的是 WebCrypto。

然后我们对照了原应用的实际输出。给定相同的输入,我们的实现生成了字节完全一致的 URL。我们还独立解码了二维码图像,以检查其中是否包含正确的 URL。

到那时,我们有了一个可用的原型。这个 Web 应用可以在本地用提取出的密钥生成二维码。这并没有解决独立登录的问题,也没有消除密钥轮换时需要获取新密钥的需求,但已经足够去健身房试一下了。

第二天,我去了那里,非常自豪地向朋友展示我构建的东西,但仍然有点不确定它在闸机口能不能用。

结果,它真的能用!

签到和签退。

再也不用仅仅为了进入某个场所去举点铁,就被迫让注意力被那些暗黑模式、操纵性的社交媒体劫持了。

脚注

  1. 很长一段时间里,我的 LinkedIn 简介都是“我讨厌 LINKEDIN”。有人告诉我,LinkedIn 结合了企业界最糟糕的部分和社交媒体最糟糕的部分,而我认为这只描述了它实际糟糕程度的一半。但唉,我现在正在找工作,需要 LinkedIn 个人资料来投递申请。↩
  2. 我不透露具体天数,是为了帮助保持这个应用的匿名性。↩

来源:Hacker News · shayon.work