破解 ReSharper 2025
2025-10-08 20:52:40

ReSharper 以前网上还很容易找到破解补丁,针对新版的补丁已经找不到了,只能自己动手了,其实并不难,和其他用 Java 开发的软件是同一套流程,激活码也是一样的。
离线安装包下载地址:https://www.jetbrains.com/resharper/download/other.html
本次破解的是最新版本 2025.2.2.1:https://download.jetbrains.com/resharper/dotUltimate.2025.2.2.1/JetBrains.dotUltimate.2025.2.2.1.exe

破解证书验证

根据破解 Java 软件的经验,可以大胆推测这个用 C# 开发的插件也是用一样的思路去破解,先解决证书验证问题,可以用 dnSpy 调试JetBrains.Platform.Shell.dll文件,搜索X509Certificate2关键字查找软件验证的地方。
根据一顿调试后发现关键的UserLicenseService.DecodeLicense方法:

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
public INewLicenseData DecodeLicense(string base64)
{
return Logger.CatchSilent<NewLicenseData>(delegate()
{
UserLicenseUtil.LicenseParts licenseParts = UserLicenseUtil.TryGetLicenseParts(base64);
if (licenseParts.IsEmpty())
{
return null;
}
byte[] left = SHA256.Create().ComputeHash(this.RootCertificate.RawData);
byte[] right = new byte[]
{
195,
173,
86,
176,
33,
92,
192,
18,
118,
94,
55,
80,
117,
231,
236,
228,
145,
204,
28,
87,
24,
107,
228,
204,
182,
102,
134,
246,
27,
198,
114,
189
};
if (!left.EqualEnumerables(right))
{
return null;
}
X509Certificate2 x509Certificate = new X509Certificate2(Convert.FromBase64String(licenseParts.CertBase64.ToString()));
if (UserLicenseService.VerifyCertificate(this.BlackListAndCrl.Crl, x509Certificate, false, new X509Certificate2[]
{
this.RootCertificate
}) != UserLicenseService.CertificateValidationResult.OK)
{
return null;
}
byte[] bytes = Convert.FromBase64String(licenseParts.LicensePartBase64.ToString());
string @string = Encoding.UTF8.GetString(bytes);
if (!UserLicenseService.VerifySignature(@string, licenseParts.SignatureBase64.ToString(), x509Certificate, HashAlgorithmName.SHA1))
{
return null;
}
NewLicenseData newLicenseData = NewLicenseData.FromJson(@string);
if (newLicenseData != null && !licenseParts.LicenseRef.Equals(newLicenseData.LicenseId))
{
return null;
}
return newLicenseData;
}, null);
}

UserLicenseService.VerifyCertificate是个静态方法,用于验证证书。

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
public static UserLicenseService.CertificateValidationResult VerifyCertificate(CertificateRevocationList crl, X509Certificate2 primaryCert, bool checkCertificateTimeValidity, params X509Certificate2[] additionalCertificates)
{
object obj = UserLicenseService.ourVerifyCertificateLock;
UserLicenseService.CertificateValidationResult result;
lock (obj)
{
X509Chain x509Chain = new X509Chain(true);
foreach (X509Certificate2 certificate in additionalCertificates)
{
x509Chain.ChainPolicy.ExtraStore.Add(certificate);
}
x509Chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
x509Chain.ChainPolicy.VerificationFlags = (X509VerificationFlags.AllowUnknownCertificateAuthority | X509VerificationFlags.IgnoreWrongUsage);
if (!checkCertificateTimeValidity)
{
x509Chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.IgnoreNotTimeValid;
}
UserLicenseService.CertificateValidationResult certificateValidationResult = UserLicenseService.CertificateValidationResult.OK;
if (!x509Chain.Build(primaryCert))
{
certificateValidationResult = UserLicenseService.FetchResultFromStatus(x509Chain.ChainStatus);
if (certificateValidationResult != UserLicenseService.CertificateValidationResult.EXPIRED)
{
return UserLicenseService.CertificateValidationResult.FAILED;
}
}
if (x509Chain.ChainElements.Count != x509Chain.ChainPolicy.ExtraStore.Count + 1)
{
result = UserLicenseService.CertificateValidationResult.FAILED;
}
else
{
for (int j = 1; j < x509Chain.ChainElements.Count; j++)
{
if (x509Chain.ChainElements[j].Certificate.Thumbprint != x509Chain.ChainPolicy.ExtraStore[j - 1].Thumbprint)
{
return UserLicenseService.CertificateValidationResult.FAILED;
}
}
for (int k = 0; k < x509Chain.ChainElements.Count; k++)
{
X509ChainElement chainElement = x509Chain.ChainElements[k];
string issuerName = chainElement.Certificate.IssuerName.Name ?? "";
if (issuerName.StartsWith("CN=", StringComparison.OrdinalIgnoreCase))
{
issuerName = issuerName.Substring(3);
}
if (crl.RevokedCertificates.Any((CertificateRevocationList.RevokedCertificate rc) => rc.SerialNumber.Equals(chainElement.Certificate.SerialNumber) && rc.Issuer.Equals(issuerName)))
{
return UserLicenseService.CertificateValidationResult.FAILED;
}
}
result = certificateValidationResult;
}
}
return result;
}

破解之法就是要让这个方法返回UserLicenseService.CertificateValidationResult.OK


一开始我用 dnSpy 直接编辑这个方法,但是修改之后 VS 就无法加载插件了,可能是 .NET 程序集强签名的关键,于是我搜索到了这篇文章:ReSharper破解过程
这篇文章已经很老了,也能看出来这么些年验证过程基本是没变化的。不过这作者解决强签名问题的办法实在太不优雅了。

制作补丁

以前在某论坛中下载过一个用 VS 插件的破解方式挺开脑洞的,于是我也尝试了下,依然是可行的,思路就是自己开发一个 VS 扩展,用第三方库 hook 去 patch 对应的代码,这样可以做到不修改JetBrains.Platform.Shell.dll文件,比较优雅。


如何制作 VS 扩展就不展开说了,网上有教程,问问 AI 十分钟就搞定了,在项目中引入Lib.Harmony库,它是用来 hook 的。
核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using HarmonyLib;
using System.Diagnostics;

namespace ReSharperCrack;

[HarmonyPatch]
internal static class Patches
{
[HarmonyPatch("JetBrains.Application.License2.NewLicenses.UserLicenseService", "VerifyCertificate")]
[HarmonyPostfix]
internal static void VerifyCertificate_Postfix(ref object __result)
{
if ((int)__result != 0)
{
__result = 0;
}
}
}

这个补丁会在VerifyCertificate返回后执行,如果返回值不是 0(UserLicenseService.CertificateValidationResult.OK)就将返回值改为UserLicenseService.CertificateValidationResult.OK


证书验证这一关过了,但是它还会向服务器验证一次

禁止联网验证

搜索字符串License server response定位到ClientUtil.ExecuteRequest

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
public static TResponse ExecuteRequest<TResponse>(string baseUrl, AbstractRequest<TResponse> request, IWebProxy proxy, [CanBeNull] BlackList blackList, int timeoutInMilliseconds = 0) where TResponse : AbstractResponse
{
TResponse tresponse;
if (blackList != null && blackList.IsBlacklisted(baseUrl))
{
ClientUtil.Logger.Trace("{0} is blacklisted", baseUrl);
string blacklistedLicenseServerMessage = ClientUtil.BlacklistedLicenseServerMessage;
long salt = 0L;
ResponseCode responseCode = ResponseCode.ERROR;
tresponse = default(TResponse);
return AbstractResponse.Error<TResponse>(blacklistedLicenseServerMessage, salt, responseCode, tresponse);
}
string text = baseUrl + "/rpc/" + request.ActionName + "?";
try
{
foreach (FieldInfo fieldInfo in request.GetType().GetFields())
{
string text2 = AbstractResponse.ConvertFieldName(fieldInfo.Name);
if (!(text2 == "actionName") && !(text2 == "class"))
{
if (!text.EndsWith("?"))
{
text += "&";
}
object value = fieldInfo.GetValue(request) ?? "";
text += string.Format("{0}={1}", text2, UrlUtil.Encode(AbstractResponse.ValueToString(value), Encoding.UTF8));
}
}
if (ClientUtil.Logger.IsTraceEnabled())
{
ClientUtil.Logger.Trace("Request: {0}", text);
ClientUtil.Logger.Trace("Request decoded: {0}", UrlUtil.Decode(text, Encoding.UTF8));
ClientUtil.Logger.Trace("Authorization header: {0}", request.AuthorizationHeader);
}
ValueTuple<string, string> valueTuple = ClientUtil.ExecuteRequest(text, proxy, request.SignatureVerifier, request.AuthorizationHeader, timeoutInMilliseconds);
string item = valueTuple.Item1;
ClientUtil.Logger.Trace("Response: {0}", item);
if (item == null)
{
string message = "No valid response returned by license server";
long salt2 = request.Salt;
ResponseCode responseCode2 = ResponseCode.ERROR;
tresponse = default(TResponse);
tresponse = AbstractResponse.Error<TResponse>(message, salt2, responseCode2, tresponse);
}
else if (item == ClientUtil.BlacklistedLicenseServerMessage)
{
tresponse = AbstractResponse.Error<TResponse>(ClientUtil.BlacklistedLicenseServerMessage, request.Salt, ResponseCode.ERROR, default(TResponse));
}
else if (item == ClientUtil.FipsEnabledLicenseServerMessage)
{
tresponse = AbstractResponse.Error<TResponse>(ClientUtil.FipsEnabledLicenseServerMessage, request.Salt, ResponseCode.ERROR, default(TResponse));
}
else if (item == ClientUtil.InvalidResponseSignatureMessage)
{
tresponse = AbstractResponse.NetworkError<TResponse>(ClientUtil.InvalidResponseSignatureMessage, request.Salt);
}
else if (item == ClientUtil.ExpiredCertificateLicenseServerMessage)
{
tresponse = AbstractResponse.NetworkError<TResponse>(ClientUtil.ExpiredCertificateLicenseServerMessage, request.Salt);
}
else
{
TResponse tresponse2 = AbstractResponse.CreateResponse<TResponse>(item);
if (tresponse2.ResponseCode == ResponseCode.SERVER_INTERNAL_ERROR)
{
tresponse = AbstractResponse.NetworkError<TResponse>("License server internal error: " + tresponse2.Message, request.Salt);
}
else if (tresponse2.ResponseCode != ResponseCode.OK)
{
tresponse = AbstractResponse.Error<TResponse>("License server response: " + tresponse2.Message, request.Salt, ResponseCode.ERROR, tresponse2);
}
else if (tresponse2.Salt != request.Salt)
{
tresponse = AbstractResponse.Error<TResponse>("Unmatched response returned by license server", request.Salt, ResponseCode.ERROR, default(TResponse));
}
else
{
IResponseWithFullTicket responseWithFullTicket = tresponse2 as IResponseWithFullTicket;
if (responseWithFullTicket != null)
{
responseWithFullTicket.TicketFull = new ValueTuple<string, string>(item, valueTuple.Item2);
}
tresponse = tresponse2;
}
}
}
catch (WebException ex)
{
ClientUtil.Logger.LogExceptionSilently(ex);
tresponse = ClientUtil.ToResponse<TResponse>(ex, "Connect to license server");
}
catch (SocketException ex2)
{
ClientUtil.Logger.LogExceptionSilently(ex2);
tresponse = ClientUtil.ToResponse<TResponse>(ex2, "Connect to license server");
}
catch (Exception ex3)
{
ClientUtil.Logger.LogExceptionSilently(ex3);
tresponse = ClientUtil.ToResponse<TResponse>(ex3, "Connect to license server");
}
return tresponse;
}

分析调用栈,发现在UserLicenseViewSubmodel.AddLicense方法中存在调用UserLicenseViewSubmodel.ValidateLicenseKey的行为:

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
public bool AddLicense(UserLicense userLicense, bool doCheckSynchronousely, bool validateLicenseKey = false)
{
this.myLogger.Trace("[A] {0}", userLicense);
object obj = this.myLock;
bool result;
lock (obj)
{
if (this.myAllLicenses.ContainsKey(userLicense) || this.myAllLicenses.Any((KeyValuePair<UserLicense, UserLicenseViewSubmodel.LicenseState> pair) => string.IsNullOrEmpty(pair.Key.UserName) && pair.Key.LicenseKey == userLicense.LicenseKey))
{
result = false;
}
else
{
this.RemoveLicenseFromSuspended(userLicense);
this.myAllLicenses.Add(userLicense, UserLicenseViewSubmodel.LicenseState.CHECKING);
if (doCheckSynchronousely)
{
this.CheckLicense(userLicense);
if (validateLicenseKey)
{
this.ValidateLicenseKey(userLicense);
}
}
else
{
this.myTaskHost.Queue(this.myLifetime, delegate()
{
this.CheckLicense(userLicense);
if (validateLicenseKey)
{
this.ValidateLicenseKey(userLicense);
}
}, TaskPriority.Normal, "Src\\License2\\UserLicenses\\UserLicenseViewSubmodel.cs", "AddLicense");
}
result = true;
}
}
return result;
}

AddLicense的第3个参数正是控制是否联网验证的关键!
继续打补丁:

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
using HarmonyLib;
using System.Diagnostics;

namespace ReSharperCrack;

[HarmonyPatch]
internal static class Patches
{
[HarmonyPatch("JetBrains.Application.License2.UserLicenses.UserLicenseViewSubmodel", "AddLicense")]
[HarmonyPrefix]
internal static bool AddLicense_Prefix(ref bool validateLicenseKey)
{
validateLicenseKey = false;
return true;
}

[HarmonyPatch("JetBrains.Application.License2.NewLicenses.UserLicenseService", "VerifyCertificate")]
[HarmonyPostfix]
internal static void VerifyCertificate_Postfix(ref object __result)
{
if ((int)__result != 0)
{
__result = 0;
}
}
}

接着只要输入一个合法的激活码就行了(参考之前文章中的 KeyGen)


授权信息:

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
{
"licenseId": "ZCB571FZHV",
"licenseeName": "jack",
"assigneeName": "",
"assigneeEmail": "",
"licenseRestriction": "",
"checkConcurrentUse": false,
"products": [
{
"code": "RS0",
"fallbackDate": "2099-12-31",
"paidUpTo": "2099-12-31",
"extended": true
},
{
"code": "RC",
"fallbackDate": "2099-12-31",
"paidUpTo": "2099-12-31",
"extended": true
},
{
"code": "DC",
"fallbackDate": "2099-12-31",
"paidUpTo": "2099-12-31",
"extended": true
},
{
"code": "DM",
"fallbackDate": "2099-12-31",
"paidUpTo": "2099-12-31",
"extended": true
},
{
"code": "DPN",
"fallbackDate": "2099-12-31",
"paidUpTo": "2099-12-31",
"extended": true
}
],
"metadata": "0120220701PSAN000005",
"hash": "TRIAL:-594988122",
"gracePeriodDays": 7,
"autoProlongated": false,
"isAutoProlongated": false
}

对应的激活码:

1
ZCB571FZHV-eyJsaWNlbnNlSWQiOiAiWkNCNTcxRlpIViIsICJsaWNlbnNlZU5hbWUiOiAiamFjayIsICJhc3NpZ25lZU5hbWUiOiAiIiwgImFzc2lnbmVlRW1haWwiOiAiIiwgImxpY2Vuc2VSZXN0cmljdGlvbiI6ICIiLCAiY2hlY2tDb25jdXJyZW50VXNlIjogZmFsc2UsICJwcm9kdWN0cyI6IFt7ImNvZGUiOiAiUlMwIiwgImZhbGxiYWNrRGF0ZSI6ICIyMDk5LTEyLTMxIiwgInBhaWRVcFRvIjogIjIwOTktMTItMzEiLCAiZXh0ZW5kZWQiOiB0cnVlfSwgeyJjb2RlIjogIlJDIiwgImZhbGxiYWNrRGF0ZSI6ICIyMDk5LTEyLTMxIiwgInBhaWRVcFRvIjogIjIwOTktMTItMzEiLCAiZXh0ZW5kZWQiOiB0cnVlfSwgeyJjb2RlIjogIkRDIiwgImZhbGxiYWNrRGF0ZSI6ICIyMDk5LTEyLTMxIiwgInBhaWRVcFRvIjogIjIwOTktMTItMzEiLCAiZXh0ZW5kZWQiOiB0cnVlfSwgeyJjb2RlIjogIkRNIiwgImZhbGxiYWNrRGF0ZSI6ICIyMDk5LTEyLTMxIiwgInBhaWRVcFRvIjogIjIwOTktMTItMzEiLCAiZXh0ZW5kZWQiOiB0cnVlfSwgeyJjb2RlIjogIkRQTiIsICJmYWxsYmFja0RhdGUiOiAiMjA5OS0xMi0zMSIsICJwYWlkVXBUbyI6ICIyMDk5LTEyLTMxIiwgImV4dGVuZGVkIjogdHJ1ZX1dLCAibWV0YWRhdGEiOiAiMDEyMDIyMDcwMVBTQU4wMDAwMDUiLCAiaGFzaCI6ICJUUklBTDotNTk0OTg4MTIyIiwgImdyYWNlUGVyaW9kRGF5cyI6IDcsICJhdXRvUHJvbG9uZ2F0ZWQiOiBmYWxzZSwgImlzQXV0b1Byb2xvbmdhdGVkIjogZmFsc2V9-KI/za46T08By0xh6MGaX5L56KPRS9vs8Qe2Pevsb+KdC143fR1m1RvmYjZzoybCC/7DLcMtm3oaC7H5J3q2YyZq7ew4rq+kxQ9atd3LCoJV7SoTWO28y8WPOFdTImno6LVZ4GhDQuKLkpNSKgLEsvCZq38hw26BKF2Chb4NxJWU/zTv4GZ/FYOrMJGCoSepn7hUZSPSzp/zwseHJoV7PXgpZHVfU5fD2n9r5hPJK7VvI2GDhBtyd/xOKz0b8UPFfKfZP+SFNjeK0yrTseY7Z2BQUsjAg42vsIalCv/1NzxnpYQ4pWq311g7Ys6Azg7Prib+vMUVn7hjGGjhVrCfz2WLl5XEa/4WHdSNoXubWUk6ojv7lCVYNAQaCVMfqxuBhm3l5xWL7UcCn8p7U03ASW7ZqzHisg8wuwNs6kbxZrkUPC3Pf4nFIrs3CqpJ/uHG1DzvsYzx1ja6bIMXJIg4W7SfoeeGWYBrlJyprEaRffWHug9+LA5SV5NitX17pQMvEuSZxPPMjtomEhLrQoy3zmHSyC1edxPKXKISsaSIT0ztoYFF0eAOVHyho/UUDVyDmvh7pmns7yvjZmvWTvMCyuGphDxhJlDUeDKyBOi8R+GH2eSKMuP/uMIwH4bSZoZBpQwl1+sI9cujBm8yXiLuogZUZs3BhO+uibQzf7xNPbRk=-MIIEtTCCAp2gAwIBAgIUG0GuXpVc8FGKPuiRv++SnXTSgkgwDQYJKoZIhvcNAQELBQAwGDEWMBQGA1UEAwwNSmV0UHJvZmlsZSBDQTAeFw0yNTEwMDIyMzQzMzZaFw0zNTEwMDEyMzQzMzZaMBExDzANBgNVBAMMBmFxdGF0YTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJKwfFtvcgxQpGC2fyLObBpPY/mz203moojM/qPlU7GhcdP+sUB/hRnoJ5JYgiUh8JH2OCRvu1ITlek0pGsAhhcppefFxTSZAXrjAD9AArlQI45jLiJE6FzzarVCxLURXbveCLFt2xH4as4xj/dtIIQypzOFj6J4S11giEu3P4UZ7428eLX7junbO9QpAZbSWK6Uws2GrA6AKZsegRPqUE2rCQ8AtMan1IHUpGyxvquTVqwMSUSvEaKbfdAF/qVQ8NQ7oWVj6D8mcnPpZvH+ckyCd3n8020yQU2Xa4ikG3C19leC0UbbaUg+WEIVoTx+fwbr6IdC3NTmZQr9qBthJHuYZ80PNLQT0SCwj4v/IQdpfGhxhVf1FeCvD1trISVZyo1aazuz5zJQUFhw7aOAyiKVTvRDwRb1FJqxuSSZ4+VpOyIF1J2zT5HEu7seHM+a6thGAbb5T3AlgprwX3fHN86IRajsJsobAWsu/1IkvuXc+hSmwAdhvw781bKxTBEHN2k1ScDV5kw/on0YRfbBEaz4262h3IQCnOJZsgUUCNkgB0vUKL54epdLo5i5rHJXO+Q4t/5UZ4/yFMinpI8x95Iv1jIsajq4stbRZzJ1bkVyCYVaamRqQmY9nYow76XiRb4dvwOmgbeEBcppMHCb3z/cWv6MjduuLBwuMcDI5Qu9AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAEdbzeOyZSuaigPxBHWsFGx8WkrqUzqg7FIiHqtC8WZiZJZZmTJ9WUk+ey10YkEfUa/hO3qEKQeEYeW5BVaygtqNQjVjq0++wTCOeyNnmSG6ITxx5VnHqDfMyYRBSSmAlEWWQdDC4cjQuWHCe2pvdzIEZJtvSob7BRTTkDbY7tNNUD8/IsXsEFrmXMNvIOMWKx800rFy1gpJJsTFL+iQDGuWaL94jiFp5ei8+If7sqPditQl6sds7Xm3ksgm2Qs/xJhzJgzwjzkbTNmCszfJHaSjEe31hjfz5lOb7XzUFx8YsSLkRwXesS5o1a3fHqSHszYldAie4lIILm/fZQ3p1yoDIFQgWa12/SLNxE8tCj/hx8N+gJzpEPaQMT89bFuAILf1Py2GC9w92ZTLXnymOKHBZCQkMFzUm2CIhcx/+RlK5Up8EdFvfWBGfDtJr1UjWiF/MDJibZJ/BO1rVdO352p3gcJVgzSwQI6cPWJUxwikYhLNAsVac4bmE9Hm5qR8YWoEHmt0jHIIZjoFiTRaB6XLko53FLV+Ns4EogRXvPk7D0ZLfcDPaXULc0MnGAQS46gvWi176dIs00Lbg1TJIEVtwAERS5r3FyZeDq3xEau8ZebL7xdTWaVfyT6DtaWAMIC3OSXJaSVaQWuDkQ98XsqESvR02+9N98vghpSVVEnR

其他几个 .NET 软件

上面只是将 VS 插件解决掉了,独立的dotCoverdotTracedotMemory软件依然是未授权的,另外一个dotPeek因为官方将其免费了所以不需要授权。

因为这几款软件不是 VS 插件,且又是 .NET 开发的,所以不能用ja-netfilter那套去破解。
全盘搜索JetBrains.Platform.Shell.dll文件,发现其实每个软件下都有一份JetBrains.Platform.Shell.dll,所以我们需要想办法在软件启动时加载我们的补丁。

查看所有JetBrains.Platform.Shell.dll文件,都是同样的版本,所以就确定了破解方法和前面的插件一样,不同的是这几个软件是独立运行的,所以要么修改JetBrains.Platform.Shell.dll,要么通过某种方法劫持打补丁。
第一反应当然是直接用 dnSpy 编辑,但是这样会破坏 DLL 的签名,导致 CLR 拒绝加载,于是我找到了这篇文章《ReSharper破解过程》
文章中的解决方法是编辑 StrongNameSignatureVerification 的返回值,测试发现行不通了。
这篇文章发布于 2016 年,.NET 也更新了 N 个版本了,至少确定在新的 .NET 中已经不用 StrongNameSignatureVerification 来验证了。
最后在签名这个事情上折腾了很久,最终还是放弃了,编辑文件弊端就是要修改很多文件,兼容性不好不说,升级软件后又要重新编辑一次。


于是我决定从主程序下手,也就是启动的那个 exe 文件,尝试用version.dllwinmm.dlluxtheme.dll劫持,它都不加载。
最终不得不修改 exe,实行 IAT 劫持:在导入表中增加对自己的 dll 文件的依赖,再通过这个原生 dll 加载自己的 .NET 程序集,再在 .NET 中对JetBrains.Platform.Shell.dll进行 hook。
具体代码可以参考《ring3注入学习(1)导入表注入》,代码有点小问题,不过能用。


如果连 exe 都不想动,那么可以试试运行时 Patch,这里有几篇文章可以参考:

原理就是写一个启动器,当作是调试器去启动程序。