申明:本帖只用于学习(其实是我自己记录下免得以后忘了),不发成品,大家也别问我要.
用到的工具de4dot,dnspy,vs2019
前几天老板变态的需求,非要把上万个工程的Excel文件转成html让其他人访问,目的是不想让人下载,随便吧,老板最大.
但同一进程打开100个以上文件就报错,于是就出现了上一篇文章.(其实写个进程重新开就行了,不过谁让.net的程序破解简单呢…)
然后逛论坛的时候发现了这个https://www.52pojie.cn/forum.php?mod=viewthread&tid=1116770
然后就觉得可以搞搞,锻炼锻炼智商.
根据该楼主的思路,下了个2012年版本的aspose.cell.开始研究.
先de4net去花,然后dnspy跑起来.(楼主说跑不起来要报错…报错你跳过就是了嘛,反正就只研究注册部分)
以下是部分程序片段
internal void method_1(Stream stream_0)
{
if (stream_0 == null)
{
throw new ArgumentNullException("stream");
}
Class827.smethod_3(0);
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(stream_0);
this.method_2(xmlDocument);
Hashtable hashtable = Class828.hashtable_0;
if (hashtable == null)
{
//hashtable = new Hashtable();
hashtable = Class828.smethod_10();
Class828.hashtable_0 = hashtable;
}
if (hashtable.ContainsKey(this.string_1))
{
throw new InvalidOperationException("This license is disabled, please contact Aspose to obtain a new license.");
}
if (Class827.smethod_2() > 0)
{
return;
}
bool flag = false;
string[] array = this.string_0;
int i = 0;
while (i < array.Length)
{
string text = array[i];
if (!text.Equals("Aspose.Total") && !text.Equals("Aspose.Total for .NET") && !text.Equals("Aspose.Cells for .NET") && !text.Equals("Aspose.Cells Product Family") && !text.Equals("Aspose.Total Product Family") && !text.Equals("Aspose.Cells"))
{
i++;
}
else
{
flag = true;
if (!flag)
{
throw new InvalidOperationException("The license is not valid for this product.");
}
DateTime t = DateTime.ParseExact("2012.01.16", "yyyy.MM.dd", CultureInfo.InvariantCulture);
if (t > this.dateTime_0)
{
throw new InvalidOperationException(string.Format("The subscription included in this license allows free upgrades until {0}, but this version of the product was released on {1}. Please renew the subscription or use a previous version of the product.", this.dateTime_0.ToString("dd MMM yyyy", CultureInfo.InvariantCulture), t.ToString("dd MMM yyyy", CultureInfo.InvariantCulture)));
}
if (DateTime.Now > this.dateTime_1)
{
throw new InvalidOperationException("The license has expired.");
}
this.enum127_0 = Enum127.const_1;
Class828.class828_0 = this;
return;
}
}
if (!flag)
{
throw new InvalidOperationException("The license is not valid for this product.");
}
DateTime t1 = DateTime.ParseExact("2012.01.16", "yyyy.MM.dd", CultureInfo.InvariantCulture);
if (t1 > this.dateTime_0)
{
throw new InvalidOperationException(string.Format("The subscription included in this license allows free upgrades until {0}, but this version of the product was released on {1}. Please renew the subscription or use a previous version of the product.", this.dateTime_0.ToString("dd MMM yyyy", CultureInfo.InvariantCulture), t1.ToString("dd MMM yyyy", CultureInfo.InvariantCulture)));
}
if (DateTime.Now > this.dateTime_1)
{
throw new InvalidOperationException("The license has expired.");
}
this.enum127_0 = Enum127.const_1;
Class828.class828_0 = this;
return;
}
先读xml,然后一步一步判断下来,哪里挡住改哪里,没难度.
然后我自信满满的跑去看它的注册算法,为了方便调试和注释,我还把它关于注册的程序挪到了vs里跑.
private static void smethod_2(XmlNode xmlNode_0, XmlNode xmlNode_1)
{
string s = (xmlNode_0 != null) ? xmlNode_0.OuterXml : ""; //data内容
byte[] bytes = Encoding.Unicode.GetBytes(s); //data字节
string s2 = (xmlNode_1 != null) ? xmlNode_1.InnerText : "";
byte[] byte_ = Convert.FromBase64String(s2); //Signature
string s3 = "0nRuwNEddXwLfXB7pw66G71MS93gW8mNzJ7vuh3Sf4VAEOBfpxtHLCotymv1PoeukxYe31K441Ivq0Pkvx1yZZG4O1KCv3Omdbs7uqzUB4xXHlOub4VsTODzDJ5MWHqlRCB1HHcGjlyT2sVGiovLt0Grvqw5+QXBuinoBY0suX0=";
string s4 = "AQAB";
byte[] byte_2 = Convert.FromBase64String(s3);
byte[] byte_3 = Convert.FromBase64String(s4);
Class823 @class = new Class823(byte_2, byte_3); //公钥
@class.method_0(bytes, byte_);
}
然后就看见了rsa的指数与那什么值,算了,暴力是唯一出路…
好了升级版本… 试到18.x,这个时期的代码依旧很友好,和12年的差别不大.
到了19.x瞬间不一样了…
以下是19.10的分析过程.
一步一步来走吧,哪里报错停哪里.然后找到这里
// Token: 0x06004D31 RID: 19761 RVA: 0x002F5DB4 File Offset: 0x002F3FB4
private object u0002(object[] u0002, Type[] u0003, Type[] u0005, object[] u0008)
{
if (u0002 == null)
{
u0002 = global::u0008u2002
断点下这里,f5 两次后报错,那么f5一次跟进.然后就到这里
private void u0005u2000()
{
long num = this.u0008u2001.u0002().u0002u2005u2005u200Bu000E();
while (!this.u0006u2000)
{
if (this.u000F != null)
{
this.u0008u2001.u0002().u0002u2005u2005u200Bu0002((long)((ulong)this.u000F.Value));
this.u000F = null;
}
this.u0002();
if (this.u0008u2001.u0002().u0002u2005u2005u200Bu000F() >= num && this.u000F == null)
{
break;
}
}
}
这里会循环N次,虚拟机嘛,一行代码都不止循环一次…this.u0002();这里跟进去看看
private void u000F()
{
long u000F_u = this.u0008u2001.u0002().u0002u2005u2005u200Bu000F();
int key = this.u0008u2001.u0003u2000();
u0005u2008.u000F u000F;
if (!this.u0006u2002.TryGetValue(key, out u000F))
{
throw new InvalidOperationException(u0006u200Au2008u2003.u0002(1761139869));
}
this.u000Fu2002 = u000F_u;
u000F.u0003(this.u0002(this.u0008u2001, u000F.u0002.u0003()));
}
到这里, out u000F得到对应的程序块,所以每次执行的内容都不一样.
然后咋办呢,我用的笨办法,断点在上一个程序块,按F5数次数,按大概70多次报错.
想想早起版本的判断流程,先读值,然后进行判断,那么那个数组值总存在在哪,于是去找了找局部变量值,发现了这个
- this {u0005u2008} u0005u2008
+ u0002u2000 {object[0x00000001]} object[]
+ u0002u2001 Count = 0x00000000 System.Collections.Generic.Dictionary
+ u0002u2002 {u0006u2006} u0006u2006
u0002u2003 null object
- u0003 {u0006[0x00000008]} u0006[]
- [0] {u000Eu2004} u0006 {u000Eu2004}
+ u0002 (u0006) null System.Type
u0002 "Aspose.Total for .NET" string
- [1] {u0006u2002} u0006 {u0006u2002}
+ u0002 (u0006) null System.Type
+ u0002 Count = 0x00000554 object {System.Collections.Hashtable}
- [2] {u000Fu2000} u0006 {u000Fu2000}
+ u0002 (u0006) null System.Type
u0002 0x00000000 int
- [3] {u000F} u0006 {u000F}
+ u0002 (u0006) null System.Type
u0002 true bool
- [4] {u000E} u0006 {u000E}
+ u0002 (u0006) null System.Type
+ u0002 {string[0x00000001]} System.Array {string[]}
- [5] {u000Fu2004} u0006 {u000Fu2004}
+ u0002 (u0006) null System.Type
+ u0002 {2019/1/15 0:00:00} object {System.DateTime}
- [6] {u000Fu2004} u0006 {u000Fu2004}
+ u0002 (u0006) null System.Type
+ u0002 {0001/1/1 0:00:00} object {System.DateTime}
- [7] {u000Fu2004} u0006 {u000Fu2004}
+ u0002 (u0006) null System.Type
+ u0002 {0001/1/1 0:00:00} object {System.DateTime}
在u0003中找到了熟悉的的东西,这就很nice了.
下面还有2个时间没出来,不用想,一个是允许升级时间,一个是注册码过期时间.
关掉程序重新开,断点老位置,F5疯狂点当u0003[5]的值出来时开始计数,然后点了12下,u0003[6]的值出来了.
重新开,u0003[5]值出来后点11下,然后跟进
// Token: 0x06004D99 RID: 19865 RVA: 0x002F86D0 File Offset: 0x002F68D0
private void u0002(int u0002)
{
global::u0006 u = this.u000Eu2000();
if (u is u000Fu2002)
{
this.u0003[u0002] = u;
return;
}
this.u0003[u0002].u0006u2005u200Bu0002(u);
}
然后就到了这里,观察下U的值
- u {u000Fu2004} u0006 {u000Fu2004}
+ u0002 (u0006) null System.Type
+ u0002 {2015/12/31 0:00:00} object {System.DateTime}
满意.把这个值改2099年,程序跑起来了.再观察下生成的xlsx文件及图片,没水印,这应该就没问题了吧.
再试试,断点下在这个程序块,不用那么多下F5了,观察下每次U的值,原来读出来的值都在这里处理一边.
然后看看时间值的类型u000Fu2004,再点进去看,然后就觉得自己特别傻,按F5好玩么…在这个时间类型的构造函数下段就行了嘛…
public u000Fu2004(object u0002)
{
if (u0002 != null && !(u0002 is ValueType))
{
throw new ArgumentException();
}
this.u0002 = u0002;
}
然后就是改程序了…
public u000Fu2004(object u0002)
{
if (u0002 != null && !(u0002 is ValueType))
{
throw new ArgumentException();
}
if(u0002 != null)
{
bool flag = ((DateTime)u0002).Year == 2015;
if (flag)
{
object now = DateTime.Now;
this.u0002=now;
return;
}
}
this.u0002 = u0002;
}
IL指令真不熟…加那么几行研究了几个小时… 大概就是vs里照猫画虎写个然后把代码挪这边来.
保存,试试,没问题了.
接着升级版本,到19.2.然后发现,上个版本的方法不行了.郁闷了好久,回头去分析下以前的验证过程
if (!flag)
{
throw new InvalidOperationException("The license is not valid for this product.");
}
DateTime t = DateTime.ParseExact("2012.01.16", "yyyy.MM.dd", CultureInfo.InvariantCulture);
if (t > this.dateTime_0)
{
throw new InvalidOperationException(string.Format("The subscription included in this license allows free upgrades until {0}, but this version of the product was released on {1}. Please renew the subscription or use a previous version of the product.", this.dateTime_0.ToString("dd MMM yyyy", CultureInfo.InvariantCulture), t.ToString("dd MMM yyyy", CultureInfo.InvariantCulture)));
}
if (DateTime.Now > this.dateTime_1)
{
throw new InvalidOperationException("The license has expired.");
}
this.enum127_0 = Enum127.const_1;
Class828.class828_0 = this;
return;
最后判断时要比较时间啊,那不在它程序里绕就行了,我到system.datetime的比较里下断点就好了嘛.发现自己好聪明哈哈.然而,人家根本不比较…
于是回退到19.1,再在datetime里下断,这次比较了.然后一步一步跟,到了这里 System.Reflection.RuntimeMethodInfo里的系统函数
// Token: 0x06004654 RID: 18004 RVA: 0x000FFBAC File Offset: 0x000FDDAC
[SecurityCritical]
[DebuggerStepThrough]
[DebuggerHidden]
private object UnsafeInvokeInternal(object obj, object[] parameters, object[] arguments)
{
if (arguments == null || arguments.Length == 0)
{
return RuntimeMethodHandle.InvokeMethod(obj, null, this.Signature, false);
}
object result = RuntimeMethodHandle.InvokeMethod(obj, arguments, this.Signature, false);
for (int i = 0; i < arguments.Length; i++)
{
parameters[i] = arguments[i];
}
return result;
}
然后看看result以及参数值,明白了.这里下断看看.
升级到19.2,找到UnsafeInvokeInternal,下断
然后就看见了可爱的比较函数,原来转INT32搞了.比较结果是1. 去看看函数的原型,返回值分别可以是1,0,-1
把result的值改改,然后跑起来.程序不报错了.但是打开生成的excel文件,格式错误…这就不好玩了啊…
想了想,应该是它判断了多次.而且不至于傻到只针对这个值判断,那就应该打开了多次文本来比较.
再看一眼虚拟机前的版本,找到这句
XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(stream_0); this.method_2(xmlDocument);
跑system.xml里去下断,这家伙果然开了2次.第二次打开文件后跟进瞧瞧,跟到这里
internal void u0002()
{
if (u000Fu2008u2002u2000.u0002 == null)
{
return;
}
u0003u2002u2002.u000F = null;
Stream u = new MemoryStream(u000Fu2008u2002u2000.u0002);
u0006u2005u2002u2002.u0002(0);
this.u0002(u);
if (this.u0008 == null)
{
return;
}
if (u0006u2005u2002u2002.u0005() > 0)
{
return;
}
Hashtable hashtable = u0003u2002u2002.u0002u2000;
if (hashtable == null)
{
hashtable = u0003u2002u2002.u000E();
u0003u2002u2002.u0002u2000 = hashtable;
}
if (hashtable == null || hashtable.ContainsKey(this.u0005))
{
return;
}
if (u0006u2005u2002u2002.u0005() > 0)
{
return;
}
bool flag = false;
foreach (string text in this.u0002)
{
if (text.Equals(u0005u2001u2009u2003.u0002(-1118133675)) || text.Equals(u0005u2001u2009u2003.u0002(-1118133664)) || text.Equals(u0005u2001u2009u2003.u0002(-1118133756)) || text.Equals(u0005u2001u2009u2003.u0002(-1118133720)) || text.Equals(u0005u2001u2009u2003.u0002(-1118133558)) || text.Equals(u0005u2001u2009u2003.u0002(-1118133532)))
{
flag = true;
break;
}
}
if (!flag)
{
return;
}
string[] array = u0005u2001u2009u2003.u0002(-1118133513).Split(new char[]
{
'.'
});
string text2 = array[0] + array[1] + array[2];
if (string.Compare(text2, this.u0008) > 0)
{
return;
}
if (this.u0006 != null)
{
string strA = DateTime.UtcNow.ToLocalTime().ToString(u0005u2001u2009u2003.u0002(-1118133632));
if (string.Compare(strA, text2) < 0 || string.Compare(strA, this.u0006) > 0)
{
return;
}
}
this.u000E = (u0005u2002u2002)1;
u0003u2002u2002.u000F = this;
}
这怎么看着那么熟悉啊.比较下我提取出来的虚拟机以前的代码(参考最上面的代码块)
呵呵…换汤不换药.比较函数改到了这里
if (string.Compare(text2, this.u0008) > 0)
{
return;
}
比较字符串大小娘的 .net还有这个操作? 学习了. 继续改.然后跑起来… 这下总算没问题了.
改IL指令的时候才发现上面我搞错的一个地方,2次比较都是system.string.compare…又蠢一回.
在这里下断能准确的找出2次比较.剩下如何改有兴趣的自己折腾吧
升级到最新版本,这个方法依旧奏效,收工



