前言
之前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
|
validate () { return new Promise((resolve, reject) => { try { 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 { $.post(app.config.validation_url, {licenseKey: licenseInfo.licenseKey}) .done(data => { resolve(data) }) .fail(err => { if (err && err.status === 499) { reject(err) } else { 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, 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 { 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 { $.post(app.config.validation_url, {licenseKey: licenseInfo.licenseKey}) .done(data => { resolve(data) }) .fail(err => { if (err && err.status === 499) { reject(err) } else { 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 { 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
|
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) { 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()
} }
|
此外engin目录下还有一个update-manager.js
,注释后,手动检测更新依旧返回已经是最新版无需更新的字样:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
class UpdateManager extends EventEmitter {
constructor () { super() this.state = 'no-update' this.updateInfo = null this.progress = null }
|
0x3.破解完成
现在我们已经获得了破解完成,最后我们在根目录使用asar pack app app.asar
将app文件夹打包成asar文件,最后将文件替换进原路径即可。
结语
破解方式至3.1.1依旧有效,不保证未来不会发生变化,仅供参考。
曾经我们上课的时候,使用的startUML的颜值,那真的是一言难尽,可能我校用的还是1.0的上古版本(最主要是教材还对的上),太恐怖了,不过现在startUML 3.x的颜值真的很不错,QAQ虽然这也不是我破解的理由。好了好了,我这条咸鱼又滚回去过我的咸鱼生活惹!告辞~