0%

OpenVPN Access Server 2.13.x 破解在线人数限制

后记:
由于 2.9.x 以上版本由于使用了 Python3 编写, 生成的库文件格式已变化, 本补丁无法处理, 本补丁目前最高支持 2.8.8 版本!
实测支持 2.5.x2.13.x 版本.

OpenVPN_Access_Server.png

前言

本人家里使用了运行了一台服务器, 为了方便远程管理, 最近在 Docker 中用 OpenVPN Access Server 跑了一个 OpenVPN 服务器, 但是想试试这个东西的最大负载, 怎奈免费版本的 OpenVPN Access Server 只允许两个连接, 本着孜孜不倦的精神于是上网冲浪一番深入研究了一下.

在网上博览群文之后发现流行的爆破补丁都是针对 2.5.0 版本的, 直接拿来用肯定是不行的, 经过分析发现 OpenVPN Access Server 的作者对盗版这个事情并不是很在意, 从 2.5.0 版本开始, 对于授权这款的代码基本是没改过的, 所以在结合网络上面其他人的博文搞了一番并且成功了之后, 特别在这里记录一下.

最开始我是时候 linuxserver 的成品镜像来跑的, 不过发现镜像版本大于 2.8.0 就跑不起来了, 所以现在我都是自己构建 Docker 镜像, 架梯子下载的, 不会出现下载不下来的情况了!
跑不起来的原因可以参考: https://github.com/linuxserver/docker-openvpn-as/issues/108
简单说就是大于 2.8.0 的新版镜像改为启动时安装 openvpn-as, 受制于墙后的网络环境很难下载成功安装包, 肯定就跑不起来了.

过程

环境准备

OpenVPN Access Server 是使用 Python 编写的, 需要并且限制了 Python Runtime 的版本, 两者必须匹配!

OpenVPN Access Server 2.9.0 以前, Python Runtime 的版本是 Python 2.8, 但是在 OpenVPN Access Server 2.9.0 及之后的版本, Python Runtime 的版本变成了 Python 3.x, 并且随着 OpenVPN Access Server 更新迭代, Python Runtime 的版本也需要同步变化. 比如 OpenVPN Access Server 2.11.0 已经需要 Python 3.10.x 的版本了.

实操阶段

  • 主要操作的文件是一个名叫 pyovpn-2.0-pyx.x.egg 的文件, 以我了解的情况来看, 从 2.5.0 开始文件名一直都是这个, 只是不同版本里面的内容不一样.
  • 这个文件有点类似 Java 当中的 jar 库文件, 也是一个 zip 压缩文件, 里面包含了一些 Python 的字节码文件.
  • 破解的原理大概是在 Python 中采用类似 Java 动态代理的技术, 将原本读取用户属性的调用返回值拦截, 修改用户限制数量再返回.

2.9.0 以下版本破解的目标文件是 /pyovpn/lic/uprop.pyo, 2.9.0 及以上是 /pyovpn/lic/uprop.pyc;

按照网上流行的破解方法, 把这个文件解压出来并改名为 uprop2.pyouprop2.pyc, 然后新建一个 uprop.py 文件, 内容如下(注意区分版本):

2.9.0 以下版本内容:

import uprop2
old_figure = None

def new_figure(self, licdict):
    ret = old_figure(self, licdict)
    ret['concurrent_connections'] = 1024
    return ret


for x in dir(uprop2):
    if x[:2] == '__':
        continue
    if x == 'UsageProperties':
        exec('old_figure = uprop2.UsageProperties.figure')
        exec('uprop2.UsageProperties.figure = new_figure')
    exec('%s = uprop2.%s' % (x, x))

2.9.0 及以上版本内容:

from pyovpn.lic import uprop2
old_figure = None

def new_figure(self, licdict):
    ret = old_figure(self, licdict)
    ret['concurrent_connections'] = 1024
    return ret


for x in dir(uprop2):
    if x[:2] == '__':
        continue
    if x == 'UsageProperties':
        exec('old_figure = uprop2.UsageProperties.figure')
        exec('uprop2.UsageProperties.figure = new_figure')
    exec('%s = uprop2.%s' % (x, x))

再将上面的 uprop.py 编译为库文件 uprop.pyouprop.pyc:

# <2.9.0
python2 -O -m compileall uprop.py
# >=2.9.0
python3 -O -m compileall uprop.py && mv __pycache__/uprop.*.pyc uprop.pyc

注意 uprop.*.pyc 文件名会随着 python 版本变化而变化.

现在我们得到了一个改文件名的文件 uprop2.pyouprop2.pyc, 和一个编译出来的 uprop.pyouprop.pyc;

把这两个文件压缩到 pyovpn-2.0-pyx.x.egg/pyovpn/lic/ 目录下, 然后去服务器替换目标文件, 重启服务就OK了.

对原理感兴趣的可以使用 python-uncompyle6 反编译原本的 uprop 字节码文件. 举个例子:

# 安装 uncompyle6
pip install uncompyle6
# 反编译 uprop.pyc, 成功之后就会在当前目录得到一个文件 uprop.py
uncompyle6 -o ./ uprop.pyc

上述操作可以得到以下的源文件内容:

# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.11 (default, Aug 17 2021, 16:15:07) 
# [GCC 10.2.1 20210110]
# Embedded file name: build/bdist.linux-x86_64/egg/pyovpn/lic/uprop.py
# Compiled at: 2021-08-17 12:27:35
# Size of source mod 2**32: 3315 bytes
from pyovpn.util.date import YYYYMMDD
from pyovpn.util.error import Passthru
from pyovpn.lic.prop import LicenseProperties
from pyovpn.util.env import get_env_debug
from pyovpn.pki.sign import rsa_verify_complex
from pyovpn.aws.info import AWSInfo
DEBUG = get_env_debug('DEBUG_UPROP')

class UsageProperties(object):

    def figure(self, licdict):
        proplist = set(('concurrent_connections', ))
        good = set()
        ret = None
        if licdict:
            for key, props in list(licdict.items()):
                if 'quota_properties' not in props:
                    print('License Manager: key %s is missing usage properties' % key)
                    continue
                proplist.update(props['quota_properties'].split(','))
                good.add(key)

        for prop in proplist:
            v_agg = 0
            v_nonagg = 0
            if licdict:
                for key, props in list(licdict.items()):
                    if key in good and prop in props:
                        try:
                            nonagg = int(props[prop])
                        except:
                            raise Passthru('license property %s (%s)' % (prop, props.get(prop).__repr__()))

                        v_nonagg = max(v_nonagg, nonagg)
                        prop_agg = '%s_aggregated' % prop
                        agg = 0
                        if prop_agg in props:
                            try:
                                agg = int(props[prop_agg])
                            except:
                                raise Passthru('aggregated license property %s (%s)' % (
                                 prop_agg, props.get(prop_agg).__repr__()))

                            v_agg += agg
                        if DEBUG:
                            print('PROP=%s KEY=%s agg=%d(%d) nonagg=%d(%d)' % (
                             prop, key, agg, v_agg, nonagg, v_nonagg))

            apc = self._apc()
            v_agg += apc
            if ret == None:
                ret = {}
            ret[prop] = max(v_agg + v_nonagg, bool('v_agg') + bool('v_nonagg'))
            ret['apc'] = bool(apc)
            if DEBUG:
                print("ret['%s'] = v_agg(%d) + v_nonagg(%d)" % (prop, v_agg, v_nonagg))

        return ret

    def _apc(self):
        try:
            pcs = AWSInfo.get_product_code()
            if pcs:
                return pcs['snoitcennoCtnerrucnoc'[::-1]]
        except:
            if DEBUG:
                print(Passthru('UsageProperties._apc'))

        return 0

    @staticmethod
    def _expired(today, props):
        if 'expiry_date' in props:
            exp = YYYYMMDD.validate(props['expiry_date'])
            return today > exp
        return False


class UsagePropertiesValidate(object):
    proplist = ('concurrent_connections', 'client_certificates')

    def validate(self, usage_properties):
        lp = LicenseProperties(usage_properties)
        lp.aggregated_post()
        lp['quota_properties'] = ','.join([p for p in self.proplist if p in lp])
        return lp

引用

本文章提到的内容只允许做个人学习研究之用,不得用于商业用途!否则后果自负! 若资金允许,请购买正版,谢谢!