前几天在看JProfiler,直接进入注册部分,试用注册支持 10天,太短了。
因此想分析一下:
Target: JProfiler 6/7 Mac OS X版本 其余版本通用
Tools: IDA Pro, DJ Java Decompiler
Steps:
1. 代码定位
2. 注册算法分析
3. 注册机实现
1. 代码定位
对于Java软件,首先查看 lib 目录,看看有没有特别的,然后看看 bin目录下的内容,
bin目录中包含jprofiler.jar 这个可能是关键
运行软件,查找特征字符串,比如 Enter license 等
定位到,经过名称混淆的类 如 com.A.A.E.B 这个类,其中包含了诸如 license.key, license, name等字符串,着重查看
再有 com.A.A.E.K 这个里面有些 Please enter a license key. 这种信息,
因此这个地方可能是界面部分的逻辑处理。
com.A.A.E.K 代码如下(经过初步 Rename)
其中 public boolean H() 这个方法必然是代码授权验证检查的地方
int i = b1.F3(strKey, strName, strCompany); 这个地方明显是检测验证信息的地方
由此可见
package com.A.A.E;
import com.A.A.B.J.E;
import com.A.A.B.e;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
public abstract class K extends JPanel
implements ActionListener {
private static final String I = "single";
private static final String L = "floating";
protected Component parent;
private JRadioButton radioSingleLicense;
private JRadioButton radioFloatingLicense;
private ButtonGroup radioGroup;
private JTextField txtKey;
private JTextField txtName;
private JTextField txtCompany;
private JTextField txtFloatLicense;
private JTextField txtFloatName;
private JTextField txtFloatCompany;
private JButton btnPaste;
private JButton btnClear;
private JPanel A;
private CardLayout B;
public K(Component component) {
parent = component;
R();
I();
E();
}
public void M() {
if (radioSingleLicense.isSelected())
txtName.requestFocus();
else
txtFloatName.requestFocus();
}
protected String J() {
return txtKey.getText();
}
public void actionPerformed(ActionEvent actionevent) {
Object obj = actionevent.getSource();
if (obj == btnClear) {
txtKey.setText("");
txtName.setText("");
txtCompany.setText("");
} else if (obj == btnPaste)
b();
else if (obj == radioSingleLicense)
B.show(A, "single");
else if (obj == radioFloatingLicense)
B.show(A, "floating");
}
/**
* Get Key Content
* @return String
*/
public String L() {
return txtKey.getText().trim();
}
public String c() {
return txtFloatLicense.getText().trim();
}
public void a() {
com.A.A.E.B b1 = U();
txtName.setText(b1.EA());
txtCompany.setText(b1.F6());
String s = b1.E4().trim();
if (S() && s.startsWith("FLOAT:")) {
txtFloatLicense.setText(s.substring("FLOAT:".length()));
B.show(A, "floating");
radioFloatingLicense.setSelected(true);
txtFloatName.requestFocus();
} else {
txtKey.setText(s);
B.show(A, "single");
radioSingleLicense.setSelected(true);
txtName.requestFocus();
}
}
public boolean H() {
com.A.A.E.B b1 = U();
boolean flag = alwaysFalse() && txtName.getText().length() == 0 && txtCompany.getText().length() == 0 && txtKey.getText().length() == 0;
if (!flag && !validInput())
return false;
String strKey;
String strName;
String strCompany;
if (radioSingleLicense.isSelected()) {
strName = txtName.getText();
strCompany = txtCompany.getText();
strKey = L(); // Get License
} else {
strName = txtFloatName.getText();
strCompany = txtFloatCompany.getText();
strKey = (new StringBuilder()).append("FLOAT:").append(c()).toString();
}
com.A.A.E.F.E();
int i = b1.F3(strKey, strName, strCompany);
if (i >= 0 || i == -1)
b1.ED(Math.max(0, i));
else
b1.ED(-1);
if (flag) {
Y(true);
return true;
}
switch (i) {
case -2:
d();
Z();
return false;
case -3:
K();
Z();
return false;
case -10:
V();
Z();
return false;
case -11:
T();
Z();
return false;
case -9:
_();
Z();
return false;
case -8:
B();
Z();
return false;
case -4:
W("The license server is invalid.");
Z();
return false;
case -5:
W("There was an error communicating with the license server.");
Z();
return false;
case -6:
W(com.A.A.E.F.I());
Z();
return false;
}
if (i < 0 && i != -1) {
throw new RuntimeException((new StringBuilder()).append("Unknown license status ").append(i).toString());
} else {
Y(false);
return true;
}
}
private void Z() {
if (radioSingleLicense.isSelected())
txtKey.requestFocus();
else
txtFloatLicense.requestFocus();
}
protected void Y(boolean flag) {
com.A.A.E.B b1 = U();
if (radioSingleLicense.isSelected() || flag) {
b1.E8(txtName.getText().trim());
b1.F8(txtCompany.getText().trim());
b1.EE(L());
} else {
b1.E8(txtFloatName.getText().trim());
b1.F8(txtFloatCompany.getText().trim());
b1.EE((new StringBuilder()).append("FLOAT:").append(c()).toString());
}
}
protected void R() {
txtName = new E(10); // JTextField
txtName.setName("name");
txtCompany = new E(10); // JTextField
txtCompany.setName("company");
txtKey = new E(10); // JTextField
txtKey.setName("key");
txtFloatName = new E(txtName.getDocument(), null, 10);
txtFloatCompany = new E(txtCompany.getDocument(), null, 10);
txtFloatLicense = new E(10);
btnPaste = new JButton("Paste From Clipboard");
btnClear = new JButton("Clear");
radioSingleLicense = new JRadioButton("Single or evaluation license", true);
radioFloatingLicense = new JRadioButton("Floating license");
radioGroup = new ButtonGroup();
radioGroup.add(radioSingleLicense);
radioGroup.add(radioFloatingLicense);
}
public abstract com.A.A.E.B U();
protected abstract void d();
protected abstract void K();
protected abstract void B();
protected abstract void _();
protected abstract void V();
protected abstract void W(String s);
protected void T() {
com.A.A.A.K().J(parent, "<html>The license key you have entered is intended for a <b>floating license server</b>.<br>Please consult your licensing email for download details of the floating license server.<br><br>If you already have an installation of the floating license server, please add this key<br>to the file <tt>license.txt</tt> in your floating license server installation.", 0);
}
protected boolean alwaysFalse() {
return false;
}
protected void I() {
setLayout(new MigLayout("insets 0", "[grow]", (new StringBuilder()).append(S() ? "[]unrel" : "").append("[grow]").toString()));
if (S()) {
add(radioSingleLicense, "split");
add(radioFloatingLicense, "wrap");
}
B = new CardLayout();
A = new JPanel(B);
A.add(G(), "single");
A.add(A(), "floating");
B.show(A, "single");
add(A, "grow");
a();
}
private JPanel G() {
JPanel jpanel = new JPanel(new MigLayout("insets 0, wmin 350px", "[][grow]", "[][]para[][]para[]"));
jpanel.add(new JLabel("Name: "));
jpanel.add(txtName, "growx, wrap");
jpanel.add(new JLabel("Company: "));
jpanel.add(txtCompany, "growx, wrap");
jpanel.add(new JLabel("License key: "));
jpanel.add(txtKey, "growx, wrap");
jpanel.add(F(), "alignx 100%, span, wrap");
jpanel.add(new e("If you have received the license key by e-mail, you can copy the entire e-mail to the clipboard and use the \"Paste from clipboard\" button to extract the license key."), "growx, span");
return jpanel;
}
private JPanel A() {
JPanel jpanel = new JPanel(new MigLayout("insets 0, wrap", "[][grow]", (new StringBuilder()).append("[][]para[][]").append(O()).toString()));
jpanel.add(new JLabel("Name: "));
jpanel.add(txtFloatName, "growx");
jpanel.add(new JLabel("Company: "));
jpanel.add(txtFloatCompany, "growx");
jpanel.add(new JLabel("License server: "));
jpanel.add(txtFloatLicense, "growx");
jpanel.add(new JLabel("(host name or IP address)"), "skip");
D(jpanel);
return jpanel;
}
protected String O() {
return "";
}
protected void D(JPanel jpanel) {
}
protected boolean S() {
return true;
}
protected void E() {
btnClear.addActionListener(this);
btnPaste.addActionListener(this);
radioSingleLicense.addActionListener(this);
radioFloatingLicense.addActionListener(this);
}
private Box F() {
Box box = Box.createHorizontalBox();
box.add(btnPaste);
box.add(btnClear);
return box;
}
private boolean validInput() {
if (txtName.getText().trim().length() == 0) {
W("Please enter a name.");
if (radioSingleLicense.isSelected())
txtName.requestFocus();
else
txtFloatName.requestFocus();
return false;
}
if (radioSingleLicense.isSelected() && L().length() == 0) {
W("Please enter a license key.");
txtKey.requestFocus();
return false;
}
if (radioFloatingLicense.isSelected() && c().length() == 0) {
W("Please enter a license server.");
txtFloatLicense.requestFocus();
return false;
} else {
return true;
}
}
private void b() {
Transferable transferable = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
if (transferable != null)
try {
Reader reader = DataFlavor.getTextPlainUnicodeFlavor().getReaderForText(transferable);
BufferedReader bufferedreader = new BufferedReader(reader);
String s;
String s1;
for (s = null; (s1 = bufferedreader.readLine()) != null && (s = Q(s1)) == null;) ;
if (s != null)
txtKey.setText(s);
else
N();
} catch (UnsupportedFlavorException unsupportedflavorexception) {
N();
} catch (IOException ioexception) {
N();
}
}
private String Q(String s) {
int i = s.indexOf("E-");
if (i == -1)
i = s.indexOf("T-");
if (i == -1)
i = s.indexOf("L-");
if (i == -1)
i = s.indexOf("A-");
if (i == -1)
i = s.indexOf("S-");
if (i == -1)
return null;
int j = s.indexOf('"', i);
if (j == -1)
j = s.indexOf(' ', i);
if (j == -1)
return s.substring(i);
else
return s.substring(i, j);
}
private void N() {
W("No license key was found on the clipboard.");
}
public boolean C() {
int i = U().F0();
if (i != -1 && i < 0) {
if (com.A.A.A.K().B(this, "No valid license has been entered. Would you like to quit the application?", com.A.A.B.B.D, com.A.A.B.B.D, 2) == com.A.A.B.B.C) {
try {
System.exit(1);
} catch (Throwable throwable) {
}
return false;
} else {
return false;
}
} else {
return true;
}
}
}
int i = b1.F3(strKey, strName, strCompany); 这个方法的代码实现是
那个这个B类中的F3方法是切入点,代码如下:
/**
* Check License status<br/>
* return values:<br/>
* 0 OK
* -1 OK
* -2
* -3
* -4 Server invalid
* -5 communicating error
* -6
* -8
* -9
* -10
* -11
* @param key
* @param name
* @param company
* @return status
*/
public int F3(String key, String name, String company) {
if (key == null)
key = l;
if (key.startsWith("FLOAT:")) { // Check float
String s4 = key.substring("FLOAT:".length());
int i1 = -1;
int j1 = s4.lastIndexOf(':');
if (j1 > -1) {
try {
i1 = Integer.parseInt(s4.substring(j1 + 1));
} catch (NumberFormatException numberformatexception) {
}
s4 = s4.substring(0, j1);
}
int k1 = E9();
int l1 = com.A.A.E.F.C(s4, i1, k1, name, company, F1(), E6() != null ? this : null);
u = com.A.A.E.F.L();
EC(l1 > 0 || l1 == -1);
return l1;
} else {
// Check License only
return E0(key);
}
}
包含两个部分,一个是针对 FLOAT license的,另外一个是针对 普通授权的 E0 这个方法
protected abstract int E0(String s1); 这个E0是抽象的,需要在类库中,搜索,
搜索的关键字可以这样,因为抽象,则必须进行重载实现,那么着这个B类的名称(Java VM格式)
就可以了 Lcom/A/A/E/B; 这个代表了一个类,那么可以找到引用的或者是重载的地方了。
经过WinRAR查找,最终定位到 com.jprofiler.frontend.J.K 这个类,其中的代码形式如下:
protected int E0(String s) {
return (new D()).M(s);
}
这个D 和 com.jprofiler.frontend.J.K 同包 即 com.jprofiler.frontend.J.D
整体代码如下
//public class D extends F
//{
//
// public D()
// {
// }
//
// protected int K()
// {
// return 6; // JProfiler 版本好
// }
//
// protected char[] A()
// {
// return (new char[] {
// 'J' // J 代表 JProfiler
// });
// }
//
// public int M(String licenseKey)
// {
// if(licenseKey.startsWith("F-"))
// return -11;
// A a = new A(licenseKey); // 生成授权对象
// int i = a.M(); // 计算授权状态,同时也代表了 试用版本的过期时间
// if(i == -3 || i == -2)
// return i;
// String s1 = a.C();
// String s2 = a.K();
// if(s1 == null || s2 == null)
// return -3;
// if(!H(s2, s1, 7, 38, 11)) // 这个地方是关键点
// return J(licenseKey);
// else
// return a.M();
// }
//}
Java部分校验的最终处理部分
protected static boolean H(String s, String s1, int i, int j, int k) {
if (s1 == null)
return false;
char ac[] = s1.toCharArray();
int l = 0;
char ac1[] = ac;
int i1 = ac1.length;
for (int j1 = 0; j1 < i1; j1++) {
char c = ac1[j1];
l += c;
}
String s2 = (new StringBuilder()).append(String.valueOf(l % i)).append(String.valueOf(l % j)).append(String.valueOf(l % k)).toString();
return s.equals(s2) || s.equals(s2.replace('0', 'a').replace('1', 'b'));
}
关键部分的授权对象 com.A.A.E.A
public class A
{
private String licenseKey;
private String B;
private String E;
private static final char C = 45;
private int A;
public A(String lic)
{
B = "";
E = "";
licenseKey = lic != null ? lic.trim() : null;
if(!validLicense())
A = -3;
else
switch(lic.charAt(0))
{
case 69: // 'E' 试用
case 84: // 'T'
A = B(lic);
break;
case 65: // 'A'
case 76: // 'L'
case 83: // 'S'
A = -1;
break;
default:
A = -3;
break;
}
}
protected int B(String s) // 检查试用版本
{
int i = com.A.A.E.F.B(s);
if(i < 4 || i > 5)
return -3;
Date date = F(s, i);
Date date1 = new Date();
if(date1.compareTo(date) > 0)
return -2;
else
return (int)((date.getTime() - date1.getTime()) / 0x5265c00L);
}
private static Date F(String s, int i)
{
int j = s.indexOf('-') + 1;
if(i > 4)
j = s.indexOf('-', j) + 1;
j = s.indexOf('-', j) + 1;
int k = s.indexOf('.', j);
int l = Integer.parseInt(s.substring(j, k));
j = k + 1;
k = s.indexOf('.', j);
int i1 = Integer.parseInt(s.substring(j, k));
j = k + 1;
k = s.indexOf('-', j);
int j1 = Integer.parseInt(s.substring(j, k));
Calendar calendar = Calendar.getInstance();
calendar.set(l, i1 - 1, j1, 0, 0);
return calendar.getTime();
}
public String I()
{
return B;
}
public String E()
{
return E;
}
public String H()
{
return licenseKey;
}
public String G()
{
byte abyte0[] = E.getBytes();
for(int i = 0; i < abyte0.length; i++)
abyte0[i] = (byte)(abyte0[i] + 2);
return new String(abyte0);
}
private boolean validLicense() // 检查注册码格式是否正确
{
if(licenseKey == null || licenseKey.length() < 3 || licenseKey.charAt(1) != '-')
return false;
switch(licenseKey.charAt(0))
{
case 69: // 'E'
case 84: // 'T'
return fourSplitLicense();
case 65: // 'A'
case 76: // 'L'
case 83: // 'S'
return twoSplitLicense(); // 正式版
}
return false;
}
private boolean D(int splitCharLength)
{
int j = com.A.A.E.F.B(licenseKey); // 查看 - 的个数
if(j < splitCharLength || j > splitCharLength + 1)
return false;
int k = A(licenseKey, 0, j);
if(k == -1 || A(licenseKey, k, 1) > -1)
{
return false;
} else
{
B = licenseKey.substring(0, k);
E = licenseKey.substring(k);
return true;
}
}
private boolean fourSplitLicense()
{
return D(4);
}
private boolean twoSplitLicense()
{
return D(2);
}
public int M()
{
return A;
}
public String C()
{
int i = E.indexOf('#');
if(i > -1)
return E.substring(0, i);
else
return E;
}
public String K()
{
int i = E.indexOf('#');
if(i > -1)
return E.substring(i + 1);
else
return null;
}
private static int A(String s, int i, int j)
{
int k = i;
for(int l = 0; l < j; l++)
{
k = s.indexOf('-', k);
if(k == -1)
return -1;
k++;
if(s.length() <= k)
return -1;
}
return k;
}
}
最终针对 Java部分的格式校验,生成的 License 格式说明如下:
LicenseType-Jversion-user#UserID-nativeHash#JavaHashCode 对于正式版
试用版
LicenseType-Jversion-user#UserID-yyyy.mm.dd-days-nativeHash#JavaHashCode
其中 Java部分主要针对的是 nativeHash 生成 JavaHashCode 的校验
2. Java部分算法分析
注意看 D 这个类的 // if(!H(s2, s1, 7, 38, 11)) // 这个地方是关键点 部分
这个是可以理解为 H(licCode, nativeHash, 7, 38, 11){
int sum = 0;
for (char ch : nativeHash){
sum += ch;
}
String realCode = (sum % 7) append (sum % 38) append (sum % 11);
compare( licJavaHashCode, realCode);
}
因此Java部分的算法分析就是按照指定的License格式,生成,最后进行 nativeHash的验证。
通过即可启动程序。
2. 2 Native 部分算法分析
单纯的实现Java注册部分,只能够启动程序,任何实际的Profile调试均不起作用。
启动后,发现 JProfiler 控制台 打印了 JProfiler> Error Licese .... 具体内容忘记了,
一般这样的话,我们可以定位这些代码都是通过 JNI 采用本地的 动态链接库实现的,
采用 IDA Pro 进行动态库的分析。
1)由于使用 Mac OS 操作系统,因此直接安装的是 Mac OS X 版本的 JProfiler
找到本地动态库 jprofiler.jnilib 这个文件,拷贝到Windows用 IDA Pro 64bit 打开。
待续
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法