CrackJavaXcology9 license analysis & crack
S0cke3t本文只做技术分析交流使用,请勿用于非法用途
前言
前几日,在我百无聊赖时。某位大佬突然v我说之前某微OA的license过期了,我一看 哦豁 真提示过期了。所以为了能让大佬愉快的挖洞,看看能不能把license过掉。所以就…
授权分析
首先我们通过该OA的路由配置确定了处理license的相关jar包,该包位于\WEB-INF\lib\ljstln.jar
。其中LNparse为验证license的主要实现类,LN为验证license的入口文件,其中包含LN具体实例化以及获取相关属性值。
我们将其相关的依赖包拷贝出来并创建IDEA项目,分析具体验证流程。
先来看LN
首先定义license相关字段值,并在构造函数中进行字段的初始化
最后就是getter/setter,其中包含的函数很多,我们先看校验入口函数InLicense
函数根据message值判断是否要导入和更新license,跟进CkLicense
首先方法传入一个currentdate
参数,其值为当前日期,随后获取程序license目录下.license文件路径,并将其传入ReadFromFile
,跟进此方法
该方法首先实例化一个LNParse
对象,随后尝试从缓存中读取license信息,如果缓存中没有相关信息,则调用LNParse
的getLNBean
进行license文件的解析,跟进。
getLNBean是验证license的主要实现,方法首先从.license文件中取出相应文件的内容
******.license其实是一个压缩包,其中包含4个文件
publicKey 解密DES key的公钥
licenseEncryptKey 私钥加密的DES key
license 加密的新版本license具体内容
license2 加密的兼容旧版本的license内容
获取文件内容后方法根据传入的key值使用不同的公钥,然后把license文件获取的publickey
进行base64编码,并将编码后的publickey
和对应key的realPublicKey
进行对比,如果一致说明该license有效
随后声明JSONObject
对象用于存放json格式license内容,并调用RSACoder.decryptByPublicKey
解密出licensekey
然后调用DESCoder.decrypt用解密出的licensekey解密出具体的license内容
最后实例化一个LNBean
并对相关属性进行赋值。回到LN.CkLicense
获取到license明文信息后,该函数判断当前日志是否大于license中expirdate的值,如果在许可日期内再判断license值,其中license值由this.companyname + this.licensecode + this.software + temphrmnum + this.expiredate + this.concurrentFlag
拼接后的字符串调用Util.getEncrypt
进行MD5加密得到。
而其中的licensecode则是对MAC地址进行MD5后加密得到,在判断所有值合法后Inlicense函数将license信息进行入库。
至此整个license的验证流程基本清晰了大概流程如下
step 1: 用license文件的公钥解密licensekey
其中licensekey是加密license内容的DES key
step2: 用解密出licensekey 使用DES解密出具体license内容
step3: 判断license和expirdate值是否相等
整个解密流程使用的DES对称和RSA非对称混合解密
流程验证
解密流程了解以后,我们根据相关方法签名,新建一个java文件,并调用getLNBean看一下license具体内容
可以看到根据调用流程可以成功拿到license的明文信息,下一步就是如何破解验证流程。
我们梳理一下解密流程后不难发现要想解密license信息就必须要知道DES key的值,而DES key是由开发商私钥进行加密,在没有私钥的情况下解密DES key是不可能的。所以我们唯一能干预的就是更改加解密的公私钥对,将解密的公钥更改为自己的公钥值,也就是getLNBean
中的realPublicKey
值,这样就能正确拿到DES key值。也就能正确解密出license的明文信息。
而要更改公钥值有两种方法可以实现,一是反编译license验证类,更改后重新打包。二是使用java agent
技术重写realPublicKey
值或者重写getLNBean
方法。由于license验证类所依赖的包实在太多,反编译起来极为繁琐,所以此方法pass。
破解
首先创建一个java agent并hook ln/LNParse类,并获取其中的getLNBean使用javassist框架重写getLNBean方法,更改其中的realPublicKey值,最后调用detach重新加载此对象。核心代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { final ClassPool classPool = ClassPool.getDefault(); try { ClassClassPath classPath = new ClassClassPath(this.getClass()); classPool.appendClassPath(".//clib//ljstln.jar"); classPool.appendClassPath(".//clib//commons-codec-1.11.jar"); classPool.appendClassPath(".//clib//json-20090211.jar"); classPool.insertClassPath(classPath); final CtClass clazz = classPool.get("ln.LNParse"); CtMethod convertToAbbr = clazz.getDeclaredMethod("getLNBean"); String methodBody = "{byte[] publicKey = ln.Zip.getZipSomeByte($1, \"publicKey\");\n" + " byte[] licenseEncryptKey = ln.Zip.getZipSomeByte($1, \"licenseEncryptKey\");\n" + " byte[] licenseFile = ln.Zip.getZipSomeByte($1, \"license\");\n" + " byte[] licenseFile2 = ln.Zip.getZipSomeByte($1, \"license2\");\n" + " String realPublicKey = \"\";\n" + " if (\"emessage2\".equals($2)) {\n" + " realPublicKey = \"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIJWRm0eoQNEgZB9aUlM1PoT0N7cKCBCfkecycpeKeg57e73Fcj4ik9uYrGB01t38ut45iHJi8TLoeORYuUAhWUCAwEAAQ==\";\n" + " } else if (\"ecology9\".equals($2)) {\n" + " realPublicKey = \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiPitGeOC5t98v3ILcS/BNeFgoaFkVoAgbo163rMkIVpYqdkauBln2vDZflJ+6mQj92G6LFzTMhi5WXgigh71ul6MIoZBa3CNg/1oXPE8p7NHRc9GkH5Y8n3Qm6r4mY6uqF1a4CNahfAi1IENjwWucHmgcSwfurihBirOQAeQX1dIkdAyvfTxUPOAyXb/CVFVhBggryJ7M83wfOk2z87DCgk9ZAre0NdaN/wCGmO6C2rAReGd32FUhwli/WdSfAYZD9bTDJ6Y0n/A2Mh54stToiRDTJm3l4qBQxVSQ+ezN91v4P1CQbrAvu4s+EdIf1TPvvuUX0yyEA8hle/uiVKKFwIDAQAB\";\n" + " }\n" + "\n" + " String publicKeyStr = new String(org.apache.commons.codec.binary.Base64.encodeBase64(publicKey));\n" + " if (!realPublicKey.equals(publicKeyStr)) {\n" + " throw new Exception(\"license error!\");\n" + " } else {\n" + " org.json.JSONObject jsonLicense;\n" + " try {\n" + " byte[] licenseKey = ln.RSACoder.decryptByPublicKey(licenseEncryptKey, publicKey);\n" + " jsonLicense = new org.json.JSONObject(new String(ln.DESCoder.decrypt(licenseFile, licenseKey)));\n" + " } catch (java.security.InvalidKeyException var15) {\n" + " var15.printStackTrace();\n" + " byte[] licenseInfo2 = ln.DESCoder.decrypt(licenseFile2, $2.getBytes());\n" + " jsonLicense = new org.json.JSONObject(new String(licenseInfo2));\n" + " }\n" + "\n" + " ln.LNBean lnb = new ln.LNBean();\n" + " lnb.setCompanyname(jsonLicense.getString(\"companyname\"));\n" + " lnb.setLicensecode(jsonLicense.getString(\"licensecode\"));\n" + " lnb.setHrmnum(jsonLicense.getString(\"hrmnum\"));\n" + " lnb.setExpiredate(jsonLicense.getString(\"expiredate\"));\n" + " lnb.setConcurrentFlag(jsonLicense.getString(\"concurrentFlag\"));\n" + " lnb.setLicense(jsonLicense.getString(\"license\"));\n" + "\n" + " try {\n" + " lnb.setCid(jsonLicense.getString(\"cid\"));\n" + " } catch (Exception var14) {\n" + " System.out.println(var14);\n" + " }\n" + "\n" + " try {\n" + " lnb.setScType(jsonLicense.getString(\"scType\"));\n" + " } catch (Exception var13) {\n" + " System.out.println(var13);\n" + " }\n" + "\n" + " try {\n" + " lnb.setScCount(jsonLicense.getString(\"scCount\"));\n" + " } catch (Exception var12) {\n" + " System.out.println(var12);\n" + " }\n" + "\n" + " return lnb;\n" + " }}"; convertToAbbr.setBody(methodBody); byte[] byteCode = clazz.toBytecode(); clazz.detach(); System.out.println(public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { final ClassPool classPool = ClassPool.getDefault(); try { ClassClassPath classPath = new ClassClassPath(this.getClass()); classPool.appendClassPath(".//clib//ljstln.jar"); classPool.appendClassPath(".//clib//commons-codec-1.11.jar"); classPool.appendClassPath(".//clib//json-20090211.jar"); classPool.insertClassPath(classPath); final CtClass clazz = classPool.get("ln.LNParse"); CtMethod convertToAbbr = clazz.getDeclaredMethod("getLNBean"); String methodBody = "{byte[] publicKey = ln.Zip.getZipSomeByte($1, \"publicKey\");\n" + " byte[] licenseEncryptKey = ln.Zip.getZipSomeByte($1, \"licenseEncryptKey\");\n" + " byte[] licenseFile = ln.Zip.getZipSomeByte($1, \"license\");\n" + " byte[] licenseFile2 = ln.Zip.getZipSomeByte($1, \"license2\");\n" + " String realPublicKey = \"\";\n" + " if (\"emessage2\".equals($2)) {\n" + " realPublicKey = \"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIJWRm0eoQNEgZB9aUlM1PoT0N7cKCBCfkecycpeKeg57e73Fcj4ik9uYrGB01t38ut45iHJi8TLoeORYuUAhWUCAwEAAQ==\";\n" + " } else if (\"ecology9\".equals($2)) {\n" + " realPublicKey = \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiPitGeOC5t98v3ILcS/BNeFgoaFkVoAgbo163rMkIVpYqdkauBln2vDZflJ+6mQj92G6LFzTMhi5WXgigh71ul6MIoZBa3CNg/1oXPE8p7NHRc9GkH5Y8n3Qm6r4mY6uqF1a4CNahfAi1IENjwWucHmgcSwfurihBirOQAeQX1dIkdAyvfTxUPOAyXb/CVFVhBggryJ7M83wfOk2z87DCgk9ZAre0NdaN/wCGmO6C2rAReGd32FUhwli/WdSfAYZD9bTDJ6Y0n/A2Mh54stToiRDTJm3l4qBQxVSQ+ezN91v4P1CQbrAvu4s+EdIf1TPvvuUX0yyEA8hle/uiVKKFwIDAQAB\";\n" + " }\n" + "\n" + " String publicKeyStr = new String(org.apache.commons.codec.binary.Base64.encodeBase64(publicKey));\n" + " if (!realPublicKey.equals(publicKeyStr)) {\n" + " throw new Exception(\"license error!\");\n" + " } else {\n" + " org.json.JSONObject jsonLicense;\n" + " try {\n" + " byte[] licenseKey = ln.RSACoder.decryptByPublicKey(licenseEncryptKey, publicKey);\n" + " jsonLicense = new org.json.JSONObject(new String(ln.DESCoder.decrypt(licenseFile, licenseKey)));\n" + " } catch (java.security.InvalidKeyException var15) {\n" + " var15.printStackTrace();\n" + " byte[] licenseInfo2 = ln.DESCoder.decrypt(licenseFile2, $2.getBytes());\n" + " jsonLicense = new org.json.JSONObject(new String(licenseInfo2));\n" + " }\n" + "\n" + " ln.LNBean lnb = new ln.LNBean();\n" + " lnb.setCompanyname(jsonLicense.getString(\"companyname\"));\n" + " lnb.setLicensecode(jsonLicense.getString(\"licensecode\"));\n" + " lnb.setHrmnum(jsonLicense.getString(\"hrmnum\"));\n" + " lnb.setExpiredate(jsonLicense.getString(\"expiredate\"));\n" + " lnb.setConcurrentFlag(jsonLicense.getString(\"concurrentFlag\"));\n" + " lnb.setLicense(jsonLicense.getString(\"license\"));\n" + "\n" + " try {\n" + " lnb.setCid(jsonLicense.getString(\"cid\"));\n" + " } catch (Exception var14) {\n" + " System.out.println(var14);\n" + " }\n" + "\n" + " try {\n" + " lnb.setScType(jsonLicense.getString(\"scType\"));\n" + " } catch (Exception var13) {\n" + " System.out.println(var13);\n" + " }\n" + "\n" + " try {\n" + " lnb.setScCount(jsonLicense.getString(\"scCount\"));\n" + " } catch (Exception var12) {\n" + " System.out.println(var12);\n" + " }\n" + "\n" + " return lnb;\n" + " }}"; convertToAbbr.setBody(methodBody); byte[] byteCode = clazz.toBytecode(); clazz.detach(); System.out.println("detach success");
|
使用maven进行打包后,将jar包附加到调试选项里,并生成一个自定义的license文件进行测试
上机测试一下,首先修改Resin的\conf\resin.properties
配置文件
加上 -javaagent:Ec-agent.jar
然后将jar包和依赖包放到Resin根目录中
启动服务后导入自定义license
Bingo~ 成品就不发出来了,怕收律师函-_- !