很久没有发贴了,但签到还是非常多的。
small_000.jpg
最新版本是2022release 2了,我分析的是上面这个次新版。
IDE没有授权时,会在工具栏显示“Buy Xxxx”的按钮,并且没有授权时,只能在 IDE 运行调试程序,不能编译成二进制的程序来发布。如下图所示,如果点“Build”,会弹出一个需要购买授权的对话框:
small_001.jpg
该IDE授权是以授权文件的方式进行授权认证的,主要有两种方式导入授权文件:
1、通过打开工程文件的方式打开授权文件进行认证;
2、通过 License Keys 菜单打开授权管理对话框,再添加授权文件。
下面是打开工程文件的入:
small_002.jpg
通过菜单 License Keys 的方式如下图:
small_003.jpg
打开的授权文件管理对话框如下图所示:
small_004.jpg
现在还没有导入授权文件。
我们可以点“Add...”,打开一个授权文件
(我已生成好了一个授权文件,这个文件是一个加密后并编码的XML文件,在后面讲如何生成该文件)。
我们用 x64dbg 打开其主程序。进行授权过程的调试,在项目界面打开授权文件(第1种模式)后,中断在如下位置(先下好断点,再打开授权文件):
small_005.jpg
可以看到,首先对文件头的前15个字符进行检查,如果是“605B6B0E6BA1BA1”开头的话,就是表示这个文件是授权文件,会进入授权验证过程,否则就是项目或其它文件,不会进入授权文件验证过程。
”605B6B0E6BA1BA1“是一个固定的字符串,保存在资源区,具体的比较位置在上面函数内的位置如下图所示,调用其内置的字符串比较函数进行比较,如下图所示:
small_006.jpg
上面用到了字符串常量结构,该结构如下所示:
[C++] 纯文本查看 复制代码
string_const_struct { dword ref_count; dword align1; qword address; //// 保存字符串的缓冲区 qword unknown1; dword length; dword properties; /// 0x08000100 qword unknown2;}
上面数据区大的红色框是字符串常量结构的地址指针,指向具体字符串,如下图数据区所示的字符串,另一个小的红色框是字符串的长度。
small_007.jpg
上图是字符串内容。
比较的结果在下面位置检查,如果检查是授权文件,开始生成前那个授权管理对话框,这个对话框有4个TAB页签,第2个页签就是"Licenses",进行授权管理的。
small_008.jpg
就是在 "AboutWindow"中来处理授权文件,首先取得该对话框的实例,然后显示该界面:
small_009.jpg
在初始化和显示的过程中,会初始化口另外一个类对象,这个类对象为:"Registration.StudioKeyValidator",是对授权文件进行验证的类,如下图所示,是该类初始化的代码:
small_010.jpg
初始化完类后,就是取得其实例,如下所示:
small_011.jpg
类实例成对象后,然后调用其 constructor 方法,对对象实例进行初始化:
small_012.jpg
其 contructor 的参数就是授权文件的内容,如上图数据区所示,又是一个 string struct,因为不是常量,字符串内容就紧接在结构的后面,中间只隔了一个字节。
另外,字符串内容的第1个字节是“FF”,这个值也是长度,当实际长度小于 255时,存的是实际长度,当长度大于或等于255时,都是存的“FF”,可以看看前面的“605B6B0E6BA1BA1”,第1个字节是“0F”,表示长度是15字节。
构造函数首先解码授权文件内容,将其转换为16进制的字符串,如下图所示:
small_013.jpg
转换前如上图数据区所示,转换后如下图数据区所示,变成了16进制格式的字符串了:
small_014.jpg
然后,会再次生成一个内存块对象(MemoryBlock)这个是进行内存数据操作的对象类,相当于C语言的指针操作了,执行完一大段该对象初始化的代码后,来到这里:
small_015.jpg
这里就是调用该对象的方法,将数据写入其缓冲区,MemoryBlock 对象结构如下所示,红框中是实际保存数据的指针:
small_016.jpg
MemoryBlock 对象结构如下所示:
[Asm] 纯文本查看 复制代码
memory_block_object_struct { pointer methods[]; pointer properties[]; pointer events[]; qword ref_count; qword unknown; qword unknown; qword size; pointer data_buffer; //// 保存数据的缓冲区的指针 byte isInit; byte isAllowDirectAccessBuffer; byte[6] mem_align;}
下图更详细的显示其结构:
small_017.jpg
在MemoryBlock 的属性表中,保存有对象的类名,如下图所示的数据区中是 memoryBlock的属性和成员表:
small_018.jpg
第3个指针就是指向类名,如下图所示:
small_019.jpg
这种结构是固定的,所以我们可以查询所有内置对象的类名,便于我们跟踪调试,知道现在运行的类对象代码是哪个类的。
回到主题,执行完前面的函数据,memoryBlock对象的缓冲区保存的解码后的授权文件,准备下一步的解密操作了:
small_020.jpg
现在解码后的数据还是加密的,加密算法是 Blowfish,并且很好确定算法,因为这是一个Blowfish对象,通过前面讲的查类名的方法就知道了。
small_021.jpg
首先是取得 class,后面又是一大段实例化和初始化的代码,下面就是调用初始化函数的位置:
small_022.jpg
初始化函数的参数是解密密码,这个密码是一个常量字符串,如上图数据区所示,看起来象是前面我们讲过的函数的返回值和参数描述,只是第1个字符是“+”号,“+”前面的“2C”是长度。
初始化好密码后,就开始循环对 memoryBlock的缓冲区数据进行解密了,如下图所示:
small_023.jpg
每次循环会解密8个字节的数据,如下图所示,已完成前面8字节的解密:
small_024.jpg
全部解密完成后,就是一个XML文件,如下图所示,已经完成整个授权文件地解密了。
small_025.jpg
可以看到,这是一个 XML 文件,第1步工作已经完成了,下一步是对授权内容进行处理了。
small_026.jpg
先把解密后的 XML 文件内容从 memoryBlock 中取出来,生成一个字符串,如上图所示。
首先,验证解密后的内容是不是一个XML格式的内容,如下图所示,检查前6个字符是否"<?xml "(最后一个字符是空格):
small_027.jpg
就是简单的取出前6字节进行较,如下图所示:
small_028.jpg
如果检查是 XML 格式的字符串,则初始化一个 XMLDocument 对象类,并通过其构造函数来解析XML,生成XMLDocument对象:
small_029.jpg
XMLDocument 对象生成成功后,就是循环取出其节点值,进行一下步的解析处理:
small_030.jpg
主要是解析3个标签的值:
1、address
2、signature
3、serial
其它的标签是不解析的,如上面的授权文件中我加了一个email标签,就不会取出来。如下图所示,是检查3个标签的位置:
small_031.jpg
取出来的标签的值,会保存到 KeyValidator 对象的属性中,如下图所示,保存的是 serial 属性:
small_032.jpg
下面保存是 address:
small_033.jpg
以及 signature:
small_034.jpg
接下就是执行 KeyValidator 的构造函数,对刚才取出来的三个XML标签值进行下一步的验证,检查授权是否合法:
small_035.jpg
因为后面还有解密过程,并且是用的 email 作为密码解密,程序会从授权信息数据库查询以前的email,这样就可以不用用户来输入解密密码了,如下所示,查询授权文件数据库(是一个全文加密的SQLite数据):
small_036.jpg
该文件保存在目录"c:\Users\用户名\AppData\Roaming\Xojo\Xojo"中,文件名是“License Keys”。如下图所示,是查询密码的SQL:
small_037.jpg
如果没有查到密码,也就是没有以前的授权文件,就会显示一个对话框,由用户输入邮箱名作为后的解密密码:
small_038.jpg
显示密码对话框:
small_039.jpg
该对话框如下图所示(2个图,1个没有输入,一个已输入email):
small_040.jpg
如果输入的邮箱地不正解,后面的解密数据会不对,在解密后的校验中,会弹出以下错误:
small_040_1.jpg
如果邮箱没有错,可以继续下一步的解密过程了。
刚才弹出的密码对话框,又会再次生成和实例化 Registration.StudioKeyValidator 对象,并再次在 constructor 中重复一遍前的解码、解密、XML解析过程,很是无聊,这就是面向对象编程的问题,每次 new Object 后,要重复 constructor 一次。
small_041.jpg
把前过程重复一次后,我们来到了这里:
small_042.jpg
这里将输入的email地址转换成全小写的字符串。
接下来就是用这个小写的 email 为密码,执行一个解密函数,生成另外一个对象:StudioKeyData 对象,保存具体的授权数据。
small_043.jpg
我们进入这个函数,跟踪处理过程(按 F7 进入函数)
首先处理 StudioKeyValidator.signature 属性:
small_044.jpg
取出 signature 属性的字符串,这是一个 1024 个字符的字符串,包含有两个部分,每部分都是 512 个字符,后面会讲到其作用。
如下图所示,先截取其前面 512 个字符:
small_045.jpg
再取剩下的一半字符,如下图位置代码来完成:
small_046.jpg
取出后半部分后,首先对其进行解码,变成16进制的字符串,长度也变成了 256 字节了:
small_047.jpg
这个 256 字节的字符串,实际上是一个加密后的 2048 bits 的 RSA 签名。
接下来是对这个加密的签名进行解密。
这次解密的密码是动态生成的,不是常量字符串了,并且跟 signatrure 的前半部分的 512字符相关。
这次密码有 64 字节,由 memoryBlock 保存,所以先初始化一个 memoryBlock 对象:
small_048.jpg
首先计算前 16 字节的密码,这16字节是我们输入的 email 地址的 MD5 码,如下图所示,计算 MD5 码:
small_049.jpg
将 16 进制的 MD5 码存入 memoryBlock 的缓冲区,如下图所示:
small_050.jpg
接下来处理序列号,生成与序列号相关的一段密码,先将序列号中的“-”去掉,如下图所示:
small_051.jpg
同样进行16制解码,变成16进制的字符串:
small_052.jpg
serial 变成 24 字节长的 16 进制字符后,填充到密码缓冲区:
small_053.jpg
第3步就是还有24字节的密码需要生成,而这24字节的密码的生成与 signature 前512个字符相关了,24个字节密码的前16个是一个字节一个节生成的,最后8字节是一次生成的(是一个8字节的双精度浮点数)。
这个步骤的代码相当长且臭,就不一一説了,就是一些查字符串位置,统计某些字符的个数等,并把得到的这些数据填充到密码表中。
small_054.jpg
查找和统计的字符串有“CAF”, "BAD", "11", "8", "A", "3", "7"等等,还有取最后一个还是2个字符的操作。如下图,是统计字符“A”的个数:
small_055.jpg
然后将个数据写入密码表:
small_056.jpg
就这样生成16字节的密码,存入密码表:
small_057.jpg
最后8字节的密码是一个幂计算的结果(8字节双精度浮点数),如下图所示:
small_058.jpg
这样,就生成了 64 字节 512 bits 的密码。
(这里讲一个问题,这个密码是512bits的,但 blowfish算法的密码为32bits到448bits,正常情况下用 512bits密码会报错,但这个IDE的Blowfish对象不报错,试过其它空码实现,都不行,会报错,所以,后面的注册机也只能用这个对象来加密,并且还要共它处理,不过,这个 Blowfish对象是 undocument 的,怎么用要自己试。。。。。。)
密码生成好了,就开始再次进行解密了,先取出密码,生成密码字符串:
small_059.jpg
一样也是 blowfish 解密,一样的循环解密:
small_060.jpg
解密完成,得到一个 RSA 签名:
small_061.jpg
那接下来就是签名验证了,是对 StudioKeyValitor.serial + StudioKeyValitor.address 进行签名验证,这个 serial 和 address 也是从 XML 授权文件中取来的
先从 MemoryBlock 中取出签名字符串:
small_062.jpg
然后就是生成签名验证的 msg 文本了,是一个 serial 和 address 合并的字符串,分隔符是 0x0A,如下图所示:
small_063.jpg
这样 RSA 签名所需的 sign 和 msg 都准备好了。
接下来就 RSA 签名验证过程了,不过这里要替换 RSA 公钥了。
先生成 RSA 对象:
small_064.jpg
读取公钥(这个公钥我已经换过了):
small_065.jpg
运行签名函数,看 al 的值,为 1 表示签名正确:
small_066.jpg
检查签名校验是否正确:
small_067.jpg
正确就生成 StudiokeyData 对象,保存相关授权信息。
相关授权信息存于 serial 中,所以 StudioKeyData 的构造函数的参数就是 serial,如下图所示:
small_068.jpg
授权都有一个过期日期,该构造函数默认会认为是 365 天,所以先将属性 expiration = 365,如下图所示:
small_069.jpg
再次将序列号的”-“去掉,方便后面hex 解码:
small_070.jpg
再次解码为 16 进制字符串:
small_071.jpg
然后又放入到 memoryBlock 中,干什么呢?当然是还要解密了。
先构造一个 memoryBlock 对象:
small_072.jpg
写入 16 进制的 serial:
small_073.jpg
这次用小写的email地址,初始化一个 Blowfish 对象:
small_074.jpg
又是一个循环解密:
small_075.jpg
完成解密,得到一个24字节的授权信息,如下图的数据区:
small_076.jpg
得到授权信息后,首先检查是否有效,就是检查 serial[21] == 0x2C,如果相等,表示有效,上图是取出该值,下图是检查是否为0x2C:
small_077.jpg
然后取出 serial[7] ,如上图所示,对这个值也要检查。
small_078.jpg
如果这个值大于1,也表示授权信息无效,会抛出不支持的异常,退出验证过程。
接下来是一个日期验证,看看授权文件的生成时间是不是比当前系统日期还晚,如果还晚就表示无效。
small_079.jpg
先取出日期,就是 serial[1]*256 + serial[2],一个 word 值。
small_080.jpg
将年份置入 Date 对象的 year 域。
接下来取 serial[3], 这是一个WeekOfYear = 0x20,只有一个字节,并赋给 Date 对象。
small_081.jpg
然后来取 serial[4], 这是一个DayOfWeek = 0x05,也只有一个字节,也赋给Date对象:
small_081_1.jpg
这样通过 year,weekOfYear, DayOfWeek生成一个日期,然后取其总秒数,用于后面的时间比较:
small_082.jpg
取得秒数后,与另一个表示当前系统时间的日期总秒数比较:
small_083.jpg
如果 shippedDate > currentSystemDate , 则授权文件无效,退出验证过程。
如果 shippedDate 有效,则进行过期日期的检查,先取出过期日期,是一个相对的 Days (天数),存于 serial[5]和 serial[6],其值不得大于 0x7FFF:
small_084.jpg
上图是取出了 expiration Days。
经过一些处理,就是计算还有多少天(去掉 currentSysyDaye - shippedDate),并保存到 StudioKeyData 对象中:
small_085.jpg
然后就是读取订阅号: subscription_id:
small_086.jpg
保存 subscription_id:
small_087.jpg
接下来就是读取 serial_index 了:
small_088.jpg
保存 serial_index:
small_089.jpg
最后取出来的是 Feature,版本特性,表示授权文件包括哪些版本特性:
small_090.jpg
版本特性是一个4字节值,是多个特性 bits 合成后的值,特性 bits 如下所示:
[HTML] 纯文本查看 复制代码
1 - Not for resale2 - Desktop4 - Web8 - Database Servers16 - iOS32 - Console64 - Pro128 - Lite Mac256 - Lite Windows512 - Lite Linux1024 - Pro Plus2048 - Single Board Computerother - Unknown
每一个 bit 代表一个版本。
保存 feature 到 keyData 对象:
small_091.jpg
另外,在保存 feature 时,会检查是否包括 console 和 pro 版本,如果包括这两个版本之一,则加上 Single board computer 版本特性,如下图所示。
small_092.jpg
这样,StudioKeyData 的 constructor 就执行完了,我们按 "CTRL + F9" 退出函数,来到下面:
small_093.jpg
我们再次按 "CTRL+F9"退出当前函数,回到下面的位置:
small_094.jpg
接下来是一个黑名单检查,如果进入黑名单,一样也是无效:
small_095.jpg
目前黑名单只有一个,检查结果在下面位置处理:
small_096.jpg
接下来就是过期日期检查了:
small_097.jpg
一样也是通过总秒数对比大小来检查是否过期:
small_098.jpg
取软件发布日期,是一个常量,不过是经过简单计算得来的:
small_099.jpg
就是 2001-01-01 加上 670896000 秒,计算后得到日期:2022-04-06
进行比较:
small_100.jpg
如果没有过期,就开始授权数据库,检查是否有老的授权文件,如果查询到了老的授权记录,就检查 serial_index 的大小:
small_101.jpg
如果数据库中的授权记录的 serial_index 大于或等于当前的 serial_index 就会报错,如果没有老的授权,取得的 serial_index = 0,肯定小于当前的 serial_index:
small_102.jpg
按“CTRL+F9”退出函数,返回到如上图的位置。
所有验证都通过了后,就是网络验证。
small_103.jpg
上图就是网络验证的位置,按“F7”进入该函数。
来到这信位置:
small_104.jpg
按“F7”进入该函数。
如下图所示位置:
small_105.jpg
再按“F7”进入函数。
来到如下图位置:
small_106.jpg
又是按"F7"进入函数。
small_107.jpg
循环写入XML节点的值。
将生成的 XMLDocument 转换成字符串:
small_108.jpg
XML字符串生成好了,按 "CTRL+F9"退出 BuildXML() 函数。
small_109.jpg
后面就是发送激活信息,我们将建立一个模拟激活服务器来处理激活过程,首先修改 Hosts 将激活服务器指向本机:
small_110.jpg
因激活服务器是 https 的,我们还要搭建一个 https 服务,最简单的 https 由 python 来搞定,创建好https需要的ssl证书就可以,另外 python 那个内置的 https 服务没有处理 POST,所以我们还要写一个定制的 post 处理的 handler。
代码如下图所示,大部分来自网络,我只是合并修改了一下:
small_111.jpg
启动上面的服务。如下图所示:
small_112.jpg
返回 x64dbg,提前下好处理返回信息的断点后(后面会讲到),直接按“F9”运行程序:
small_113.jpg
激活服务会收到 POST 过来的激活XML内容:
small_114.jpg
然后 x64dbg 断在我们提前下好的接收处理函数内的断点上:
small_115.jpg
前面説过,我们要先在激活回应处理的函数(都是事件处理函数),这个事件是网络事件,是对象 RealID_Message 的事件,他是另一个类的继承,这个类是 _wrapper_ServerComm,_wrapper_ServerComm又是StudioLicenseKeyPasswordDialog对象的一个类模块,这个 StudioLicenseKeyPasswordDialog 就是那个询问密码(邮箱地址)的对话框类。
激活服务器返回的是一个 XML 格式的文本内容,格式如下所示:
[XML] 纯文本查看 复制代码
<?xml version="1.0" ?><methodResponse><params><param><value><int>8888</int></value></param></params></methodResponse>
其中只有一个有用的值,就是<int>8888</int>,表示返回的是整数,值为 8888,而这个 8888 表示的是剩余可激活的次数(同一台机器多次激活也只算1次,也可以説是剩余可激活的设备数)。
F8往下走,来到这里,对收到的XML内容进行处理:
small_116.jpg
按"F7"进入该函数。
还是老办法,生成一个 XMLDocument 对象来处理 XML 文档,如下图所示:
small_117.jpg
继续往下走一段代码,来到如下位置,就是解析并取得那个最终的响应值了:
small_118.jpg
这个函数返回的是一个 Variant<Double> 类型的值,其实就是 8888。
Variant 也可看作一个对象,结构如下:
[C++] 纯文本查看 复制代码
variant_struct { pointer methods[]; /// methods = NULL pointer properties[]; /// pointer events[]; /// events = NULL qword ref_count; /// reference count qword unknown1; qword unknown2; qword value_or_address; //// 当时基本类型值时,保存的是实际值;当是字符串或对象时,保存的是地址指针}
取得这个值后,接下传递给下一个事件,是以 Delegate 调用的方式来处理的,如下图所示:
small_119.jpg
这样,我们进入另一个对象(ServerComm)的事件处理,这是前面讲过的 StudioLicenseKeyPasswordDialog._wrapper_ServerComm 的一个实例。
small_120.jpg
这个事件处理函数的参数就是前面的 Variant<Double>类型的变量。
small_121.jpg
在处理这个数据前,先恢复成整数值,如上图所示。
然后就是对这个数据进行对比,根据返回值进行对应的处理和提示:
small_122.jpg
如果 int >= 1,表示激活正确,还有剩余激活次数。
如果 int = -1,表示激活异常,激活失败了。
如果 int = -2, 表示激活次过多,不能再激活了。
上面会根据不同的返回值,取得不同的提示信息字符串。
接着往下走,如果激活成功,就会保存当前授权文件的内容到数据库了,下面就是调用保数据的位置:
small_123.jpg
这个函数内有保存到数据库的代码,相当长,生成SQL的位置如下:
small_124.jpg
接下来就提示激活成功了:
small_125.jpg
上面还剩余多次的情况,如果只剩一次了,提示如下(其实就是处理了一下英文名词单数和复数的显示问题)
small_125_1.jpg
如果失败,就不会保存授权数据到数据库,同时会提示出错:
small_126.jpg
这个就是前面判断<int>-1</int>的情况。
small_127.jpg
这里是前面判断<int>-2</int>的情况,表示激活次数(设备)过多,超过次数,只能迁移以前的授权过来了。
还説明一下,前面那个判断是有 BUG 的,当值为 0 或 -3 及以及下的负数时,也是激活成功的:
small_127_1.jpg
small_127_2.jpg
前面我们説过 serial_index 的问题,就是新加的授权文件中的 serial_index 一定要大于数据库中的授权记录中的 serial_index,就是:
xml_serial_index > db_serial_index 要满足,否则就会报以下错误:
small_128.jpg
只要将同一个授权文件添加两次,第2次就会报这个错误了。
这样,整个授权检查和激活过程就结束了。
下面,我説一下如何快速查看一个对象的类名吧,一个对象类的基本结构如下:
[C++] 纯文本查看 复制代码
object_struct { pointer events[]; pointer properties[]; pointer metheds[]; qword ref_count; qword inherited_count; //// 不确定 dword unknown1; pointer ref_object; //// 最后引用的对象,没有则是指向对象本身 dword unknown3; dword unknown4; dword unknown5;}
下面是上面结束 properties[] 的前面几个属性:
[Asm] 纯文本查看 复制代码
properties_struct { pointer base_class; qword unknown1; char * class_name; dword unknown2; dword unknown3; ....... properties_list}
可以看到,第3个属性就是类名了,下面以图説明:
small_129.jpg
上图中,rcx 指向一个对象,[rcx + 0x08] 就是属性和成员表了。
small_129_1.jpg
在数据区按CTRL+G,输入[rcx+0x08]定位到属性和成员表,如上图所示位置,其中第3个属性是一个地址,指向类名,也就是指向 [[rcx + 0x08] + 0x10]。
small_129_2.jpg
再在数据区按CTRL+G,输入[[rcx+0x08] + 0x10] 就可以看到类名,这样只要知道 rcx 是一个对象,直接 [[rcx+0x08] + 0x10]就会显示类名了。
还有就是它的对类的方法和属性的名称,函数的返回值和参数的类型,也都可以查到,如下图所示是 XMLDocument 的一个方法:
small_130.jpg
============================ 分界线 ======================================
下面我们进入注册机的主题了,生成一个可用的授权文件(当然前提是 RSA 的公钥要换成我们自己的)。
这里还要説明一下,它那个 RSA 签名过程与标准的开源的不一样,我作了对比,有些差异,并且它的 RSA 签名过程是通过 Plugin 来完成的,我们也要通过这种方式来完成,否则签名是通不过的。
还有就是系统自带了 RSA 签名的函数,但这个函数生成的签名也是通不过的,这个函数如下:
[Visual Basic] 纯文本查看 复制代码
RSASign(data As MemoryBlock, privateKey As MemoryBlock) As MemoryBlock
所以我们也只能调用它的插件来实现签名,但是系统没有带这个插件,其插件目录中没有这个 RSA 签名的插件。但是系统的库目录(Xojo Libs)带的 RSA 签名的动态库,有这个就可以了,我们自己来依葫芦画瓢制作一个,
我们查看一下插件目录(Xojo Libs),有系统自带的插件,其实这些插件是 ZIP 格式的文件,我们用压缩软件打开一个插件,就可以看到,其结构很简单,就是包括不同编译目标平台的动态库。
small_131.jpg
我们只有 x86_64 的库,只要这个就可以,如下图所示,我们自制了一个 OpenSSL 插件:
small_132.jpg
插件中的DLL来自其 Libs 目录中的库文件。
保存这个文件到其安装目录下的 Plugins 子目录,并重新启动 IDE 就可以用了。具体怎么用可看看其 plugin SDK 的文档,有c++的接口説明,再用 IDA 看看DLL就差不多理解了。
还有就是前面説过的 Blowfish 的密码长度超过正常的长度,所以得用其内置的 _Blowfish 类来处理 Blowfish的加密,不然也是通不过的,而这个 _Blowfish 类是不公开的,也就上没有文档説明,其文档中的两个Blowfish相关函数:[Visual Basic] 纯文本查看 复制代码
BlowFishDecrypt(publicKey As String, data As MemoryBlock, blockMode As Crypto = BlockModes.CBC, initializationVector As MemoryBlock) As MemoryBlockBlowFishEncrypt(publicKey As String, data As MemoryBlock, blockMode As Crypto = BlockModes.CBC, initializationVector As MemoryBlock) As MemoryBlock
也不是通过这个类来实现的,这两个函数也不能处理超过长度的密码。
以下是注册机的主要代码:
[Visual Basic] 纯文本查看 复制代码
const NID_SHA1 as Integer = 64 ////&H40var features() as int64 = Array(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 2147479552)var crlf as String = EndOfLine.UNIXconst xml_cryptoKey as String = "+%%o<Graphics>o<RectControl>o<RBControl>i4i4"//// email and passwordvar email as String = txtEmail.Text.Trim.Lowercaseif email = "" then MessageBox "Please Input a Email Address!!!" exit subend if//if lstEditions.SelectedRowIndex = ListBox.NoSelection then//MessageBox "Please select edition features!!!"//exit sub//end if///// ==================================================================================================================///// 生成序列号var password as string = email//if password = "" then//MessageBox "Password(email address) is Null, Input eMail Address First!!!!!!"//exit sub//end ifvar mem as MemoryBlock = new MemoryBlock(24)mem.LittleEndian = false// unusedmem.Int8Value(0) = &H20// release date// 2022-04-06/// shipped_datevar today as date = new Datevar shippedDate as date = dtpShippedDate.SelectedDatevar year as UInt16 = shippedDate.Yearvar wks as UInt8 = shippedDate.WeekOfYearvar wDays as UInt8 = shippedDate.DayOfWeek/// shipped datemem.UInt16Value(1) = Yearmem.UInt8Value(3) = wks // yestodaymem.UInt8Value(4) = wDays/// expiration = valid_days - today - 1var expiredDays as Integer = CType((dtpLicensedDate.SelectedDate.SecondsFrom1970 - dtpShippedDate.SelectedDate.SecondsFrom1970) / 86400, Integer)if expiredDays < 0 then MessageBox "Licensed Date Must be more than Shipped Date!!!" Exit subend if if expiredDays > (&H7FFE - today.Day) then MessageBox "Licensed Date too bigger!!!" Exit subend if//mem.Int16Value(5) = expiredDays //// key的有效天数,默认 365 天,生成的授权文件在 365 天内加入都 有效,否则会失效mem.Int16Value(5) = &H7FFF - 365// loop one, [7]>1 则 raise UnsupportedFormatExceptionmem.Int8Value(7) = 1 // not loop// unusedmem.Int8Value(8) = &H20mem.Int8Value(9) = &H20// SubscriptionIDmem.Int32Value(10) = Integer.FromString(txtSubscriptionID.Text.Trim)//&H88888888// serial_indexmem.Int16Value(14) = Integer.FromString(txtSerialIndex.Text.Trim) //&H6667// unusedmem.Int8Value(16) = &H20//featurevar feature as UInt32 = 0for i as Integer = 0 to lstEditions.LastRowIndex if lstEditions.RowAt(i).Selected then feature = feature or features(i) end ifnext iif bitwise.BitAnd(feature, &H36) = 0 then //// Desktop, Web, iOS, Console 必选 feature = &H476 // Desktop, Web, iOS, Console, Pro, Pro Plusend ifmem.UInt32Value(17) = feature//&H7FFFFFFE// tagmem.Int8Value(21) = &H2C /// 逗号 ',',序列号有效性标志// unusedmem.Int8Value(22) = &H20mem.Int8Value(23) = &H20 //Crypto.BlowFishEncrypt(key As String, data As MemoryBlock, blockMode As BlockModes = BlockModes.CBC, initializationVector As MemoryBlock) As MemoryBlockvar mem2 as MemoryBlock = Crypto.BlowFishEncrypt(password, mem, Crypto.BlockModes.ECB, nil)var serial_hex as string = mem2.StringValue(0, mem2.Size)var serial_str as string = EncodeHex(serial_hex)var serial_xml as string = serial_str.Middle(0, 8) + "-" + serial_str.Middle(8, 8) + "-" + serial_str.Middle(16, 8) + "-" + serial_str.Middle(24, 8) + "-" + serial_str.Middle(32, 8) + "-" + serial_str.Middle(40, 8)////// debug////MessageBox "SerialNo: " + serial_xml////// ==================================================================================================================///// 生成序列号和地址信息的签名var privateKey as String = Self.rsa_private_key.ReplaceLineEndings(EndOfLine.UNIX) //// 取得私钥if privateKey = "" then MessageBox "RSA private key is null!!!!" exit subend if//// 用户名,一般为电脑登陆名var userName as String = txtName.Text.Trimif userName = "" then userName = app.LoggedInUserName ///end if/// 生成地址信息var address as String = txtName.Text + crlf + txtAddress1.Text + crlf + txtAddress2.Text + crlf + txtCity.Text + "," + txtProvince.Text + "," + txtPostCode.Text + crlf + txtCountry.Textaddress = address.ReplaceLineEndings(EndOfLine.UNIX)//// 生成RSA签名文本var msg as String = serial_xml + crlf + addressvar signRet as String = ""var private_key_pem as String = "-----BEGIN PRIVATE KEY-----" + EndOfLine.UNIX + privateKey + EndOfLine.UNIX + "-----END PRIVATE KEY-----"//var public_key_pem as String = "-----BEGIN PUBLIC KEY-----" + EndOfLine.UNIX + publicKey + EndOfLine.UNIX + "-----END LUBLIC KEY-----"//// 注意:需要安装插件 OpenSSLPlugin.xojo_plugin,否则不能调用下面4个OpenSSL函数,该插件可自制//// 注意:需要安装插件 OpenSSLPlugin.xojo_plugin,否则不能调用下面4个OpenSSL函数,该插件可自制//// 注意:需要安装插件 OpenSSLPlugin.xojo_plugin,否则不能调用下面4个OpenSSL函数,该插件可自制var rsa as Ptr = OpenSSL.RSA_New()rsa = OpenSSL.PEM_read_RSAPrivateKey(private_key_pem, rsa)var isRsaSignOK as Boolean = OpenSSL.RSA_Sign(NID_SHA1, msg, signRet, rsa) //// 生成RSA签名: signRetOpenSSL.RSA_Free(rsa)var signRet_str as String = ""if isRsaSignOK then signRet_str = EncodeHex(signRet) //// debugInfo ////MessageBox "RSA Sinature return: " + crlf + signRet_strelse MessageBox "Generate RSA Signature failure!!!!!!" exit subend ifif signRet_str.Length <> 512 then MessageBox "Encode RSA Signature failure!!!!!!" exit subend if////// debug///taLicenseKeyContent.Text = signRet_str/////==================================================================================================================///// 生成 XML 文件的 signature 标签内容var sign_pass As MemoryBlock = new MemoryBlock(64)sign_pass.LittleEndian = False//MD5var md5Value As MemoryBlock = Crypto.MD5(password)sign_pass.StringValue(0, 16) = md5Value.StringValue(0,16)//Serialsign_pass.StringValue(16, 24) = serial_hexvar signature_password_constant as String = password_retrieval_table //// 含一定的字符串:“CAF", "BAD", "11", "8", "B", "3", "7", "D", "F", "C"等var signature_verify as String = signRet_strif signature_verify = "" then MessageBox "RSA Signature First!!!!!" exit subend ifvar pass_str as String if chkRandomPwdBase.Value then //var randBytes as MemoryBlock = Crypto.GenerateRandomBytes(256) //var signature_password_random as String = EncodeHex(randBytes.StringValue(0, 256), false) var signature_password_random as String = getRandomString() //// 生成随机的密码检索表 //// debugInfo //// MessageBox "random string: " + signature_password_random pass_str = signature_password_random //// 随机else pass_str = signature_password_constant //// 固定end ifvar sign_verify_str as String = signature_verify var sign_verify_str_hex as String = app.MyDecodeHex(sign_verify_str) // 16 进制的签名if pass_str.Length <> 512 then MessageBox "pass base string length is not 512 bytes!" exit subend ifif sign_verify_str.Length <> 512 then MessageBox "signature verity string length is not 512 bytes!" exit subend//MessageBox "sign_verify_str.length = " + sign_verify_str.Length.ToString//MessageBox "sign_verify_str_hex.length = " + sign_verify_str_hex.Length.ToStringwhile (sign_verify_str_hex.length mod 8) <> 0 sign_verify_str_hex = sign_verify_str_hex + " "wendvar count as Integer = 0var min_val as Integer = 0var sum_val as Integer = 0var position as Integer = 0var start as Integer = 0var pow_val as Double = 0.0var str_tmp1 as string = ""var str_tmp2 as string = ""var str_tmp3 as string = ""/// byte(40)count = CountOfStr(pass_str, "A")sign_pass.Int8Value(40) = count/// byte(41)str_tmp1 = pass_str.Right(1)str_tmp2 = MidStr(pass_str, pass_str.Length-2, 1)position = charAt(pass_str, str_tmp2) // Instr()count = CountOfStr(pass_str, str_tmp1, position)sign_pass.Int8Value(41) = count//messagebox str_tmp1 + ", " + str_tmp2 + "," + position.ToString/// byte(42)count = CountOfStr(pass_str, "CAF")sign_pass.Int8Value(42) = count//messagebox "count of ""CAF"": " + count.ToString/// byte(43)count = CountOfStr(pass_str, "8")sign_pass.Int8Value(43) = count//messagebox "count of ""8"": " + count.ToString/// byte(44)position = charAt(pass_str, "B") // Instr()min_val = Min(position * 8, 255)sign_pass.Int8Value(44) = min_val//messagebox "min of InStr(""B"")*3 between ""255"": " + min_val.ToString/// byte(45)count = CountOfStr(pass_str, "BAD")sign_pass.Int8Value(45) = count//messagebox "count of ""BAD"": " + count.ToString//// byte(46)position = charAt(pass_str, "3") // Instr()count = CountOfStr(pass_str, "7", position)min_val = Min(count, 12)sign_pass.Int8Value(46) = min_val//messagebox "min of InStr(""7"", InStr(""3"")) between ""12"": " + min_val.ToString/// byte(47)sign_pass.Int8Value(47) = 9 /// '\t'/// byte(48)sign_pass.Int8Value(48) = password.Length // 用户密码长度/// byte(49)str_tmp1 = MidStr(pass_str, 21, 1)str_tmp2 = MidStr(pass_str, 203, 1)str_tmp3 = MidStr(pass_str, 48, 1)sum_val = str_tmp1.Asc + str_tmp2.Asc + str_tmp3.Ascsign_pass.Int8Value(49) = sum_val//MessageBox str_tmp1 + ", " + str_tmp2 + ", " + str_tmp3/// byte(50)position = charAt(pass_str, "11") // Instr()if position > 0 then sign_pass.Int8Value(50) = 1else sign_pass.Int8Value(50) = 0 /// 默认就是0,可跳过end if/// byte(51)sign_pass.Int8Value(51) = 42/// byte(52)sign_pass.Int8Value(52) = sign_pass.Int8Value(0) /// byte(52) = MD5Bytes[0]/// byte(53)sign_pass.Int8Value(53) = sign_pass.Int8Value(31) * 7 /// byte(53) = serialBytes[15] * 7/// byte(54)count = CountOfStr(pass_str, "D", 127)sign_pass.Int8Value(54) = count/// byte(55)str_tmp1 = MidStr(pass_str, pass_str.Length-2, 1)str_tmp2 = pass_str.Right(1)position = charAt(pass_str, str_tmp2)count = CountOfStr(pass_str, str_tmp1, position)sign_pass.Int8Value(55) = count//MessageBox str_tmp1 + ", " + str_tmp2 + ", pos = " + position.ToString + ", count = " + count.ToString/// byte(56) ~ byte(63)position = charAt(pass_str, "F")count = CountOfStr(pass_str, "C", 1)pow_val = Pow(position, count)sign_pass.DoubleValue(56) = pow_valvar sign_pass_string as String = sign_pass.StringValue(0, 64)/// enrypted signature_verify_partvar sign_verify_data as MemoryBlock = new MemoryBlock(sign_verify_str_hex.Length)sign_verify_data.StringValue(0, sign_verify_str_hex.Length) = sign_verify_str_hex//// 加密var bf as _Blowfish = new _Blowfish(sign_pass_string)//MessageBox "sign_verify_str_hex.length = " + sign_verify_str_hex.Length.ToStringvar data0 as MemoryBlock = new MemoryBlock(sign_verify_str_hex.Length)data0.StringValue(0, sign_verify_str_hex.Length) = sign_verify_str_hexvar data as MemoryBlock = new MemoryBlock(sign_verify_str_hex.Length)data.StringValue(0, sign_verify_str_hex.Length) = sign_verify_str_hexdata.LittleEndian = Falsebf.Encipher(data) /// 加密//// 256var xml_signature as String = ""var encrypted_signature as Stringif data <> nil then //MessageBox encrypt_verify_data.Size.ToString encrypted_signature = EncodeHex(data.StringValue(0, data.Size), false) xml_signature = pass_str + encrypted_signature //MessageBox "encryped signature: "+ crlf + encrypted_signature //MessageBox "signature ok!!!"else MessageBox "XML Signature is null" exit subend if////// debug////taLicenseKeyContent.Text = xml_signature/////==================================================================================================================///// 生成 XML 文件内容if encrypted_signature = "" then MessageBox "XML Signature is empty." exit subend ifif encrypted_signature.Length <> 512 then MessageBox "Ecrypted Signature String Length not equal 512" exit subend if//if pass_str.Length <> 512 then//MessageBox "Password Base String Length not equal 512"//exit sub//end if//// XML 文件var xml_str as String = "<?xml version=""1.0"" encoding=""UTF-8""?>" + EndOfLine.Unix + "<key>" + EndOfLine.Unix + "</key>"var xml as XmlDocument = new XmlDocument()xml.PreserveWhitespace = truexml.LoadXml(xml_str)var xml_element as XmlElement = xml.DocumentElementvar n as Integer = xml_element.ChildCountvar serialNode as XmlTextNode = xml.CreateTextNode(serial_xml)xml_element.AppendChild(xml.CreateElement("serial")).AppendChild(serialNode)var addressNode as XmlTextNode = xml.CreateTextNode(address)xml_element.AppendChild(xml.CreateElement("address")).AppendChild(addressNode)var emailNode as XmlTextNode = xml.CreateTextNode(email) //// passwordxml_element.AppendChild(xml.CreateElement("email")).AppendChild(emailNode)var signatureNode as XmlTextNode = xml.CreateTextNode(xml_signature)xml_element.AppendChild(xml.CreateElement("signature")).AppendChild(signatureNode)////// debug///taLicenseKeyContent.Text = xml.ToString/////==================================================================================================================///// 生成 最终加密的 XML 文件内容// 加密var sData as String = xml.ToString.ReplaceLineEndings(EndOfLine.Unix)while ((sData.Length mod 8) <> 0) sData = sData + " "wendvar mbData as MemoryBlock = new MemoryBlock(sData.Length)mbData.LittleEndian = FalsembData.StringValue(0, sData.Length) = sData// BlowFishEncrypt(publicKey As String, data As MemoryBlock, blockMode As Crypto = Crypto, initializationVector As MemoryBlock) As MemoryBlockvar encryptData as MemoryBlock = Crypto.BlowFishEncrypt(xml_cryptoKey, mbData, Crypto.blockModes.ECB, Nil)var encryptedXML as string = EncodeHex(encryptData.StringValue(0, encryptData.Size-8), False) ///// 需要去除 XML 加密后多出来的8个padding字符,否则XMLDocument会出错////// show license keytaLicenseKeyContent.Text = encryptedXML////MessageBox "Generate License Key Success!!!!!"
主界面如下:
small_133.jpg
下面附上激活服务器和注册机的工程源码:
激活服务器: https_server.zip2022-8-15 10:10 上传点击文件名下载附件
下载积分: 吾爱币 -1 CB
注册机源码: xojo_license.zip2022-8-15 10:10 上传点击文件名下载附件
下载积分: 吾爱币 -1 CB
----- game over -----
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]