Smart Input插件激活原理【仅支持windows】

前言

论坛有人推荐Smart Input插件,试用了一下还不错,但是发现这个玩意居然需要激活,这能忍?
https://linux.do/t/topic/42158?u=crazer

让我看看怎么个事情

一键重新授权

image-20240422164309590

万年不变的试用

这里就是在线激活,那我没网就没得用咯?

我直接一键断网

离线输入假code,哦豁,激活失败。

复制下授权链接,使用ja进行联网屏蔽

https://xiaolvpuzi.cn/smartinput/authorize?uuid=**********************

配置url

[URL]
PREFIX,https://xiaolvpuzi.cn

万能jadx-gui大法直接开干

关键字搜索

image-20240422165134685

找到验证方法直接跳

验证代码解析

看下激活码

H0H2H3wdknJf4Ns5L47Jv51iIqtk5xsu000098MGs3MVNJZzJJMFBVWGQ1MgCGN4x8ISc0CiVlX6w0kj2d9sUmSaHs3/6dmb0Y6P5BQYxY9f8Cj3iXTwBVt2NQ9fOOJNHrEh5SJBybZ6hFuhQkj2dzeBQmudIXetbqyFJFdPg8cXZlufszypQ73hDcFQ==tmZVJJhpHRXu1KZn5M0iIX29yHid0tzBG8N/KLXxbAQ7bEQ9VQ1jK+8qpMBjH0R236kJxBxtuU618C/fFV4YWlCZ5HVfBulc4gFd9grwYm6DdW3/Ux2NcL/EC1DaajrX998TPdxCZxZtwYqOuh8j4AuRsTD3xWj898fRelti/BpUT8MuXTBYwNGMCBl+9N8H+Vu9Y+e3fJHQfR/Sx/T2+pJn55LG9SSDRHW9bOTZlpbMm5Ne6izELapCuEHGYRo66vzFmUXIGMGuX1dACaIYZShZtXBnXtgWR+UcW/KsmFM2xymy0w1CpccbKZLNPW3Cijzltjhq9wIGTjpg+SsCyg==

分割一下

前面是我们要用到的

H0H2H3wdknJf4Ns5L47Jv51iIqtk5xsu000098MGs3MVNJZzJJMFBVWGQ1MgCGN4x8ISc0CiVlX6w0kj2d9sUmSaHs3/6dmb0Y6P5BQYxY9f8Cj3iXTwBVt2NQ9fOOJNHrEh5SJBybZ6hFuhQkj2dzeBQmudIXetbqyFJFdPg8cXZlufszypQ73hDcFQ==

后面这串,在dll会对整个lincese进行校验,具体看 @YeloChick 大佬提供的 Smart Input插件激活原理【仅支持windows】 - #60,来自 YeloChick

tmZVJJhpHRXu1KZn5M0iIX29yHid0tzBG8N/KLXxbAQ7bEQ9VQ1jK+8qpMBjH0R236kJxBxtuU618C/fFV4YWlCZ5HVfBulc4gFd9grwYm6DdW3/Ux2NcL/EC1DaajrX998TPdxCZxZtwYqOuh8j4AuRsTD3xWj898fRelti/BpUT8MuXTBYwNGMCBl+9N8H+Vu9Y+e3fJHQfR/Sx/T2+pJn55LG9SSDRHW9bOTZlpbMm5Ne6izELapCuEHGYRo66vzFmUXIGMGuX1dACaIYZShZtXBnXtgWR+UcW/KsmFM2xymy0w1CpccbKZLNPW3Cijzltjhq9wIGTjpg+SsCyg==

代码解释

启用许可证

这个方法就是验证解密后的数据是否符合一系列规范,符合就返回true激活成功,否则激活失败

public boolean enableLicense(String str) {
    try {
        String decrypt = decrypt(str);
        // 判断解密后是否为空
        if (StringUtil.isNullOrBlank(decrypt)) {
            return false;
        }
        long parseLong = Long.parseLong(decrypt.substring(0, 10)) * 1000;
        // 判断激活码里面的激活时间是否到期
        if (parseLong < System.currentTimeMillis()) {
            return false;
        }
        //判断激活码里面的IJ/AI是否一致
        if (!Objects.equals((O00O0o0.O000000o() + "@@@@@@@@@@@@@@@@@@@@@@@@@@@").substring(0, 5), decrypt.substring(10,15))) {
            return false;
        }
        //判断激活码里面的用户名是否一致
        if (!Objects.equals((System.getProperty("user.name") + "@@@@@@@@@@@@@@@@@@@@@@@@@@@").substring(0, 20), decrypt.substring(15, 35))) {
            return false;
        }
        decrypt.substring(35, 55);
        // 判断激活码里面的uuid是否一致
        if (!Objects.equals(this.uuid, decrypt.substring(55))) {
            return false;
        }
        this.license = str;
        this.expireTimestamp = Long.valueOf(parseLong);
        return true;
    } catch (Throwable th) {
        return false;
    }
}

解密方法

标准的AES解密方法

密钥:前 32 个字符

bArrbArr2:分别用于存储 IV 和加密数据的字节数组。

那么我们只需要找到key和iv这个AES算法我们就可以逆向出来了。

private static String decrypt(String str) throws Exception {
    String substring = str.substring(0, 32);// key
    byte[] decode = Base64.getDecoder().decode(str.substring(32 + 6, 32 + 6 + Integer.parseInt(str.substring(32, 32 + 6), 16)));
    byte[] bArr = new byte[16];	// iv
    byte[] bArr2 = new byte[decode.length - 16];//密文
    System.arraycopy(decode, 0, bArr, 0, 16);
    System.arraycopy(decode, 16, bArr2, 0, decode.length - 16);
    SecretKeySpec secretKeySpec = new SecretKeySpec(substring.getBytes(), "AES");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(bArr);
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(2, secretKeySpec, ivParameterSpec);
    return new String(cipher.doFinal(bArr2), StandardCharsets.UTF_8);
}

输出看下具体值

key:H0H2H3wdknJf4Ns5L47Jv51iIqtk5xsu

IV:base64编码:MGs3MVNJZzJJMFBVWGQ1Mg==
IV:base64解码:0k71SIg2I0PUXd52
下一步就是编写注册机咯

激活

在上述的授权链接里面就有uuid和自己的用户名信息,访问一下链接即可

生成license

public static void main(String[] args) throws Exception {
    String license = getLicense("2099-12-31 00:00:00", "dell", "4C4C4544-0020-2010-8020-A0C04F202020");
    System.out.println("激活码 = " + license);
}
  /**
   * 获取激活码
   *
   * @param expireTime 到期时间
   * @param userName   用户名
   * @param uuid       UUID
   * @return
   */
  public static String getLicense(String expireTime, String userName, String uuid) throws Exception {
      long expireTimestamp = DateUtil.parse(expireTime).getTime() / 1000;
      // 获取填充
      String padding = padUsername(userName);
      String licenseStr = String.format("%sIJ@@@%s%s", expireTimestamp, padding, uuid);
      // 获取key
      String key = "H0H2H3wdknJf4Ns5L47Jv51iIqtk5xsu";
      // 获取iv
      byte[] bArr = "0k71SIg2I0PUXd52".getBytes();
      return encrypt(key, bArr, licenseStr);
  }
//公钥
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtx28zsKlYjCPzUMR0lHN
uVCxkSy7oR2P9Zq//yOO8XXLh4dhNDsL4ntS38YGIJBuNcFa5bg9zXcobUZmP+ur
B3ZKgtpyO4CFqJ8RmLWO0XWVSX7fMY0katdqIl0skU3ckF5iNpb6251y1WlXL1bm
Ii4X15ux8Por++lg4JW9jPc9R98QdxmX9GOMoReCogNlpqYTcouwRf/wvIEY9m6Z
D7bouY/JOBpoyNKPMGC5GI4op6OKCNT09W51nlZ59Qvv/IPFaQGoS5UAyL8fVT1a
Sbk1O8y5pWqy1H7H5ufV+f7XR9zu5dZCTfky4RifX6XX4JKCMR2D3JTReXFVr72E
8QIDAQAB
-----END PUBLIC KEY-----
//私钥
-----BEGIN RSA PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC3HbzOwqViMI/N
QxHSUc25ULGRLLuhHY/1mr//I47xdcuHh2E0Owvie1LfxgYgkG41wVrluD3Ndyht
RmY/66sHdkqC2nI7gIWonxGYtY7RdZVJft8xjSRq12oiXSyRTdyQXmI2lvrbnXLV
aVcvVuYiLhfXm7Hw+iv76WDglb2M9z1H3xB3GZf0Y4yhF4KiA2WmphNyi7BF//C8
gRj2bpkPtui5j8k4GmjI0o8wYLkYjiino4oI1PT1bnWeVnn1C+/8g8VpAahLlQDI
vx9VPVpJuTU7zLmlarLUfsfm59X5/tdH3O7l1kJN+TLhGJ9fpdfgkoIxHYPclNF5
cVWvvYTxAgMBAAECggEAFwHZQGgz492in8FiyphlHvjMyqcCbxiaBxuZrnqfAecR
OrbPh4K4uEzS6ZNFn09OTZo867qFp1xmm645+COJ4l7iN0Uvj5rTEE/mI9gB+P7L
UkfqzpzDe9oTd3xZ9mrAQPJe6Cl5nODQNhCtd+D0+svsSnaBObwRDS5yMd2WipCq
bIhc7Yi5vVpUbDd6+y7+fhkpbpJJgfTpth0cKtOQC+jRLkA0+SclLmG0rUI/ctiV
dFWn6O4IU340yHyzOX9UnBkjlAThWvJdqay4DCMdknF/ChBHhES+kbHmxIWBB0UK
QUm+LX3xv8hjxZfhnDC8oNjoXx3qUhYCyivr8r9IPQKBgQDjmY3y1taiM+ZXRHKH
m5AtEYhCQoJkgPfhY9HjcA4Fl0g4hwLRLyDku5OBW2ahpzichlYPMNrGr2DXkaQo
nOplNvWC43n7deXXGiT6/RN23RLhBHFfQSQVgWOtc5z3DUE0qADcuXWKI1fiMD1z
6T3FbDbMTXtTC9CiwslsP2JstQKBgQDN9zKYPTKHQb/8/LRtDJdPhhZstNY0l66S
J8M6190mAyyYvPsY1sjVp0qSKIxaq9IRUhoqozBWuHJS4GbvLiiEtCNEX9HL+fOC
kOxGILXNBcGiTcczW240Wi/9LJAVJPba4PYiCY240fNKBcvMIbMizBuQ1F4NR6yI
7hUKZX2YzQKBgQCH2ALNSkVZErkMpSHmpobrH2fAhInnCsLol/eSVDNKSv0kIBEi
YYq0evCTaMZc/b3gTp3W+0XZCJw4jgj45I9SUfc/ZB5OZBQGyuf/lkWj0FeQTmKM
hAjRfSTNRhANtK+SiiPZmif5hxbRPhGvuSrRRsF+N1DARHEGdFRcc4h/cQKBgAd3
ltX+If7VW2iIoOHzOukfK2D1jW6KsUGLP4C6osHmC4/eChx0bQOR9Ronbi87W3pV
R62UDQSX20015YV2XvGwtjacYrbKcRGiv24rcWvlcYe42if6gJxVSLgdDXw2wtxc
m4/QWNsCgZeFbkYQUrZIQBeYG3DP0GmGeCzQUSVhAoGBAOM4MTBhgfc5Mi+N38Hx
y4XWkifjjH8iyEDVcaXCrHjkinU7mqXhICU23WG+l0hNH99lE4VvAqiLzZjkdc+e
+kllPlvceRR7jHmp/TpHoJN1ta6ezC1zmRxkGviOMrU9q9Xvvz5iYo6awD/uJ3sl
up9NRN9yIsnEf9nm9tnX8Msv
-----END RSA PRIVATE KEY-----

/**
  * 使用AES加密
  *
  * @param key       AES的key
  * @param iv        AES的iv
  * @param plaintext 明文
  * @return
  */
private static String encrypt(String key, byte[] iv, String plaintext) throws Exception {
    String encryptedString = "";
    String suffix = "";

    // 使用给定的 key 和 IV 初始化 Cipher
    SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

    // 对明文进行加密
    byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));

    // 拼接 IV 和加密后的数据
    byte[] ivAndCiphertext = new byte[iv.length + ciphertext.length];
    System.arraycopy(iv, 0, ivAndCiphertext, 0, iv.length);
    System.arraycopy(ciphertext, 0, ivAndCiphertext, iv.length, ciphertext.length);

    // 对拼接后的数据进行 Base64 编码
    String encodedData = Base64.getEncoder().encodeToString(ivAndCiphertext);

    // 拼接密钥长度和编码后的数据,并用 0 填充到 6 位
    String lengthString = String.format("%06X", encodedData.length());
    encryptedString = key + lengthString + encodedData;
 
    // 这里读取的就是上述的私钥文件
    PrivateKey privateKey = PemUtil.readPemPrivateKey(new FileInputStream("E:\\Work\\privateKey2048.pem"));
    // 创建 Signature 对象并初始化为签名模式
    Signature signature = Signature.getInstance("SHA256withRSA");
    signature.initSign(privateKey);
    // 更新要签名的数据
    signature.update(encodedData.getBytes());
    // 对数据进行签名
    byte[] signatureBytes = signature.sign();
    // 将签名转换为 Base64 编码的字符串
    String signatureBase64 = Base64.getEncoder().encodeToString(signatureBytes);

    return encryptedString + signatureBase64;
}
/**
  * 填充格式
  *
  * @param username 用户名
  * @return
  */
public static String padUsername(String username) {
    int usernameLength = username.length();
    int paddingLength = 40 - usernameLength;
    if (paddingLength <= 0) {
        return username;
    } else {
        StringBuilder paddedUsername = new StringBuilder(username);
        for (int i = 0; i < paddingLength; i++) {
            paddedUsername.append("@");
        }
        return paddedUsername.toString();
    }
}

替换dll

因为dll中会进行签名验证,这里这里暂时没有什么好的办法,只支持windows替换dll
我这里使用的winhex进行替换,dll里面密钥对

修改后dll下载链接:wism.zip官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘 提取码:iH7s

使用方法

  1. 找到ide的安装目录\bin下面,删除wism.dll文件
    image

  2. 找到ide安装插件路径替换instrumented-Smart Input-6.0.4.jar里面dll


    ps:我这里是改了它的存储路径,默认的 C:\Users\用户名\AppData\Roaming\JetBrains\IntelliJIdea2024.1类似这种

    3.重启ide

食用方法

目前只支持windows平台!!!

1、重新获取授权

image

2、激活插件

image

3、在打开的网页中,复制请求地址中uuid和用户名

4、调用上述 生成license方法【看上文 激活章节

5、配置url【看上文 配置url章节

6、替换dll 【看上文 替换dll章节

激活看下

image-20240422172839695
切换看下
d1e3b0273b13edb905630d078b7dc0fe
完工,告辞!

我真不会破解 :grinning:

51 个赞

一股小鹏味

2 个赞

潇哥:ox:b

2 个赞

6 个赞

6,实在6

1 个赞

潇哥6啊

7 个赞

好好
不过我去年用的时候还是老老实实地去关注了公众号激活的:joy:

2 个赞

潇哥牛逼

1 个赞

厉害

1 个赞

把你的注册机教程来一遍

1 个赞

不会啊 :thinking:破解都不会,注册机更别说了 :melting_face:

1 个赞

大佬太谦虚了

1 个赞

跟着搞了一遍,好像还不行,我再瞅瞅

去年用过一段时间这个插件,那时候安装就能直接用,不过有些场景还是有BUG,我就没用了,不知道现在优化得咋样了

我去 你怎么还会发光?

怎么复杂,完全不会

我去,为什么你名字蓝的,还有光

牛的,正好昨天下载了,今天打开电脑发现居然需要激活,这不正好赶上

代码我调整下.有点问题

可以了,在试下