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

VSole2022-07-29 16:52:08

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

首先通过在程序界面 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);// licenseIdfillString("this is lice_id", 16, buffer);// licenseType ULTIMATE('U', "Ultimate license"),buffer.put((byte) 85); // 'U' = 85// licenseIssueTimebuffer.putLong(SIMPLE_DATE_FORMAT.parse("2020-01-01").getTime());// licenseStartTimebuffer.putLong(SIMPLE_DATE_FORMAT.parse("2020-01-01").getTime());// licenseEndTimebuffer.putLong(SIMPLE_DATE_FORMAT.parse("2099-01-01").getTime());// flagsbuffer.putLong(250L);// proeudcIdfillString("dbeaver-ue", 16, buffer);// productVersionfillString("prod_version", 8, buffer);// ownerIdfillString("this is owner_id", 16, buffer);// ownerCompanyfillString("this is ownerCompany", 64, buffer);// ownerNamefillString("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 注册一下试试;

哎,居然报错了; 跟进去再看看,

发现个坏消息 , 原来除了用 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,

后面的也看不懂是啥, 只看到有个过期时间 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_strif __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 -

然后在看看我们的程序;

终于注册成功 !!

后记

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

仅供研究学习使用,请勿用于非法用途

注:若转载请注明来源(本贴地址)与作者信息。

base64dbeaver
本作品采用《CC 协议》,转载必须注明作者和本文链接
官方下载地址: https://dbeaver.com/download/ultimate/
适用场景:使用burp作为代理,经过burp的数据包会自动进行base64编码。
多次测试后发现确实都相等,确定就是标准的Base64编码。发现与之前的111极为相似。
菜刀、冰蝎、蚁剑、哥斯拉的流量特征
[VNCTF2022]gocalc0复现
2022-05-07 16:02:38
看雪论坛作者ID:H3h3QAQ
Satacom下载程序,也称为LegionLoader,是2019年出现的一个著名的恶意软件家族。该恶意软件利用查询DNS服务器的技术获取base64编码的URL,以便接收当前由Satacom传播的另一恶意软件家族的下一阶段。
在渗透测试过程中,我们经常会遇到一些特殊的编码,如地址栏的URL编码,如抓包过程中遇到的Unicode编码、base64编码等。这款工具界面美观,使用稳定,相当好用。之前使用的小葵转码工具,一旦输错就会崩溃,相当淡疼。下面看下它的样子:可以看到它支持多种编码转换,前几个选项应用场景比较少,从base64往下的选项就比较常用了。
Decodify是一款功能强大的字符串安全处理工具,在该工具的帮助下,广大研究人员能够轻松地以递归的方式检测和解码编码字符串。假设现在有一个字符串“s0md3v”,然后使用Base64对其编码,结果如下:czBtZDN2. 解码凯撒密码我们可以使用--rot选项来提供偏移量,或者使用--rot all命令来告诉Decodify使用1-26偏移量来解码目标字符串。
前言前几天对自己学校进行的一次渗透测试,由于深信服过于变态,而且拦截会直接封ip,整个过程有点曲折期间进行了后缀名绕过,jspx命名空间绕过、获取网站根目录、base64五层编码写入shell等操作0x01 获取网站接口主界面:上传点:由于该应用是内嵌企业微信的套皮Html,所以我们首先用Burp Suite抓包获取接口和cookie任意文件上传:文件名强制命名为code+学号,后缀为最后一次点号出现之后的字母0x02 后缀名绕过代码不限制后缀名,但是waf限制呀!
VSole
网络安全专家