吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9202|回复: 78
上一主题 下一主题
收起左侧

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

  [复制链接]
跳转到指定楼层
楼主
Vvvvvoid 发表于 2022-7-28 17:29 回帖奖励
本帖最后由 Vvvvvoid 于 2022-8-2 09:43 编辑

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

DBeaver Ultimate edition 算法分析

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

前记

前几天出了 该软件的 爆破分析; 机票 :
DBeaver Ultimate edition JAVA程序逆向分析
这次来尝试下不一样的...([b]尽量不硬编码源程序) ;

环境

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

分析

license

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

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

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

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

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

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


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");

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

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

subscription

仔细瞅瞅,看到 接口响应成功的 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);

现在文件都有了, 还差一部, 就是让搭建一个本地服务器用来验证 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)

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

* 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 -

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

后记

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

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

免费评分

参与人数 32吾爱币 +33 热心值 +28 收起 理由
sywee + 1 + 1 用心讨论,共获提升!
白票怪给你点赞 + 1 + 1 我很赞同!
wuyanzu001 + 1 + 1 我很赞同!
朮小凯 + 1 + 1 谢谢@Thanks!
XIAOHUANGDI520 + 1 + 1 用心讨论,共获提升!
time2s + 1 + 1 我很赞同!
see1 + 1 + 1 谢谢@Thanks!
黄教主 + 1 + 1 谢谢@Thanks!
chenjingyes + 1 + 1 谢谢@Thanks!
jasonA + 1 + 1 热心回复!
简单メ传说 + 1 + 1 用心讨论,共获提升!
笙若 + 1 + 1 谢谢@Thanks!
m52pj + 1 热心回复!
victos + 1 + 1 谢谢@Thanks!
unbeaten + 1 + 1 谢谢@Thanks!
RayC02 + 1 热心回复!
timeni + 1 + 1 用心讨论,共获提升!
七个涨停一倍 + 1 鼓励转贴优秀软件安全工具和文档!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
gaosld + 1 + 1 谢谢@Thanks!
Liona + 1 + 1 楼主厉害,受益匪浅,自己还要好好学习一下,感谢楼主分享
小朋友呢 + 2 热心回复!
zhczf + 1 + 1 我很赞同!
774258 + 1 用心讨论,共获提升!
努力加载中 + 1 + 1 热心回复!
HongHu106 + 1 + 1 谢谢@Thanks!
BtBoy666 + 1 我很赞同!
cchume + 1 + 1 我很赞同!
xiaolong23330 + 1 + 1 围观大佬的首次精华耶
chakid125 + 1 + 1 谢谢@Thanks!
夜泉 + 1 + 1 谢谢@Thanks!
三滑稽甲苯 + 2 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

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

推荐
Patches 发表于 2022-7-28 18:27
大佬厉害啊   什么时候再做个win版的教程  mac版还是比较简单
推荐
 楼主| Vvvvvoid 发表于 2022-7-28 18:54 |楼主
Patches 发表于 2022-7-28 18:27
大佬厉害啊   什么时候再做个win版的教程  mac版还是比较简单

都一样。。
java开发的,依赖的jar包也一样;不晓得你说的 mac 简单是指什么。。
4#
lanlinux 发表于 2022-7-28 17:46
5#
jone33 发表于 2022-7-28 17:47
这个很棒 点赞
6#
yiidii 发表于 2022-7-28 17:52
牛牛牛牛牛牛牛牛牛牛牛
7#
zuriaake 发表于 2022-7-28 18:19
这个对所有平台都生效么?
8#
 楼主| Vvvvvoid 发表于 2022-7-28 18:53 |楼主
zuriaake 发表于 2022-7-28 18:19
这个对所有平台都生效么?

mac/win10 测试通过,
基于 java的跨平台 ,别的平台应该没问题
9#
Allen666 发表于 2022-7-28 19:02
好用,感谢大佬
10#
yis1 发表于 2022-7-28 19:05
感谢分享,谢谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-3-28 17:12

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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