idea agent 激活原理
破解原理
jetbrains提供了一个付费插件如何校验 License 的示例代码, 点击 CheckLicense.java 查看
现在对代码进行分析,校验license
的入口是isKeyValid
方法
1 | private static boolean isKeyValid(String key) { |
下面进行逐步分析:
License的格式
1 | String[] licenseParts = key.split("-"); |
透过这几行,我们可以发现License由4部分组成,这4部分用-拼接
licenseId
licenseId 没什么好解释的,大家写代码对一条数据会给他一个id标识,这里也是一样的
licensePartBase64
既然是base64我们使用base64解码试试,先从网上找个license
再写几行代码解码打印一下
打印结果,下面是json格式化后的
1 | { |
可以看出,这个json存储了license的信息了,过期时间,产品编码,等等
signatureBase64
这个虽然也是bash64编码的,但是不可读,从后续的代码中可以看到这个是licensePartBase64的签名(防止你修改licensePartBase64的内容)
certBase64
这个从名字就可以知道,这是一个证书的base64,我们可以写几行代码试试将其转成X509Certificate
打印结果
1 | [ |
可以看出这个一个由JetProfile CA签名的子证书
验证证书
1 | final Signature sig = Signature.getInstance("SHA1withRSA"); |
Here it is only important that the key was signed with an authentic JetBrains certificate.翻译一下就是:这里唯一重要的是密钥是使用真实的 JetBrains 证书签名的
说明了这段代码唯一的目的就是校验证书是JetBrains签名的,createCertificate核心代码如下
1 | final X509CertSelector selector = new X509CertSelector(); |
ROOT_CERTIFICATES 是内置的根证书,使用根证书来验证传入的证书是不是根证书签发的,后面的代码都是使用java的库进行证书链验证
这里涉及到了证书链 证书签名
证书包含的内容可以概述为三部分,用户的信息、用户的公钥、还有CA中心对该证书里面的信息的签名。我们在验证证书的有效性的时候,会逐级去寻找签发者的证书,直至根证书为结束,然后通过公钥一级一级验证数字签名的正确性。
证书签名即使用上一级机构的私钥对证书里的信息进行hash再加密
用户使用上一级的公钥对签名进行解密得到一个值
用户使用证书里的信息进行hash的到一个值
如果这两者相同,则说明证书是正确的
验证licensePart
验证完证书,还需要验证license中的信息是不是被修改过,
1 | final Signature sig = Signature.getInstance("SHA1withRSA"); |
其实就使用证书里包含的公钥与SHA1withRSA 算法进行验证
破解的思路
首先,我们需要一个证书, 还要有这个证书的私钥,网上找的license只能拿到证书,没有私钥,没有私钥就无法对licensePart进行签名
如果我们自己签一个证书,那么验证证书时是无法通过的, 我们需要一点魔法来搞定这一点, Java agent 技术修改jdk的字节码, 使得校验我们的证书时总是通过
有了证书后,使用证书的私钥对我们修改过的licensePart进行签名,最后拼接得到license
为什么不直接改掉这个函数?
上面的代码只是一个demo,并不代表实际软件里是这么实现的,并且idea的代码经过混淆加密,根本无法查看,无法定位到是那个方法校验的license
破解的实现
生成证书
使用下面的python脚本即可
1 | import datetime |
执行这个脚本会生成保存一个证书ca.crt, 以及一个私钥ca.key
使用证书和私钥生成license
1 | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
这里需要一个第三方的库
1 | <dependency> |
执行main方法,因该能得到类似以下内容, 证书不同, licensePart不同,所以内容每个人都不一样使用的jdk是OpenJdk17
1 | MOUGH5FKDV-eyJsaWNlbnNlSWQiOiJNT1VHSDVGS0RWIiwibGljZW5zZWVOYW1lIjoiaGFoYSIsImFzc2lnbmVlTmFtZSI6Im5vdmljZS5saSIsImFzc2lnbmVlRW1haWwiOiIiLCJsaWNlbnNlUmVzdHJpY3Rpb24iOiIiLCJjaGVja0NvbmN1cnJlbnRVc2UiOmZhbHNlLCJwcm9kdWN0cyI6W3siY29kZSI6IlBDV01QIiwiZmFsbGJhY2tEYXRlIjoiMjAyNi0wOS0xNCIsInBhaWRVcFRvIjoiMjAyNi0wOS0xNCIsImV4dGVuZGVkIjp0cnVlfSx7ImNvZGUiOiJQU0kiLCJmYWxsYmFja0RhdGUiOiIyMDI2LTA5LTE0IiwicGFpZFVwVG8iOiIyMDI2LTA5LTE0IiwiZXh0ZW5kZWQiOnRydWV9LHsiY29kZSI6IldTIiwiZmFsbGJhY2tEYXRlIjoiMjAyNi0wOS0xNCIsInBhaWRVcFRvIjoiMjAyNi0wOS0xNCIsImV4dGVuZGVkIjpmYWxzZX1dLCJtZXRhZGF0YSI6IjAxMjAyMzA5MTRQU0FYMDAwMDA1IiwiaGFzaCI6IlRSSUFMOi0xOTIwMjA0Mjg5IiwiZ3JhY2VQZXJpb2REYXlzIjo3LCJhdXRvUHJvbG9uZ2F0ZWQiOmZhbHNlLCJpc0F1dG9Qcm9sb25nYXRlZCI6ZmFsc2V9-EtkxIjIOL6nbRjpnwjn3xoS5ch3k/gMZMFZ8/oqxIqNhlwDsQajZw/UASlLpd3t6tWRqefL4kucMfpNyUEPbwe3jcA0cbE7bD7wj1O2UUCTBDpCTY3Hh57dgdkvYIizIaqdFFay7GtQf46ltXraYt3YUpiQfx0ferOR9tDdg1zgL2n0vxLtlChYAMvBo2yMnyMvAS490tQu4c5Oi87KNcTWmUihL6oiDIv1a2/DaMYpKY10PvAENwfq0wuQtWQqrb1azF1LIziratnBY0AfpMOb6HMIYf0iPGMCyYcwmyDfQ5nM5gKnip9V8gsyQFX+Ja4dzDbEoRt5AavvShbYJjYTB8RCXWuLW6uLhoaJJMKK5VWDNPwqd2iL4cRWmXLVOM6IDdtP0asLVjgfugbr7NYHZepfhQhgPCZrJR4MeLbYtdJakDwI+JBim7gSlYDJi8DNxXl5qvnzTL9oS66zwvx84E5KNCePC08MkUVallELohQpcs7pt8CfRPsqTJpSDMS/h3aTvhLOVLCY5zCXc2G1bmh6HdDYWPvbhU4uiGlWcqTiBVjUo324tO0085yU40VUk6S4QL+Q69HX1i13k43saaDg1OirnLY7pGq6bgW6fqALqVrrOgjy4Pkhwyzx/hJWTqxcY1kHFyqIFpWT/lWJ8shpU/Lp0NTQ6OIDe4Ag=-MIIExTCCAq2gAwIBAgIUONPn09E4/d2ARDMbTgtVdG78L5owDQYJKoZIhvcNAQELBQAwGDEWMBQGA1UEAwwNSmV0UHJvZmlsZSBDQTAeFw0yMzA5MjIxMjI3NTRaFw0zMzA5MjAxMjI3NTRaMCExHzAdBgNVBAMMFk1vWXVuby1mcm9tLTIwMjItMDctMjUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgoO4tub6olCRbJ38I0K4kWr+suajcuwTDbrAvWqkNTt3U1SWArWQa7s6QJfKAAFVKEQpFW2eu861YLC8GUVKKZS4ol9dihVy+Zp7ThUuSkz64d9kwQ4/5JwKEAW51fWORHbjs3dhdShAq9tmuOGohi0KjBhkTzeKpTMa2k/TlxHD0pwdVDEQygQ02+ddCkSuozF/9/mzafG2RB6iYiKld7vUs68n2FVotktpN2eGrXv72nenuQLKVT7ecXNT0m6KzY6+aelAFqeSUhnyqVe9Zm0oPIPL9QAJXmjUpzyw6vu2ZYkCoJcGjtogs9uilqlOaKGFQc1OGxuoQhOerBOZm6n93YIobjr5RwRb0img6AtkknFsXoUKNHxAGu8iCFlRGykWU5K+h+/NRd7JAO2AAMv2Qa5CwG+wcc1YXZQejL7YkBTw70zUI3107on2n4KTbtNYpdwccqAjWcdycG4eekBMxGDGKS43aH1adT54ICQsm8jsq5/zTg/8PR4PBG/jXyUGR0JRlwy14aWyJwaLIdscDGU3S6nfM8f2mtchKnDySzkn17FucNkW9nXbV979WsxX+TPX3y/vNAQzM04z+6Vjfp/1AIFu+LC0VaOhbI1yqYZOvUdQX5XyhZ6N/rCFj6Hsv4fuCVCTMuSs/qGS83js6LVxd9ZygRlhcc8YL7wIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQAI3XgTh8LKomEU8T0Zd2fYjT7I/XdNgY3KvW/DzsdG6A0wRlijwA74aWcD7N/Rrrz9vObI1PUMUbJzUwwcHu6ZvlL0f23QRu4oXASEVuU34QYDIY5/1nMaFyjLKGrXfctz9+qXnn/admaiUVj81jroLKbHFoAga9I2i987fwaVnG1CwDOIkG54xwvCT34M3d7sxEUZwSI/+yMS3JVG19kOqf16KBar5WlZcSYA+rd01RfuBKW4ciu5VplAoKBfU6QOeh5SfQdoG8cxUHI5KBUWzhDUZo8TK5donK8ZQs5Pka5mWLCAWW6gq+9JzCeEFxyscr8pTMWegQpEQG6B8ULtMuT69DTx6RRQzwDe9oQAzZqqAjnuiLr87y6IKKwRaZe3zqsMkdqnEslDOMFuYZce4vGyYZr5vx5774kjIv3AvCowx8O6UMOhJgi7aDnEbTdF3AswUg3fGGsMwujKY6VLMzzIJ8i6P+X3Abdb51hr0hEimWnbBIi4Vdg9Ga6sLR+gAr4PAy/LX2tfxTfIryehcC12C5s2uMDtoHPi5MtQZ7ZLDj7F1opGd4RjEtdAuA81e+usCPbpc2A9DLEFTzqJCxmbhd4obzlj75Bb2ih83MeJwvlhNl3oLb89Ur05VyF/XF5vWdtFA7EOx7Tb/s5Vv4N5PptsDX8jjFNdTrbByA== |
实现agent
不借助第三方库实现agent过于麻烦,这里使用了bytebuddy
1 | <dependencies> |
agent入口
1 | public class MyAgent { |
BigInteger字节码修改
1 | public class BigIntegerAdvice { |
上面的几个大数字是由我们生成的证书计算来的,后面会给出计算代码,并作出解释
HttpClient字节码修改
1 | public class HttpClientAdvice { |
maven打包配置
记得修改pom的打包配置, 记得修改配置里的包名,改成你自己的
1 | <build> |
使用生成的激活码
编辑打包
给webstorm的jvm配置加上下面的参数
1 | --add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED |
其中javaAgent的路径需要修改成你们自己打包后存放的路径,启动再输入生成的激活码
BigIntegerAdvice 中的几个数字是如何生成,这段代码为什么是这样?
对证书验证其实就是验证证书中携带的签名是否和jetbains计算的签名是否一致,jetbrains会使用其内置根证书z尝试对签名解密,即计算:x.modpow(y,z)
(具体的签名验签原理可以看RSA加密&签名
),但是这里的证书不是由jetbrains签发,所以要替换计算的结果
x:证书的签名密文
y:证书指数 固定 65535
z:内置根证书的公钥,文章最开始验证激活码的demo代码里硬编码的
r : 对 DER 编码的证书信息(即来自该证书的tbsCertificate) 进行sha265摘要计算,计算的结果转换为ASN1格式数据,ASN1格式数据再进行填充得到的
当检测到使用modpow
方法解密我们证书的签名时,返回r,这样idea再对r做一些计算得到的值等于证书信息的sha265摘要,idea就会认为可信的证书,下面是sun.security.rsa.RSASignature
的验签实现
1 | protected boolean engineVerify(byte[] sigBytes) throws SignatureException { |
下面是计算 x
y
z
r
的代码
1 | import java.io.ByteArrayInputStream; |
参考资料
插件?
插件的破解其实是一模一样的,当你配置好agent后,使用激活码生成工具,修改licensePart中的products.code
为插件的code
即可
idea 所需 code
1 | { |
jetbrains产品列表
1 | [ |
仅供学习交流使用
以上仅供学习交流使用!!!
以上仅供学习交流使用!!!
以上仅供学习交流使用!!!