【重磅】Aspose全家桶永久去限制全攻略(支持.NET 4x/6/8)
Aspose.Total全家桶包含我们日常经常要用到的Aspose.Cells/Aspose.Words/Aspose.Slides/Aspose.PDF/...等一系列库文件。我们通过Visual Studio 2022中NuGet安装最新版到本地试用时,基本都有水印限制。
今天我们就用.Net HOOK技术来去掉所有限制,体验全功能的强大魅力。
首先,讲解下实现思路。先从网上去找一个过期的License(我找到一个20200827过期的License),调用SetLicense方法来设置这个License。
这个时候库会去判断这个License是否过期,然后我们就可以通过Hook技术拦截它的这个判断,让它永久返回不过期,从而达到预期结果。
说起来简单,实现起来真的太难了。目前有很多开源库,都实现了.net4.8及以下版本Framework的Hook技术,比如Harmony/HarmonyX等等。
但是.net6/7/8却一个都没有,在这方面研究的人太少,也没什么成果出来。当然也可能是我没发现,有知道的评论区告诉我。
没办法,只有自己去摸索了。在加班加点辛苦了快两年,终于让我整理出一个支持所有.net 4x/6/8的Hook技术库,我把它命名为Crane MethodHook。
这个Hook库的实现原理太复杂,有时间我专门开贴详细介绍。今天主要介绍一下使用它来破解Aspose全家桶。
我们先建个控制台应用(框架选择.Net 4x/6/8都是可以的),使用NuGet安装一下这个Crane.MethodHook库,最新版是我今天刚发布的V1.0.6版。
然后我们设置一下License,不同的Aspose库设置方法不一样,但是基本都是new Aspose.<product>.License().SetLicense()方法。
设置License之前我们还必须进行方法Hook绑定,注意顺序先hook再设置License。
其中有三个重要的方法:MethodBase.Invoke(),string.Compare,XmlElement.get_InnerText
我们分别对这三个方法进行Hook,先建立这三个方法的新版本:
public static object NewMethodInvoke(MethodBase method, object obj, object[] parameters)
{
if (Assembly.GetCallingAssembly() != null && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && method.Name == "ParseExact" && parameters.Length > 0 && parameters.ToString().Contains("0827"))
{
var ret = DateTime.ParseExact(DATE_CHANGED_TO, "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture);
ShowLog(method, ret, obj, parameters, true);
return ret;
}
else if (Assembly.GetCallingAssembly() != null && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && method.Name == "ParseExact" && parameters.Length > 0 && System.Text.RegularExpressions.Regex.Match(parameters.ToString(), @"^\d{4}\.\d{2}\.\d{2}$").Success)
{
var ret = DateTime.ParseExact("20200501", "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture);
ShowLog(method, ret, obj, parameters, true);
return ret;
}
else if (Assembly.GetCallingAssembly() != null && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && method.DeclaringType != null && method.DeclaringType.Name == "String" && method.Name == "Compare")
{
if (parameters.Length == 2)
{
if (parameters.ToString() == "20200827")
{
var ret = 1;
ShowLog(method, ret, obj, parameters, true);
return ret;
}
else if (parameters.ToString() == "20200827")
{
var ret = -1;
ShowLog(method, ret, obj, parameters, true);
return ret;
}
}
}
else if (Assembly.GetCallingAssembly() != null && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && method.Name == "op_GreaterThan" && parameters.Length == 2 && parameters is DateTime && ((DateTime)parameters).ToString("MMdd") == "0827")
{
ShowLog(method, false, obj, parameters, true);
return false;
}
else if (Assembly.GetCallingAssembly() != null && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && method.Name == "Split" && System.Text.RegularExpressions.Regex.Match(obj.ToString(), @"^\d{4}\.\d{2}\.\d{2}$").Success
&& obj != null && obj.ToString().Substring(0, 4) == DateTime.Now.Year.ToString())
{
var ret = new string[] { "2019", "08", "27" };
ShowLog(method, ret, obj, parameters, true);
return ret;
}
var hook = MethodHookManager.Instance.GetHook(System.Reflection.MethodBase.GetCurrentMethod());
var result = hook.InvokeOriginal<object>(method, obj, parameters?.ToArray());
ShowLog(method, result, obj, parameters);
return result;
}
public static int NewCompare(string s1, string s2)
{
if (Assembly.GetCallingAssembly() != null && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && s2 == "20200827")
{
Utils.LogWriteLine($"HOOK SUCCESS: From {Assembly.GetCallingAssembly().GetName().Name} String.Compare({s1},{s2}) return -1;", ConsoleColor.Green);
return -1;
}
else
{
var hook = MethodHookManager.Instance.GetHook(MethodBase.GetCurrentMethod());
var ret = hook.InvokeOriginal<int>(null, s1, s2);
Utils.LogWriteLine($"NOT Aspose Call: From {Assembly.GetCallingAssembly().GetName().Name} String.Compare({s1},{s2}) return {ret};", ConsoleColor.DarkRed);
return ret;
}
}
private static readonly string DATE_CHANGED_TO = (DateTime.Today.Year + 1).ToString() + "0827";
public static string NewInnerText(XmlElement element)
{
if (Assembly.GetCallingAssembly() != null && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.Words") == false && Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.Hook") == false && element.Name == "SubscriptionExpiry")
{
Utils.LogWriteLine($"HOOK SUCCESS: From {Assembly.GetCallingAssembly().GetName().Name} XmlElement.InnerText ({element.Name},{element.InnerXml}) return {DATE_CHANGED_TO};", ConsoleColor.Green);
return DATE_CHANGED_TO;
}
else
{
var hook = MethodHookManager.Instance.GetHook(System.Reflection.MethodBase.GetCurrentMethod());
return hook.InvokeOriginal<string>(element);
}
}
然后执行Hook绑定,这很简单,先调用AddHook方法。
Crane.MethodHook.MethodHookManager.Instance.AddHook(new MethodHook(
typeof(MethodBase).GetMethod("Invoke", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(object), typeof(object[]) }, null),
typeof(NewHookMethods).GetMethod(nameof(NewHookMethods.NewMethodInvoke), BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(MethodBase), typeof(object), typeof(object[]) }, null)
));
Crane.MethodHook.MethodHookManager.Instance.AddHook(new MethodHook(
typeof(string).GetMethod("Compare", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(string) }, null),
typeof(NewHookMethods).GetMethod(nameof(NewHookMethods.NewCompare), BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(string) }, null)
));
注意一下XmlElement.get_InnerText方法在.net4x和.net6/8下的获取稍有不同:
#if NET40
Hook = new MethodHook(
typeof(XmlElement).GetProperty("InnerText", BindingFlags.Public | BindingFlags.Instance).GetGetMethod(true),
typeof(NewHookMethods).GetMethod(nameof(NewHookMethods.NewInnerText), BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(XmlElement) }, null)
),
#else
Hook = new MethodHook(
typeof(XmlElement).GetProperty("InnerText", BindingFlags.Public | BindingFlags.Instance).GetMethod,
typeof(NewHookMethods).GetMethod(nameof(NewHookMethods.NewInnerText), BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(XmlElement) }, null)
),
#endif
然后启用Hook即可。
Crane.MethodHook.MethodHookManager.Instance.StartHook();
那么现在Hook也设置好了,License也设置了,下面就来测试下成果。
顺便提一句,设置License如果明确知道目标应用要用到哪个Aspose产品,可以直接调用对应产品的SetLicense方法。
而我更喜欢用反射遍历的方式去查找当前应用引用了哪些Aspose产品,然后动态去调用其SetLicense方法,并且已经封装到库中,方便调用,也没有依赖。
我们分别测试Aspose.Cells/Aspose.Words/Aspose.Slides/Aspose.PDF这几款常用产品,检验一下是否达成预期。
通过日志,可以清晰的看到每款产品在SetLicense过程中都有哪些关键方法调用,有助于我们把握其中值得推敲和改善的关键点。
通过测试我们看到每款产品都能全功能完美使用,目标达成。
测试发现,不止是Aspose最新版,就是其历史所有老版本,同样的代码都是能支持的。
这就是Method Hook技术的强大魅力,不用修改库文件,就能改变其功能。值得珍藏。
好了,思路、过程、工具、关键点、结果、总结都有了。你不妨一试,有疑问评论区告诉我。我会给你解惑所有。
同时希望大家都能重视.Net Hook技术,把它应用到未来所有的挑战中。
也欢迎大家和我一起交流技术,共同进步。
丶酆麟 发表于 2024-5-11 11:16
我也用碰到这个问题 , 能否发一下你用的许可证 , 我没找到 , 你发的另一篇帖子我还没有权限查看 ...
我发了我用的License,你可以看下下楼的回复。 布鲁斯李 发表于 2024-4-25 14:49
用了大佬的方法试了一下,23.12以前的都能用,新的24.开头的版本就不行了,应该是没有用24年的License。会 ...
我测试了最新的V24.4和24.5都没问题。
用深思或者DNG之类的hook了jit的,是否可以用这种方式hook Aspose是什么软件哦 谢谢分享,大牛啊! CrackLy 发表于 2024-4-2 17:45
用深思或者DNG之类的hook了jit的,是否可以用这种方式hook
应该是可以的 学编程的闹钟 发表于 2024-4-2 18:06
Aspose是什么软件哦
小众软件 壹佰 发表于 2024-4-2 21:26
小众软件
OK,谢谢楼主 学编程的闹钟 发表于 2024-4-2 18:06
Aspose是什么软件哦
好多文档处理类工具都是用这个开发的,简单点就是调用DLL开发,然后套个界面 jyjjf 发表于 2024-4-3 12:41
好多文档处理类工具都是用这个开发的,简单点就是调用DLL开发,然后套个界面 ...
就是xls文档吗 谢谢大佬的分享。