记一次对StarUML-3.2.2的解包

前言

感觉很久没写帖子了…
老是把上学期的实训还没有做完当借口感觉也不是很行
索性就随便写写吧
(项目还没有做完,cao!)

准备

上系统分析与设计的课需要用到一个StartUML的软件
虽然老师已经提供了完整的工具了
但是人嘛,总是喜新厌旧的
不是最新版这手就不舒服…
直接上官网下载了最新版的
高高兴兴的替换
What the f**k? 直接黑屏

不行,上网找找看看有没有新的解决方法
emmm,找了一会,找到了好东西

吾爱论坛的一个帖子: 另一种 StarUML 3.x 完美**的思路

感觉能行,是我最擅长的js
直接开搞

StartXML官网:StarUML 3

这次使用的版本为最新版,版本号为3.2.2
除此之外需要node环境,需要npm的一个asar的一个包
这个就不细讲,因为我也不懂emmm…

过程

看完吾爱那个帖子还是蛮吃惊的
这应用好像是Electron的…
简单讲就是套了个谷歌浏览器的桌面应用

ok那开始,先把最新的app.asar文件解包出来
asar extract app.asar app
extract解包,app.asar为需要解包的文件 app为解包的目录

解包出来
emmm,挺熟悉的…

马不停蹄,直接奔着about-dialog.js文件去
src/dialogs文件下
在这个文件下的showDialog函数下果然找到了这一段代码

这段代码判断根据凭证管理器返回的状态来给模态框设置内容

1
2
3
4
5
if (app.licenseManager.getStatus() === true) {
// ...
} else {
// ...
}

所以现在首要的是找到app这个变量
这个变量有着licenseManager的实例
ctrl这个变量发现有两处存在这个变量
分别是src/main-process/application.jssrc/index.js

其中application.js文件的代码段为

metadataManager?
很明显不是我们的licenseManager
直接不管
src/index.js文件

new AppContxt() 按照字面意思应该是app的上下文
应该是这个不会错
点进去发现是一个类
ctrl+f搜索licenseManager
果然在38行发现了引入的凭证管理器

ok定位到src/enginer/license-manager.js这个文件
继续ctrl+f搜索getStatus
找到代码,是LicenseManager的一个方法
这个方法返回了一个定义在文件中的变量status

根据前面我们知道,当status为true,就会往模态框渲染已注册的内容了
所以继续搜索status
发现一个setStatus函数
这个函数不在类中
(截图好累,直接贴代码吧….)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Set Registration Status
* This function is out of LicenseManager class for the security reason
* (To disable changing License status by API)
* @private
* @param {boolean} newStat
* @return {string}
*/
function setStatus(licenseManager, newStat) {
if (status !== newStat) {
status = newStat
licenseManager.emit('statusChanged', newStat)
}
}

注释也给出了没写在类方法中的原因

To disable changing License status by API
译过来就是:防止通过api的方式去改变凭证

也就是不想让license管理器对象直接设置status状态
这也反过来证明这个status状态确实决定注册的状态
(有点心虚的味道…)

这里还可以发现设置状态会广播一个statusChanged的事件
licenseManager.emit('statusChanged', newStat)
状态根据传进来的newStat决定

接下来看到底谁调用了setStatus这个函数了
搜索发现checkLicenseValidity函数和register函数都调用了

怎么有两个函数调用…
看了看方法,找到猫腻
类中有一个方法appReady
代码是这样的

1
2
3
4
5
6
7
8
class LicenseManager extends EventEmitter {

// ...

appReady() {
this.checkLicenseValidity()
}
}

看方法名就知道了,在应用准备的时候调用这个函数
那八九不离十是checkLicenseValidity这个函数了
而且register函数的注释也让我更加确定

Check the license key in server and store it as license.key file in local
通过服务器检查凭证并将它以license.key文件进行储存

不难分析,刚安装的软件肯定是没有注册的
需要通过键入密钥来激活
也就是这几种情况

  • 开启app - 有本地文件 - 进行检验 - 显示检验状态
  • 开启app - 没有本地文件 - 显示未注册
  • 开启app - 没有本地文件 - 输入密钥 - 成功 - 生成文件方便下次启动的验证

当然我们手头是没有密钥的,所以register函数关系不大
目的是在启动检测的时候假装存在检验文件, 并检测成功

观察checkLicenseValidity函数,它调用了validate函数进行验证
但是validate函数其实已经不重要了
为啥呢?
因为validate函数没有调用setStatus这个函数了
也就是注册状态的变化和validate函数块无关了
validate函数执行的对应回调有关
也就是checkLicenseValidity函数中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class LicenseManager extends EventEmitter {

// ...

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

这里可以看到成功就设置状态为true
失败设置状态为false并展示一个未注册的展示框

OK大体上摸清楚了
只要把代码这个函数的代码改为

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

// ...

checkLicenseValidity() {
this.validate().then(() => {
setStatus(this, true)
}, () => {
// 失败也设置未注册成功
setStatus(this, true)
// 失败不展示失败的模态框
// UnregisteredDialog.showDialog()
})
}
}

其实大部分工作都已经完成了
接下来回到最开始的if判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (app.licenseManager.getStatus() === true) {
var info = app.licenseManager.getLicenseInfo();
var licenseTypeName = 'Unknown';
switch (info.licenseType) {
case 'PS':
licenseTypeName = 'Personal';
break;
case 'CO':
licenseTypeName = 'Commercial';
break;
case 'ED':
licenseTypeName = 'Educational';
break;
case 'CR':
licenseTypeName = 'Classroom';
break;
}
$license.html('Licensed to ' + info.name);
$licenseType.html(licenseTypeName + ' License');
$quantity.html(info.quantity + ' User(s)');
} else {
$license.html('UNREGISTERED');
}

判断成功后会获取凭证的信息
在LicenseManager类中也可以找到这个方法
也就是只要构造出符合结构的对象就可以了
也可以直接在方法体里面改
我的话通过返回的对象来改
也就是

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

// ...

getLicenseInfo() {
// 不返回lincenseInfo了
return licenseInfo
//这里获取密钥所包含的信息,具体可以看validate函数,里面有对密钥的解析,也就是json格式
return {
name: "17软件工程",
product: "系统分析与设计",
licenseType: "PS",
quantity: "INDEX",
timestamp: "1583395020",
licenseKey: "仅供学习,请勿用于商业用途",
crackedAuthor: "Dedicatus545"
};
}
}

到这里其实基本就完成了
最后就是重新打包这个文件
asar pack app app.asar
替换之后就完成了

附上一张截图

仅供学习使用,请勿使用于商业用途。