自从开始使用DotNet做网站以来便顺手开发一个自己的技术论坛,倒没有别的目的,只是想检验一下自己对DotNet的掌握程度。整个论坛的帖子作为叶片绑定在Web树控件上,树控件使用的是微软的Microsoft.Web.UI.WebControls组件中的TreeView,由于Microsoft.Web.UI.WebControls的资源是指向IIS主目录的,当服务商未装载Microsoft.Web.UI.WebControls组件时,这棵树便成了一堆干柴,无奈之下,便下载了Microsoft.Web.UI.WebControls的源码啃了一阵,将资源指向了站点的虚拟目录,编译成自己的版本。论坛终于在局域网中建起来了,我给他起名就叫 “好大一棵树论坛”。朋友给我找了个免费空间让我测试,我便用那只56K的老猫灌起水来,运行当然是正常,但速度实在是不理想,闪烁问题虽然可以通过关闭回传来避免,但这是以牺牲论坛的实时性和其他重要功能为代价的。改用宽带再试,效果果然好了许多,但我的论坛便也就被同事戏称为“好大一棵树论坛 DotNet宽带版”了。由于一时找不到轻巧好用的Web树控件,这棵大树也就一直锁在深闺。
前几日在CSDN看到大家在对微软的TreeView大贬一通的同时,极力推崇r.a.d.treeview ,这便又一次勾起了我对那个论坛改换“树种”的想法。到r.a.d.treeview的老家http://www.telerik.com 上看了一下,果然响应速度和视觉效果都十分可心,便顺手下载了一个最新的2.5版。装入本机后,其演示画面如下:
由于是30天的免费试用版,我把计算机的日期向后调整了一年,结果随机的出现了如下过期提示。
由于两天前刚探究过DotNetTextBox1.3的授权方式(也顺便写了一篇破解手记,可惜这里不便发表),便直接用Reflector打开了RadTreeView.DLL,并直奔控件重载的渲染方法Render()。
反汇编得出如下代码:
VB代码 | 对应的IL汇编代码 |
Protected Overrides Sub Render(ByVal output As HtmlTextWriter) Me.licenseMessage = String.Empty If ( Not Me . licenseMessage Is String.Empty) Then output.Write(Me.licenseMessage) Else Me.Page.VerifyRenderingInServerForm(Me) Dim text1 As String = String.Format("", Me.UniqueID, "trigger") Dim objArray1 As Object() = New Object() { String.Concat(Me.UniqueID, "_expanded"), String.Concat(Me.UniqueID, "_expanded"), Me.UniqueID, "trigger" } Dim text2 As String = String.Format("", objArray1) objArray1 = New Object() { String.Concat(Me.UniqueID, "_checked"), String.Concat(Me.UniqueID, "_checked"), Me.UniqueID, "trigger" } Dim text3 As String = String.Format("", objArray1) objArray1 = New Object() { String.Concat(Me.UniqueID, "_selected"), String.Concat(Me.UniqueID, "_selected"), Me.UniqueID, "trigger" } Dim text4 As String = String.Format("", objArray1) output.Write(text1) output.Write(text2) output.Write(text3) output.Write(text4) End If End Sub | .method family virtual instance void Render([System.Web]System.Web.UI.HtmlTextWriter output) cil managed { // Code Size: 344 byte(s) .maxstack 5 .locals ( string text1, string text2, string text3, string text4, object[] objArray1) L_0000: ldarg.0 L_0001: ldsfld string [mscorlib]System.String::Empty L_0006: stfld string WebControlLibrary1.WebCustomControl1::licenseMessage L_000b: ldarg.0 L_000c: ldfld string WebControlLibrary1.WebCustomControl1::licenseMessage L_0011: ldsfld string [mscorlib]System.String::Empty L_0016: beq.s L_0029 L_0018: ldarg.1 L_0019: ldarg.0 ……中间无关代码略 L_0150: ldarg.1 L_0151: ldloc.3 L_0152: callvirt instance void [System.Web]System.Web.UI.HtmlTextWriter::Write(string) L_0157: ret } |
与RadTreeView.DLL的Render方法的反汇编代码相对照,在WebControlLibrary1中得出如下对应关系
VB | 对应的IL汇编代码 |
Me.licenseMessage = String.Empty | L_0000: ldarg.0 L_0001: ldsfld string [mscorlib]System.String::Empty L_0006: stfld string Telerik.WebControls.RadTreeView::licenseMessage |
现在,可以添加IL代码了。打开Visual Studio .NET 2003 命令提示
键入ILDASM 打开RadTreeView.DLL
然后转储IL反汇编代码,保存RadTreeView.IL,附属文件也一并出现在文件夹中
用记事本打开RadTreeView.IL文件,现取消强名称验证。在文件头部找到如下代码将其删除:
.publickey =(00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 // .$.............. 00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00 // .$..RSA1........ CD 62 12 05 0E 7C CD 6F 51 AF 2C 41 FD CC 65 44 // .b...|.oQ.,A..eD AC E3 CF 79 6A 19 49 C5 80 C3 FF 52 7C AC 91 1D // ...yj.I....R|... 9B E0 5F AD 28 47 CE F4 E7 E5 EC 87 9F C9 4B E4 // .._.(G........K. 9E 31 C7 97 C2 B8 39 25 C4 ED F6 AA 83 FA 78 A3 // .1....9%......x. 5A 47 C0 F4 7B 44 A8 F9 3F D1 44 A9 B7 96 BF 74 // ZG..{D..?.D....t 9E 8D FC B3 99 82 11 52 A9 5C 7A 37 EB A3 82 B6 // .......R./z7.... 9D A5 8B 7A 1C 87 DA 5C ED 0B 7A 72 BA B1 3F 12 // ...z.../..zr..?. 52 C6 2F 50 DD 35 44 06 E6 F3 B0 4B AF F4 19 BD ) // R./P.5D....K.... |
查找Render方法,可见到其代码片断如下:
.method family hidebysig virtual instance void Render(class [System.Web]System.Web.UI.HtmlTextWriter output) cil managed { // 代码大小 313 (0x139) .maxstack 4 .locals init (class Telerik.WebControls.RadTreeViewHtmlRenderer V_0, string V_1, string V_2, string V_3, string V_4) IL_0000: ldarg.0 IL_0001: ldfld string Telerik.WebControls.RadTreeView::licenseMessage IL_0006: ldsfld string [mscorlib]System.String::Empty IL_000b: call bool [mscorlib]System.String::op_Inequality(string, string) IL_0010: brfalse.s IL_001f ……中间无关代码略 IL_0124: callvirt instance void [mscorlib]System.IO.TextWriter::Write(string) IL_0129: ldarg.1 IL_012a: ldloc.3 IL_012b: callvirt instance void [mscorlib]System.IO.TextWriter::Write(string) IL_0130: ldarg.1 IL_0131: ldloc.s V_4 IL_0133: callvirt instance void [mscorlib]System.IO.TextWriter::Write(string) IL_0138: ret } // end of method RadTreeView::Render |
将“仿真”的IL代码插入Render方法的最前面,见如下加粗代码。
.method family hidebysig virtual instance void Render(class [System.Web]System.Web.UI.HtmlTextWriter output) cil managed { // 代码大小 313 (0x139) .maxstack 4 .locals init (class Telerik.WebControls.RadTreeViewHtmlRenderer V_0, string V_1, string V_2, string V_3, string V_4)
L_0000: ldarg.0
L_0001: ldsfld string [mscorlib]System.String::Empty
L_0006: stfld string Telerik.WebControls.RadTreeView::licenseMessage
IL_0000: ldarg.0 IL_0001: ldfld string Telerik.WebControls.RadTreeView::licenseMessage IL_0006: ldsfld string [mscorlib]System.String::Empty IL_000b: call bool [mscorlib]System.String::op_Inequality(string, string) IL_0010: brfalse.s IL_001f ……中间无关代码略 IL_0133: callvirt instance void [mscorlib]System.IO.TextWriter::Write(string) IL_0138: ret } // end of method RadTreeView::Render |
保存RadTreeView.IL准备使用ilasm重新编译,为应对编译出现反复,用记事本再做一个批处理文件Myasm.bat保存在同一目录中,内容如下:
ilasm /dll /resource: RadTreeView.res /output: RadTreeView.dll RadTreeView.IL
在Visual Studio .NET 2003 命令提示行运行Myasm,几秒钟后,新的RadTreeView.dll出现了。
为检查添加的IL指令的正确性,用Reflector打开破解后的RadTreeView.dll,反编译Render方法为VB代码,可惜的一幕出现了,Me.licenseMessage = String.Empty
指令准确地出现在我们预定的位置上,可见“仿真”的IL代码是正确的。见下图:
进入Microsoft Visual .NET 开发环境,将新的RadTreeView.dll添加到工具箱。仿照示例代码建立一测试页面WebForm1.aspx,将RadTreeView所需的资源TreeViewImages目录考入测试站点的虚拟目录中。
<%@ Register TagPrefix="radt" Namespace="Telerik.WebControls" Assembly="RadTreeView" %> <%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="WebTest.WebForm1"%>
|
进行必要的设置后编译测试项目,在本机浏览器中用localhost方式进行检测,可见页面上的Web树工作正常,各节点展缩自如,不再出现过期警告。截屏如下:
在本机和其他机器上使用域名或IP地址访问测试页,则发现尽管也不再出现过期警告,但Web树不再响应鼠标点击。浏览器状态行还出现页面有错误,缺少对象的警告。看来,破解并未最终完成。
仔细查看RadTreeView帮助文档,发现该控件与域名和IP地址进行了捆绑。当时使用域名和IP地址访问时,如未通过验证则显示过期警告,并不再发送控件脚本至客户端。之前的工作仅屏蔽了过期警告,却未解决有关的脚本问题。
看来必须找到检测许可证的方法或函数,还是用Reflector,在Telerik.WebControls的RadTreeView节点上有一个CheckLicenseKeys函数,从其函数名便可以基本确定它就是我们要找的东西,如下图:
反汇编出CheckLicenseKeys()的代码如下
Private Function CheckLicenseKeys() As String Dim text1 As String = String.Empty Dim num1 As Integer = -1 Dim text2 As String = "" Dim text3 As String = Me.Page.Request.ServerVariables.Get("SERVER_NAME").ToLower Dim text4 As String = Me.Page.Request.ServerVariables.Get("LOCAL_ADDR") If (((Me.Company Is String.Empty) AndAlso (Me.LicenseKey Is String.Empty)) AndAlso ( Not Me . LicenseFile Is String.Empty)) Then Dim text5 As String = Me.Page.Request.MapPath(Me.LicenseFile.Replace("~/", Me.applicationPath)) Dim document1 As XmlDocument = New XmlDocument Try document1.Load(text5) Dim reader1 As XmlTextReader = New XmlTextReader(Assembly.GetExecutingAssembly.GetManifestResourceStream ("Telerik.WebControls.TreeViewResources.SchemaLicense.xsd")) If Class2.Function4(document1, reader1) Then Dim node1 As XmlNode For Each node1 In document1.SelectNodes("//license[@control = 'treeview']") Dim num2 As Integer = Class2.Function2(node1.Attributes.ItemOf("company").Value, node1.Attributes.ItemOf("licenseKey").Value, text3, text4) If (num2 > num1) Then num1 = num2 End If If (num1 = 1) Then goto Label_016A End If Next End If Label_016A: reader1.Close Catch exception1 As XmlException End Try Else num1 = Class2.Function2(Me.Company, Me.LicenseKey, text3, text4) End If If ((num1 <= 0) AndAlso ((text3 Is "localhost") OrElse (text4 Is "127.0.0.1"))) Then num1 = 1 text2 = "
End If If (num1 > 0) Then If (text2.Length <= 0) Then Return text1 End If Dim time1 As DateTime = DateTime.Now Dim random1 As Random = New Random(time1.Millisecond) Dim num3 As Integer = random1.Next(19) If (num3 <> 7) Then Return text1 End If Return text2 End If Me.Controls.Clear If (num1 < 0) Then Return "r.a.d. treeview v.2.1. You have not provided valid license key or company, or you are trying to access the control by domain name, IP, or server name. Please, use http://localhost instead or obtain a 30-day trial key." End If Return "r.a.d.treeview v.2.1. Your trial key has expired. To extend your key contact sales@telerik.com." End Function |
在记事本中打开RadTreeView.IL并搜索CheckLicenseKeys,发现整个控件仅在OnPreRender方法一处调用了CheckLicenseKeys函数,OnPreRender方法的VB代码为:
VB代码 | 对应的IL汇编代码 |
Protected Overrides Sub OnPreRender(ByVal e As EventArgs) MyBase.OnPreRender(e) Me.licenseMessage = Me.CheckLicenseKeys If (Me.licenseMessage Is String.Empty) Then If (Me.IsEmpty AndAlso ( Not Me . ContentFile Is String.Empty)) Then Me.LoadContentFile(Me.ContentFile) End If If ((Me.ContextMenus.Count = 0) AndAlso ( Not Me . ContextMenuContentFile Is String.Empty)) Then Dim document1 As XmlDocument = New XmlDocument document1.Load(Me.Context.Server.MapPath(Me.ContextMenuContentFile.Replace("~/", Me.applicationPath))) Me.LoadContextMenusXmlString(document1.OuterXml) End If If Not Me.IsEmpty Then Me.Page.RegisterArrayDeclaration("tlrkTreeViews", Me.ID) Me.Page.GetPostBackEventReference(Me, "dummy") If ( Not Me . CssFile Is String.Empty) Then Me.Page.RegisterClientScriptBlock(String.Concat(Me.ID, "css"), String.Concat(" Me.CssFile.Replace("~/", Me.applicationPath), ">")) End If If Not Me.Page.IsClientScriptBlockRegistered("RadTreeViewJavaScript") Then If ( Not Me . PathToJavaScript Is String.Empty) Then Me.PathToJavaScript = Me.PathToJavaScript.Replace("~/", Me.applicationPath) Me.Page.RegisterClientScriptBlock("RadTreeViewJavaScript", String.Concat(" <script language=javascript src='"Me.PathToJavaScript.Replace("~/", Me.applicationPath), "'></script> " & ChrW(10))) Else Dim reader1 As StreamReader = New StreamReader(Assembly.GetExecutingAssembly.GetManifestResourceStream("Telerik.WebControls.TreeViewResources.RadTreeView_Client.js")) Me.Page.RegisterClientScriptBlock("RadTreeViewJavaScript", String.Concat(" <script language=javascript>", reader1.ReadToEnd, "</script> " & ChrW(10))) End If End If End If End If End Sub |
.method family hidebysig virtual instance void OnPreRender([mscorlib]System.EventArgs e) cil managed
{
// Code Size: 488 byte(s)
.maxstack 7
.locals (
[System.Xml]System.Xml.XmlDocument document1,
[mscorlib]System.IO.StreamReader reader1)
L_0000: ldarg.0
L_0001: ldarg.1
L_0002: call instance void [System.Web]System.Web.UI.
WebControls.WebControl::OnPreRender([mscorlib]System.EventArgs)
L_0007: ldarg.0
L_0008: ldarg.0
L_0009: call instance string Telerik.WebControls.RadTreeView::
CheckLicenseKeys()
L_000e: stfld string Telerik.WebControls.RadTreeView::licenseMessage
L_0013: ldarg.0
L_0014: ldfld string Telerik.WebControls.RadTreeView::licenseMessage
L_0019: ldsfld string [mscorlib]System.String::Empty
L_001e: call bool string::op_Inequality(string, string)
L_0023: brfalse.s L_0026
L_0025: ret
L_0026: ldarg.0
L_0027: call instance bool Telerik.WebControls.RadTreeView::
get_IsEmpty()
L_002c: brfalse.s L_004c
L_002e: ldarg.0
L_002f: call instance string Telerik.WebControls.RadTreeView::
get_ContentFile()
L_0034: ldsfld string [mscorlib]System.String::Empty
L_0039: call bool string::op_Inequality(string, string)
L_003e: brfalse.s L_004c
L_0040: ldarg.0
……中间无关代码略 L_01c8: ldstr "RadTreeViewJavaScript"
L_01cd: ldstr "<script language=javascript>"
L_01d2: ldloc.1
L_01d3: callvirt instance string
[mscorlib]System.IO.TextReader::ReadToEnd()
L_01d8: ldstr "</script>/n"
L_01e2: callvirt instance void [System.Web]System.Web.UI.Page::
RegisterClientScriptBlock(string, string)
L_01e7: ret
}
|
看来,licenseMessage的值时从CheckLicenseKeys函数或取的,只要返回的字符串是空值便会将控件完整的渲染到客户端。这样一来,我们可以不必仔细研究CheckLicenseKey的代码了,反正我们又不是要制作授权码生成器。如果判断没有错误的话,现在唯一需要做的就是将OnPreRender方法中的 Me.licenseMessage = Me.CheckLicenseKeys 改为 Me.licenseMessage = String.Empty。结合前面分析Render方法的实的经验,我们得出了如下的代码对应关系。
VB代码 | 对应的IL汇编代码 |
Me.licenseMessage = String.Empty | L_0000: ldarg.0
L_0001: ldsfld string [mscorlib] System.String::Empty
L_0006: stfld string Telerik.WebControls.RadTreeView::licenseMessage |
Me.licenseMessage = Me.CheckLicenseKeys | L_0007: ldarg.0 L_0008: ldarg.0 L_0009: call instance string Telerik.WebControls .RadTreeView::CheckLicenseKeys() L_000e: stfld string Telerik.WebControls.RadTreeView:: |
现在,再次用记事本打开RadTreeView.IL ,找到OnPreRender方法, 根据上面的代码对应关系,将Me.licenseMessage = Me.CheckLicenseKeys 的IL代码改为 Me.licenseMessage = String.Empty 的IL代码后存盘,并再次编译,测试结果表明,破解成功。
趁热打铁,最后就是解决示例文档与破解空间的完美结合问题。
在Visual Studio .NET中用C#新建一web项目r.a.d.treeview2.5x,并按下图将程序及名称和默认命名空间均设为tree
添加项目对新的RadTreeView.IL的引用后,将r.a.d.treeview2.5原示例站点中的除bin目录的全部目录及文件复制后经过Visual Studio .NET的解决方案资源管理器粘贴到r.a.d.treeview2.5x项目中,设计器会弹出如下对话框,选择“是”,建立一个关联的类文件。
用Reflector打开原示例站点bin目录中的tree.dll,反汇编出Tree.WebForm1的Page_Load的C#代码
private void Page_Load(object sender, EventArgs e) { string text2; int num1 = 1; string text1 = "Welcome"; if ((text2 = base.Request.QueryString.Get("Tab")) == null) { goto Label_009D; } text2 = string.IsInterned(text2); if (text2 != "Quickstart") { if (text2 == "Examples") { goto Label_0077; } if (text2 == "Help") { goto Label_0081; } if (text2 == "Support") { goto Label_008B; } if (text2 == "Purchase") { goto Label_0095; } goto Label_009D; } num1 = 2; text1 = "Quickstart"; goto Label_009D; Label_0077: num1 = 3; text1 = "Examples"; goto Label_009D; Label_0081: num1 = 4; text1 = "Help"; goto Label_009D; Label_008B: num1 = 5; text1 = "Support"; goto Label_009D; Label_0095: num1 = 6; text1 = "Purchase"; Label_009D: this.leftNavigation.Controls.Add(base.LoadControl(string.Format("~/Examples/{0}Navigation.ascx", text1))); this.content.Controls.Add(base.LoadControl(string.Format("~/Examples/{0}.ascx", text1))); this.tabsImage.Src = string.Format("Img/tab{0}.gif", num1); } |
将上述代码填入Default.aspx.cs的private void Page_Load(object sender, System.EventArgs e)中。
生成这个示例项目后,在浏览器地址栏键入http://您的IP/r.a.d.treeview2.5x/,一个完美的r.a.d.treeview2.5破解版便诞生了。
看看她的全貌吧:
我是边破解边写这篇文档的,用了整整一天的时间。回过头来看,如果不走弯路,两个小时解决战斗是完全有可能的。
(另外,由于篇幅问题,在由WORD向这个WEB在线编辑器贴文章的时候,感觉这里采用的WEB在线编辑器对于图文并茂的文章作者时间十分痛苦的事,是在不如我破解的DotNetTextBox1.3。设置的编辑幅面太小,版面调整太困难,还不能会表格,急死人了。还有其他事,谁有什么好的控件请推荐给我,发到我的邮箱aspdotnet@yeah.net即可)
三君DotNet工作室 君仁
2004年9月17日星期五23时于 大连 旅顺