欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

JProfiler 6/7 注册机制逆向工程分析

JProfiler 6/7 注册机制逆向工程分析

前几天在看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 打开。
待续

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功