StarUML Keygen分析

查看 67|回复 9
作者:JonesDean   
前言
有关于StarUML v7.0+的逆向方案,仅供用于学习交流
1 查找突破点
1.1 搜索关键词
进去先随便看看搜索相关关键词,不出意外找到了license-client.js,疑似授权验证逻辑的所在


1.png (361.64 KB, 下载次数: 2)
下载附件
2025-9-12 21:08 上传

1.2 常量分析
看到了一些关键常量,分析一下
const LICENSE_PRODUCT_ID = packageJson.productId;
// 字符串,值为'STARUML.V7',对应了软件版本
const LICENSE_CRYPTO_KEY = "y0JMc9mvB1uvIi82GhdMJQXzVJxl+1Lc0RqZqWaQvx0=";
// AES对称加密算法,很可能是activation.key激活文件的生成密钥
const LICENSE_SERVER_URL = "https://dev.staruml-io-astro.pages.dev/api/license-manager";
// 官方服务器的激活url
const LICENSE_FILE_NAME = "activation.key";
// 激活文件的名称
const LICENSE_TRIAL_MODE_ENABLED = true;
// 启用试用方案
const LICENSE_TRIAL_TIMESTAMP_FILE_NAME = "lib.so";
// 试用的时间戳信息,内容为用户首次打开软件的时间,这个时间加上下面的试用时长得出试用失效日期
const LICENSE_TRIAL_MODE_DAYS = 30; // negative for infinite, positive for days left
// 这一段官方说的,负数即可破解,无限试用,但我试了没用
2 修改方案
看到这里有两个方案,一个是直接修改lib.so的时间戳实现无限试用,二是尝试分析激活逻辑
2.1 无限试用
文本形式修改lib.so文件内容为4102329600000,也就是2099年,就可以无限试用了,但是每次都有弹窗,有点麻烦
2.2 Keygen
2.2.1 AI分析
直接把license-client.js丢给ai分析一波,得到了流程图突然就豁然开朗了


3.png (125.5 KB, 下载次数: 1)
下载附件
2025-9-12 21:23 上传

2.2.2 分析localValidate方法
async function localValidate() {
  try {
    const filePath = path.join(app.getPath("userData"), LICENSE_FILE_NAME);
    // %appdata%/StarUML/activation.key,这是授权文件的绝对路径
    const activationCode = await fs.readFile(filePath, "utf8");
    const decoded = await decodeActivationCode(activationCode); // 这个函数就是解码activation.key的逻辑了,其实就是AES对称加密,key就是之前看到的常量,直接喂给AI生成Keygen
    const deviceId = await getDeviceId();
    const isProductMatched = decoded.product === LICENSE_PRODUCT_ID;
    const isDeviceIdMatched =
      decoded.deviceId === "*" || decoded.deviceId === deviceId;  // 设备id区别'*'为通配,和个人设备id,两者均通过(但后面发现'*'只能离线激活)
    if (!isProductMatched) { // 判断产品软件版本是否匹配
      await checkTrialMode();
      await localDeactivate();
      return {
        success: false,
        message: "Invalid activation code (product mismatch)",
      };
    }
    if (!isDeviceIdMatched) { // 判断产品授权的设备id是否匹配
      await checkTrialMode();
      await localDeactivate();
      return {
        success: false,
        message: "Invalid activation code (device ID mismatch)",
      };
    }
    // 上面两条判断均通过则成功授权,赋值产品信息
    licenseStatus = {
      activated: true,
      name: decoded.name,
      product: decoded.product,
      edition: decoded.edition,
      productDisplayName: getProductDisplayName(
        decoded.product,
        decoded.edition,
      ),
      deviceId: decoded.deviceId,
      licenseKey: decoded.licenseKey,
      activationCode: activationCode,
      trial: false,
      trialDaysLeft: 0,
    };
    return {
      success: true,
      message: "Local validation successful (activated)",
    };
  } catch (err) {
    // if the file does not exist, assume the license is not activated
  }
  await localDeactivate();
  return {
    success: true,
    message: "Local validation successful (not activated)",
  };
}
2.2.3 生成Keygen
已知算法和解码方法,直接把license-client.js丢给AI,生成Keygen,下面是AI给的代码
// aes-gcm-node.js  
// Node.js AES-GCM (Base64 key) encrypt/decrypt compatible with WebCrypto output format:  
// "ivBase64:encryptedBase64" where encryptedBase64 = base64(ciphertext || authTag)  
const crypto = require("crypto");  
/**  
* Base64 -> Buffer * @Param {string} b64  
* @returns {Buffer}  
*/  
function base64ToBuffer(b64) {  
    return Buffer.from(b64, "base64");  
}  
/**  
* Buffer -> Base64 * @param {Buffer} buf  
* @returns {string}  
*/  
function bufferToBase64(buf) {  
    return buf.toString("base64");  
}  
/**  
* Encrypt a UTF-8 string with AES-GCM using a base64-encoded raw key. * @param {string} plaintext  
* @param {string} base64Key  // raw AES key in base64 (e.g. 32 bytes -> AES-256-GCM)  
* @returns {string} "ivBase64:encryptedBase64"  
*/function encryptString(plaintext, base64Key) {  
    const key = base64ToBuffer(base64Key);  
    // 12 bytes IV is recommended for AES-GCM  
    const iv = crypto.randomBytes(12);  
    const cipher = crypto.createCipheriv(`aes-${key.length * 8}-gcm`, key, iv);  
    const ptBuf = Buffer.from(plaintext, "utf8");  
    const ct = Buffer.concat([cipher.update(ptBuf), cipher.final()]);  
    const authTag = cipher.getAuthTag();  
    // Put ciphertext and tag together (WebCrypto expects ciphertext||tag)  
    const encryptedBuffer = Buffer.concat([ct, authTag]);  
    return `${bufferToBase64(iv)}:${bufferToBase64(encryptedBuffer)}`;  
}  
/**  
* Decrypt a string in the "ivBase64:encryptedBase64" format using base64-encoded raw key. * @param {string} encryptedText  
* @param {string} base64Key  
* @returns {string} plaintext  
*/function decryptString(encryptedText, base64Key) {  
    const key = base64ToBuffer(base64Key);  
    const [ivB64, encryptedB64] = encryptedText.split(":");  
    if (!ivB64 || !encryptedB64) {  
        throw new Error("Invalid encryptedText format. Expect iv:encrypted");  
    }    const iv = base64ToBuffer(ivB64);  
    const encryptedBuffer = base64ToBuffer(encryptedB64);  
    // authTag is last 16 bytes for AES-GCM  
    const authTag = encryptedBuffer.slice(encryptedBuffer.length - 16);  
    const ciphertext = encryptedBuffer.slice(0, encryptedBuffer.length - 16);  
    const decipher = crypto.createDecipheriv(`aes-${key.length * 8}-gcm`, key, iv);  
    decipher.setAuthTag(authTag);  
    const pt = Buffer.concat([decipher.update(ciphertext), decipher.final()]);  
    return pt.toString("utf8");  
}  
module.exports = {  
    encryptString,  
    decryptString,  
};
自己再写一个命令行交互接口,调用AI给的代码方法encryptString()
plaintext为JSON.stringify(licenseStatus)
key是y0JMc9mvB1uvIi82GhdMJQXzVJxl+1Lc0RqZqWaQvx0=
示例:
> [email protected] start
> node main.js
2. {STARUML.V2: StarUML V2}
3. {STARUML.V3: StarUML V3}
4. {STARUML.V4: StarUML V4}
5. {STARUML.V5: StarUML V5}
6. {STARUML.V6: StarUML V6}
7. {STARUML.V7: StarUML V7}
choose product version:
7
--------------------------------
0. {STD: Standard}
1. {PRO: Professional}
2. {CO: Commercial}
3. {ED: Educational}
4. {PS: Personal}
5. {CR: Classroom}
6. {CAMPUS: Campus}
7. {SITE: Site}
choose product edition:
1
--------------------------------
input your user name:
dean
--------------------------------
input your deviceId (default '*', not recommend):
--------------------------------
YQisnVjmlSZFrp/5:6mSudJku7gJhSc7zijcOolZGG83zJ9e8yaIx2RJCc0qVDqrZM6gfM+jlyf/gnaG41iSLuq7EeFc4RcjYP0r+X97xgBRSMd8cA/1QrtQaEJ/BbNbUTRD0xkFUZgueeabpgX7xEqN5jzyVtk1x12Ocf94ymuyxtzTa
2.3  潜在问题修改
发现联网状态下会在执行localvalidate后会调用remoteValidate方法进行联网验证,导致激活失败。有两种解决思路,一是防火墙出站规则拦截掉,二是修改远程验证函数。前者防火墙拦截会导致每次启动都有网络验证耗时,导致启动缓慢,后者则需要拆包修改
2.3.1 拆包修改
防火墙拦截百度一下即可,这里聊一下拆包。确保执行npm i asar -g安装完成
打包: asar pack app app.asar
解包:  asar extract app.asar app
找到src\utils\license-client.js中的remoteValidate()
直接把网络请求注释掉,添加返回成功结果即可,修改后打包回去覆盖
/**
* Validate the activation code with the server.
*/
async function remoteValidate() {
  const { activated, activationCode, deviceId } = licenseStatus;
  if (!activated) {
    return {
      success: true,
      message: "Validation successful (not activated)",
    };
  }
  try {
    if (deviceId === "*") {
      // if offline activation, deactivate if the server is reachable
      const response = await fetch(`${LICENSE_SERVER_URL}/ping`, {
        method: "POST",
      });
      if (response.ok) {
        await localDeactivate();
        return {
          success: false,
          message: "License deactivated (illegal offline use)",
        };
      }
    } else {
      // if online activation, validate the activation code with the server
      /* modify
      const response = await fetch(`${LICENSE_SERVER_URL}/validate`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          activation_code: activationCode,
        }),
      });
      if (response.ok) {
        const data = await response.json();
        if (data.success) {
          const decoded = await decodeValidationCode(data.validation_code);
          if (decoded.deviceId === deviceId) {
            return {
              success: true,
              message: "Validation successful (activated)",
            };
          }
        }
      }
      */
      return {
        success: true,
        message: "Validation successful (activated)",
      };  
      await localDeactivate();
      return {
        success: false,
        message: "License deactivated by server",
      };
    }
  } catch (err) {
    // if server is not accessible, assume the validation is success.
    return {
      success: true,
      message: "Validation successful (offline)",
    };
  }
}
3 结尾
测试成功
附件:

StarUML_Keygen.rar
(2.49 KB, 下载次数: 52)
2025-9-12 21:13 上传
点击文件名下载附件
仅供交流学习下载积分: 吾爱币 -1 CB



2.png (74.93 KB, 下载次数: 2)
下载附件
2025-9-12 21:23 上传

下载次数, 常量

814182193   

一直用的还是最古老的改js的版本
pjboy   

感谢分享
Lty20000423   

之前用的rational rose,找了好久没有相关的资源
iSummer999   

感谢楼主分享
lianzai   

思路很好
lsword2000   

厉害,成功注册,不过怎么设置中文呢?
NBlueSky   

感谢楼主分享,我之前看过这个软件的破解教程,但是没有教原理,只说了要用nodejs,反编译xxxxx.asar,修改js文件
JonesDean
OP
  


lsword2000 发表于 2025-9-13 17:59
厉害,成功注册,不过怎么设置中文呢?

软件没有中文,只能尝试打汉化补丁了😀
JonesDean
OP
  


NBlueSky 发表于 2025-9-13 22:04
感谢楼主分享,我之前看过这个软件的破解教程,但是没有教原理,只说了要用nodejs,反编译xxxxx.asar,修改 ...

好的,写得有些匆忙,稍后完善一下
您需要登录后才可以回帖 登录 | 立即注册

返回顶部