声明

CLion程序版权为jetBrains全部、注冊码授权为jetBrains及其付费用户全部,本篇仅仅从兴趣出发,研究其注冊码生成算法。

  • 不会释出不论什么完整的源码.
  • 网上查了下。已有注冊机,所以想要key的同学不要找我:p

背景

打算学习cocos2dx,奈何vim仅仅会ggvG,被jetBrains惯坏了,找到了CLion,试了下,果然神器。我等菜鸟正好能够拿来愉快地学习书写c++了。

可是,试用版有30天的限制。又没有学生授权。懒得折腾,看下它的注冊算法吧。

本篇用到的主要工具和命令:

  • jd-gui 1.2
  • jdb
  • zip/unzip
  • jar

找出注冊算法的代码段

先用jd-gui看下反编译的效果,大概找了下,看到这个MainImpl类:

反编译的效果还是能够,只是注意到一些类和方法的名称被混淆了。如上图的a.a().a(new a.a_())....

尝试直接建立java文件。发现非常多类缺失,比較难补,遂放弃。

尝试用java attachagent方式来dump执行时的class,比較难偷,遂放弃。

换换思路

CLion启动后,假设未输入正确的注冊码,或者没有选择试用,会打开一个对话框,提示输入注冊码。

例如以下图:

假设选择试用,先进入主界面。打开关于对话框。能够看到部分授权信息。

例如以下图:

好。从这两个点出发,大概找找相关代码,缩小分析范围:

分析MainImpl启动前后的类载入信息

改动$CLION_HOME/bin下的clion.sh

在启动參数中,添加-verbose.

又一次启动CLion,收集到MainImpl载入前后的一些信息。例如以下图:

继续翻看,又发现例如以下一些信息:

发现 com.intellij.ide.a包和com.intellij.a包严重混淆,而且在MainImpl载入前后出现.

能够初步判定这些包是分析重点.

分析AboutPopup类的信息

打开关于/About对话框,观察比对代码,发现例如以下信息:

在粗略分析相关代码后,跟踪到这里:

LicensingFacade是个抽象类,跟一些实现,发现仅仅有一个子类:

接着跟,又发现一个抽象类bb

这个类在com.intellij.ide.a.g包下。当中一段关键代码:

public void a(q.a_ parama_)
{
boolean bool = GraphicsEnvironment.isHeadless(); f();
a(this.f);
/// 这里的u()是关键
for (s locals : u())
{
if (locals.d() == null) {
locals.c();
}
com.intellij.a.b.e locale = locals.d();
if (locale != null)
{
g_ localg_ = a(parama_, locals);
if (localg_ != g_.SKIP)
{
UpdateChecker.addUpdateRequestParameter("license", a(locale));
if (localg_ != g_.OK) {
break;
}
int i1 = (!bool) && (a(locale, locale instanceof l)) ? 1 : 0;
if (i1 == 0)
{
a(parama_, locals, locale);
return;
}
break;
}
}
}
if (bool)
{
b.error("No valid license found");
System.exit(-1);
}
else
{
b(parama_);
}
}

接着看com.intellij.ide.a.g.bb.u()方法:

private s[] u()
{
s[] arrayOfs = v();
Object localObject1 = null;
for (Object localObject3 : arrayOfs)
{
try
{
((s)localObject3).c();
if (((s)localObject3).d() != null) { /// 这里的b()是关键
((s)localObject3).b();
} else {
continue;
}
}
catch (Exception localException)
{
continue;
}
localObject1 = localObject3;
break;
}
if (localObject1 != null)
{
??? = new ArrayList(Arrays.asList(arrayOfs));
((ArrayList)?? ?).remove(localObject1);
((ArrayList)???).add(0, localObject1);
return (s[])((ArrayList)?? ?).toArray(new s[((ArrayList)??? ).size()]);
}
return arrayOfs;
}

中间的过程比較繁琐。考验的是耐心、猜測、和笔记功夫。

通过相关代码的类型分析、方法分析、參数分析、变量分析,逐步缩小关键代码段的分析范围,同一时候添加对混淆代码的熟悉。


分析注冊码算法解密验证的重点包

结合上面的对于MainImpl载入前后的类信息分析。重点关注com.intellij.a.g这个包.

终于跟到这样一个类com.intellij.a.g.c

package com.intellij.a.g;

import java.math.BigInteger;

public class c
{ /// 这个a是关键。通过后面的实验,这是RSA加密算法的公钥部分的n .
private static BigInteger a = new BigInteger("8eeb1420b7d8b90aa3b95c7ff73628e46e12c19dc91531be5f517a54b042e99c17445ce7b23834a3ec80d2691b463231be43aab7e897cc334bc9b8bb9f0d55f5", 16);
private static BigInteger b = new BigInteger("10001", 16); /// 这种方法是关键
/// 通过猜測和实验。能够确定paramString1和paramString2是username和代码,或者相反。
public static h a(String paramString1, String paramString2)
throws a
{
// 这个h类型是关键,记录了注冊码类型、产品类型、username、过期时间、维护到期时间、主版本、小版本等关键信息
h localh = new h(); // 这里m.a(,,,)方法完毕第一次解密
byte[] arrayOfByte = m.a(paramString1, localh, b, a);
// m.a(,,,)方法验证注冊码解密后的字节信息。从中提取关键属性
m.a(arrayOfByte, localh, paramString2, 14);
// 继续验证
m.b(arrayOfByte, localh, paramString2, -1);
return localh;
}
}

为了确定静态分析过程的正确性,使用jdb来看一下简单的调用栈:

Idea Main Thread 1.0.4#CL-141.874.66, eap:false[1] wherei 0x6f4

  /// 能够确定。每次载入MainImpl。调用其start方法,都会来以下这种方法中验证注冊码,这是迄今为止最为关键的发现
[1] com.intellij.a.g.c.a (c.java:18), pc = 13 [2] com.intellij.ide.a.b.b$0.b (b$0.java:71), pc = 2
[3] com.intellij.ide.a.b.b$0.a (b$0.java:67), pc = 3
[4] com.intellij.a.b.d.x (d.java:34), pc = 12
[5] com.intellij.a.b.d.t (d.java:13), pc = 1
[6] com.intellij.a.b.b.s (b.java:40), pc = 10
[7] com.intellij.a.b.b.c (b.java:33), pc = 1
[8] com.intellij.ide.a.g.t.h (t.java:108), pc = 41
[9] com.intellij.ide.a.g.t.a (t.java:27), pc = 8
[10] com.intellij.ide.a.g.m.b (m.java:29), pc = 27 // bb类是个非常重要的类。兴许分析
[11] com.intellij.ide.a.g.bb.u (bb.java:152), pc = 48
[12] com.intellij.ide.a.g.bb.a (bb.java:78), pc = 18
[13] com.intellij.idea.MainImpl$1.start (MainImpl.java:45), pc = 19
[14] com.intellij.idea.StartupUtil.prepareAndStart (StartupUtil.java:117), pc = 115
[15] com.intellij.idea.MainImpl.start (MainImpl.java:40), pc = 9
[16] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method)
[17] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100
[18] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6
[19] java.lang.reflect.Method.invoke (Method.java:497), pc = 56
[20] com.intellij.ide.plugins.PluginManager$2.run (PluginManager.java:91), pc = 53
[21] java.lang.Thread.run (Thread.java:745), pc = 11

分析注冊码字节数组


/// 我们重点跟一下com.intellij.a.g.c.a()方法: package com.intellij.a.g; import java.math.BigInteger; public class c
{
private static BigInteger a = new BigInteger("8eeb1420b7d8b90aa3b95c7ff73628e46e12c19dc91531be5f517a54b042e99c17445ce7b23834a3ec80d2691b463231be43aab7e897cc334bc9b8bb9f0d55f5", 16);
private static BigInteger b = new BigInteger("10001", 16); /// 重点跟这种方法
public static h a(String paramString1, String paramString2)
throws a
{
h localh = new h(); byte[] arrayOfByte = m.a(paramString1, localh, b, a);
m.a(arrayOfByte, localh, paramString2, 14);
m.b(arrayOfByte, localh, paramString2, -1);
return localh;
}
}

在上面的方法中,类型h非常关键。这里的字符串没有混淆,给我们猜測注冊码结构提供了极大的便利 O^. ^O.

    /// 这里的一些字段和变量的意思,已经给出来了
public String toString() {
StringBuffer var1 = new StringBuffer();
var1.append("\n");
var1.append("user name:" + this.b + "\n");
var1.append("customer id:" + this.c + "\n");
var1.append("product id:" + j.a(this.d) + "\n");
var1.append("license type:" + i.a(this.e) + "\n");
var1.append("major version:" + this.f + "\n");
var1.append("minor version:" + this.g + "\n");
var1.append("generationDate:" + this.h + "\n");
var1.append("expirationDate:" + this.i + "\n");
return var1.toString();
} static {
a.b = "Fake";
a.d = 0;
a.e = 1;
a.f = 1;
a.g = 0;
a.h = d.a(1995, 1, 1);
a.i = d.a(1995, 1, 2);
}

跟这种方法:byte[] arrayOfByte = m.a(paramString1, localh, b, a);

protected static byte[] a(String var0, h var1, BigInteger var2, BigInteger var3) throws a {
Matcher var4 = c.matcher(var0);
var0 = var4.replaceAll("").trim(); // 10是ASCII码中的"\n"换行符
// 13是ASCII码中的"\r"回车符
// 以下的代码告诉我们一个重要信息:
// 加密的注冊码至少包括1个\n(linux)或者\r(windox|mac)
int var5 = var0.indexOf(10);
if(var5 < 0) {
var5 = var0.indexOf(13);
} if(var5 < 0) {
throw new a();
} else {
// 45是ASCII码中的"-"
// 重要信息:加密的注冊码至少包括一个"-",且出如今"\n"或"\r"之前
String var6 = var0.substring(0, var5);
//"-"
int var7 = var6.indexOf(45);
if(var7 > 0) {
try { // 从[xx-? ?\n? ??]中提取xx,并赋值给var1.c
// 依据上面的h类型的信息,这里的xx是cutomerId,所以我们有了:
// license : customerId-??\n??? var1.c = Integer.parseInt(var6.substring(0, var7));
} catch (NumberFormatException var9) {
throw new a();
}
} else {
var1.c = -1;
} // 这里能够确定传入的var2是公钥的n,var3是公钥的e
// 同一时候依据RSADecoder我们也找到了相应的RSAEncoder,兴许直接拷贝RSAEncoder过来,作注冊码加密用^^
return (new RSADecoder(var2, var3, 64)).decode(var0.substring(var5 + 1));
}
}

跟这种方法:m.a(arrayOfByte, localh, paramString2, 14);

static void a(byte[] var0, h var1, String var2, int var3) throws a {
//这里,我们依据传入的var3 = 14,得知:
//license的长度为14个字节
if(var0.length != var3) {
byte[] var4;
if(var0.length == var3 + 1) {
if(var0[0] != 0) {
throw new a();
} var4 = new byte[var3];
System.arraycopy(var0, 1, var4, 0, var3);
var0 = var4;
} else {
if(var0.length >= var3) {
throw new a();
} var4 = new byte[var3];
System.arraycopy(var0, 0, var4, var3 - var0.length, var0.length);
var0 = var4;
}
} // 这里我们依据分析,得到:
//var2 : userName , var1.c : customerId , var3 : 14 // 假设lic是个14字节长的字节数组,我们有:
//lic[12] = customerId & 0xFF;
//lic[13] = customerId >> 8 & 0xFF; if(var2 != null) {
/*
以下的o.a(,,)方法的实现:
能够直接把这段代码copy到我们做注冊码的地方^^ public static short a(String var0, int var1, byte[] var2) {
CRC32 var3 = new CRC32();
int var4;
if(var0 != null) {
for(var4 = 0; var4 < var0.length(); ++var4) {
char var5 = var0.charAt(var4);
var3.update(var5);
}
} var3.update(var1);
var3.update(var1 >> 8);
var3.update(var1 >> 16);
var3.update(var1 >> 24); for(var4 = 0; var4 < var2.length - 2; ++var4) {
byte var6 = var2[var4];
var3.update(var6);
} return (short)((int)var3.getValue());
}
*/ short var5 = o.a(var2, var1.c, var0);
// 这里得到:
// lic[12] = crc32 & 0xFF
// lic[13] = crc32 >> 8 & 0xFF
if(var0[var3 - 2] != (byte)(var5 & 255)) {
throw new a();
} if(var0[var3 - 1] != (byte)(var5 >> 8 & 255)) {
throw new a();
}
}

努力,继续跟这种方法:m.b(arrayOfByte, localh, paramString2, -1);

 static void b(byte[] var0, h var1, String var2, int var3) throws a {
try {
/** 对照
var1.append("user name:" + this.b + "\n");
var1.append("customer id:" + this.c + "\n");
var1.append("product id:" + j.a(this.d) + "\n");
var1.append("license type:" + i.a(this.e) + "\n");
var1.append("major version:" + this.f + "\n");
var1.append("minor version:" + this.g + "\n");
var1.append("generationDate:" + this.h + "\n");
var1.append("expirationDate:" + this.i + "\n");
*/ // licTypeId是注冊码类型的Id,prodId是产品类型的Id
/**
* lic[0] = licTypeId << 4 + (prodId & 0xFF);
*/ var1.b = var2;
var1.d = var0[0] & 15;
var1.e = var0[0] >> 4;
var1.g = var0[1] >> 4; //这里得到generationDate信息,也就是now了。以毫秒表示,并带符号右移16位,然后按4字节存储
/**
* long now = System.currentTimeMillis() >> 16;
* lic[2] = now & 0xFF;
* lic[3] = now >> 8 & 0xFF;
* lic[4] = now >> 16 & 0xFF;
* lic[5] = now >> 24 & 0xFF;
*/ long var4 = ((long)var0[2] & 255L) + (((long)var0[3] & 255L) << 8) + (((long)var0[4] & 255L) << 16) + (((long)var0[5] & 255L) << 24) << 16;
var1.h = new Date(var4); /*
* 注意到上面有句 :var1.g = var0[1] >> 4;
* 结合以下的代码,我们有:
* lic[1] = minorVer >> 4 + (majorVer & 0xFF);
*/ var1.f = var0[1] & 15; /**
* lic[6] = delta & 0xFF;
* lic[7] = delta >> 8 & 0xFF;
*/ int var6 = (var0[6] & 255) + ((var0[7] & 255) << 8);
if(var6 != 0) {
//expire date
var1.i = new Date(var4 + (long)var6 * 24L * 60L * 60L * 1000L);
} //var3:-1
//lic[10] = any
//lic[11] = any
int var7 = var3 > -1?var3:(var0[10] & 255) + ((var0[11] & 255) << 8);
if(var6 != 0) {
var7 = var6;
} //var1.l = maintenanceDueDate ,维护到期时间
var1.l = new Date(var4 + (long)var7 * 24L * 60L * 60L * 1000L);
} catch (ArrayIndexOutOfBoundsException var8) {
throw new a();
}
}

得到产品信息和注冊码类型信息

如今我们基本拿到了注冊码的字节信息,还差一点。产品信息和注冊码类型信息这里处理的是Id,那么实际信息在哪里?

还记得我们前面说过一个bb类吧。对它的子类进行分析。能够得到非常多相似这种代码:

    static a a(com.intellij.ide.a.r var0, q var1) {
return new a(var0, "AppCode", 8, Products.APPCODE, 3, 30, "6", var1, new g() {
public h b(String var1, String var2) throws com.intellij.a.g.a {
return com.intellij.a.g.b.a(var1, var2);
}
});
}

看来jetBrains的非常多产品的加密算法都是差点儿相同,真是比較懒啊.

通过分析bb类所在的包com.intellij.ide.a.a,以及com.intellij.a.g包,我们得到例如以下实用信息:



以及:


写出注冊码生成算法

  • 关键字节数组。14字节长:

  • CRC校验:

package rsa;

import java.util.zip.CRC32;

public class GroupUtil {
public static final int a = 12;
public static final int b = 14;
public GroupUtil() {
} public static short computeCRC32(String userName, int customerId, byte[] licBytes) {
CRC32 crc32 = new CRC32();
if(userName != null) {
for(int i = 0; i < userName.length(); ++i) {
char var5 = userName.charAt(i);
crc32.update(var5);
}
} crc32.update(customerId);
crc32.update(customerId >> 8);
crc32.update(customerId >> 16);
crc32.update(customerId >> 24); for(int i = 0; i < licBytes.length - 2; i++) {
byte var6 = licBytes[i];
crc32.update(var6);
} return (short)((int)crc32.getValue());
}
  • RSA加密,可直接拷贝反编译代码
  • 注冊码生成


測试注冊码

眼下的问题是,我们没有私钥。怎么搞?

能够任意生成一对RSA的公钥/私钥。然后用我们生成的公钥的n,替代class文件里的n,这里的nmodulus.

看看clion.jar中的com.intellij.a.g.c.class的信息:

注意右边ASCII字符的部分,上文有提到这个类的n。我们能够替换之。

怎么替换呢?

方法非常多,jar、zip、sed,编程方式(javassist、asm、zip)都能够搞定,这个留给读者吧:]


终于效果:

CLion注冊码算法逆向分析实录(纯研究)的更多相关文章

  1. CLion注册码算法逆向分析实录

    Link is : http://www.cnblogs.com/foreach-break/p/CLion_License_Fake_Crack.html

  2. (cLion、RubyMine、PyCharm、WebStorm、PhpStorm、Appcode、Clion、Idea) 万能破解,获取自己的注冊码

    听说cLion的ide编写c/c++很的棒.今天下载了一个仅仅有30天的使用时间.作为程序猿破解它. 下载破解文件 | 点击下载 |password: 7biu 解压压缩包,然后打开命令行 cd 到解 ...

  3. 2014年到期的myeclipse5.5.1注冊码

    假设点击Myeclipse的载入项目到server的图标没有反应,这就是MyEclipse过期了,下面是还能用一年的注冊码: subscriber: axin  Serial:nLR8ZC-85557 ...

  4. myeclipse6.0下载及注冊码

    myeclipse6.0 下载地址.官方下载地址: http://www.myeclipseide.com/module-htmlpages-display-pid-4.html 本地快速下载地址: ...

  5. 最新Connectify注冊码(序列号) Connectify3.7序列号 破解版

    Connectify序列号.最新注冊码 今天给大家公布一个Connectify最新版的序列号(注冊码) 今天给大家公布一个Connectify最新版的序列号(注冊码) 经本人測试该注冊码为最新版Con ...

  6. MyEclipse 8.0注冊码+原版下载_Java开发软件

    MyEclipse是一个十分优秀的用于开发Java, J2EE的Eclipse插件集合,MyEclipse的功能很强大,支持也十分广泛,尤其是对各种开元产品的支持十分不错.MyEclipse眼下支持J ...

  7. winrar3.7-winrar4.0的注冊码

    首先新建记事本文件(txt文件),把下面红色代码复制进去,然后将文件另存为以 rarreg.key 为文件名称的文件(当然因为设置的不同,可能出现你保存后的文件为 rarreg.key.txt 没关系 ...

  8. Myeclipse7.5 下载 安装 注冊 注冊码 100%成功

    myeclipse7.5启动画面 1.下载Myeclipse官方原版 官方原版:或者 http://downloads.myeclipseide.com/downloads/products/ewor ...

  9. WebStorm 7.0 注冊码

    经測试 WebStorm 6 7均能够使用例如以下注冊码,简直就是神key啊! WebStorm 6.0 7.0 注冊码 User Name: EMBRACE   License Key: ===== ...

随机推荐

  1. springboot的几个缓存相关注解

    @Cacheable:查询 几个属性: ​ cacheNames/value:指定缓存组件的名字: ​ key:缓存数据使用的key,可以用来指定.默认即使用方法参数的值 ​ keyGenerator ...

  2. Vue生命周期函数的应用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. Ajax兼容性问题

    对于IE7及以上直接使用 XMLHttpRequest 就行,但对于过老版本IE建议直接提示用户下载新版浏览器更佳.或者用以下代码兼容IE6: function CreateXHR() { if(XM ...

  4. 用​M​y​E​c​l​i​p​s​e​ ​打​包​J​A​R文件

    用​M​y​E​c​l​i​p​s​e​ ​将自己定义标签打​成​J​A​R​包 1.新建一个javaproject 2.将标签有关的java代码拷贝到新建javaproject的一个包中,这时会报错 ...

  5. C++实现页码数字统计

    #include<iostream> #include<iomanip> #include<cstdlib> #include<ctime> #incl ...

  6. Linux - Redmine使用方式 | SVN提交代码

    Redmine使用方式 | SVN提交代码 本文地址:http://blog.csdn.net/caroline_wendy RbTools 1. 安装: svn co https://dev.cxx ...

  7. Boost Log 基本使用方法

    Boost Log 基本使用方法 flyfish 2014-11-5 依据boost提供的代码演示样例,学习Boost Log 的基本使用方法 前提 boost版本号boost_1_56_0 演示样例 ...

  8. Oracle 实现 mysql 更新 update limit

    oracle给人的感觉非常落后.使用非常不方便,Toad 这个软件又笨又迟钝.pl/sql更是,90年代的界面风格,速度还卡得要死.并且oracle不支持limit .by default7#zbph ...

  9. Android This Activity already has an action bar supplied by the window decor

    This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_ ...

  10. 使用Xcode-debug模式和release模式

    在开发过程中,我们常常须要用到NSLog输出一些信息,甚至有的开发过程.必须在控制台查看输出,有经验的程序猿通过控制台输出就能知道整个数据交互的一个流程. 可是一个公布的程序,里面带有太多的NSLog ...