Skip to content

标题: 小议JEB3逆向工程

创建: 2020-06-05 12:32 更新: 链接: https://scz.617.cn/misc/202006051232.txt https://www.52pojie.cn/thread-1194011-1-1.html

2020.6.1有个JEB Pro 3.19.1.202005071620被放出来了,小钻风说可能是信用卡欺 诈弄来的。当时给他弄了个传统keygen对付着用,第二天他说有人放出另一种keygen, 可以离线算注册码,就是许可证密钥(License key)。这事我以前没干过,有点意思。

以调试方式启动JEB3,用Eclipse Attach。

点击"Manual Key Generation",看到"许可证数据(License data)"。在Eclipse中 Suspend JVM,主线程调用栈回溯是:

org.eclipse.swt.internal.win32.OS.WaitMessage() line: not available [native method] org.eclipse.swt.widgets.Display.sleep() line: 4528 com.pnfsoftware.jeb.rcpclient.util.SwtUtil.sleep(org.eclipse.swt.widgets.Display) line: 52 com.pnfsoftware.jeb.rcpclient.dialogs.LicenseKeyDialog(com.pnfsoftware.jeb.rcpclient.dialogs.JebDialog).open() line: 395 com.pnfsoftware.jeb.rcpclient.dialogs.LicenseKeyDialog.open() line: 50 com.pnfsoftware.jeb.rcpclient.dialogs.LicenseKeyAutoDialog$3.widgetSelected(org.eclipse.swt.events.SelectionEvent) line: 190 org.eclipse.swt.widgets.TypedListener.handleEvent(org.eclipse.swt.widgets.Event) line: 252 org.eclipse.swt.widgets.EventTable.sendEvent(org.eclipse.swt.widgets.Event) line: 89 org.eclipse.swt.widgets.Display.sendEvent(org.eclipse.swt.widgets.EventTable, org.eclipse.swt.widgets.Event) line: 4105 org.eclipse.swt.widgets.Button(org.eclipse.swt.widgets.Widget).sendEvent(org.eclipse.swt.widgets.Event) line: 1037 org.eclipse.swt.widgets.Display.runDeferredEvents() line: 3922 org.eclipse.swt.widgets.Display.readAndDispatch() line: 3524 com.pnfsoftware.jeb.rcpclient.dialogs.LicenseKeyAutoDialog(com.pnfsoftware.jeb.rcpclient.dialogs.JebDialog).open() line: 394 com.pnfsoftware.jeb.rcpclient.dialogs.LicenseKeyAutoDialog.open() line: 83 com.pnfsoftware.jeb.rcpclient.RcpClientContext.retrieveLicenseKey(java.lang.String) line: 1699 com.pnfsoftware.jeb.rcpclient.RcpClientContext(com.pnfsoftware.jeb.client.AbstractClientContext).prepareCheckLicenseKey() line: 736 com.pnfsoftware.jeb.rcpclient.RcpClientContext(com.pnfsoftware.jeb.client.AbstractClientContext).start() line: 442 com.pnfsoftware.jeb.rcpclient.RcpClientContext.start() line: 1522 com.pnfsoftware.jeb.rcpclient.RcpClientContext.onApplicationReady(com.pnfsoftware.jeb.rcpclient.extensions.app.App) line: 1212 com.pnfsoftware.jeb.rcpclient.JebApp.onApplicationReady() line: 73 com.pnfsoftware.jeb.rcpclient.JebApp(com.pnfsoftware.jeb.rcpclient.extensions.app.App).run() line: 186 com.pnfsoftware.jeb.rcpclient.Launcher.main(java.lang.String[]) line: 35


/ * com.pnfsoftware.jeb.rcpclient.RcpClientContext.retrieveLicenseKey(String) : String / public String retrieveLicenseKey(String licdata)


这个函数负责弹框显示"许可证数据(License data)",形参licdata就是对话框里的 数字串。


/ * com.pnfsoftware.jeb.client.AbstractClientContext.prepareCheckLicenseKey() : void / private void prepareCheckLicenseKey() { ip.RF();

String str1 = this.pm.getString(".LicenseKey"); int[] arrayOfInt = new int[1];

int i = 0; if (!Strings.isBlank(str1)) { long[] arrayOfLong1 = ek.Ql(); for (long l2 : arrayOfLong1) { Fp localFp2 = new Fp(l2); if (localFp2.RF(str1, arrayOfInt)) { i = 1; break; } } } if (i == 0) { ip.Ql(); / * 733行,返回MachineId / long l1 = ek.RF(); / * 734行 / Fp localFp1 = new Fp(l1); / * 735行,返回"许可证数据(License data)" / String str2 = localFp1.RF(); / * 736行,str2即"许可证数据(License data)",str1即"许可证密钥(License key)" / str1 = retrieveLicenseKey(str2); ip.RF(); if (!localFp1.RF(str1, arrayOfInt)) { logger.info(S.s(437), new Object[0]); terminate(); } this.pm.setString(".LicenseKey", str1.trim()); } Licensing.setLicenseTimestamp(arrayOfInt[0]);

int j = Licensing.getExpirationTimestamp(); if (j != 0) { if ((j < 0) || (getStartTimestamp() >= j)) { this.pm.setBoolean(".SupportExpired", Boolean.valueOf(true)); notifySupportExpired(); } else if (this.pm.getBoolean(".SupportExpired")) { this.pm.setBoolean(".SupportExpired", Boolean.valueOf(false)); } } }


/ * com.pnfsoftware.jebglobal.Fp / public class Fp { / * MachineId / private long RF;

/ * 设置MachineId / public Fp(long paramLong) { this.RF = paramLong; }

/ * 返回"许可证数据(License data)" / public String RF() { try { ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();

  LEDataOutputStream localLEDataOutputStream = new LEDataOutputStream(localByteArrayOutputStream);
  localLEDataOutputStream.writeLong(this.RF);
  localLEDataOutputStream.close();

/ * 41行,localByteArrayOutputStream.toByteArray()等于8字节little-endian序 * 的MachineId * * this.RF也是MachineId * * 返回"许可证数据(License data)" / byte[] arrayOfByte = wR.RF(this.RF, localByteArrayOutputStream.toByteArray(), null); / * 42行,对RC4结果进一步处理,就是格式化输出,没有数学变换 / return wR.RF(arrayOfByte); } catch (Exception localException) {} return ""; }


com.pnfsoftware.jebglobal.wR.RF(long, byte[], int[]) line: 104 com.pnfsoftware.jebglobal.Fp.RF() line: 41 [local variables unavailable] com.pnfsoftware.jeb.rcpclient.RcpClientContext(com.pnfsoftware.jeb.client.AbstractClientContext).prepareCheckLicenseKey() line: 735 [local variables unavailable] com.pnfsoftware.jeb.rcpclient.RcpClientContext(com.pnfsoftware.jeb.client.AbstractClientContext).start() line: 442 com.pnfsoftware.jeb.rcpclient.RcpClientContext.start() line: 1522 com.pnfsoftware.jeb.rcpclient.RcpClientContext.onApplicationReady(com.pnfsoftware.jeb.rcpclient.extensions.app.App) line: 1212 com.pnfsoftware.jeb.rcpclient.JebApp.onApplicationReady() line: 73 com.pnfsoftware.jeb.rcpclient.JebApp(com.pnfsoftware.jeb.rcpclient.extensions.app.App).run() line: 186 com.pnfsoftware.jeb.rcpclient.Launcher.main(java.lang.String[]) line: 35


/ * com.pnfsoftware.jebglobal.wR.RF(long, byte[], int[]) : byte[] / public static byte[] RF(long paramLong, byte[] paramArrayOfByte, int[] paramArrayOfInt) { try { if (paramArrayOfByte == null) { return null; } ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();

LEDataOutputStream localLEDataOutputStream = new LEDataOutputStream(localByteArrayOutputStream);

localLEDataOutputStream.writeInt(64 + paramArrayOfByte.length);

/ * 113行,随机数 / localLEDataOutputStream.writeInt(UL.nextInt());

localLEDataOutputStream.writeInt(5);

/ * 116行,占坑,后面会回填CRC32 / localLEDataOutputStream.writeInt(0);

localLEDataOutputStream.writeInt(Licensing.user_id);
localLEDataOutputStream.writeLong(Licensing.license_id);

/ * 120行,MachineId / localLEDataOutputStream.writeLong(paramLong); localLEDataOutputStream.writeInt(Licensing.build_type);

localLEDataOutputStream.writeInt(AbstractContext.app_ver.getMajor());
localLEDataOutputStream.writeInt(AbstractContext.app_ver.getMinor());
localLEDataOutputStream.writeInt(AbstractContext.app_ver.getBuildid());
localLEDataOutputStream.writeLong(AbstractContext.app_ver.getTimestamp());

int i = (int)((System.currentTimeMillis() - Tk) / 1000L);
localLEDataOutputStream.writeInt(i);

/ * 131行 * * com.pnfsoftware.jebglobal.wR.Az() : int / int j = Az();

localLEDataOutputStream.writeInt(j);

/ * 135行,随机数 / int k = UL.nextInt(); localLEDataOutputStream.writeInt(k); / * 138行,MachineId / localLEDataOutputStream.write(paramArrayOfByte); localLEDataOutputStream.close();

byte[] arrayOfByte1 = localByteArrayOutputStream.toByteArray();
ByteBuffer localByteBuffer = ByteBuffer.wrap(arrayOfByte1);
localByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
localByteBuffer.putInt(0, arrayOfByte1.length - 8);

/ * 145行,计算[0x10:]的CRC32,回填相应字段 / localByteBuffer.putInt(12, Hash.calculateCRC32(Arrays.copyOfRange(arrayOfByte1, 16, arrayOfByte1.length)));

byte[] arrayOfByte2 = new byte[16];
for (int m = 0; m < 8; m++) {
  arrayOfByte2[m] = arrayOfByte1[m];
}
for (m = 8; m < 16; m++) {
  arrayOfByte2[m] = RF[(m - 8)];
}

/ * 154行,RC4() / xS.RF(arrayOfByte2, arrayOfByte1, 8, arrayOfByte1.length); if ((paramArrayOfInt != null) && (paramArrayOfInt.length >= 1)) { paramArrayOfInt[0] = k; } return arrayOfByte1; } catch (Exception localException) {} return null; }


/ * RC4() / com.pnfsoftware.jebglobal.xS.RF(byte[], byte[], int, int) : void


对前述xS.RF()的第2形参尝试手工解码:


48 00 00 00 // +0x0 64 + 8 05 05 6e 1d // +0x4 随机数 05 00 00 00 // +0x8 固定值5 4a 56 83 5f // +0xc 后面所有数据的CRC32 06 58 76 3e // +0x10 // User ID: 1047943174 9b b9 d0 17 50 fe da 00 // +0x14 // License ID: 61641164873316763 xx xx xx xx xx xx xx xx // +0x1c MachineId ... // +0x24 (后略)


RC4是流式对称加密算法,从out[]解密出in[],即可得到LicenseId、MachineId。

简化版调用关系:


RcpClientContext.start // 3.19.1.202005071620 AbstractClientContext.start // RcpClientContext:1522 AbstractClientContext.prepareCheckLicenseKey // AbstractClientContext:442 ek.RF // AbstractClientContext:733 // 返回MachineId Fp.RF // AbstractClientContext:735 // 返回"许可证数据(License data)"的String形式 // 对话框中显示之 wR.RF // Fp:41 // 返回"许可证数据(License data)"的byte[]形式 xS.RF // wR:154 // RC4()/PrivateTransform() RcpClientContext.retrieveLicenseKey // AbstractClientContext:736 // 形参是"许可证数据(License data)"的String形式


这种离线keygen确实更通用,参与运算的LicenseId、MachineId都从LicenseData解 密获取,同时适用于Windows、Linux、Mac,不需要关心MachineId的数据源在不同OS 上的获取方式。

说一下过期时间的事。JEB3的这个过期时间我不确认是否真地影响过期后的继续使用, 曾经有过不同的情况,比如只影响升级,不影响继续使用。如果真影响过期后的继续 使用,可能得动态Patch一个类:

com.pnfsoftware.jeb.client.Licensing

具体是这个函数:

com.pnfsoftware.jeb.client.Licensing.getExpirationTimestamp() : int

该函数中用到常量86400,将之改成864000,则过期时间变成"2030-05-30"。不要贪 心地改成8640000,否则过期时间回绕到1984。

$ fc /b old new 00000034: 01 0D 00000035: 51 2F 00000036: 80 00

可以用你喜欢的任一动态Patch技术去完成内存中的常量修改,不建议静态Patch。

感慨一句,JEB2真是我的伤心之地,以至于每看到一个新版JEB出场,都要长叹一声, 欲语还休。