冰凌汇编

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 45|回复: 0
收起左侧

[其它] DBeaver Ultimate edition 算法分析/本地服务器搭建

[复制链接]
uqwndggg 发表于 2022-7-30 21:36:25

DBeaver Ultimate edition 算法分析/本地服务器搭建

DBeaver Ultimate edition 算法分析

官方下载地址: https://dbeaver.com/download/ultimate/
当前分析版本号: dbeaver-ue-22.1.0

前记

前几天出了 该软件的 爆破分析; 机票 :
DBeaver Ultimate edition JAVA程序逆向分析
这次来尝试下不一样的...;

环境

JAVA/javassist/Python
流程:
1.解析license算法, 生成注册器, 生成 publicKey/privateKey/license/subscription
2.替换程序自带的 publicKey
3.配置 host 映射, 搭建本地验证服务器
4.利用第一步生成的 license 注册
WIN10/Mac m1 测试通过

分析

license

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

首先通过在程序界面 import 一个license ,
查看关键字, 定位到目标 jar包 (也可以查看程序 log 日志, 位置: /Users/artemis/Library/DBeaverData/workspace6/.metadata)

可以看到, 程序先将我们输入到 icense去除空格以及删除‘-’,‘#’这些特殊标记符号之后 base64 解密成字节数组
这里格式化字符串写的挺好,
就是你的license 可以有换行, 可以有 ----PUBLIC-KEY--- 这些文件头, 也可以有 # this is comment 这些注释, 格式化之后会只留下关键的 license
程序操作publicKey/license/subscription 都有这个逻辑;  当然; 这个并不重要..

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

然后在licenseManager.importLicense 将 字节数组 解析成 注册凭证,
核心的算法都在 importLicense 里面,我们继续看

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

可以看到, 获取了一个publicKey 用来解密, 既然公钥解密, 那么加密就是私钥了 (这里有用,我们后面会生成密钥,用来注册license)
刚开始以为 publicKey 是走接口获取到的, 毕竟里面有个 URL 什么的;
后来发现其实是 jar包里的资源文件;

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

我们模糊搜索下‘-public.key’, 定位到了这个 jar包: com.dbeaver.app.ultimate_22.1.0.202206121904.jar (这里有用, 后面我们会替换该 publicKey )
推算出 productId = ‘dbeaver-ue’ , 这个应该就是跟着发版来走的; 而且是固定值
众所周知,publicKey 与 privateKey 是 成双成对的, 而且 我们拿不到私钥, 后面只能想办法用我们自己生成的 公钥与私钥了

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

接着往下看, 拿到publicKey 后,开始解密, ras 解密, 没什么好说的, 关键是解密后的 license 格式:
即使解密成功, 但是 license 格式不对, 也是不行的 ;

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

STANDARD((byte)0, 218, "Initial basic license format"),
EXTENDED((byte)1, 238, "Extended format with owner email and corporate license info");

if (buffer.capacity() != this.licenseFormat.getEncryptedLength()) {
    throw new LMException("Bad " + this.licenseFormat + " license length (" + buffer.capacity() + ")");

可以看到 license 第一位字节为 凭证格式,是个枚举类型, 不同的格式 license 长度是不同的,
后面我们选 STANDARD 这种license的话 , 第一位就是0,并且 license总字节长度必须是 218
后 16为 是 license_id字符串, 在往后是 licenseType,licenseStartTime,licenseEndTime..... 这里不多解析了, 生成的时候按这个规则来就行
有一点注意到事, 必须满足位数要求, 比如 license_id 是16位, 你可以只写10位, 剩下的6为必须用0 或者空填充
可以看到, license 的读取是用的 JAVA 的Buffer 来操作的; 我们生成的话 也可以用 Buffer 来操作;
好; 接下来开始手写 license 的生成,具体代码如下;

// 填充字符串
for (int i = 0; i < 64; i++) {
    FILL += " ";
}

// 生成公钥/私钥
KeyPair keyPair = LMEncryption.generateKeyPair(2048);
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();

String pubKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
String priKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());

// license 原始
ByteBuffer buffer = ByteBuffer.allocate(218);
buffer.put((byte) 0);
// licenseId
fillString("this is lice_id", 16, buffer);
// licenseType ULTIMATE('U', "Ultimate license"),
buffer.put((byte) 85); // 'U' = 85
// licenseIssueTime
buffer.putLong(SIMPLE_DATE_FORMAT.parse("2020-01-01").getTime());
// licenseStartTime
buffer.putLong(SIMPLE_DATE_FORMAT.parse("2020-01-01").getTime());
// licenseEndTime
buffer.putLong(SIMPLE_DATE_FORMAT.parse("2099-01-01").getTime());
// flags
buffer.putLong(250L);
// proeudcId
fillString("dbeaver-ue", 16, buffer);
// productVersion
fillString("prod_version", 8, buffer);
// ownerId
fillString("this is owner_id", 16, buffer);
// ownerCompany
fillString("this is ownerCompany", 64, buffer);
// ownerName
fillString("this is ownerName", 64, buffer);

// license 私钥加密
encodeToString = Base64.getEncoder().encodeToString(LMEncryption.encrypt(buffer.array(), privateKey));

// 将 publicKey/privateKey/license 保存到文件
System.out.println("--- PUBLIC KEY ---");
System.out.println(org.jkiss.utils.Base64.splitLines(org.jkiss.utils.Base64.encode(publicKey.getEncoded()), 76));
try (FileWriter fileWriter = new FileWriter(outPath + "dbeaver-ue-public.key")) {
    fileWriter.write(org.jkiss.utils.Base64.splitLines(org.jkiss.utils.Base64.encode(publicKey.getEncoded()), 76));
}
try (FileWriter fileWriter = new FileWriter(outPath + "dbeaver-ue-private.key")) {
    fileWriter.write(org.jkiss.utils.Base64.splitLines(org.jkiss.utils.Base64.encode(privateKey.getEncoded()), 76));
}
try (FileWriter fileWriter = new FileWriter(outPath + "license.key")) {
    fileWriter.write(org.jkiss.utils.Base64.splitLines(org.jkiss.utils.Base64.encode(LMEncryption.encrypt(buffer.array(), privateKey)), 76));
}
// 替换原程序中的公钥
JarFileReplaceUtil.replaceFile(srcPath + "/com.dbeaver.app.ultimate_22.1.0.202206121904.jar", outPath + "dbeaver-ue-public.key", "keys/dbeaver-ue-public.key");

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

生成之后,可以看到多了这三个文件
然后复制生成的 license.key ,重新启动程序 (替换了 jar包里的 公钥资源文件,所以需要重启) ,
注册一下试试;

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

哎,居然报错了; 跟进去再看看,
发现个坏消息 , 原来除了用 publicKey 解密, 还有一层校验,
程序会会带着 本地解密后的 license信息 请求 服务器,来获取 subscription 订阅信息;
服务器的 数据 我们是没法子整的 , 但是我们也可以 本地搭建一个服务器; 让程序来请求我们自己的本地服务器来验证;

subscription

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

仔细瞅瞅,看到 接口响应成功的 response 也是有些规则的,示例如下:

response = "VALID:SUCCESS" + "\r\n" + "-- SUBSCRIPTION" + "\r\n" + subscription

VALID 与 SUCCESS 为状态, 并且必须用: 切割, 程序例有教研 ,':' 的ascii 为 58;
如果响应文本没有包含':'的话, 会直接报错;

int divPos = licenseStatusString.indexOf(58);
if (divPos == -1) {
var19 = new LicenseCheckResult(GeneralUtils.makeErrorStatus("Bad check status: " + licenseStatusString), LMLicenseStatus.INVALID);
return var19;
}

除了状态, resposne 后面的文本才是 subscription 主体, 其中包含了服务器返回的订阅信息;
如果这里为空 也是会注册失败的;
不过好消息是, subscription 的算法 与 license 如出一辙;只不过 subscription 的 格式有些不一样;

try {
    this.format = LMSubscriptionFormat.valueOf(buffer.get());
} catch (Exception var5) {
    log.warning("Unsupported subscription format: " + buffer.get(0));
    this.format = LMSubscriptionFormat.STANDARD;
}

if (buffer.capacity() != this.format.getEncryptedLength()) {
    throw new LMException("Bad " + this.format + " subscription length (" + buffer.capacity() + ")");
} else {
    this.licenseId = LMUtils.getStringFromBuffer(buffer, 16);
    this.period = LMSubscriptionPeriod.getById((char)buffer.get());
    this.periodDays = buffer.getInt();
    this.lastRenewDate = LMUtils.getDateFromBuffer(buffer);
    this.expirationDate = LMUtils.getDateFromBuffer(buffer);
    this.activationDate = LMUtils.getDateFromBuffer(buffer);
    this.deactivationDate = LMUtils.getDateFromBuffer(buffer);
    this.totalRenewCount = buffer.getInt();
    this.active = buffer.get() != 0;
}
public enum LMSubscriptionFormat implements LMSerializeFormat {
STANDARD((byte)0, 59, "Initial subscription format");
}

可以看到, subscription 第一位 LMSubscriptionFormat 固定为 0,
并且 subscription 解密后的字节长度 也固定为 59,
这里要注意 subscription 的 license_id 与 前面 license 的 license_id 必须一致;
在往后面的几个字段也看不懂是啥, 只看到有个过期时间 expirationDate, 别的瞎填写吧;

看懂规则后, 我们按前面生成 license 的地方 顺便把 subscription 也生成一份出来, 代码如下:

// STANDARD((byte)0, 59, "Initial subscription format");
ByteBuffer subscriptionBuffer = ByteBuffer.allocate(59);
subscriptionBuffer.put((byte) 0);
fillString("this is lice_id", 16, subscriptionBuffer);
subscriptionBuffer.put((byte) 89);
subscriptionBuffer.putInt(1);
subscriptionBuffer.putLong(SIMPLE_DATE_FORMAT.parse("2099-01-01").getTime());
subscriptionBuffer.putLong(SIMPLE_DATE_FORMAT.parse("2099-01-01").getTime());
subscriptionBuffer.putLong(SIMPLE_DATE_FORMAT.parse("2099-01-01").getTime());
subscriptionBuffer.putLong(SIMPLE_DATE_FORMAT.parse("2099-01-01").getTime());
subscriptionBuffer.putInt(1);
subscriptionBuffer.put((byte) 1);
subscriptionToString = Base64.getEncoder().encodeToString(LMEncryption.encrypt(subscriptionBuffer.array(), privateKey));
try (FileWriter fileWriter = new FileWriter(outPath + "subscription.key")) {
fileWriter.write(subscriptionToString);
}
System.out.println("subscriptionToString : \r\n" + subscriptionToString);

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

现在文件都有了, 还差一部, 就是让搭建一个本地服务器用来验证 subscription ;
这里我就用 python 搭了; (简单方便快捷)
然后还需要配置 host 将 ‘dbeaver.com’ 转发到 127.0.0.1 (这个地址是 开charles 之后 将 DBeaver 配置代{过}{滤}理后抓到的)
这里踩了一些坑, 因为 原域名是开启了 SSL 的, 所以我们也必须开始; 但是 java虚拟机又不信任这个SSL证书, 折腾了好久才折腾通过;
具体代码如下: (实际上就提供一个接口, 将我们之前生成的 subscription 返回, 可以顺便把 请求信息记录下)

搭建本地服务器

@app.route('/<re(".*"):path>', methods=['GET', 'POST', 'PUT', "DELETE", "PATCH"])
def welcome(path):

    f = open('/Users/artemis/Downloads/keys/subscription.key', 'r')
    sub_str = "".join(f.readlines())
    return "VALID:SUCCESS" + "\r\n" + "-- SUBSCRIPTION" + "\r\n" + sub_str

if __name__ == "__main__":
    '''
    # 配置文件mySsl.conf:
    # 生成私钥:
    openssl genrsa -out server.key 4096
    # 生成证书请求文件:
    openssl req -new -sha256 -out server.csr -key server.key -config mySsl.conf 
    # 检查证书申请文件内容:
    openssl req -text -noout -in server.csr
    # 利用证书请求文件生成证书,执行如下命令:
    openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt -extensions req_ext -extfile mySsl.conf
    '''

    '''
    # 配置 JDK 信任证书
    keystore=/Users/artemis/Library/Java/JavaVirtualMachines/azul-11.0.13/Contents/Home/lib/security/cacerts
    keytool -delete -alias alias_dev  -keystore $keystore -storepass changeit
    keytool -import -file /Users/artemis/Documents/work_space/python/python_demo/tools/ssl/server.crt -alias alias_dev -storepass changeit -keystore $keystore
    keytool -v -list -keystore $keystore -storepass changeit
    '''

    current_directory = os.path.dirname(os.path.abspath(__file__))

    app.run(ssl_context=(
        f'{current_directory}/tools/ssl/server.crt',
        f'{current_directory}/tools/ssl/server.key'
    ), host='0.0.0.0', port=443)

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

配置好 host 文件, 然后我们再次复制 license,重启程序 注册;
可以看到 python web 服务, 收到请求了,请求信息如下:

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

* Running on https://127.0.0.1:443
 * Running on https://172.18.20.57:443 (Press CTRL+C to quit)
{
    "method": "GET",
    "root": "https://dbeaver.com/",
    "full_path": "/lmp/checkLicense?crc=1ae7edc9687e503c9fc6d079a91bca5f&product=dbeaver-ue&version=22.1&license=this%20is%20lice_id",
    "headers": [
        "X-Referrer: D1XXSUQM97WAZN",
        "User-Agent: DBeaver Ultimate 22.1.0 [Mac OS X aarch64]",
        "Host: dbeaver.com",
        "Connection: Keep-Alive",
        "Accept-Encoding: gzip",
        "",
        ""
    ],
    "args": {
        "crc": "1ae7edc9687e503c9fc6d079a91bca5f",
        "product": "dbeaver-ue",
        "version": "22.1",
        "license": "this is lice_id"
    },
    "form_data": {},
    "raw_data": "",
    "cookies": {}
}
127.0.0.1 - - [28/Jul/2022 17:11:48] "GET /lmp/checkLicense?crc=1ae7edc9687e503c9fc6d079a91bca5f&product=dbeaver-ue&version=22.1&license=this%20is%20lice_id HTTP/1.1" 200 -

然后在看看我们的程序;
终于注册成功 !!

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

DBeaver Ultimate edition 算法分析/本地服务器搭建 - uqwndggg_冰凌汇编

后记

总体研究下来, 这个过程比之前的爆破要麻烦也复杂的多, 但是刺激啊....

仅供研究学习使用,请勿用于非法用途
注:若转载请注明来源(本贴地址)与作者信息。

冰凌汇编免责声明
以上内容均来自网友转发或原创,如存在侵权请发送到站方邮件9003554@qq.com处理。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|站点统计|Archiver|小黑屋|RSS|冰凌汇编 ( 滇ICP备2022002049号 滇公网安备 53032102000029号)|网站地图

GMT+8, 2022-10-6 22:42 , Processed in 0.131390 second(s), 8 queries , Redis On.

冰凌汇编 - 建立于2021年12月20日

Powered by Discuz! © 2001-2022 Comsenz Inc.

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