⌨ 技术

极客逆向开发图书馆抢座助手

标签: 逆向工程 JavaScript 浏览器扩展 油猴脚本 算法

💡 起因:在人海中寻找那张“连座”

高校图书馆的座位总是供不应求,尤其是在期末考试季,想和朋友找个“并排”或者“对坐”的连座简直比登天还难。为了解决这个痛点,我决定动手写一个“座位助手”插件,一键扫描全自习室的空闲连座。

但随着开发的深入,我发现事情没那么简单:我们学校(MUC)的海淀和丰台两个校区,因为建设批次不同,图书馆系统使用了完全不同的底层架构和加密机制

这注定是一场与系统架构师“斗智斗勇”的硬核逆向之旅。

---

🏛️ 海淀校区:全量快照与时空防碰撞引擎(主动出击)

海淀校区的接口相对古老且“大开大合”,没有复杂的动态加密,因此我采用了**“主动出击 + 全量快照分析”**的策略。

1. 无感鉴权与快照拉取

通过重写底层的 XMLHttpRequest 和 fetch,再加上对 localStorage 和 Vuex 状态树的定时轮询,插件能在用户登录的瞬间“偷”到鉴权 Token。

拿到 Token 后,插件直接调用最核心的快照接口 /ic-web/reserve。这个接口非常给力,一次请求就能拉回整个房间全天所有座位的被预约记录。

2. 高并发时空防碰撞计算

拿到全天数据后,真正的挑战是计算时间轴的重叠。

比如你想找 14:00 - 18:00 的座位,而某个座位在 15:00 - 16:00 被人占了。在本地,我写了一个精密的时空碰撞算法:

JavaScript

// 只要满足以下条件,即判定为“时空碰撞”
if (endTs > r.startTime && startTs \< r.endTime) {
isConflict \= true;
break;
}

经过碰撞剔除后,剩下的就是在这个时间段内绝对纯净的空闲座位

3. 应对“上古遗留”的移动端代码

在测试时遇到一个坑:如果用手机浏览器访问海淀系统,官方会强制定向到 mobile.html。而且官方在这个页面里使用了早已被现代浏览器淘汰的 document.write(),导致控制台疯狂报 [Violation] 警告。

为了完美兼容,我通过深度匹配 DOM 和路由,让插件无视官方的报错,在移动端也能稳定运行。

---

🏢 丰台校区:HMAC 破壁与寄生虫拦截(借刀杀人)

如果说海淀是“平原冲锋”,那丰台新版系统就是“铁壁防守”。

丰台系统加入了极强的防爬虫机制:每次请求的 Header 里都有 x-request-date、x-request-id 以及经过极其复杂的前端混淆计算出的 HMAC 动态签名(x-hmac-request-key)。如果外挂敢自己伪造发 fetch,哪怕错了一个字符,也会被服务器以 403 Forbidden 无情拒绝。

面对这种级别的加密,硬刚是不明智的。我选择了最优雅的**“寄生虫被动截胡”**策略。

1. 放弃主动请求,全面监听

彻底不发任何网络请求,而是借用官方前端的“刀”。

当用户在官方网页上点击某个自习室时,官方自带的 JS 会乖乖地算好那个恶心的 HMAC 签名并发出合法请求。此时,我在底层挂载的探针触发,神不知鬼不觉地将服务器返回的纯净空位数据(freeSeatIdsDuration)复制到外挂的本地缓存池中。

2. 智能 AB/CD 区域拓扑匹配

丰台校区虽然反爬严格,但在物理设计上高度模块化。经过实地踩点和逆向推导,我发现:

  • 所有 A区 / B区 的物理布局完全一致。
  • 所有 C区 / D区 的物理布局完全一致。
    基于此,我编写了“智能路由识别”。插件在截获数据的同时,抓取网页标题。如果检测到标题含有 A/B,就自动套用 AB_LAYOUT 的图论字典;检测到 C/D 就套用 CD_LAYOUT,极大减少了维护成本。

---

🧠 核心算法:基于 DFS 的连通图搜索

无论是海淀还是丰台,拿到空闲座位后,如何找出“连座”才是核心。

我在代码中内置了各区域的静态二维拓扑图(JSON 邻接表),定义了每个座位的 side(并排)和 face(对坐)关系。然后运用深度优先搜索 (DFS) 算法:

JavaScript

function dfs(path) {
if (path.length \=== N) { // 找到了指定人数的连座
let pathId \= [...path].sort().join(',');
if (!clusters.has(pathId)) {
clusters.add(pathId); results.push(path);
}
return;
}
// ... 沿着拓扑图的 side/face 节点继续下探
}

这套算法完全在浏览器本地内存中运行,0 网络开销,在不到 1 毫秒的时间内,就能将散落的空位拼凑成完美的连座矩阵!

---

🎨 细节打磨:极致的 UI/UX 与移动端自适应

为了让这个插件不仅“能用”,还要“好用且好看”,我在 UI 层下了狠功夫:

  1. 赛博朋克风渲染:抛弃了传统的表格,改用纯 CSS 绘制“暗黑霓虹方块”,通过 linear-gradient 和发光边框,将绝配的连座组合直观地展示给用户。
  2. 移动端 Touch 引擎防穿透:为了在手机上能顺滑拖拽悬浮面板,我抛弃了单纯的 mousedown,接入了 touchstart/touchmove 事件。并且加入了 e.preventDefault() 阻断和 closest() 寻址,保证拖动面板时背后的网页不会跟着乱晃,同时点击内部按钮也不会误判为拖拽。
  3. 真·按需加载(动态 DOM):在面板的“支持打赏”功能中,最初使用了普通的 \<img> 或 data-src 懒加载,但发现依然会有不可控的预解析。最终重构为动态 DOM 插入——只有在用户点击按钮的瞬间,JS 才会 createElement('img') 并赋予真实 URL。不点开弹窗,绝对不消耗用户一丁点流量。

---

🏁 结语:微服务架构的胜利

在开发中期,我曾试图将两个校区的代码塞进同一个文件中,结果因为上下文错乱、API 张冠李戴导致了严重的 404 错误。

这让我意识到,针对完全不同底层架构的系统,“物理隔离”才是最稳妥的做法。最终,我将插件拆分为了 inject_haidian.js 和 inject_fengtai.js 两个独立的组件。在浏览器扩展版本中,由 content.js 充当智能路由器,根据当前 URL 决定注入哪个核心。

做逆向开发的过程,就像是在和系统的架构师下棋。面对古老的系统,我们拼的是算法效率与兼容性;面对严密防守的新系统,我们玩的是太极和借力打力。

技术的魅力,莫过于此!🚀

评论区

共 0 条
🍃 还没有评论,沙发虚位以待~
🚇

穿越虫洞?

虫洞会把你随机传送到另一位陌生博主的网站——
可能是技术大佬,也可能是诗歌爱好者,开盲盒的时间到了。

森林精灵