记录一次startUML破解

  1. 1. 前言
  2. 2. 破解步骤
    1. 2.1. 0x0.前置工作
    2. 2.2. 0x1.破解license校验
    3. 2.3. 0x2.去除启动更新检测
    4. 2.4. 0x3.破解完成
  3. 3. 结语

前言

之前startUML还在3.1.0的时候其实已经破解过一次了,网上的破解教程其实也有很多。讲道理现在startUML的颜值还是很高的。startUML目前的版本都是通过electron打包生成的软件,纯前端开发的东西,并且代码清晰,该有注释有注释,业务逻辑一点混淆都没做,搞得我破解的怪不好意思的。我没有搞事情!但是因为最近StartUML 3.1.1版本发布,发现退出时会强制弹出更新,怪难受的,上次没注意选了是,结果瞬间给我装完了。更加增加了我去除更新的决心。

破解步骤

0x0.前置工作

1.首先电脑应该安装node环境,这我就不介绍了,各位自行下载。
2.安装asar包,为后面的解压app.asar做准备npm install asar -g-g参数是全局安装,node的工作机制我也不介绍了(嘘,其实是我不懂,就是一个弱鸡前端)。
3.找到startUML安装目录,找到resources文件夹,里面的app.asar就是我们的目标了。
4.打开命令行使用npm extract app.asar app将app.asar的文件解压到app文件夹。

0x1.破解license校验

license校验的文件就在app/src/engine/license-manager.js中,打开文件我们可以看到这样一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* Check license validity
*
* @return {Promise}
*/
validate () {
return new Promise((resolve, reject) => {
try {
// Local check
var file = this.findLicense()
if (!file) {
reject('License key not found')
} else {
var data = fs.readFileSync(file, 'utf8')
licenseInfo = JSON.parse(data)
var base = SK + licenseInfo.name +
SK + licenseInfo.product + '-' + licenseInfo.licenseType +
SK + licenseInfo.quantity +
SK + licenseInfo.timestamp + SK
var _key = crypto.createHash('sha1').update(base).digest('hex').toUpperCase()
if (_key !== licenseInfo.licenseKey) {
reject('Invalid license key')
} else {
// Server check
$.post(app.config.validation_url, {licenseKey: licenseInfo.licenseKey})
.done(data => {
resolve(data)
})
.fail(err => {
if (err && err.status === 499) { /* License key not exists */
reject(err)
} else {
// If server is not available, assume that license key is valid
resolve(licenseInfo)
}
})
}
}
} catch (err) {
reject(err)
}
})
}

checkLicenseValidity () {
this.validate().then(() => {
setStatus(this, true)
}, () => {
setStatus(this, false)
UnregisteredDialog.showDialog()
})
}

很显然这就是我们本次的目标惹!但是网上几乎全部的破解的步骤都是只改了2行代码:

1
2
3
4
5
6
7
8
9
10
11
12
checkLicenseValidity () {
this.validate().then(() => {
setStatus(this, true)
}, () => {
// 去除失败部分的逻辑
// setStatus(this, false)
// UnregisteredDialog.showDialog()

// 强行将失败部分逻辑的状态设置成与success部分一致
setStatus(this, true)
})
}

虽然这样确实so easy的就将startUML的license校验给干掉了,但是主动输入密钥的话还是会提示秘钥无效,破解的一点都不完整,这才不是我想要的破解!(PS:小声逼逼,真的没见过比这个更容易的破解了)

我们现在分析一下其余部分的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
validate () {
return new Promise((resolve, reject) => {
try {
// Local check
// 注释写着本地判断,如果license文件不存在则直接返回license无效的异常,否则则进入下一段逻辑
var file = this.findLicense()
if (!file) {
reject('License key not found')
} else {
// 读取license文件
var data = fs.readFileSync(file, 'utf8')
licenseInfo = JSON.parse(data)
// 将信息拼接字符串
var base = SK + licenseInfo.name +
SK + licenseInfo.product + '-' + licenseInfo.licenseType +
SK + licenseInfo.quantity +
SK + licenseInfo.timestamp + SK
// 字符串哈希并且转换大写
var _key = crypto.createHash('sha1').update(base).digest('hex').toUpperCase()
// 如果本地校验失败则不通过,否则进入服务器请求逻辑
if (_key !== licenseInfo.licenseKey) {
reject('Invalid license key')
} else {
// Server check
// 服务器判断
$.post(app.config.validation_url, {licenseKey: licenseInfo.licenseKey})
.done(data => {
resolve(data)
})
.fail(err => {
// 注释说的很清楚了,如果status === 499则代表license无效
if (err && err.status === 499) { /* License key not exists */
reject(err)
} else {
// If server is not available, assume that license key is valid
// 如果没有返回异常并且返回值 status !== 499,则通过校验
resolve(licenseInfo)
}
})
}
}
} catch (err) {
reject(err)
}
})
}

仔细分析一下代码,我们可以得知到了所有的判断逻辑,那么我们只需要将服务器相关的逻辑干掉即可,只保留读取文件以及回调的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
validate () {
return new Promise((resolve, reject) => {
try {
// Local check
var file = this.findLicense()
if (!file) {
reject('License key not found')
} else {
var data = fs.readFileSync(file, 'utf8')
licenseInfo = JSON.parse(data)
resolve(licenseInfo)
}
} catch (err) {
reject(err)
}
})
}

然后再将licenseKey注册部分逻辑解决即可,这是原逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Check the license key in server and store it as license.key file in local
*
* @param {string} licenseKey
*/
register (licenseKey) {
return new Promise((resolve, reject) => {
$.post(app.config.validation_url, {licenseKey: licenseKey})
.done(data => {
var file = path.join(app.getUserPath(), '/license.key')
fs.writeFileSync(file, JSON.stringify(data, 2))
licenseInfo = data
setStatus(this, true)
resolve(data)
})
.fail(err => {
setStatus(this, false)
if (err.status === 499) { /* License key not exists */
reject('invalid')
} else {
reject()
}
})
})
}

这部分逻辑就是注册license的逻辑了,逻辑理解没有任何难度,是不是还记得上面有一段哈希的操作我们照着键值原样仿制一个license的json对象即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
register (licenseKey) {
return new Promise((resolve, reject) => {
var data = {
name: "Teble",
product: "Teble product",
licenseType: "PS",
quantity: "Teble Quantity",
timestamp: "1575275098",
licenseKey: "It's Cracked!!",
crackedAuthor: "Teble"
}
var file = path.join(app.getUserPath(), '/license.key')
fs.writeFileSync(file, JSON.stringify(data, 2))
licenseInfo = data
setStatus(this, true)
resolve(data)
})
}

破解到这里可以基本告一段落了,软件是能使用了,但是更新的问题还没解决呢,如果startUML检测到版本有更新,或者手动误操作点击了更新,依旧是会进行后台下载不需要经过你二次同意的。

0x2.去除启动更新检测

这部分逻辑比我想象中的难找了一点,我还纳闷了一会,他到底是怎么做到关闭软件时再弹出安装操作的,然后经历了几次卸载,观察发现,当软件开启时就会检测是否有更新,如果有则立马开启后台下载,网速嗖嗖嗖的飚,路径下载到了AppData\Roaming\StarUML这个路径下,下载完成后,如果关闭软件,则会立马触发安装操作。

这部分逻辑在app/src/main-process/application.js文件下注释一句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Application extends EventEmitter {

constructor () {
super()
// ...
this.loadExtensions()
this.handleCommands()
this.handleMessages()
this.handleEvents()

// Check for updates on start
// 启动时判断更新,注释就完事了
//autoUpdater.checkForUpdatesAndNotify()
}
// ...
}

此外engin目录下还有一个update-manager.js,注释后,手动检测更新依旧返回已经是最新版无需更新的字样:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Update Manager
*/
class UpdateManager extends EventEmitter {

constructor () {
super()
this.state = 'no-update'
this.updateInfo = null
this.progress = null
// 注释这一句屏蔽手动更新检测
//this.handleMessages()
}

0x3.破解完成

现在我们已经获得了破解完成,最后我们在根目录使用asar pack app app.asar将app文件夹打包成asar文件,最后将文件替换进原路径即可。

结语

破解方式至3.1.1依旧有效,不保证未来不会发生变化,仅供参考。

曾经我们上课的时候,使用的startUML的颜值,那真的是一言难尽,可能我校用的还是1.0的上古版本(最主要是教材还对的上),太恐怖了,不过现在startUML 3.x的颜值真的很不错,QAQ虽然这也不是我破解的理由。好了好了,我这条咸鱼又滚回去过我的咸鱼生活惹!告辞~