前言

最近使用docker创建了个人的rocket chat应用,看着后台提示没有license总觉得缺了点啥。于是通过github进行review code 分析它的许可验证逻辑。

定位license

随意添加license信息,可以通过F12看到请求的路径为 api/v1/licenses.add

5fa3394a676ec1b099aec84c1ccbb5d1.png

因为rocket chat是开源的,所以可以在github 上随时查看源码

直接查找关键词 licenses.add 可以看到有很多结果,这里看到 apps/meteor/ee/app/license/server/license.ts 这个文件中存在加载license、解密等过程。

7415154ee10c38c459d582212600522d.png

跟进解密方法

查看decrypt方法,在 apps/meteor/ee/app/license/server/decrypt.ts 文件中存在相关的解密方法。

81de4be24e480b8bec50f0610ceaca3b.png

根据代码,公钥是经过base64编码后的公钥,位数是2048位
43d20fb4352da101e39f0d3751e13d97.png
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqWSskd9/6zRx8kyPcics
b32w2wxVuw7yBT96rQ/8D+yMeCMO9wSSpHa/9nFywowEtigpt/wroPN+VGSwbtwP
FXBeqElBndGFAl86e5+EliH8Kz/hGnCmJNmXpxEK2RE03X4IxsYX7DDB7Mtx/iqs
cjB/OuvSBkjiSleS7blNIT/dA7K4/CJ3oiu02bL4ExclCHepzqNMeP3wUZgpxOnf
NOud8IXYK73zScuE8E157YwpzCKpVaHX7ZJf8QuNsOO5W/aIjKl3L6269+eIOErG
wONmaHnzfg9FLpJhzgpO38aVn76vD5KKjBajWskY+4a2gSQmKNeFqaqOozyEFM0e
cFWZVVZ3Leh4vEMoYVPyIzx96x8f2/ymPnhIuvQv5wN4fyepa7EY5UCcp71z8kfP
4FcUzPA0IDWyMihXR/G6XgQQZ4Gb/qBBhvrviJCFzfYDcJgL7FegFYHP3PGL07Qg
vLevMK+iQZPrxrbxySqdPOkgurKjVrXTUr4A9TgiLyIX5UlJq3E/RV7mfOqZnLTa
SCVXHBhuPlnCGZR01ToTCfKhMG1u0CFnL2+15hC9fqOmWv9Qke43qlJ0PgF3VJ/X
ux/mTpnk9gnbG9JH+mfH39RoFvTNinYwSMvYzutVOn69sOzdwhDla90l3ACh4xCV
K7JOX+uHkoNu3g2iVxiZUM0CAwEAAQ==
-----END PUBLIC KEY-----

这种RSA 2048基本是无法破解的,只能采用公钥替换的形式。

获取license格式

采用在线试用的方式获取到了30天的试用license

JnIab9mmS9Vg4GIkTA/9zXLiexyrWIguhmhI85coxHIgk7Y6KOXO3g2KU92VfxR7GSgFJE3B/7lEagDlzZi8orWKZ0lAxLTORyO0wXPgqFY/42M2iXGyo3FacwINbooZA+sPiKJLUavplatdCBQF+v9UYa33fA8Yw+0E44V5Vr6VddyC7Bezpcmn2HxLXpL9XRZxvSl/LIeiUT2u8LtUu3HmW3hmCx3eCFAdE2ZIKLosdVrRYl9tG+FTPQINC7p7iQc/QZw8iX3JKAE1Aw3WmhbkWI63FQfsL/W1DM46vaZlCR1eX7beY5mbb4pBnfsBAsX7WxBi8nWsQ1yGDprmaQofG0YagIu/NGFA6Sw+7i0qN8jVqq/634d4hXmZQntGkhFckuUpMsX2qITyNpcZCcOn8lR7rhMy5fyLdrd6q6LXKeF5dW1y0fMUH5vYsc6EmBBnodC9neX8fkaewXEIvHc/BHxWVOsOrW2Skz4s3lkzz4wqkArlNdsIhEBRKB3OhZhgHbWyMPtYuoo4LfC0FLVmEEJrKXxJ8YibIYGaLfZbfuBwx3kBkBg8p2rlafEqZ/+5ca32RCuN0Ly3TGdOnOY6EFFD0OuP24JslcO5MpXgzXVTuhnAxG9FtZEjqYTamHnAgKD4G1ZOmNWkczpUf7yYsMQ0lEjf4jr10Z1Y6dk=

使用在线解密对其密文解密

44431babeb1ea9a682398bd52b2d5f50.png

明文license信息如下:
{"version":2,"url":"chat.synology.pub","expiry":"2023-05-03","maxRoomsPerGuest":1,"modules":["enterprise:*"],"tag":{"name":"Enterprise","color":"#F3BE08"},"meta":{"trial":true,"trialEnd":"2023-05-03T14:44:27.63697163Z","workspaceId":"642ae64cec4c1b00011e954d"}}

替换公钥

docker运行环境中的源码都是被打包过的文件,通过 grep 公钥头确定文件在 /app/bundle/programs/server/app/app.js 中

因为默认的docker容器中没有vim,只能先导出文件修改完成再复制回去。

55c008cadc58da054eb9da6d0f9fb2dd.png

大小为50多M,只能通过vim进行修改了。

替换为我们自己的公钥就行,然后替换到docker容器内。

24cfd9ee27a959c9695c30964bc7f69f.png

然后就是我们使用自己的私钥进行对license内容进行加密

使用nodejs生成破解代码:

const fs = require('fs');
const crypto = require('crypto');
 
 
const publicKey =
    'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFxV1Nza2Q5LzZ6Ung4a3lQY2ljcwpiMzJ3Mnd4VnV3N3lCVDk2clEvOEQreU1lQ01POXdTU3BIYS85bkZ5d293RXRpZ3B0L3dyb1BOK1ZHU3didHdQCkZYQmVxRWxCbmRHRkFsODZlNStFbGlIOEt6L2hHbkNtSk5tWHB4RUsyUkUwM1g0SXhzWVg3RERCN010eC9pcXMKY2pCL091dlNCa2ppU2xlUzdibE5JVC9kQTdLNC9DSjNvaXUwMmJMNEV4Y2xDSGVwenFOTWVQM3dVWmdweE9uZgpOT3VkOElYWUs3M3pTY3VFOEUxNTdZd3B6Q0twVmFIWDdaSmY4UXVOc09PNVcvYUlqS2wzTDYyNjkrZUlPRXJHCndPTm1hSG56Zmc5RkxwSmh6Z3BPMzhhVm43NnZENUtLakJhaldza1krNGEyZ1NRbUtOZUZxYXFPb3p5RUZNMGUKY0ZXWlZWWjNMZWg0dkVNb1lWUHlJeng5Nng4ZjIveW1QbmhJdXZRdjV3TjRmeWVwYTdFWTVVQ2NwNzF6OGtmUAo0RmNVelBBMElEV3lNaWhYUi9HNlhnUVFaNEdiL3FCQmh2cnZpSkNGemZZRGNKZ0w3RmVnRllIUDNQR0wwN1FnCnZMZXZNSytpUVpQcnhyYnh5U3FkUE9rZ3VyS2pWclhUVXI0QTlUZ2lMeUlYNVVsSnEzRS9SVjdtZk9xWm5MVGEKU0NWWEhCaHVQbG5DR1pSMDFUb1RDZktoTUcxdTBDRm5MMisxNWhDOWZxT21XdjlRa2U0M3FsSjBQZ0YzVkovWAp1eC9tVHBuazlnbmJHOUpIK21mSDM5Um9GdlROaW5Zd1NNdll6dXRWT242OXNPemR3aERsYTkwbDNBQ2g0eENWCks3Sk9YK3VIa29OdTNnMmlWeGlaVU0wQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=';
 
const encrypted = 'JnIab9mmS9Vg4GIkTA/9zXLiexyrWIguhmhI85coxHIgk7Y6KOXO3g2KU92VfxR7GSgFJE3B/7lEagDlzZi8orWKZ0lAxLTORyO0wXPgqFY/42M2iXGyo3FacwINbooZA+sPiKJLUavplatdCBQF+v9UYa33fA8Yw+0E44V5Vr6VddyC7Bezpcmn2HxLXpL9XRZxvSl/LIeiUT2u8LtUu3HmW3hmCx3eCFAdE2ZIKLosdVrRYl9tG+FTPQINC7p7iQc/QZw8iX3JKAE1Aw3WmhbkWI63FQfsL/W1DM46vaZlCR1eX7beY5mbb4pBnfsBAsX7WxBi8nWsQ1yGDprmaQofG0YagIu/NGFA6Sw+7i0qN8jVqq/634d4hXmZQntGkhFckuUpMsX2qITyNpcZCcOn8lR7rhMy5fyLdrd6q6LXKeF5dW1y0fMUH5vYsc6EmBBnodC9neX8fkaewXEIvHc/BHxWVOsOrW2Skz4s3lkzz4wqkArlNdsIhEBRKB3OhZhgHbWyMPtYuoo4LfC0FLVmEEJrKXxJ8YibIYGaLfZbfuBwx3kBkBg8p2rlafEqZ/+5ca32RCuN0Ly3TGdOnOY6EFFD0OuP24JslcO5MpXgzXVTuhnAxG9FtZEjqYTamHnAgKD4G1ZOmNWkczpUf7yYsMQ0lEjf4jr10Z1Y6dk=';
function decrypt(encrypted) {
    const decrypted = crypto.publicDecrypt(Buffer.from(publicKey, 'base64').toString('utf-8'), Buffer.from(encrypted, 'base64'));
    return decrypted.toString('utf-8');
}
console.log(decrypt(encrypted));
 
// 读取私钥文件
const privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAkNZsLxCKSwENCKk40fNqH6t9ZNOzbEZXmy89/8Ie7DdA/QP/
N6X26oAJssrKL4Ym927T4hq8vZvZvHdHIj5DBv6C0JdwQHmhtzqwgfJoUtDFSgd7
66fokd4ns2NYdiIztsFPzuQj84Ys2Rbxf/dn19buIgXevM6wni1g4faYErLHEIaK
zczFE91wq6kbkeO4KrpuhFUplwFsWJ2SBhiiA8+mYclhfhiAgGMhBAJKHXUs8qmS
u+37vkLtlHxtragAv2hUWjwi0JKAmAE7VgB2Jz4bYhLVEWzVcpBFPq4ikvvwTkXg
Tcxhj9Wq2PRy9iiDBPia8DIqzxjAoEQrSF4iYwIDAQABAoIBAAd0qKHNMPempWXq
VoM+pU13Pz+b3JyYpZUJ2+zFsJNp5+238VqLL8HnUJh98xjgx8vs58aEqDqXQm1L
mcNosq9lfC5oqhEBMd+D8ebk9GHVXvjuMbILzqw5/cLKPMxRlMy1+GAm3LYbrhG7
kJ4mijEeK0JgTJSQWNJYdUHftvkfWwsUyzdGjHtj9St1piOyxk3cyiBMFf/H3vKK
YJlUkEbS7ZZgi+/6f3xnozb+Ei18UZnITrFcWfMEn0EdS56Ms05e0v0q9f/aFLBY
4i4RtqOHx3xOisBacMsIOi/ynuje3KKwm4nu82gwDlF+WrxKoQH3k/6JHSShKKG7
hV75TKECgYEAkgeDhLAPnJIbuS96hD3zYnd5/yqnNM3Q1nEvbsoYUurShHqsSx4G
px+0baKZFEkLTKHdPFeosyknnWpSYYBgYjlL2JpDAQsH507FxK4EwRpn5rLtI8Ap
c/UjaG34Eyu1vJeOlW8VX+xaWHyewUyrbijqud8NZoSo603HovdJrr0CgYEA/ekn
Nn5+KcGGqtdz+po6fN5NAIn0oGnrqJnOqnNHCB4G74njbCy1jhAw8gMoSWkyw2GX
Lt3RNDucsFBOmRa7u08ClHVHBWPrQ3HY3cE+y4nkHyB0QgEpT7ZpfergCNMymdN1
ExHiP86QQem673FaUcOLg7jKw16Jx95nWmpQN58CgYBuuTvDtCtiMHbM528iLkcI
9kaOb6zwoM4kixXID3x6Aos04D8bhdzNg4CvUIZ5lxj2NhUl1+GWVzIubZuhSlHK
qF8WEYGUnOSVQmk6RChessLtbeXZIa9MuSbr29Yp0w6tvMzkCaJPZUrrpTJKpvOl
R2kTHklu3k+mewdQTeiUkQKBgDHK6zmwjKU7omEWZ1QZsqaSIZ+dbi+XFfO2VeTv
PlrFKK8I52RrUB9P5YlQPTJIQwA1vyQds8z+c7fPx9oVrzMIR4U9inPwKE7NoK28
G8hmfinsf2ACQkuzhfR/fve8Eww/f5IBy9CffYKvh001eXTXWCC4uGqfu31KjBIb
DygZAoGBAINF9lv8nRn/jh1ZJy/7xZzKYSEDg8QxD+Gj1bgD8ixnWbJdQoWHs+Bx
aIVu5bDNk+Pg01OEcwvDlyt0K4DLRz84143MzoFK2h59A0x6XTw388EihKmSJScr
6cFIoYu6BEIUuZ7uiEaRaWOhk8Lav/5UOIMWyf6aLpL8c9Nb8Hju
-----END RSA PRIVATE KEY-----
`;
 
// 读取明文文件
const plainText = `{"version":2,"url":"chat.synology.pub","expiry":"2099-12-01","maxRoomsPerGuest":999,"modules":["enterprise:*"],"tag":{"name":"Enterprise","color":"#F3BE08"},"meta":{"trial":false,"workspaceId":"642ae64cec4c1b00011e954d"}}`;
 
function encrypt(message) {
    const bufferContent = Buffer.from(message);
    const encrypted = crypto.privateEncrypt(privateKey, bufferContent);
    console.log(encrypted.toString("base64"));
}
 
encrypt(plainText);

结果:

XZ+1eHE+hezUeV85wapf3ZdX9+6Rvs9yllpTX/K9Lgf+oh2IeRL3VZGlmdAYyGekJB60puixEfG3Sfgdw5hvdgqwh2uc1z2E/QjIblvI/AaKAdSj5bwvp8Vjz0Na2zrEwwsALbMjQha2fHQl8lYyU1CrNohtKCV8G6gRZIpYsurCV9TIScgozvmdoyDU8Pp8/fC6+usY4FAsU1HNowRL9xro+RzeBF3ZZqdglhF9US8QPkDRQ3raEAGougt+9m9kSlW06VNXN2HVqtKsRxmuc4R7hT77H3hdUXOPn2JsFnIu+SaMRF2lGfNWsDkbsPuYy8KV3emJsQEBouClS9F5Tg==

最终展示

替换到容器文件后需要重启容器,粘贴上面生成的license信息

a1f36e6cd54cdc33c43532ee7355bc06.png

遇到的坑

RSA私钥2048位加密最多只能加密245个字节好像,所以要缩减license的长度。