吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 15849|回复: 99
收起左侧

[原创] DBeaver Ultimate Edition License验证分析

    [复制链接]
pengsx01 发表于 2022-7-29 18:55
本帖最后由 pengsx01 于 2022-8-2 17:48 编辑

前言
看了论坛大佬写的两篇帖子,
DBeaver Ultimate edition JAVA程序逆向分析
DBeaver Ultimate edition 算法分析/本地服务器搭建

研究了一波DBeaver的源码,在这里分享一下我的过程与方法,补充一种绕过网络验证的方法。


测试环境
操作系统:Windows10
Java版本:JDK11

软件版本:DBeaver Ultimate 22.1.0

License解密分析
下载安装后打开,弹窗提示如下:
图片1.png

点击ImportLicense,随便填写一串字符串,界面会出现以下报错信息:
图片2.png

查看运行日志,日志路径:C:\Users\Niclas\AppData\Roaming\DBeaverData\workspace6\.metadata\dbeaver-debug.log能看到报错信息:
图片3.png

以上能看出导入License调用的方法在:com.dbeaver.lm.embedded.LicenseServiceEmbedded.importProductLicense
根据此处线索去安装目录下找对应的jar包,查找到的jar为安装目录下:DBeaverUltimate\plugins\com.dbeaver.lm.core_2.0.112.202206121739.jar
反编译jar包之后能找到对应的方法源码如下:
图片4.png

分析方法内代码,发现调用了方法:org.jkiss.lm.LMLicenseManager#importLicense(org.jkiss.lm.LMProduct,byte[])
查找到对应的jar包为:DBeaverUltimate\plugins\org.jkiss.lm_1.0.136.202206121739.jar
导入项目中,反编译之后能看到如下代码:
图片5.png

分析此处获取解密Key的方法,可以看到如下源码:
图片6.png

这里可以看出密钥来自于某一个jar包根目录下的keys文件夹中,文件名是以-public.key结尾。
在安装目录中查找后,发现该文件在:plugins\com.dbeaver.app.ultimate_22.1.0.202206121739.jar
如图:
图片7.png

此处也能推出String id =product.getId();中获取的id是:dbeaver-ue
回到方法org.jkiss.lm.LMLicenseManager#importLicense(org.jkiss.lm.LMProduct,byte[])
继续分析 LMLicense license = new LMLicense(licenseData,decryptionKey);,
查找其解密方法,如图:
图片8.png

可以发现其加解密方式为:RSA/ECB/PKCS1Padding
jar包中找到的dbeaver-ue-public.key文件为RAS解密需要的公钥。

License校验分析
根据上述解密过程分析,生成相应的LicenseLicense的生成方式见下面破解步骤)导入激活,出现如下弹窗:

图片9.png

查看日志文件dbeaver-debug.log,异常信息如下:
图片10.png

从日志可以看出验证的方法在:com.dbeaver.lm.valIDAte.PublicLicenseValidator.validateLicense
根据路径查看该方法的代码,如下:
图片11.png

分析其逻辑,验证的步骤在:result =LMPublicAPI.checkLicenseStatus(monitor,clientId, licenseManager, license, product);
查看其代码如下:
图片12.png
分析代码,发现
StringlicenseStatusText = client.checkLicenseStatus(license, product);
返回的licenseStatusText如果为空,那么该方法将会直接返回LMLicenseStatus.VALID的校验状态(即有效的状态),
继续分析调用的checkLicenseStatus方法,代码如下:
图片13.png

这里能看出请求了网络接口,调用的请求方法为父类中的方法,在安装目录下找到PublicServiceClient的父类在com.dbeaver.remote.client_1.0.2.202206121739.jar中,
在类com.dbeaver.remote.client.AbstractRemoteClient中能看到HTTP请求的客户端构造代码如下:
图片14.png

返回类com.dbeaver.lm.validate.PublicServiceClient中,能看到其构造客户端的代码如下:
图片15.png

能分析出PUBLIC_SERVICE_URL为接口的请求地址,继续追溯该变量的来源,其逻辑如下:
图片16.png

回到获取校验结果StringlicenseStatusText = client.checkLicenseStatus(license, product);的代码,
请求的路径无法访问时,那么得到的licenseStatusText就为空值了,就能直接得到有效的验证结果。
从上述代码中,能看出DEBUG_MODEtrue或者是https://dbeaver.com/lmp/无法访问时,请求结果将会返回空值。

由此得到了两种通过网络校验的方法:

1.       hosts文件中配置dbeaver.comIP127.0.0.1
2.       在配置文件中设置lm.debug.mode=true

破解思路
1.       生成一对RAS加解密使用的公钥和私钥;
2.       com.dbeaver.app.ultimate_22.1.0.202206121739.jar中的公钥替换成自己生成的公钥;
3.       用自己的私钥生成一个License文件导入到DBEaver中;
4.       绕过License网络校验。

破解步骤
1.       查看org.jkiss.lm_1.0.136.202206121739.jar内的代码,发现类org.jkiss.lm.LMMain中已经实现了生成密钥、生成License、解密License、导入License的测试代码,只需稍作修改就可以直接使用;
2.       导入项目需要依赖的jar包,需要依赖的jar包如下图:
图片17.png
3.       创建一个新的类,修改org.jkiss.lm.LMMain中的代码,得到如下代码:
[Java] 纯文本查看 复制代码
import org.jkiss.lm.*;
import org.jkiss.utils.Base64;
 
import java.io.*;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
 
 
public class DBeaverLicenseActiveMain {
    private static final LMProduct TEST_PRODUCT;
 
    static {
        TEST_PRODUCT = new LMProduct(
                "dbeaver-ue",
                "DB",
                "DBeaver Ultimate",
                "DBeaver Ultimate Edition",
                "22.1",
                LMProductType.DESKTOP,
                new Date(),
                new String[0]
        );
    }
 
 
    public static void main(String[] args) throws Exception {
        System.out.println("LM 2.0");
        // 生成RAS密钥对
        if (args.length > 0 && args[0].equals("gen-keys")) {
            System.out.println("Test key generation");
            generateKeyPair();
        // 生成加密License
        } else if (args.length > 0 && args[0].equals("encrypt-license")) {
            System.out.println("Encrypt license");
            encryptLicense();
        // 解密License
        } else if (args.length > 0 && args[0].equals("decrypt-license")) {
            System.out.println("Decrypt license");
            decryptLicense();
        // 导入License测试
        } else if (args.length > 0 && args[0].equals("import-license")) {
            System.out.println("Import license");
            importLicense();
        // 生成明文License
        } else {
            System.out.println("Test license generation");
            generateLicense();
        }
    }
 
    private static void encryptLicense() throws Exception {
        PrivateKey privateKey = readPrivateKey();
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String licenseID = LMUtils.generateLicenseId(TEST_PRODUCT);
        System.out.println("License ID: " + licenseID);
        System.out.println("Product ID (" + TEST_PRODUCT.getId() + "):");
        String productID = in.readLine();
        if (productID.isEmpty()) {
            productID = TEST_PRODUCT.getId();
        }
 
        System.out.println("Product version (" + TEST_PRODUCT.getVersion() + "):");
        String productVersion = in.readLine();
        if (productVersion.isEmpty()) {
            productVersion = TEST_PRODUCT.getVersion();
        }
 
        System.out.println("Owner ID (10000):");
        String ownerID = in.readLine();
        if (ownerID.isEmpty()) {
            ownerID = "10000";
        }
 
        System.out.println("Owner company (JKISS):");
        String ownerCompany = in.readLine();
        if (ownerCompany.isEmpty()) {
            ownerCompany = "JKISS";
        }
 
        System.out.println("Owner name (Serge Rider):");
        String ownerName = in.readLine();
        if (ownerName.isEmpty()) {
            ownerName = "Serge Rider";
        }
 
        System.out.println("Owner email (serge@jkiss.org):");
        String ownerEmail = in.readLine();
        if (ownerEmail.isEmpty()) {
            ownerEmail = "serge@jkiss.org";
        }
 
        LMLicense license = new LMLicense(
                licenseID,
                LMLicenseType.ULTIMATE,
                new Date(),
                new Date(),
                (Date)null,
                LMLicense.FLAG_UNLIMITED_SERVERS,
                productID,
                productVersion,
                ownerID,
                ownerCompany,
                ownerName,
                ownerEmail);
        byte[] licenseData = license.getData();
        byte[] licenseEncrypted = LMEncryption.encrypt(licenseData, privateKey);
        System.out.println("--- LICENSE ---");
        System.out.println(Base64.splitLines(Base64.encode(licenseEncrypted), 76));
    }
 
    private static void decryptLicense() throws Exception {
        PublicKey publicKey = readPublicKey();
        System.out.println("License:");
        byte[] encryptedLicense = LMUtils.readEncryptedString(System.in);
        LMLicense license = new LMLicense(encryptedLicense, publicKey);
        System.out.println(license);
    }
 
    private static void importLicense() throws Exception {
        final PrivateKey privateKey = readPrivateKey();
        final PublicKey publicKey = readPublicKey();
        System.out.println("License:");
        byte[] encryptedLicense = LMUtils.readEncryptedString(System.in);
        LMLicenseManager lm = new LMLicenseManager(new LMKeyProvider() {
            public Key getEncryptionKey(LMProduct product) {
                return privateKey;
            }
 
            public Key getDecryptionKey(LMProduct product) {
                return publicKey;
            }
        }, (LMLicenseValidator)null);
        lm.importLicense(TEST_PRODUCT, encryptedLicense);
    }
 
    private static void generateKeyPair() throws LMException {
        KeyPair keyPair = LMEncryption.generateKeyPair(2048);
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        System.out.println("--- PUBLIC KEY ---");
        System.out.println(Base64.splitLines(Base64.encode(publicKey.getEncoded()), 76));
        System.out.println("--- PRIVATE KEY ---");
        System.out.println(Base64.splitLines(Base64.encode(privateKey.getEncoded()), 76));
    }
 
    private static void generateLicense() throws LMException {
        System.out.println("Gen keys");
        KeyPair keyPair = LMEncryption.generateKeyPair(2048);
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        System.out.println("Gen Test license");
        String licenseID = LMUtils.generateLicenseId(TEST_PRODUCT);
        LMLicense license = new LMLicense(
                licenseID,
                LMLicenseType.ULTIMATE,
                new Date(),
                new Date(),
                (Date)null,
                LMLicense.FLAG_UNLIMITED_SERVERS,
                TEST_PRODUCT.getId(),
                TEST_PRODUCT.getVersion(),
                "10000",
                "JKISS",
                "Serge Rider",
                "serge@jkiss.org");
        byte[] subData = license.getData();
        byte[] encrypted = LMEncryption.encrypt(subData, privateKey);
        String encodedBase64 = Base64.splitLines(Base64.encode(encrypted), 76);
        byte[] encodedBinary = Base64.decode(encodedBase64);
        LMLicense licenseCopy = new LMLicense(encodedBinary, publicKey);
        System.out.println(licenseCopy);
        System.out.println("Gen subscription");
        LMSubscription subscription = new LMSubscription(
                licenseID,
                LMSubscriptionPeriod.MONTH,
                new Date(),
                new Date(),
                1,
                true);
        subData = LMEncryption.encrypt(subscription.getData(), privateKey);
        String subBase64 = Base64.splitLines(Base64.encode(subData), 76);
        byte[] subBinary = Base64.decode(subBase64);
        LMSubscription subCopy = new LMSubscription(subBinary, publicKey);
        System.out.println(subCopy);
    }
 
    private static PrivateKey readPrivateKey() throws LMException {
        File keyFile = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "private-key.txt");
        if (!keyFile.exists()) {
            throw new LMException("Cannot find private key file (" + keyFile.getAbsolutePath() + ")");
        } else {
            try {
                Throwable var1 = null;
                Object var2 = null;
 
                try {
                    InputStream keyStream = new FileInputStream(keyFile);
 
                    PrivateKey var10000;
                    try {
                        byte[] privateKeyData = LMUtils.readEncryptedString(keyStream);
                        var10000 = LMEncryption.generatePrivateKey(privateKeyData);
                    } finally {
                        if (keyStream != null) {
                            keyStream.close();
                        }
 
                    }
 
                    return var10000;
                } catch (Throwable var12) {
                    if (var1 == null) {
                        var1 = var12;
                    } else if (var1 != var12) {
                        var1.addSuppressed(var12);
                    }
                    throw var1;
                }
            } catch (Throwable var13) {
                throw new LMException(var13);
            }
        }
    }
 
    private static PublicKey readPublicKey() throws LMException {
        File keyFile = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "public-key.txt");
        if (!keyFile.exists()) {
            throw new LMException("Cannot find public key file (" + keyFile.getAbsolutePath() + ")");
        } else {
            try {
                Throwable var1 = null;
                try {
                    InputStream keyStream = new FileInputStream(keyFile);
 
                    PublicKey var10000;
                    try {
                        byte[] keyData = LMUtils.readEncryptedString(keyStream);
                        var10000 = LMEncryption.generatePublicKey(keyData);
                    } finally {
                        if (keyStream != null) {
                            keyStream.close();
                        }
 
                    }
 
                    return var10000;
                } catch (Throwable var12) {
                    if (var1 == null) {
                        var1 = var12;
                    } else if (var1 != var12) {
                        var1.addSuppressed(var12);
                    }
 
                    throw var1;
                }
            } catch (Throwable var13) {
                throw new LMException(var13);
            }
        }
    }
}

4.       上述代码传入参数gen-keys生成密钥对如下:
  Plain  Text
  
--- PUBLIC KEY ---
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhXxuid2ALfSF68yUrH0H3e2eJ0S9MfJu
  Vil1r03M/JXTu5FgjTyUf1Q5KJnXSLaxiibtHjAJUXRk10C9iks7FFNeOP8diDL2KcbPTSdy7iap
  CYAaxZilIRIq0Ab/Zhi+HwPh9wuJk/6XEgoUIC1eiKj0aVMz25G5udXHtIx8OjavH2eoFdlOpDwm
  6ZMj1ZSbkY50cnOb6eJv38oKF8fL4b3i3J0vv5lk90NYpVSGFwPs9S2ANsWT1CXXfl3wQOuBHOfL
  KToJN9V5fqMgiAoPNT8BvO5NMMJ8d9MKQD4rtqq3S40E7iIZ/fN51h/kQ0ynCHXdsdze48jQ+quS
  sifHTwIDAQAB
  --- PRIVATE KEY ---
  MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCFfG6J3YAt9IXrzJSsfQfd7Z4n
  RL0x8m5WKXWvTcz8ldO7kWCNPJR/VDkomddItrGKJu0eMAlRdGTXQL2KSzsUU144/x2IMvYpxs9N
  J3LuJqkJgBrFmKUhEirQBv9mGL4fA+H3C4mT/pcSChQgLV6IqPRpUzPbkbm51ce0jHw6Nq8fZ6gV
  2U6kPCbpkyPVlJuRjnRyc5vp4m/fygoXx8vhveLcnS+/mWT3Q1ilVIYXA+z1LYA2xZPUJdd+XfBA
  64Ec58spOgk31Xl+oyCICg81PwG87k0wwnx30wpAPiu2qrdLjQTuIhn983nWH+RDTKcIdd2x3N7j
  yND6q5KyJ8dPAgMBAAECggEATJu5JM5GfhlTspxaxxOKrEdu+MJugnfL8w8gR1ezSVMDjSZF70jR
  QLIpi6+e6lBPXCYy95xB/Ml8Bj1VikTaxzOBY9ymKkB1HkzHNFRrlVoCsT0gID8WpgAzKeiaMxII
  KuyjhpDMiG8YbHX0TvM6yduNSdVCccUUfh6+2lO2CAH1fRT+FJqEI8tUGbuB16YvM6t/mNjtbOo1
  dSsRacc/7fV3vPP7a3kqc0PHpIDAyuKcLWn1HwEzBeAgp/TlX9J1bU8WcijKQBLcrxYmxAqDZOPD
  imcV0XfKs6I2JUHEePUHiAoG59BhVGA/rJnkyQEpaD3mKkFImIIKm6poLvzboQKBgQD1iLWE4gKe
  g/5TUAFO/aMhiMQG3vP410eBoWHZnvQt72VKX6hlgGwvZld1UF7hqljK1ICvvwFe2aGWDJAk5Zz5
  0ipPj1UVkk5FsLoi/YT3Pj8tsNT0xJrilXDlpYAEsecbqvBs5QBGBKH+4QkFoCvCb6qWuDWQMNYJ
  Ja/Su97nLQKBgQCLLQtqRutmw96XzkfV9yuqQ9nzk7Z7CfM00O4l0jP0tKTXomuSW1nRiYc3UfZJ
  cJfPR91y40N4v7A8DaiReH5T5F6Mt6gnnSmRt/J6vE5tz3lODnoaUVOmjEpj7ytQdtr2xUYNOnEu
  oRH2lPkhUdIBM35EfySpKBu7yWfc3ap16wKBgQDGZzKububI6kWvUp3ME34nUdl859njASph4GMu
  M5iCKckCgSuU4WIKJzuSq2AQH9NiCrb1zHUyDM/abMppVjUzVZUk9uA87x1aiQTP02YHV4A7zoE2
  TEwPvcwddU9t+8eQ/t8KTz2aVpIEYBknN5dEpXEGG1IE8sFxYMejlHX4/QKBgECo/NSzfkqQVapR
  vC48V50TSP9RcUZYqRWwu/P2ZQ0boDpOy4uDxYcETj31ZmdYWC+FQ+1MiNxgspA0CE0NniN7xjG6
  YfWFnvqEa7N6KTX7XnBVaYUwo5yNMUKcq5MGpVRg8trSfCMd0iqtq9E/IkJMmi1YpL+yUrA8MnT6
  x2dhAoGBALSWWmS3fBhUi55YwnmxMGcZKRw3SR4qvgfMVsXx/wraLg6HdHYv5eugQ5JimqlAw6Bv
  B6EIBnhrWT41s1uLkVrD3bYvOT5dKlgHmNUuosZdTkAZQAptiPq0tNnXJ5N++4NIf4vmTgbC4OhG
  b5eH0TuNW8cBSErqYoCf/tVrjVTq
  
5.       创建文件public-key.txtprivate-key.txtdbeaver-ue-public.key,将私钥复制到private-key.txt中,将公钥复制到public-key.txtdbeaver-ue-public.key中;
6.       public-key.txtprivate-key.txt文件放到用户目录下的.jkiss-lm文件夹中,dbeaver-ue-public.key文件替换com.dbeaver.app.ultimate_22.1.0.202206121739.jar内的keys\dbeaver-ue-public.key文件
7.       Java代码中传入参数encrypt-license生成License字符串,如下:
  Plain  Text
  
ReJalXttbssteHQ63re1sGcW2XbP4emJiJxLGaa/JEXXcRiuUzwfwAHh5gUWD+blYrIVKy4Ibx9k
  xm6XcIlg4X1eWI4UrlTep+D5l2cKSqw63Hzi8hyau8H9OFfPe9PHig1Kla+u4fQCkn7AidqZPkV7
  QVK2F++ZIUcPmc+qEkm3suOEtRgEKKBfsZpHTg+CUrUb37DlEz6qWnOI+5hy95B3z892TTJzkARc
  HOhSdFM/1/q4WMUtfjcVcav7x7qiFDIfuWQsMNASVMxBJtbKsKv9tqc+2ExGV+9bZBHls7JehGOW
  bG7YOpoZA49Ha6DnWlhoWAeFizL6zFLbH8+tZQ==
  
8.       修改配置文件dbeaver.ini,增加lm.debug.mode=true配置,如下:
  Plain  Text
  
-startup
  plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar
  --launcher.library
  plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.2.400.v20211117-0650
  -vmargs
  -XX:+IgnoreUnrecognizedVMOptions
  --add-modules=ALL-SYSTEM
  -Dosgi.requiredJavaVersion=11
  -Xms128m
  -Xmx2048m
  -Djavax.net.ssl.trustStoreType=WINDOWS-ROOT
  -Ddbeaver.distribution.type=exe
  -Dlm.debug.mode=true
  

9.       打开DBeaver导入License,如图:
图片18.png
图片19.png

后记
仅供研究学习使用,请勿用于非法用途,请支持正版!

免费评分

参与人数 19威望 +2 吾爱币 +119 热心值 +17 收起 理由
ccpw_cn + 1 我很赞同!
lhdigo + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
lifeigump + 1 + 1 谢谢@Thanks!
wisokey + 1 + 1 我很赞同!
BingMG + 1 + 1 谢谢 @Thanks!
cd_ll + 1 + 1 我很赞同!
黄教主 + 1 + 1 用心讨论,共获提升!
DSUPER + 1 + 1 学习啦!
kHXBc8kSg + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
楚谜 + 1 + 1 我很赞同!硬核文章,分析思路很清晰,表达很清晰
jockers + 1 我很赞同!
sfc9982 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
wapj258 + 1 + 1 测试成功!感谢分享!
gaosld + 1 + 1 热心回复!
AlohaRE + 1 + 1 热心回复!
splic2012 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
小朋友呢 + 2 + 1 热心回复!
tianyulouzhu + 1 + 1 感谢分享,收纳了
云在天 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

moresun 发表于 2024-3-28 18:22
TonyLiu 发表于 2024-3-28 16:26
老哥  可以贴下代码吗?


import com.dbeaver.lm.api.LMEncryption;
import com.dbeaver.lm.api.LMException;
import com.dbeaver.lm.api.LMLicense;
import com.dbeaver.lm.api.LMLicenseType;
import com.dbeaver.lm.api.LMProduct;
import com.dbeaver.lm.api.LMProductType;
import com.dbeaver.lm.api.LMUtils;
import org.jkiss.utils.Base64;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

/**
* 本代码依赖 2 个 jar 包
* <ul>
*     <li>${DBEAVER_ECLIPSE_HOME}/plugins/org.jkiss.utils_**.jar</li>
*     <li>${DBEAVER_ECLIPSE_HOME}/plugins/com.dbeaver.lm.**.jar</li>
* </ul>
*/
public class DBeaverLicenseCrack {
    private static String productID = "dbeaver-ue";
    private static String productVersion = "24.0.0";
    private static String ownerID = "10006";
    private static String ownerCompany = "DBeaver Corporation";
    private static String ownerName = "Ultimate User";
    private static String ownerEmail = "developer@dbeaver.com";
    /**
     * 根据当前版本修改实际jar包名称
     * 需要替换公钥的文件 (com.dbeaver.app.ultimate_**.jar)
     */
    private static String replaceJarFile = "com.dbeaver.app.ultimate_24.0.0.202403110838.jar";

    private static File privateKeyFile = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "private-key.txt");
    private static File publicKeyFile = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "public-key.txt");
    private static File licenseFile = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "license.txt");
    /**
     * 使用该文件,需要替换 jar 包中的 key 文件(请保留原始 jar 包), 并在 dbeaver.ini 添加参数-Dlm.debug.mode=true
     * <p>
     * 如果修改 jar 包中的 plugin.xml 的 product 节点添加 <property name="lmServerURL" value="http://localhost:7879"/> 需要一个授权服务器
     *     <ul>
     *         <li>Mac 版本的 jar 包路径是 ${DBEAVER_ECLIPSE_HOME}/plugins/com.dbeaver.app.ultimate_23.1.0.202306120813.jar</li>
     *         <li>Mac 版本的配置文件路径是 ${DBEAVER_ECLIPSE_HOME}/dbeaver.ini</li>
     *     </ul>
     * </p>
     */
    private static File jarPublicKey = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "dbeaver-ue-public.key");

    private static final String DBEAVER_ECLIPSE_HOME = "/Applications/DBeaverUltimate.app/Contents/Eclipse";

    public static void main(String[] args) throws Exception {
        String dbeaverPluginDir = DBEAVER_ECLIPSE_HOME + File.separator + "plugins";
        // 需要替换公钥的文件 (com.dbeaver.app.ultimate_**.jar)
        List<File> matchingFiles = findFilesByPattern(dbeaverPluginDir, replaceJarFile);
        if (matchingFiles.size() != 1) {
            throw new IOException("未找到需要替换的 jar 文件");
        }
        String dbeaverJar = matchingFiles.get(0).getAbsolutePath();
        generateKeyLicenseAndPatch(dbeaverJar, "keys/dbeaver-ue-public.key");
        String iniFile = DBEAVER_ECLIPSE_HOME + File.separator + "dbeaver.ini";
        updateEclipseIniFile(iniFile, "-Dlm.debug.mode=true");
    }

    /**
     * 在文件中追加一行配置文本(已经存在则跳过)
     *
     * @Param iniFile
     * @param configLine
     * @throws IOException
     */
    public static void updateEclipseIniFile(String iniFile, String configLine) throws IOException {
        boolean targetLineFound = false;

        // 读取 ini 文件
        File file = new File(iniFile);
        FileReader fr = new FileReader(file);
        BufferedReader br = new BufferedReader(fr);

        String line;
        StringBuilder content = new StringBuilder();
        while ((line = br.readLine()) != null) {
            content.append(line).append(System.lineSeparator());
            if (line.trim().equals(configLine)) {
                targetLineFound = true;
            }
        }
        br.close();

        // 如果未找到目标行,在内容的末尾追加文本
        if (!targetLineFound) {
            content.append(configLine).append(System.lineSeparator());

            // 将更新的内容写回 ini 文件
            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(content.toString());
            bw.close();
        }
    }

    /**
     * 通过正则表达匹配,搜索匹配的文件
     *
     * @param directoryPath 目录
     * @param pattern       文件匹配正则
     * @return
     */
    public static List<File> findFilesByPattern(String directoryPath, String pattern) {
        List<File> matchingFiles = new ArrayList<>();
        File directory = new File(directoryPath);

        if (directory.exists() && directory.isDirectory()) {
            File[] files = directory.listFiles((dir, name) -> name.matches(pattern));

            if (files != null) {
                for (File file : files) {
                    if (file.isFile()) {
                        matchingFiles.add(file);
                    }
                }
            }
        } else {
            System.out.println("无效的目录,或者找不到目录路径!");
        }

        return matchingFiles;
    }

    /**
     * 生成公钥、私钥、授权码,并替换文件
     *
     * @param jarFilePath   需要替换公钥的 jar 文件
     * @param filePathInJar jar 包中的公钥文件路径
     * @throws LMException
     * @throws IOException
     */
    public static void generateKeyLicenseAndPatch(String jarFilePath, String filePathInJar) throws LMException, IOException {
        // 从 LMMain.generateKeyPair() 复制的代码
        KeyPair keyPair = LMEncryption.generateKeyPair(2048);
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        System.out.println("\n--- PUBLIC KEY ---");
        String publicKeyContent = Base64.splitLines(Base64.encode(publicKey.getEncoded()), 76);
        System.out.println(publicKeyContent);
        writeFileToPath(publicKeyContent, publicKeyFile, "公钥");
        System.out.println("\n--- PRIVATE KEY ---");
        String privateKeyContent = Base64.splitLines(Base64.encode(privateKey.getEncoded()), 76);
        System.out.println(privateKeyContent);
        writeFileToPath(publicKeyContent, privateKeyFile, "私钥");
        writeFileToPath(publicKeyContent, jarPublicKey, "公钥");

        // 替换 jar 包中的 public key 文件
        replaceFileInJar(jarFilePath, filePathInJar, jarPublicKey.getAbsolutePath());

        // 从 LMMain.encryptLicense() 复制的代码
        LMProduct TEST_PRODUCT = new LMProduct("dbeaver-ue", "DB", "DBeaver Ultimate", "DBeaver Ultimate Edition", productVersion, LMProductType.DESKTOP, new Date(), new String[0]);
        String licenseID = LMUtils.generateLicenseId(TEST_PRODUCT);
        LMLicense license = new LMLicense(licenseID, LMLicenseType.ULTIMATE, new Date(), new Date(), (Date) null, LMLicense.FLAG_UNLIMITED_SERVERS, productID, productVersion, ownerID, ownerCompany, ownerName, ownerEmail);
        byte[] licenseData = license.getData();
        byte[] licenseEncrypted = LMEncryption.encrypt(licenseData, privateKey);

        String licenseContent = Base64.splitLines(Base64.encode(licenseEncrypted), 76);
        System.out.println("\n--- LICENSE ---");
        System.out.println(licenseContent);
        System.out.println("--- 处理完成,请打开软件使用以上授权码(已复制到剪切板) ---");
        copyToClipboard(licenseContent);
        writeFileToPath(licenseContent, licenseFile, "授权码");
    }

    /**
     * 写入并覆盖文件内容
     *
     * @param content  写入覆盖的内容
     * @param filePath 要写入的文件路径
     * @param tip      提示
     */
    public static void writeFileToPath(String content, File filePath, String tip) {
        try {
            Path path = Paths.get(filePath.getAbsolutePath());
            Files.write(path, content.getBytes());
            System.out.println(String.format("[%s内容]已成功写入文件:%s", tip, path));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 替换 jar 包中的指定路径的文件为新文件
     *
     * @param jarFilePath
     * @param targetFile
     * @param newFile
     * @throws IOException
     */
    public static void replaceFileInJar(String jarFilePath, String targetFile, String newFile) throws IOException {
        File tempFile = File.createTempFile("temp-file", ".tmp");
        File tempJarFile = File.createTempFile("temp-jar", ".tmp");

        boolean replaceSuccess = false;
        try (JarFile jarFile = new JarFile(new File(jarFilePath));
             JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(tempJarFile))) {

            // Copy the existing entries to the temp jar, excluding the entry to be replaced
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();

                if (!entry.getName().equals(targetFile)) {
                    tempJarOutputStream.putNextEntry(new JarEntry(entry.getName()));
                    try (InputStream inputStream = jarFile.getInputStream(entry)) {
                        copyStream(inputStream, tempJarOutputStream);
                    }
                }
            }

            // 将新文件添加到临时jar
            tempJarOutputStream.putNextEntry(new JarEntry(targetFile));
            try (InputStream newFileStream = new FileInputStream(newFile)) {
                copyStream(newFileStream, tempJarOutputStream);
            }
            replaceSuccess = true;
        } finally {
            // 将原始 jar 文件替换为临时 jar 文件
            File originalJarFile = new File(jarFilePath);
            if (originalJarFile.delete() && tempJarFile.renameTo(originalJarFile) && replaceSuccess) {
                System.out.println(String.format("jar包中的文件 [%s] 替换成功!", targetFile));
            } else {
                System.out.println(String.format("jar包中的文件 [%s] 替换失败!", targetFile));
            }

            tempFile.delete();
            tempJarFile.delete();
        }
    }

    private static void copyStream(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
    }

    /**
     * 将指定字符串复制到剪切板
     *
     * @param text
     */
    public static void copyToClipboard(String text) {
        StringSelection stringSelection = new StringSelection(text);
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(stringSelection, null);
    }
}

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
alen456 + 1 + 1 谢谢@Thanks!

查看全部评分

moresun 发表于 2024-3-21 13:48
最新版本24.0.0需要调整一下引入的jar依赖 。原来的 org.jkiss.lm_**.jar 改为 com.dbeaver.lm.**.jar 然后修改一下import 路径即可,其他不变。已成功激活

image.png
pengxinkui 发表于 2023-7-29 23:59
成功了,感谢楼主分享,核心其实就是 LMMain.generateKeyPair() 方法生成密钥对,调用 LMMain.encryptLicense() 生成授权码。
以下是使用 main 方法直接生成并替换的代码(Mac 23.1.0):
项目需要 JDK 11 和 2 个 jar 包

[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
package src;
 
import org.jkiss.lm.LMEncryption;
import org.jkiss.lm.LMException;
import org.jkiss.lm.LMLicense;
import org.jkiss.lm.LMLicenseType;
import org.jkiss.lm.LMProduct;
import org.jkiss.lm.LMProductType;
import org.jkiss.lm.LMUtils;
import org.jkiss.utils.Base64;
 
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
 
/**
 * 本代码依赖 2 个 jar 包
 * <ul>
 *     <li>${DBEAVER_ECLIPSE_HOME}/plugins/org.jkiss.utils_**.jar</li>
 *     <li>${DBEAVER_ECLIPSE_HOME}/plugins/org.jkiss.lm_**.jar</li>
 * </ul>
 */
public class DBeaverLicenseCrack {
    private static String productID = "dbeaver-ue";
    private static String productVersion = "23.1.0";
    private static String ownerID = "10006";
    private static String ownerCompany = "DBeaver Corporation";
    private static String ownerName = "Ultimate User";
    private static String ownerEmail = "developer@dbeaver.com";
 
    private static File privateKeyFile = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "private-key.txt");
    private static File publicKeyFile = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "public-key.txt");
    private static File licenseFile = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "license.txt");
    /**
     * 使用该文件,需要替换 jar 包中的 key 文件(请保留原始 jar 包), 并在 dbeaver.ini 添加参数-Dlm.debug.mode=true
     * <p>
     * 如果修改 jar 包中的 plugin.xml 的 product 节点添加 <property name="lmServerURL" value="http://localhost:7879"/> 需要一个授权服务器
     *     <ul>
     *         <li>Mac 版本的 jar 包路径是 ${DBEAVER_ECLIPSE_HOME}/plugins/com.dbeaver.app.ultimate_23.1.0.202306120813.jar</li>
     *         <li>Mac 版本的配置文件路径是 ${DBEAVER_ECLIPSE_HOME}/dbeaver.ini</li>
     *     </ul>
     * </p>
     */
    private static File jarPublicKey = new File(new File(System.getProperty("user.home"), ".jkiss-lm"), "dbeaver-ue-public.key");
 
    private static final String DBEAVER_ECLIPSE_HOME = "/Applications/DBeaverUltimate.app/Contents/Eclipse";
 
    public static void main(String[] args) throws Exception {
        String dbeaverPluginDir = DBEAVER_ECLIPSE_HOME + File.separator + "plugins";
        // 需要替换公钥的文件 (com.dbeaver.app.ultimate_**.jar)
        String replaceJarFile = "com\\.dbeaver\\.app\\.ultimate_.+\\.jar";
        List<File> matchingFiles = findFilesByPattern(dbeaverPluginDir, replaceJarFile);
        if (matchingFiles.size() != 1) {
            throw new IOException("未找到需要替换的 jar 文件");
        }
        String dbeaverJar = matchingFiles.get(0).getAbsolutePath();
        generateKeyLicenseAndPatch(dbeaverJar, "keys/dbeaver-ue-public.key");
        String iniFile = DBEAVER_ECLIPSE_HOME + File.separator + "dbeaver.ini";
        updateEclipseIniFile(iniFile, "-Dlm.debug.mode=true");
    }
 
    /**
     * 在文件中追加一行配置文本(已经存在则跳过)
     *
     * [url=home.php?mod=space&uid=952169]@Param[/url] iniFile
     * @param configLine
     * @throws IOException
     */
    public static void updateEclipseIniFile(String iniFile, String configLine) throws IOException {
        boolean targetLineFound = false;
 
        // 读取 ini 文件
        File file = new File(iniFile);
        FileReader fr = new FileReader(file);
        BufferedReader br = new BufferedReader(fr);
 
        String line;
        StringBuilder content = new StringBuilder();
        while ((line = br.readLine()) != null) {
            content.append(line).append(System.lineSeparator());
            if (line.trim().equals(configLine)) {
                targetLineFound = true;
            }
        }
        br.close();
 
        // 如果未找到目标行,在内容的末尾追加文本
        if (!targetLineFound) {
            content.append(configLine).append(System.lineSeparator());
 
            // 将更新的内容写回 ini 文件
            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(content.toString());
            bw.close();
        }
    }
 
    /**
     * 通过正则表达匹配,搜索匹配的文件
     *
     * @param directoryPath 目录
     * @param pattern       文件匹配正则
     * @return
     */
    public static List<File> findFilesByPattern(String directoryPath, String pattern) {
        List<File> matchingFiles = new ArrayList<>();
        File directory = new File(directoryPath);
 
        if (directory.exists() && directory.isDirectory()) {
            File[] files = directory.listFiles((dir, name) -> name.matches(pattern));
 
            if (files != null) {
                for (File file : files) {
                    if (file.isFile()) {
                        matchingFiles.add(file);
                    }
                }
            }
        } else {
            System.out.println("无效的目录,或者找不到目录路径!");
        }
 
        return matchingFiles;
    }
 
    /**
     * 生成公钥、私钥、授权码,并替换文件
     *
     * @param jarFilePath   需要替换公钥的 jar 文件
     * @param filePathInJar jar 包中的公钥文件路径
     * @throws LMException
     * @throws IOException
     */
    public static void generateKeyLicenseAndPatch(String jarFilePath, String filePathInJar) throws LMException, IOException {
        // 从 LMMain.generateKeyPair() 复制的代码
        KeyPair keyPair = LMEncryption.generateKeyPair(2048);
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        System.out.println("\n--- PUBLIC KEY ---");
        String publicKeyContent = Base64.splitLines(Base64.encode(publicKey.getEncoded()), 76);
        System.out.println(publicKeyContent);
        writeFileToPath(publicKeyContent, publicKeyFile, "公钥");
        System.out.println("\n--- PRIVATE KEY ---");
        String privateKeyContent = Base64.splitLines(Base64.encode(privateKey.getEncoded()), 76);
        System.out.println(privateKeyContent);
        writeFileToPath(publicKeyContent, privateKeyFile, "私钥");
        writeFileToPath(publicKeyContent, jarPublicKey, "公钥");
 
        // 替换 jar 包中的 public key 文件
        replaceFileInJar(jarFilePath, filePathInJar, jarPublicKey.getAbsolutePath());
 
        // 从 LMMain.encryptLicense() 复制的代码
        LMProduct TEST_PRODUCT = new LMProduct("dbeaver-ue", "DB", "DBeaver Ultimate", "DBeaver Ultimate Edition", "23.1", LMProductType.DESKTOP, new Date(), new String[0]);
        String licenseID = LMUtils.generateLicenseId(TEST_PRODUCT);
        LMLicense license = new LMLicense(licenseID, LMLicenseType.ULTIMATE, new Date(), new Date(), (Date) null, LMLicense.FLAG_UNLIMITED_SERVERS, productID, productVersion, ownerID, ownerCompany, ownerName, ownerEmail);
        byte[] licenseData = license.getData();
        byte[] licenseEncrypted = LMEncryption.encrypt(licenseData, privateKey);
 
        String licenseContent = Base64.splitLines(Base64.encode(licenseEncrypted), 76);
        System.out.println("\n--- LICENSE ---");
        System.out.println(licenseContent);
        System.out.println("--- 处理完成,请打开软件使用以上授权码(已复制到剪切板) ---");
        copyToClipboard(licenseContent);
        writeFileToPath(licenseContent, licenseFile, "授权码");
    }
 
    /**
     * 写入并覆盖文件内容
     *
     * @param content  写入覆盖的内容
     * @param filePath 要写入的文件路径
     * @param tip      提示
     */
    public static void writeFileToPath(String content, File filePath, String tip) {
        try {
            Path path = Paths.get(filePath.getAbsolutePath());
            Files.write(path, content.getBytes());
            System.out.println(String.format("[%s内容]已成功写入文件:%s", tip, path));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 替换 jar 包中的指定路径的文件为新文件
     *
     * @param jarFilePath
     * @param targetFile
     * @param newFile
     * @throws IOException
     */
    public static void replaceFileInJar(String jarFilePath, String targetFile, String newFile) throws IOException {
        File tempFile = File.createTempFile("temp-file", ".tmp");
        File tempJarFile = File.createTempFile("temp-jar", ".tmp");
 
        boolean replaceSuccess = false;
        try (JarFile jarFile = new JarFile(new File(jarFilePath));
             JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(tempJarFile))) {
 
            // Copy the existing entries to the temp jar, excluding the entry to be replaced
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
 
                if (!entry.getName().equals(targetFile)) {
                    tempJarOutputStream.putNextEntry(new JarEntry(entry.getName()));
                    try (InputStream inputStream = jarFile.getInputStream(entry)) {
                        copyStream(inputStream, tempJarOutputStream);
                    }
                }
            }
 
            // 将新文件添加到临时jar
            tempJarOutputStream.putNextEntry(new JarEntry(targetFile));
            try (InputStream newFileStream = new FileInputStream(newFile)) {
                copyStream(newFileStream, tempJarOutputStream);
            }
            replaceSuccess = true;
        } finally {
            // 将原始 jar 文件替换为临时 jar 文件
            File originalJarFile = new File(jarFilePath);
            if (originalJarFile.delete() && tempJarFile.renameTo(originalJarFile) && replaceSuccess) {
                System.out.println(String.format("jar包中的文件 [%s] 替换成功!", targetFile));
            } else {
                System.out.println(String.format("jar包中的文件 [%s] 替换失败!", targetFile));
            }
 
            tempFile.delete();
            tempJarFile.delete();
        }
    }
 
    private static void copyStream(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
    }
 
    /**
     * 将指定字符串复制到剪切板
     *
     * @param text
     */
    public static void copyToClipboard(String text) {
        StringSelection stringSelection = new StringSelection(text);
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(stringSelection, null);
    }
}
dbeaver.png
WuAiKeLe 发表于 2022-8-2 08:05
很详细,每天都在用社区版.真没想过要研究那么深,顶楼主的研究精神.
daiqing 发表于 2022-8-2 08:27
Dlm.debug.mode=true 好,https验证一直没成功
yuwan1994 发表于 2022-8-2 10:51
这个思路不错,可惜不会java,要不然可以研究下
tianyulouzhu 发表于 2022-8-3 11:21
不会java ..... 按照教程成功了,免费评分冷却ing...
goastship 发表于 2022-8-3 21:49
看了楼主的讲解,明白了两种通过网络校验的方法的来由,非常感谢.
Airs_Lau 发表于 2022-8-4 10:04
感谢大佬的分析
AwesomeOne 发表于 2022-8-5 10:39
感谢楼主分享!
lijingboforever 发表于 2022-8-5 11:14
学习了,那是不是 全家桶都可以按照这个思路进行反编译和破解了
 楼主| pengsx01 发表于 2022-8-5 11:38
lijingboforever 发表于 2022-8-5 11:14
学习了,那是不是 全家桶都可以按照这个思路进行反编译和破解了

那个文件太多了,难度高了一大截
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-16 14:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表