• DiskGenius注册算法简析


    初次接触DiskGenius已经成为遥远的记忆,那个时候还只有DOS版本。后来到Windows版,用它来处理过几个找回丢失分区的案例,方便实用。到现在它的功能越来越强大,成为喜好启动技术和桌面支持人员的必备工具之一。

    回想起好几年前的一个案例,一个用了很久的老机器突然崩盘,磁盘引导部分物理损坏,无法启动系统。一般的数据当然可以用DiskGenius等工具来恢复,但重点已经不在这里了,因为大部分数据我有备份。
    让人焦虑的是我有一个EFS加密的文件和Outlook邮件偏偏就没有备份。里面含有个人和公司全部软件系统及服务器的帐户、授权信息。进不了系统,就意味着我要和它们永久地说拜拜了,着实令人不寒而栗。
    尝试了很多数据恢复工具,包括Windows系统和其他系统的"NTFS/EFS Recovery"、"Raw Copy"等等,全都没用。因为没有数字证书,这个EFS文件等同于一堆垃圾字节,也没办法进入邮件帐户。
    交给专业的数据恢复公司又不放心,当然我没有冠希同学那样的秘密,要是他当初EFS一下,就不会招致那么多的口水和对那么多人的生活造成影响,呵呵。
    后来自己终归解决了这个问题,说穿了很简单:在另一个系统里构造一个和宕机的系统完全一样的帐户,至少我还记得用户名和密码。要点是保证新建帐户和原帐户的SID一致,这样SAM里的Hash就和原来的相同,系统通过SID将帐户和文件的所有者对应起来。
    在Windows系统中,得到了别人的这个Hash,就可以进入他的系统。曾在Marcus Murray的某个Microsoft TechEd演示"Knowing the Enemy - A lightning demonstration on how hackers attack networks"中看到,使用Hash将网络中的一台机器作为跳板入侵另一台机器。这方面还有大家熟知的Mark Russinovich。
    得到的教训是,鸡蛋不能放在一个篮子里:备份、备份还是备份;另外,EFS就真的那么安全?真要用的话,请遵循“最佳实践”:备份证书,并将其从系统中移除。

    这个故事脱离了本主题,按下不表。事情的起因是前段时间的一个“事故”:小弟的小弟替员工重装系统,拿错了映像,造成其他分区数据丢失。这就要命,搁谁也谁也郁闷,得帮人家恢复。
    好久都没干这种事了,凭以往的经验用DiskGenius和R-STUDIO,感觉DiskGenius更有效和准确一些。

    DiskGenius分免费版和专业版。免费版是体验用的,恢复文件时有64KB大小限制,搜索算法也相对简单,和未注册的专业版相同。由于有限制,基本上没法用,要么购买要么“破解”。出于对逆向的偏好,加上对传说中“暗桩”的好奇,决定玩一玩,也学习一下它的方法。
    当然首先要找几个“破解版”在虚拟机上试一下,发现只有版本3.8(在本论坛找到的)那个是“真破解”,其他都是“伪破解”。甚至某“破解补丁”在内存中改程序的Title和About,难免被人批为大日本帝国的军人——“自慰”队员。很少有人提到激活文件Options.ini,见过的其中注册码也是错误的。

    本文以目前最新的DiskGenius Pro 4.2.0.100为例,设想是不通过“破解”,而是弄清楚它的注册算法,实现完美激活。乱套一下“不战而屈人之兵”,是谓伐谋与攻心,乃最高境界。
    先官样文章地声明一下,纯技术探讨,觉得软件有用请购买。这里不会暴露具体的细节,故命题为“简析”,到是整个过程中一些好玩的东西可能更有趣。

    一.注册码的奥秘
    未注册的专业版用户界面窗口标题会有“未注册”字样,“关于”对话框多一个“立即注册”按钮:

    点进去,出现“注册DiskGenius”对话框,随便填些东东,再点“立即激活”,提示“无效的注册码,请重新输入。”。

    非常友善,是不是。从这个对话框我们了解到,激活分网络(在线/离线)和加密锁两种方式,另外输入的注册码不符合它的要求,类似网页提交前客户端有一个初步的验证。直截了当,就从此对话框的窗口过程入手,找到它的验证算法,一探注册码的奥秘。
    通过代码分析得知,内部有一个简单加密的Base36字符集,但不是标准序列、而是自定义的,最后4个字符是'01IO',从解码算法证实:注册码实际上是Base32编码,弃用了容易误识的数字和字母,每5个字符一组、5组共25个字符,以'-'分隔。类似Microsoft的CDKEY或Product Key,不过MSFT用的是Base24,还弃用了易被误认的'5AELNSUZ'。不会涉及椭圆曲线签名算法吧?让人望而生畏!有一点可以肯定,Hash(或者说Checksum bits)是有的,用于验证注册码的有效性。

    显然,Base32比Base24表示的大整数要大很多,意味着我们选择的余地更大、相对容易一些。有了Base32字符集和校验算法,我们就可以开始生成自己的注册码了。但是我不想抄那些反汇编代码,换一种玩法:用HTML页面+浏览器作UI,Javascript作算法实现,只需一个支持高亮和好用的文本编辑器,比如EmEditor就可以方便地设计界面和修改程序进行调试了。
    因为我有一个现成的来自About.com的"Password Generator"页面,稍加修改成为"regcode Generator"随机生成注册码,再用校验算法得到Hash。About的页面简洁、美观,Layout全部用DIV+CSS,不象其他大多数人用TABLE,记得以前有过DIV和TABLE的争论。
    Javascript和C++都是面向Object的,Javascript具有C++没有的"Regular Expressions",但两者都没有汇编指令ROR/ROL这样简单的运算符,两条指令C++实现的例子可在Bruce Eckel的"Thinking in C++", Volume 1中找到。

    但是注册码生成器中Javascript有两个问题需要解决,大整数和无符号整数。
    在Javascript里,数字都用符合IEE754规范的64位双精度浮点数表示。显然注册码轻松地超过了64位限制,需要找一个简单的Javascript库处理"Big Number",可参考stanford.edu的"RSA and ECC in JavaScript"里相关内容,但我们这里不用整这么复杂。
    数据类型方面,MSFT到"Internet Explorer 10"才在"Windows Runtime Types"中引入UInt8、Int32、Int64[-2^53, 2^53]和Uint64[0, 2^53]等等;Mozilla到是早就在ctypes里支持Int64和UInt64。同样不打算弄得这么繁琐,只将必要的运算(左移位、加/带位加、异或/或等)改写,保证结果为UINT类型。
    另外,数字需要经常在二进制、十进制和十六进制间转换,Javascript的Number.toPrecision([precision])却没法用,精度不够,得自己想办法。IE严格遵循JScript文档,precision超过21就报错,而Firefox中将precision设到40时返回的还是近似数。
    FF的Javascript性能表现远远超过IE,这在编码大整数为Base32时能明显感觉到。意料中的事,MSFT早已不满足于将IE定位为单纯的Browser,IE已然成为一个试图一统天下的超级客户端,以满足服务器端产品的要求,同时和操作系统密不可分,十分地臃肿。

    下图为"regcode Generator for DiskGenius"的页面:

    这部分很容易,先随机生成注册码的前22位,再将16bits的Hash值编码为Base32字符得到注册码的末3位。

    二.激活请求发送了什么
    现在注册码是有效的了,试着激活。但这次是服务器回答“Error: 注册码无效!”,这里可理解为:注册码仍然有问题、确实无效,或有效、但在它的数据库中没有记录,还可能数据库存在记录、但其他字段不匹配。无论那种情况都不可能指望它返回有用的信息,也不好意思老去骚扰人家的服务器。
    换个思路,转而从处理返回结果的代码着手。为方便和避免错误,我需要在本地启用HTTP服务,将激活服务器的地址指向本地,进行模拟。程序需要什么数据,就按要求响应它,让我能一步一步走下去。

    但再这之前,得搞清楚程序向激活服务器发送了些什么数据,以帮助我们猜测和理解返回数据的构成。用Wireshark这类的工具记录一下HTTP的Request和Response内容。
    捕获的数据简单明了,它使用GET方法发送请求数据,URL中各字段依次为(在跟程序时也可观察到):
    代码:

    ver=4.2.0.100
    code=YH23U-R65WC-CKPA2-RN2JB-XENVZ
    name=MistHill
    email=misthill%40easycompany.com
    mid=mOD_CDOcD^OFHLOHLHLL%1A%1A%1A%1A%1A%1CL%1C
    appname=DiskGenius
    diskinfo1=XCyo|kXg|z{obGJKFo|jJ|gxkQ>%3F>>>>>>>>>>>>>>>>>%3FQ:%3F7:%3D>:>

    注意到:首先,传递的值一定要编码为有效的URI(Uniform Resource Identifier)。其次,mid和diskinfo1的内容是加密的,其他字段的含义一目了然。
    mid从字面上理解意为机器码,为三次CPUID指令的结果变化而成。
    diskinfo1显然代表磁盘信息,我在调试的机器上都只挂了一块硬盘,两块及以上的情况是不是还会发送diskinfo?,没仔细查程序代码,不能确认。
    程序为找到的每一个磁盘都创建一个磁盘对象,共支持0x80个磁盘。对每个磁盘对象,调用两次KERNEL32.DeviceIoControl。第一次取Geometry,第二次取ProductId和SerialNumber。
    根据Geometry计算出磁盘的总扇区数,再加上ProductId(型号)和SerialNumber(序列号)得到diskinfo1的内容。

    三.实现一个简单的HTTP服务
    前面讲到激活分网络和加密锁两种方式。没有加密锁可以写一个Driver来模拟,为使事情变得简单,我选择网络方式。在本地如果有现成的IIS或Apache[Tomcat],可简单地写个页面来响应请求。
    这里我决定借用现成的模板写一个APP来实现简单的HTTP服务,我就不用在调试虚拟机上搞一大堆东西,也方便以后类似的工作。
    可以找到很多类似的东西,但我希望尽可能简单,并一定要有详细的文档。来自IBM的Nigel Griffiths有一篇"nweb: a tiny, safe Web server (static pages only)"就非常不错,全部C源码仅200行,支持静态页面.html或.htm、图像、压缩文件。遗憾它需要UNIX系统,如Ubuntu、Fedora、OpenSUSE和Debian等。
    我需要Windows环境的。MSFT的David Cook一篇"Write a Simple HTTP-based Server Using MFC and Windows Sockets"正是我要找的,文章发表于MSJ(Microsoft Systems Journal - MSDN的前身)February 1996。嗯…,这么古老的东西?没错,就是它。越早越容易被奉为经典。
    它的工程名称为Webster,文章十分详细,涵盖HTML、HTTP、Sockets等,可设置Server的基本选项,高度可控;"MIME Data Types"可扩展;支持详细的调试和日志记录;支持"System Tray"图标;源码注释得也很好。
    编译连接后,先用Telnet测试一下:

    啊哈,工作完全正常!现在我们可以使用自己的“激活服务器”了。但是激活时Webster服务器Hung在那里,追查Bug:发现用于接收GET请求URI的Buffer太小,请求的URI字符串太长造成“缓冲区溢出”。
    再来,这次没问题了:

    对工程Webster改了大概以下几处:
    a) HTTP/1.1协议支持,96年那时还只有HTTP/0.9和HTTP/1.0。
    b) URI缓冲区大小。
    c) 由CScrollView派生的CWebView水平滚动条支持。
    d) 由CPropertySheet派生的CWebProps各属性标签CPropertyPage里Controls的字体。

    四.完美激活
    接下来,根据程序处理激活码那部分代码的分析,开始写注册机。因为要用到CPUID指令和API调用,用VC++来写。

    激活码(Activation Key)实际上是对激活时提交信息进行变换、加密处理后的结果。这是激活服务器后台干的事,程序解密激活码后对那些信息逐一进行对比,没有问题激活就成功了。
    激活码可视为一张证书,分好几种类型。比如是否限制了特定版本,若无则通用证书适用于其他版本;还有试用证书,可指定过期日期。大概还有一种,决定硬件信息的比对来源,是加密锁还是API,没详细看代码,无法肯定。
    图中uncheck两个Encrypted的Checkbox会显示mid和diskinfo1各自的明文。
    激活成功后,程序会写激活文件Options.ini,用于重启验证;同时会复制一份到dos文件夹给DOS版使用。格式基本上是这样子:
    代码:

    [license]
    user = "MistHill"
    email = "misthill@easycompany.com"
    regcode = "YH23U-R65WC-CKPA2-RN2JB-XENVZ"
    key = "9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA"
    [LicenseInfo]
    Show = "N"

    这里注册码(regcode)目前看来是“有效的”;激活码(key)是我手工敲的,显然无效,不能通过验证。但已足够让程序去掉Title里的“未注册”字样和“关于”对话框的“立即注册”按钮:

    貌视已注册成功,此为“暗桩”之一。

    五.注册码兮,注册码
    将key的内容改回注册机(DiskGenius Keygen)里"Activation Key"的内容,这下能通过重启验证,应该是“完美激活”了吧?
    No, No, No..., Something wrong! 试图恢复大于64KB的文件时失败,错误类型表明还是“未注册”!
    看来还有玄机。重新审查激活码验证通过后的代码,发现确实有调用对注册码再次进行验证,当前这个regcode没有通过!
    在仔细研究了那段代码后,我才恍然大悟,它还真是跟MSFT的CDKEY学来的,注册码由三部分组成:序列号(SerialNumber) + 签名(Signature) + 校验位(Hash)。签名由序列号按签名算法得到,校验位为根据前两部分计算出的结果。
    同时也搞清楚了哪几个关键标志DWORDs的值应该是什么才表明它是真正注册成功了。
    回到第一节的"regcode Generator for DiskGenius"页面,"Phase I"解决了Hash的问题,接下来"Phase II"解决签名的问题。先随机生成一个RegCode,得到序列号部分,根据它的逆算法得到签名部分,最后计算校验位部分。

    这样整个DiskGenius的注册过程分为两个步骤:1) 用HTML页面产生有效的注册码;2) 用注册机生成激活码。Webster只是分析问题时的一个工具,不再需要了。
    再试一试恢复大于64KB的文件,这次当然不会再有问题:

    最后,感谢DiskGenius的作者,很好的工具,希望它更加强大!我从分析它代码的过程中得到乐趣。
    谢谢您阅读此文!谬误之处,请批评指正。

  • 相关阅读:
    洛谷—— P2234 [HNOI2002]营业额统计
    BZOJ——3555: [Ctsc2014]企鹅QQ
    CodeVs——T 4919 线段树练习4
    python(35)- 异常处理
    August 29th 2016 Week 36th Monday
    August 28th 2016 Week 36th Sunday
    August 27th 2016 Week 35th Saturday
    August 26th 2016 Week 35th Friday
    August 25th 2016 Week 35th Thursday
    August 24th 2016 Week 35th Wednesday
  • 原文地址:https://www.cnblogs.com/zc520/p/3190490.html
Copyright © 2020-2023  润新知