JS逆向实战:优雅去除 PVE “无效订阅”提示

大家好,好久不见了,今天分享一种利用 JS Hook 技术去除 Proxmox Virtual Environment (PVE) “无效订阅”弹窗的方法。相较于直接修改源码,此方案更为优雅,可避免因 PVE 版本更新导致修改失效的问题。

PVE

首先,为不熟悉 PVE 的小伙伴简单介绍一下:

PVE(Proxmox Virtual Environment)是一个开源的服务器虚拟化管理平台。它基于Debian Linux,集成了KVM(Kernel-based Virtual Machine)用于虚拟机管理和LXC(Linux Containers)用于容器管理。PVE允许你在同一台物理服务器上运行多个独立的操作系统实例,无论是Windows、Linux虚拟机还是轻量级容器。它提供了一个直观的Web界面,方便用户创建、配置、监控和管理这些虚拟资源,是构建私有云或虚拟化环境的理想选择。

为什么会有这个需求呢?因为我之前在家里搭了一个 PVE 服务端,开了一堆虚拟机和容器,顺便也可以把它当做 nas 来使用,开几个 smb 文件夹,还是很香的。

PVE 完全开源免费,它的商业模式是提供增值服务,如果不订阅也不会影响任何功能的使用。对于个人用户而言,免费的无订阅模式已足够使用,其主要区别在于更新源的稳定性——无订阅源可能包含未充分测试的更新,不建议用于生产环境。

image-20250807214052171

目前我使用 2000 多块的硬件跑了 2 年+也挺稳定的,除非有更新否则不用关机,完全满足我的需求。

尽管功能不受影响,但使用无订阅源会在每次登录成功后弹出一个“无有效订阅”的提示框。有时该弹窗的“确定”按钮甚至无法点击,必须刷新页面才能关闭,这在日常使用很是烦人。

传统解决方案是直接修改 PVE 的前端 JS 源码,但这种方式的缺点在于,一旦 PVE 面板更新,就需要重新修改,操作较为繁琐。因此,我采用 JS 逆向的思路,通过编写油猴(Tampermonkey)脚本来 Hook 关键函数,从而一劳永逸地屏蔽该弹窗。image-20250807213911048

寻找 hook 点

image-20250807220716721

要 hook 弹窗,首先要找到弹窗的地方,这里可以直接参考网上其他人的方法,使用关键字 data.status 来搜索,基本上第一个结果就是我们要找的地方。

image-20250807221832486

image-20250807220849422

或者可以完整的执行一遍登录的流程,查看所有的接口请求,根据关键字找到 URL,然后搜索 URL 也可以找到弹窗的地方。

hook 弹窗

找到 hook 点以后,就要思考如何让弹窗无法弹出来,先分析一下代码。

image-20250807222020657

首先请求使用了一个工具函数 Proxmox.Utils.API2Request 发出,这里是第一个 hook 点,可以判断 URL 是否是特定的 URL,然后直接忽略请求,也就不会执行请求的 callback 代码了。继续往下看,弹窗是由另一个工具函数 Ext.Msg.show 弹出来的,这里是第二个 hook 点,可以 hook 弹窗的工具函数,让它在某个 title 的时候不执行。

继续往下看,请求成功以后会执行一个 orig_cmd 方法,为了不影响正常的调用流程,我这里选择的第二个 hook 点,在弹窗的时候判断是否是无订阅的弹窗,然后让其不再执行。

hook 代码

新建一个油猴脚本,写入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ==UserScript==
// @name PVE 移除订阅按钮
// @namespace http://tampermonkey.net/
// @version 2025-08-06
// @description try to take over the world!
// @author You
// @match https://xxxxx:8006/
// @icon https://www.google.com/s2/favicons?sz=64&domain=233.27
// @grant none
// @run-at document-idle
// ==/UserScript==

(function () {
'use strict';
console.log('loaded!');
let s = Ext.Msg.show;
Ext.Msg.show = function (a) {
if (a.title === '无有效订阅') {
console.log('return!')
return;
} else {
s(a);
}
}
})();

代码很简单就不多解释了,别忘了添加:// @run-at document-idle,它的作用是让油猴脚本在页面加载完成后再注入,因为过早的注入可能工具函数还没有初始化完成,会导致注入失败。

hook 尝试

image-20250807222756063

刷新页面会发现报错了,提示没有找到 show 属性,油猴注入的时机已经很晚了,在页面加载后才执行,为什么还会报错呢?猜测可能是因为有些代码不是立马就初始化的,可能使用了 settimeout 之类的方案使得初始化被延迟执行了。

分析具体的执行逻辑比较麻烦,我这里选择一个比较简单的方案,同样使用 settimeout 延迟执行,因为我测试了一下在页面加载完成后手动执行 hook 脚本是可以正常执行的。

修改后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function () {
'use strict';
setTimeout(function () {console.log('loaded!');
let s = Ext.Msg.show;
Ext.Msg.show = function (a) {
if (a.title === '无有效订阅') {
console.log('return!')
return;
} else {
s(a);
}
}},2000)
})();

刷新页面重新测试。

image-20250807225651735

image-20250807225754448

可以看到,控制台成功打印了加载信息,登录后的弹窗也不再出现。至此,问题完美解决!

总结

以上就是本次利用 JS Hook 去除 PVE 订阅提示的全过程。相较于直接修改源码,这种方式无疑更加灵活和持久,感兴趣的小伙伴可以自己试一下。

本文章首发于个人博客 LLLibra146’s blog

本文作者:LLLibra146

更多文章请关注公众号 (LLLibra146):LLLibra146

版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!

本文链接
https://blog.d77.xyz/archives/3c3c2dca.html