完全原创,winddw的SOHU博客(http://winddw.blog.sohu.com/)及看雪学院首发,转载请注明出处!

在看雪注册了好几年了,也潜水好几年了都没怎么发帖子,刚好最近分析了下一个Java的小插件,有点心得写出来和大家分享,也算为论坛做一点贡献吧 

Log4e是一款Eclipse的日志帮助类的插件,提供丰富的日志代码嵌入的功能,是一个编写Java代码时实用的小工具。它提供自动插入logger定义、对方法进行log、对变量进行log等代码,可以一定程度上提高工作效率。同时它支持众多的日志工具,使用中可以看出侧重支持log4j,毕竟普及率高嘛。这里可以下载Log4e: http://log4e.jayefem.de/content/view/3/2/

其实想破解这个插件的想法很偶然,那天下载Log4e的时候发现作者主页上提供了完全免费的标准版之外还提供了Pro版,懒得去网站上查Pro版和标准版的功能区别,信手Google注册机居然没有?忽然激起我浓厚的兴趣,这种情况要么是用的人极少,要么是破解有一定难度。正好对Crack有兴趣,也Crack过几个Windows下的软件,但Java写的还没尝试过,就决定拿它练练手了。

这里就主要写分析以及破解的过程了,至于编译执行解释执行之类的原理请参看网上的资料吧。简单说一句Java程序与Windows程序不同,Win 32程序最终只可以反汇编为比较难懂的汇编语言代码,所以才需要断点、跟踪、分析得出算法进而写出注册机,Java程序是可以反编译成具有一定可读性的Java代码,这是Java程序的天生软肋。因此从某些角度上讲Java程序的破解要易于Win32程序,但是Java程序也不是完全不设防的,目前最有效的手段就是代码混淆。

当前有很多优秀的Java混淆器,有开源的也有商业产品,无论哪种最终目的只有一个:让反编译的Java代码可读性变得尽量的差,同时想尽办法为反编译者设置障碍。Log4e就是经过混淆后发布的,在分析过它之后感觉比较有代表性,因此决定花时间写个破解日志把其中的过程与经验与大家分享,共同探讨。

背景介绍完毕,进入正题,希望老鸟们不会嫌我嗦,能让新人尽可能的多了解些大家都会高兴的。开始分析过程:我下载的版本是Log4e Pro v1.3.1,用WinRAR打开时提示错误“不可预料的压缩文件末端”(Java程序发布时使用的.jar文件是zip格式,可以用压缩工具解压缩),稍感奇怪,继续用WinRAR浏览目录,可以看到深层目录里出现了名字为“OoO……O00”模样的目录和文件,明白是被混淆过了。于是琢磨如何先把它解包,因为跟踪Java程序的过程无非是反编译->找关键部位 ->分析代码,前提是要获得.class文件。

这个压缩包无法解压缩让我着实头疼了一阵子,试过jar命令行不管用,其他解压缩工具不管用,甚至换到linux下也不管用,都是无法解包,在郁闷的时候再次用WinRAR打开log4e的jar包的时候发现右侧显示压缩文件信息的地方显示了一行英文“Obfuscation by yGuard v1.5.0_03……”,这明显是使用了开源的混淆器,上网查了一下yGuard,的确是一个开源的混淆器,有源代码可以分析混淆的原理。不过,要是去看我就真的疯了,为了这么个小软件不值得花费大量的时间。这会儿心里已经有点谱了,yGuard利用了操作系统以及JVM对目录、文件名最大长度限制的不同来混淆目录和文件名,它把文件名、目录名、方法名、部分变量名混淆成最少256个字符的o和O。这样做有两个好处,一是不能正常途径解包会给分析文件造成一定障碍;再者混淆成“OoO……O00”这个样子的类名包名方法和变量名在读反编译后的代码时也远比其他一些软件只是把类名方法名混淆成ABCD这种要难读的多。原理找到了,就可以逆向来做了。

既然JVM可以识别,那么就可以用Java程序来解包,于是打开Eclipse,决定写一个小程序来解包,并替换长字符串的名称。这时候我犯了一个错误,忽略了一个关键。这个错误就是在我写好了解包jar的代码的时候忽然想到,如果按现在的名称来解包一样不能在硬盘上创建合法的文件名的文件,如果把长文件名和包做了替换虽然可以解包但是会造成.class内的名称和实际包名称不符,反编译器一定没法识别!结果,果然JD无法打开改名后的.class。这个错误是愚蠢的,没思考就行动的结果。忽略的关键就是既然JVM可以运行这个插件,那么,基于Java的反编译器也就可以识别并反编译……

于是迅速下载了JODE,一个非常优秀的Java写的反编译器,设好CLASSPATH,把Log4j包也加到JODE的CLASSPATH中,可以清楚的看到反编译后的代码,像这样的:(提示:千万别一行一行看这段代码……你会疯)

try {
    OOoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo_8_
  = (new OOoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
     ());
    if (oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo_8_
      .ò000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000new
  (string, string_3_, string_5_, string_7_)) { // 注册码判断的入口
  de.jayefem.ooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.O0oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.ooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
      .o000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000super
      (getShell(),
       (de.jayefem.log4e.o0oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.OoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
      .o000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000super
        ("info.title")),
       (de.jayefem.log4e.o0oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.OoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
      .o000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000super
        ("re.message6")));
  super.okPressed();
    } else // 注册码错误,显示错误提示
  de.jayefem.ooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.O0oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.ooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
      .o000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000super
      (getShell(),
       (de.jayefem.log4e.o0oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.OoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
      .o000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000super
        ("info.title")),
       (de.jayefem.log4e.o0oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.OoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
      .o000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000super
        ("re.message7")));  // 在这里,显示错误提示
} catch (Exception exception) {
    ó000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Object
  .error("okPressed()", exception);
}

怎么样?第一直觉就是根本没法看吧?其实仔细想一下就发现很简单了,我只需要分析程序流程,并不需要保证它可以执行,因此完全可以把长文件名替换成可读性好的名称,将可以确定是包名的替换成类似package01、类替换成class01这类的名称。

但是被混淆的文件太多,不可能一个一个都替换,老规矩,根据提示先找关键代码。Log4e的注册界面提供了4个文本框,First Name,Last Name,Email,Registration key,随便输入些内容点,提示错误“The registration key is not valid”,因为代码比较多同时JODE没有提供保存和搜索的功能,不方便也没必要把所有文件都另存出来再查找,可以用WinRAR的搜索功能,这个功能很好,很强大,推荐常用。忽略错误提示,最终查到在文件de\jayefem\log4e\oOoOOOO……\……文件下,需要耐心和细心一点点的查找。回到JODE找到相应文件(有个小技巧,找文件时只需要看前几个字母,混淆器在混淆时之混淆了前几位和末尾的字串,中间的200多个字符都是充数的,一般前5个字符就可以区别出了),打开相应的反编译后代码,复制到UltraEdit里,查找错误提示,找到了如下代码:

{ "general.buton.preview", "Preview >" },
{ "pref.logger.pos_message", "Logger Message (${message})" },
{ "PreviewWizardMultiFilePage.next_Change", "Next change" },
{ "re.message7", "The registration key is not valid." }, // 注意这里,提示信息出现
{ "version.info.deprecated",
  "Version of this template: {0}\nCurrent version: {1}\n\nThe version of this template definition has been deprecated. It will be automatically updated when saving the dialog (after pressing OK in the templates dialog)." },

可以看出Log4e在这个文件里定义了一个二维数组用以保存所有程序中用到的message,注册错误的提示“{ "re.message7", "The registration key is not valid." },”就在这里,位置在387行。

从结构可以看出,“re.message7”是“The registration key is not valid.”的引用名,再回到WinRAR查找“re.message7”,这次得到4个位置,除去定义它本身所在的文件依次查找其它3个文件的反编译代码,可以找到我在开始粘贴的那一大堆乱糟糟的代码,在末尾catch行的上面可以找到re.message7,在这里显示了错误信息。倒着往上看,是一个if-else的判断,显示错误信息的位置是在else块里,再看if块,里面出现了“re.message6”的引用,回过头来按前面的方法查找“re.message7”的定义,果然是注册码正确的提示信息,因此确定if的条件既是注册码判断的入口,同时可以看到4个参数“(string, string_3_, string_5_, string_7_)”,可以猜测出是4个输入的内容作为参数。

继续定位这个方法,按照前面我说的头5个字符判断法很容易可以找到注册码判断的类,第一个变量一下引起了我的注意“public static final String o0000…… = "b1K49dF4";”,不用说,十有八九是一个密钥,呵呵,需要指定密钥参与的算法有哪些呢?老鸟可能很快就可以联想到几个,不过不知道也没关系,继续往下看。虽然已经知道了入口方法,但是我的习惯是先从上往下扫一遍所有代码可以有个大概的了解,密钥往下出现了File类的定义,因此可以猜测Log4e会保存一个文件作为是否已注册的判断依据;再看最后的部分有一个方法定义,参数只有一个字符串:

private String o0000…… (String string) {
    String string_33_ = "";
    int i = 0;
    int i_34_ = 4;
    int i_35_ = i + i_34_;
    int i_36_ = 5;
    for (int i_37_ = 0; i_37_ < i_36_; i_37_++) {
        string_33_ += string.substring(i, i_35_);
        i = i_35_;
        i_35_ = i + i_34_;
        if (i_35_ >= string.length())
          break;
        if (i_37_ + 1 < i_36_)
          string_33_ += "-";
    }
return string_33_;
}

可以看出这个方法是用“-”分割字符串的,每4位分隔,保留20位。可以猜测出注册码是“xxxx-xxxx-xxxx-xxxx-xxxx”形式。

这里插一句,在代码的中间有这么一段:

String[][] strings
      = { { null, null, null, "25c1-0490-7d1b-d197-e3d5" },
    { "D4S", "ChinaDForce", "ginkgo_4_D4S",
      "02c8-0775-0185-ddd0-7b89" } };

居然是“ChinaDForce”?万能码?不像啊?试了一下,是无效的注册码,看网站Log4e应该是个德国人写的,那么这段应该就是被屏蔽掉的注册码,估计是以前版本的,ChinaDForce算出了码之后发布到了网上,被作者在程序里屏蔽了。

继续,回过头来找到注册码判断的入口函数,很好找,就在开头的密钥下面,跟进。发现了一个if (bool)的判断,看bool变量一定是判断注册码的核心位置了,继续跟进,找到相应文件下的该方法。在68行找到该方法,跟下去是调用了一个返回值为byte[]的方法,内容如下:

byte[] is_1_;
try {
    Cipher cipher = Cipher.getInstance("DES");
    SecretKeySpec secretkeyspec = new SecretKeySpec(string.getBytes(), "DES");
    cipher.init(i, secretkeyspec);
    byte[] is_2_ = cipher.doFinal(is);
    is_1_ = is_2_;
} catch (Exception exception) {
    throw new de.jayefem.OoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
      (exception);
}
return is_1_;

不用多说了,DES算法,密钥前面已经给了,因为调用JDK的方法是无法混淆的,否则JVM就不认了,要想做到这个层级的混淆估计就要单独写一个虚拟机了,或者做一个底层的解释器。

剩下的就好办了,再往下可以追到注册码的算法:

// 注册码核心算法
for (int i = 0; i < is.length; i++) {
    int i_7_ = is[i] & 0xff; // 按位与
    String string_8_ = Integer.toHexString(i_7_);
    
    if (string_8_.length() == 1) {
        string_8_ = "0" + string_8_; // 补位
    }
    
    string_6_ += (String) string_8_;
}

相信有点编程基础的都能看得懂这个算法并且写出相应的注册机了,Good Luck!

总结,对Java程序进行逆向工程并不难,只要解决掉混淆器设的坎儿,再加上一点点耐心就可以了。也许也有朋友分析过这个小插件,并且有另一种解决方式,那么请务必和我分享你的经验,可以给我发邮件交流:winddw@sohu.com,也可以到我的博客留言给我 http://winddw.blog.sohu.com/

    最后,请破解爱好者尊重软件开发者的辛苦劳动,作为技术人员我深深了解开发软件的辛苦和所耗费的心血。出于爱好我们可以从技术角度研究、分析,但是请尽可能的不要编写和散布注册机和注册码吧!