北京
随着硬盘存储技术的发展,电脑硬盘的容量已达到 TB 级。对企业和个人而言,存储在硬盘上的数据,从普通的文档、电子相片到珍贵的视频、财务数据,包罗万象。由于各种原因,如恶意程序入侵、用户误操作、硬盘物理损坏等,导致数据丢失的现象不断发生。为了尽可能把因数据丢失而带来的经济损失降到最低,数据恢复成为数据安全重要的避难所。
如今市面上常用的数据恢复软件有WinHex、R-Studio、EasyRecover、FinalData等。R-Studio、EasyRecover和FinalData等软件虽然操作简单易用,但同时也失去了数据恢复的灵活性。在常用的数据恢复软件中,WinHex 以文件体积小、运行高效和操作灵活而著称,它既有十六进制文件编辑功能,也有磁盘编辑的功能,对手动恢复删除文件、手动修复硬盘损坏具有非常好的效果,得到了国外著名媒体ZDNet Software Library五颗星的最高评价。在数据恢复和电子取证行业内, WinHex的角色有点类似操作系统中的Linux,用户可以利用其强大的脚本与API功能,灵活高效地完成许多数据恢复工作。
虽然现在市面上已经有一些数据恢复的书籍,但大多数图书都是讲解文件系统的原理与数据恢复的实践,对于初学者来说过于理论化、也过于深奥。目前,数据恢复和电子取证行业针对WinHex的培训书籍非常匮乏,很多初学者无法自如地、充分地利用WinHex强大的数据恢复功能,而有志于数据恢复技术研究的人士也不知如何利用WinHex研究文件系统、研究文件结构。本书的推出可谓正逢其时,不仅有助于读者对WinHex的深入理解和实际应用,而且有助于爱好者研究文件系统,从业者开发数据恢复软件,因此,本书的面世对整个数据恢复行业来说都具有不可忽视的、深远的意义。
本书图文并茂、文笔诙谐,读者可以在轻松愉快的氛围中学会使用WinHex提供的各项功能。难能可贵的是,本书并非照本宣科简单介绍WinHex的功能,而是大胆地将WinHex的技术实现过程呈现出来,敢于剖析WinHex所用的算法,这对广大的初学者与数据恢复技术研究者来说都是大有裨益的。本书既授人以鱼,又授人以渔。书中披露的WinHex API 函数与实例运用在数据恢复行业内尚属首次,这些API的说明与实例运用,对于开发数据恢复软件的入门者而言是弥足珍贵的。相信在本书的帮助和引领下,读者在深入地学习和细致地研究之后都能成为数据恢复高手。
从个人喜好来说,数据恢复是一项很有趣的工作——成功会带给你喜悦,失败会带给你思考。当你帮助他人解决了数据丢失带来的痛苦之后,荣耀的光环会让你感到肩头使命的沉重与职业意义的严肃,而这些不是可以用金钱来衡量的。
以行业发展而论,作为计算机产业中的小股力量,经历数十年的跌宕起伏,数据恢复在今天能呈现出“门派林立,百花齐放”的局面,的确令人感到鼓舞。可以看到,无论是声名鹊起的行业骄子,还是初出茅庐的技术学徒,他们都在以自己的热情和智慧为这个行业的持续发展尽力。尽管在当前,数据恢复行业还存在一些良莠不齐的现象,但这些都无碍行业的整体发展与进步。
在这个行业摸爬滚打了十几年,我一直致力于数据恢复技术的推广和教育工作,一手创办了“中国硬盘基地”论坛。今天,它已经成为广大数据恢复爱好者交流技术、展示创新的乐园。能亲眼看到一批批高手的成长历程,是多么惬意的事情。
我认识“困惑的浪漫”是在2008年,那时他在论坛发表的“国内最强WinHex教程”引起了不小的波澜,随后两年里,他乐此不疲地撰写了一系列“WinHex高级专题”文章,以通俗易懂、深入浅出的方式赢得了相当数量会员的青睐。
当我得知 WinHex 教程即将出版的消息时,先是惊讶,而后是喜悦——数年磨一剑,必定锐不可当。令我欣慰的是,该书从数据存储介质分析切入,系统地将 WinHex 功能与数据恢复实际操作结合在一起,并且通过对脚本编程及API函数的深入讲解,极大地拓展了WinHex的用武之地。
其实,任何一个数据恢复技术高手的成长之路,不外乎两条,即“学习”与“解决”。“学习”不仅是通晓、掌握知识,而是要反复思考、反复研究;而“解决”则促使我们在实践中更深入地领会数据恢复的精髓,掌握数据恢复的思维方法,它直接影响到数据恢复的质量和成功率。我敢肯定,本书既可以成为大家“学习”中的良师益友,又能成为大家主动“解决”问题的精神源泉。
更难能可贵的是,本书从不同的角度和视野,力图展示“数据恢复之美”,以激发大家对数据恢复研究的兴趣,而不是将枯燥乏味的专业知识,简单机械地堆砌在人们眼前,平添阅读难度。作者以很低的姿态,以生动有趣的文笔,为迷失在“数据森林”中的读者指明了方向。
学习完本书,相信大家会明白:数据恢复技术的价值绝不仅仅是最后的“工作成果”,而是学习、研究、实践中所付出的心血。只有站在一定高度的人,才可以看到那片美丽的天地!相信“识数寻踪”也必将成为数据恢复行业又一个诗情画意的代名词!
最后,对国内数据恢复先驱者们的无私奉献精神致以崇高的敬意,正是有了他们,整个数据恢复行业才得以不断前行。
中国硬盘基地创始人、站长 山东鑫开天数据恢复研究中心总经理 八喜(田茂帅)
三百万魂湮荒冢,六千枯载征战延;
醉生梦死朝上客,血染狂沙关外人。
恍惚只闻刀剑笑,依稀可辨赤兔吟;
辰卯但求金乌美,奈何呓语秦汉年。
2004年我还是学生,和同学搭伴去玉门游玩,眼见一路戈壁广袤、沙海连天,长城在朦胧中蜿蜒,心潮澎湃却欲语词穷,直到大漠观日,被壮美之景震慑得似醒非醒,秦汉之风、边关战阵跃然于眼,这才按捺不住,仿效古来墨客,写下开篇蹩脚诗一首,表达激动心情,虽然那时的我尚未体会人生之路的曲折艰辛,却也迸发出报效民族的热血豪情,使我在后来的成长中时刻保持一种乐观向上的活力。今天的我在21世纪的中国社会中犹如沧海一粟,但是我希望用自己仅有的一点营养,去滋润这片崛起中的土地。
仔细想想,我接触数据恢复行业已经将近7年,今天的我,虽尚未立业,但也幸福成家,工作稳定,回忆当年初涉职场之忐忑心情,亦可甘之如饴。至于说自身技术,虽有蜕变,仍不足为高人道也。
我常闻关羽自负败亡于蒙,祢衡狂傲获罪于操。乘此英雄辈出之季,我又何尝敢以英雄自居,大放厥词于天下?原只求游弋于论坛社区之腹地,徘徊在论文专利之隘口,写些琐碎文字,期盼同道中人登门指教,以图双赢之效。创作此书,实乃乾坤机缘妙到颠毫所致,更有编辑松峰,愿为伯乐,提携之恩,实不敢忘。厦门虽弹丸之地,却乃藏龙卧虎之城。我所识志伟、云峰、王炜皆良师益友,特邀三人,共谋此出版之事。此刻斗胆提笔,只求别班门弄斧,贻笑大众尔。回忆职业生涯之往昔,北京求学之情景,犹如深痕在脑,历历于目。七日虽短,所学有限,但实有雏鸭下水、幼虎初食之效,在此对当年众恩师三扣愚首,聊表谢意。
“梦里回廊彩幔轻,佳人凝望,白藕衬花裳。珠帘窸窸檀香婉,朱唇戚戚玉剪愁。孤星难黯痴夜色,暖风细语,似嗔寡情郎。天水幽幽君何往,热盼艾艾聚首时。”最近频繁和业内朋友聚餐,发现许多人都过着夫妻两地分居的生活。正如这首《蝶恋花》所述,老婆“棉被冷,空闺寂”,思之切时,难免埋怨。正所谓“忍耐方显伟大”,在此对我们背后默默奉献的家属们表示敬意。
古人云,“既来之,则安之。”选择了某个行业,就要为其穷毕生精力,以求无怨无悔,随之而来的颠沛流离都不在话下。所以同行的兄弟,你们为了崇高的事业理想,天南海北奔波,为了追求更高层次的技术境界,不惜牺牲正常的个人生活,换来的是一页页包罗万象的文字、一行行满富诗意的代码和一个个智慧化成的经济产品。在我眼里,你们是真正的侠士。因为你们都认定:信念才是软化苦涩的蜜糖。
我要说,这本书是众多数据恢复研究人员的智慧果实,我只是把这些成果用恰当的语言总结出来而已,正所谓“尽绵薄之力,全浩瀚之义”,能与大家共勉,是我的责任,更是我的心愿。
主要内容
本书虽以WinHex功能模块为主线,实为数据恢复学习、工作、研究经验之谈,估计难登大雅,权当诸位读者闲暇之时翻看之杂谈随笔,即便无用,也可催眠。
本书共13章,以WinHex菜单为主线,从左到右、从上到下讲述其功能,偶尔穿插一些代码和论述,都是对功能实现原理之揣测,错漏之处,万望海涵,敬请指正,以利修改提高。
第1章:主要讲述一些平时想说却没有平台去说的话,还介绍了两款数据恢复软件及一些数据恢复技术的研究方法。
第2章:从WinHex主界面入手,讲述了一些我们经常用到的功能。
第3章:讲述“文件”菜单包含的各种功能。
第4章:讲解“编辑”菜单中一些特殊功能的用法。
第5章:介绍“搜索”菜单中相关子功能的使用方法。
第6章:简单阐述“位置”菜单的用途。
第7章:重点讲述WinHex模板的语法,并介绍一些范例。
第8章:介绍“工具”菜单中的数据恢复、文件处理功能。
第9章:介绍WinHex的一些高级功能。
第10章:讲解WinHex的各种设置方法。
第11章:学习WinHex的脚本编程方法。
第12章:讲述WinHexAPI函数的使用方法。
第13章:讲述关于MDF文件的案例。
致谢
本书得以出版,感谢厦门大学吴顺详老师的大力支持;感谢中国硬盘基地网站创始人、山东鑫开天数据恢复研究中心总经理八喜大力推荐;感谢挚友王炜对本书所做的审校、排版工作;感谢厦门大学杨伟健、周佳林、张洪国、赵鑫同学为本书提供的部分章节内容和代码;感谢本书作者的亲属,没有你们的支持,我们将无所适从。
注意:全书代码均为实验代码,并非商业程序,旨在描述功能、阐明原理,考虑不周全之处在所难免,恳请广大读者批评指正。
本章为独立一篇,和WinHex并无太大瓜葛。但是如果将其他章节比作皎洁月色,那么本章就是为它们提供光亮的太阳,夜晚虽不可见,却是漫天神采之根源。
通过本章的学习,我们将:
• 发掘数据恢复的职业价值,以坚定自身信念。
• 找到学习数据恢复的最佳途径,以求事半功倍。
• 展望未来,了解数据恢复的发展方向。
• 走马观花地看看我们平时经常用到的两款软件。
• 了解数据恢复研究的方式和方法。
我们整日把数据恢复挂在嘴边,那么何为数据恢复?以往这里必须来一段老生常谈的名词解释,本书却有全新的理解:数据恢复,就是找到有价值的数据而已。为什么说“找到”?因为我们都清楚数据恢复的本质,乃是一种数据定位、检索技术,如果数据真的丢失,任何技术都回天乏力了,正所谓“存在定有迹可循,毁灭必无影无踪。”数据丢失就好比一本书撕掉了目录,如果正文还在我们无论如何都有办法读到想要的内容,但是如果正文也不在了,那我们就丧失了阅读的前提。我们再看个例子:某客户硬盘中目录组织得过于复杂,以至于以他的能力无法找到一个急需的文件,而我们接手后,发现该文件也许没被删除,只是藏得较深而已,于是我们用Windows的搜索功能帮助他找到了文件。我想问,这算是数据恢复吗?答案是肯定的,因为我们帮他找到了文件,而这个文件对他有价值,况且对他而言,并不需要关心我们用了多么复杂的技术,结果才是最重要的。
为什么学习数据恢复?作者也时常问自己。于是,作者经过长时间的调查,基本归纳出以下几个原因。
• 原本从事电脑维修行业,想提升到更高的技术层次。
• 想独自创业,希望付出较低的创业成本。
• 刚毕业的学生想寻找一份上手容易又不失体面的工作。
• 热爱数据恢复行业,希望在成全自己的同时也成全他人。
从道德层面讲,前3个理由都无可厚非。对于从事电脑维修的读者来说,经常会碰到因为硬盘故障、操作系统损坏、客户误操作导致的数据丢失情况。“兵来将挡”,长时间的磨练使相当一部分维修技师已经可以熟练地使用经典数据恢复工具来完成一些简单的数据恢复任务,对他们来说,数据恢复不仅是技术层次的提升,更意味着经济收入的提升,于是他们中的大多数都选择转行为数据恢复工程师,在公司中充当技术骨干角色。对于一部分急于创业的朋友来说,快速培训、二手电脑、面积不大的写字间都是节约资金的理由,数据恢复正好可以满足低廉的创业计划,一个月总有几单业务可以帮助其运转维持。对于刚毕业的学生来说,数据恢复可以作为职业生涯的起步,以便伺机跨入收入更高的行业中。
但是,这些理由听起来又或多或少有些问题,难道数据恢复仅仅是一个转行的跳板吗?当然不是!前3个理由我们都可以归结为“职业价值观”问题。也就是说,直到今天,很多数据恢复从业人员还是既没有合理的职业规划,又不清楚自己手头工作的意义。数据恢复,作为一种灾难拯救手段,把无数客户从劫难中挽救回来,是一项受人尊敬的崇高使命。数据恢复不仅仅是一份工作,还应该成为一项事业。
正所谓“热爱是相互的”,你热爱数据恢复,它自然会加倍地给你回报。数据恢复的原理决定它必须利用许多旁系学科作为基础,从而促进从业者不断学习,这难道不是一种动力?作者身边的数据恢复高手无一例外都是软件高手,就是因为在他们眼里,数据恢复是一种事业,围绕这个事业,他们愿意学习任何有助于数据恢复研究的知识。最终,他们不仅获得了行业地位,也在其他领域建树颇丰,同时获得了相应的经济收入。
当然,也有人说,数据恢复技术过于简单,缺乏技术壁垒,最终会沦为垃圾产业。我必须反驳:那是他们还没有真正窥到“上乘武学之门”。美亚柏科、效率源、ACE 它们哪一个不是坐拥高端技术从而取得了巨大的成功?对不思进取者而言,任何产业都是垃圾产业,都是事业的坟墓。
说了这么多,为什么选择数据恢复行业,对不同的人来说,也许答案并不唯一,我只能说,既然选择了“她”,就好好爱“她”。
我说不需要任何基础未免有些自欺欺人,可是要我详细地列出学习数据恢复所需要的知识,我只能说,一切跟计算机有关的基础知识都要重视,包括数学、英语、微机原理,它们都是不可或缺的,举几个例子来说明。
• 我们经常需要了解国外的最新科研成果,如果没有基本的英语阅读能力,连搜索文献都成问题。
• Fisher 线性判别已经被国外科研人员用于文件碎片分类。如果我们没有概率统计学的基础,别说对其进行研究,恐怕看都看不懂。
• 如果我们要了解PC3000的秘密,不懂微机原理,不懂ATA/SCSI协议,那无异于痴人说梦。
正所谓“霓裳虽美始于宫娥之糙手,灵药神奇源自贫医之素陶”,知识也会从平庸基础累积为华美广厦,所以,请大家重视基础。
在这里,作者也说句大道理:成功=汗水+智慧+运气。小时候听到这个等式总是不以为然,现在越来越体会到其中的深意。汗水就是努力,任何拥有伟大成就的科学家,无一例外都付出了99%的努力,尚未听说哪个人能轻轻松松地取得惊人成就。智慧也是必须的,但是智慧也是在努力中锻炼出来的。有时候我们需要那么一点点运气。拿金庸的武侠小说来说,虽然里面的主人公都是勤奋好学、天赋异禀,却也几乎都通过“奇缘”的方式完成了武学的升华,可见有时候运气还是能起主导作用的。
汗水和智慧“事在人为”,但是运气呢?没有运气,难道我们辛苦所学就都将化作梦幻?虽然我不敢说每个人一生都能碰到一次改变命运的“祥瑞”,但是,我相信毅力会增加碰到好运的几率。居里夫人是偶然提炼出铀的吗?正所谓“学海茫茫孤帆冷,日复一日摇橹人”,耐得住寂寞,学会坚持,学会等待,才能在广阔的知识海洋中找到属于自己的栖身之岛。
中国数据恢复产业可以用“良莠不齐”来形容。一方面,大公司正在加紧领导制定行业规范;另一方面,许多小公司也结成渠道联盟试图正规化。当然,相当一部分小公司仅仅是拿数据恢复当成一种增值业务,没有认真对待。未来是显而易见的,市场将掌握在大公司或技术联盟手中。
值得一提的是,民间数据恢复技术交流正如火如荼地进行,典型的技术社区如中华硬盘基地已经发展成拥有数十万会员的大型论坛,一批技术高手活跃在这座技术舞台上,“八喜”、“甜橙”、“雨荷”、“海云”、Windows Hao、“华山剑客”等都是技术研究的领军人物。
目前数据恢复的学术成果尚不多见,除了戴士剑老师的《数据恢复技术》外,汪中夏老师、刘伟老师、马林老师的专著也在国内外引起很大的反响。
任何学科都有其相应的学习规划。在大学里,老师们按照教育专家们指定的学习规划制定专业的教学任务表,我们只需要跟着老师的引导去学习,就可以按部就班地完成学习任务。但是本节不打算给大家列出一张严谨的课程表,我们仍然还是以轻松的语调,从勤奋、机遇、自爱3个角度谈谈数据恢复的学习方法。
从勤奋的角度,数据恢复倒是有很多的学习方法,也就是我们称之为“笨办法”的办法。
1.好问
多向高手请教是成为高手的捷径。当然,请教也是在自己冥思苦想、头晕脑胀时不得已采取的学习手段,不能所有问题不予思索一概求人,养成懒惰的思考习惯则会取得适得其反的学习效果。
2.多参与技术讨论
讨论是一种平等的学习交流方式,数据恢复重在讨论。一个疑难杂症的解决,往往是众多大脑一起努力的结果。即便讨论没有得出正确的结论,人们也会在讨论中学习到很多其他的知识。
3.总结经验
我们在FAT文件系统的第6个扇区发现了引导扇区备份,在NTFS分区的后面发现了引导扇区备份,于是我们认为Ext2的超级块也有备份……直到我们发现大部分文件系统都为“错误恢复”预留了后路。于是在数据恢复工作中,我们的第一反应就是去寻找备份,这就是一种经验的总结。
拿Windows来说,临时文件几乎成为其安全漏洞。对于可存档的文件,甚至加密后的NTFS,几乎总是可以从临时文件中找到突破口。这也是经验。
对于硬盘被Ghost成一个大分区的案例,我们用类似“分区表医生”之类的软件即可快速解决。这仍然是一种经验。
总之,善于总结的人总是能够高效、高速地完成工作。因为当技术成了一种直觉或习惯,就没什么困难可以阻挡我们了。
4.硬着头皮写技术文章
写技术文章可以帮助数据恢复从业人员巩固自身知识。这跟人类固有的“虚荣心”有一定的关系:技术文章是写给别人看的,为了不闹出笑话,必须严谨对待。
5.抄书
这是笨办法中的较高境界了。古人云,“眼过千遍不如手过一遍”,还是有一定道理的。数据恢复通常靠自学,书中晦涩难懂的知识点必须通过投入高度集中的注意力才能加以理解,所以抄书是集中注意力的一种方法,也是帮助大家迅速熟悉相关行业术语的方法。当然,也有抄书走神的,但是总比瞪眼干看着强多了。
6.实验、再实验
每当我们对前辈大师的研究成果叹为观止时,首先想到的往往是他们过人的智慧。曾经有人认为数学家一晚上就可以推导出几个公式来,这纯粹是一种臆想。很多时候,研究就是不断实验的过程,实验数据积累到一定程度,埋藏的珍宝就会显现出来。当今数学界,很多问题只能依靠计算机来证明,无非是看重了计算机的实验能力,数据恢复何尝不是如此,只要我们不断实验,知识和技能自然与我们“亲近”。
7.多干活
多争取一些数据恢复业务增加自身的实战经验是上上策。
8.尝试编写一些简单的数据恢复实验代码
编写代码的过程就是一个严谨的学习过程,原因很简单:如果有一个环节没搞懂,代码就编写不下去。
1.去大公司
有机会一定要去规模较大的公司待上两年,感受企业的文化氛围,学习规范化的工作流程,这对大家的职业生涯将产生不可估量的影响。当然这需要机遇。
2.一鸣惊人
数据恢复行业中,一鸣惊人的案例也很多,重要客户的重要数据往往成为数据恢复工程师振翅高飞的起点,也会大大增加其学习的信心。这也需要机遇。
3.名师
遇到一位愿意将生平所学倾囊相授的名师。当然,名师不仅技术精湛,而且愿意循循善诱,让我们在不知不觉中发现学习的乐趣。
《阿房宫赋》有云:“灭六国者,六国也,非秦也;族秦者,秦也,非天下也。”一个朝代的灭亡,与其不适宜的政治管理方式密不可分。相应地,个人又何尝不是如此?
1.不要泯灭良心
有的数据恢复公司会恶意破坏客户的数据,迫使客户不得不选择他们。相信天理昭彰、报应不爽,这是行业的耻辱,等待他们的将是冰冷的手铐和远播的恶名。
2.养成良好的习惯,保持身体健康
身体是一切脑力活动的基础。举个最简单的例子,乏力体质的人一般缺乏学习的毅力,如果年纪轻轻疾病就接踵而来,那还谈什么职业生涯。
3.让大脑时刻保持在思考状态
我们经常看到程序高手落寞地坐在电脑屏幕前发呆,可以连续几个小时纹丝不动。大家应当羡慕他们,有这样“上了轨道”的思维习惯,还有什么难题不能攻克?当然,前提是不能为此影响健康。
4.关心家人
关心家人也是自爱的一种。当你孤独地走在异乡凄冷的大街上时,当你满怀希望求职却被无情拒绝时,家人永远是你坚强的后盾。
未雨绸缪,真英雄也。只有牢牢把握一门学科的发展方向,才能在技术大潮中游刃有余,应对自如。
目前,固态硬盘正在不断蚕食存储市场的份额,嵌入式设备又大行其道,FLASH闪存使用量达到前所未有的程度,与之相关的 FLASH 芯片数据恢复业务也如影随形地跟了过来。国内美亚柏科、效率源等公司都在积极研发此类技术。
数据恢复往往不能达到完美的效果,某些时候,只能得到一些残余信息或文件碎片而已。这样就引申出一个关键问题:对客户而言,这些数据是否还存在价值?
实际情况是,90%的客户在即便只有“数据肢体”的情况下,也要将其带回去。
就拿 Word 文档来说,很多情况下,我们可以得到里面的文字而无法保留其格式,客户会惊呼:“天哪!格式也是很重要的,排版花了我几个星期的时间。”可难道因为格式丢失他们就心甘情愿地放弃这些文字内容吗?答案是否定的,他们无一例外会在这些文字的基础上重新排版,因为工作量尚不算太大。但是假设损坏的是一张财务表格,相信大部分人都会非常遗憾,因为丢失了格式的财务数据几乎没有意义,即便我们取出一堆数值,也无法和账套信息精确地对应起来。可是某些客户还是会说:“能否给我报表某列的合计结果,这是目前最重要的。”于是,我们忧心忡忡地从一大堆数据里找出最大的几个数值,或者编写程序对那些较小的数值进行尝试性求和,看看结果最接近哪个值,最终我们找到了最像合计结果的那个数值,客户也许会说:“好像就是这个,这太幸运了。”这单业务也就算起死回生了。此时此刻,真正挽救数据的是残余数据分析技术而并非数据恢复技术。帮助客户从残余数据中提取、总结有价值的信息,就是残余数据分析。
未来,残余数据分析有可能从目前的被动需求变为主动业务,分析范围也不再局限于丢失或残缺的数据。分析方向会因为数据源的行业背景而各不相同,从而衍生出很多行业分支。目前,某些“计算机取证工具”或“财务审查工具”已经开始朝“恢复+分析”这个趋势演变。总之,既然数码世界无限广大,那么残余数据分析的用武之地也同样广大。
“云”概念突然被炒得火热,和大公司的业务竞争密切相关。但是,“云是计算机技术发展的必然趋势”这个观点是不可动摇的。“云”最大的优势并非来源于技术上的变革,而是它加快了“产品到服务”的转型速度。
数据恢复也可以“云”化,但主要是针对“数据恢复软件”而言,互联网上已经出现了“硬盘在线解密服务”的雏形,据说效果尚可。专业数据恢复机构在很长时间内依然拥有存在的价值。
最近几年,文件碎片重组技术得到了人工智能学科的大力支持,很多原本用于模式识别、模式分类领域的“分类器”、“自组织神经网络”、“熵”等概念被引入,大大增强了数据恢复的科技含量。本书中我们也要予以适当关注。
虽然数据恢复工具并不是我们安身立命的根本,但是在很多情况下,它们能帮助我们更有效地完成工作。从计算机的角度看,它们只是拥有特定功能的程序,所以它们只能按照预先设定好的流程来工作,一旦数据环境的复杂程度超出了所能掌控的范围,它们就会失去效力甚至给我们造成一定程度的误导。
有人说,两种数据恢复工具的恢复效果是有差别的,这是当然,因为它们分别代表了不同开发人员的不同思路。大家完全可以凭借自身经验为自己挑选适合的数据恢复工具,当然,做人不能太死板,对于“难啃的骨头”,我们可以找工具去解决。
1.主界面
R-Studio具有相当人性化的界面设计(见图1-1)。其主界面大致分为操作区、属性区和日志区3个部分。操作区负责管理识别到的介质或镜像文件,通过菜单或工具栏向所选介质发送文件系统扫描、创建镜像文件、组织RAID结构等控制命令。属性区负责展示介质或镜像文件的基本信息,如设备名称、设备GUID、设备容量、文件系统参数、IO方式等。日志区负责展示工作中出现的异常现象并以文字的方式提供给用户。
2.扫描
R-Studio具备强大的文件系统扫描功能(见图1-2),可以支持FAT/ExFAT、NTFS、Ext2/3/4、UFS、HFS+等主流文件系统。其扫描原理是逐单位(扇区或簇)搜索文件系统数据结构特征并予以保存,然后根据需要动态解析文件系统重要参数,以求尽可能平衡系统资源。R-Studio 还支持区段扫描,灵活度不言自明。扫描文件系统的同时,R-Studio 仍可以根据文件特征记录文件的存储范围,留作数据恢复终极解决方案。
3.数据编辑器
R-Studio 拥有和 WinHex 类似的数据编辑器(见图 1-3),但功能上不可相提并论。R-Studio的数据编辑器可以实现字节和扇区一级的地址跳转,也拥有一部分模板功能和查找功能。
4.文件展示
扫描结束后,R-Studio 会根据自己所记录的文件系统数据结构特征组织出可能的文件系统方案,一般排在第一位颜色为绿色(见图1-4)的一项是最优方案。
文件以目录树的形式展示(见图1-5),左边主要展示根目录下各父目录的名称,右边主要展示目录内部信息。如果需要恢复文件,我们可以选择数据后右击,选择快捷菜单中的“恢复”命令进行恢复,也可以将需要的文件做好标记,然后统一恢复。
注意R-Studio具有极强的文件归类能力,可以按照类型、时间等进行精确分类。R-Studio提供完整的数据预览功能,可以无需恢复直接预览文档、照片等主要数据。此外,文件展示与数据编辑器模块紧密耦合,可以互相调用、互相影响。
5.高级数据恢复功能
高级数据恢复功能主要指R-Studio的RAID重建功能(见图1-6)。不得不承认,R-Studio已经成为事实上的RAID数据恢复技术领跑者,最新版本的R-Studio不仅对标准化的RAID0、RAID5给予强大的支持,甚至对非标准的各种RAID6也关注甚深。
提到速度,就不得不提起Handy Recovery(见图1-7),该软件操作简单,稍有计算机基础的人在一天内学会其操作也并非难事。Handy Recovery擅长恢复误删除、误格式化的数据。
Handy Recovery支持的文件系统类型有FAT12/16/32,NTFS/NTFS 5 + EFS,HFS/HFS+,为Windows、苹果等操作系统提供了强大的反删除方案。Handy Recovery以快速分区表搜索与虚拟重建功能为主线,使各个模块保持紧凑的状态,化繁为简、运行流畅。
1.选择磁盘分区
选择一个磁盘分区(见图1-8),顾名思义,就是去选择需恢复的对象。
此时我们只需选择分区,然后单击Analyze按钮,就可以进行数据恢复工作,完全是向导式操作。从图1-9中可以看到部分丢失的子目录,只是目录名称无迹可寻,这里软件已经用它自己的方式命名了。
2.分区搜索
搜索丢失分区模块(见图1-10)不仅运行速度奇快,而且可以指定搜索起始位置和结束位置,找到的分区会自动显示在“磁盘选择列表”中。
选择硬盘后,先填入扫描起始位置,再指定扫描区域(可以用滚动条来调节),确定文件系统类型后,单击Start按钮,就可以进行分区扫描了(见图1-11)。
3.文件预览
文件预览是一个非常实用的功能,可以提前知悉文件内容以判断其是否需要恢复。该功能应用了COM组件技术,可以直接调动Word浏览(见图1-12)文档的内容。
研究=研发素材+智慧。任何时候都要牢记这个公式。
第一个问题,在没有数据编辑工具或数据恢复软件的环境中,怎样最大限度地利用操作系统资源收集研究素材?
答案当然是去Windows工具集中寻找,操作系统何其庞大,自带的应用程序五花八门,涵盖了系统管理的各个方面,此外,微软的工程师出于提高开发效率的目的,经常会编写一些小工具,即小巧又实用,我们对此却知之甚浅,不过现在还不算晚,我们至少发现这个叫fsutil(见图1-13~图1-16)的程序可以帮助我们实现对文件系统的初步控制。
第二个问题,如果Windows中实在没有想要的素材,又该何去何从?
这是个好问题,我们的志向肯定不只是想获得一些基本的文件系统信息,有时候,我们希望访问一些“管理禁区”,如文件系统元文件信息,或者每个文件的存储范围。只要会上网,就肯定能找到一个叫“Windows OEM 支持工具箱”的工具集,里面有一款 nfi 程序。其使用方法如图1-17所示。
我们看到,程序不仅列出了相关的驻留和非驻留属性名称,还以逻辑扇区为单位给出了文件在卷中的存储范围。
nfi 可以按照扇区号来显示该扇区所在的文件的信息(见图1-18),包括元文件。
第三个问题,如果凭借一己之力,实在无法解决某些问题,是放弃还是前进?
微软成功的原因之一就在于它给予程序员巨大的技术支持,这些主要体现在MSDN上(国内很多开发人员都没有上MSDN的习惯)。
安装Visual Studio时往往会同时安装MSDN(见图1-19)的一部分内容,包括详细的结构定义、API文档、例程等,已经可以满足我们正常的研究需要。使用MSDN要注意如下3点。
• 其信息量庞大,链接层次较深,有时候通过目录树很难发现自己想要的内容。遇到这种情况,我们可以换种思路。例如,我们需要查找一个函数的使用文档,只要能想起函数的前几个符号,就可以通过“索引”功能来完成。
• 尽量使用MSDN的英文版本。
• 多多参与社区中的讨论。
第四个问题,要是我们的问题过于生僻,整个MSDN中都没有它的踪迹,是否该求教他人。
现在还不到求教的时候,MSDN不能完成,我们就去Windows代码中寻找。当然,Windows是封闭型操作系统,想阅读源码谈何容易,但是WDK我们总能下载,里面包含了Windows开发所需要的所有“头文件”,这些头文件中不仅记录了大量的结构和函数原型,也提供了精彩的注释内容,这些注释往往比MSDN更直观,提供更丰富的信息量。此外,ReactOS和WRK项目间接提供了部分Windows源代码,至少可以借鉴。
第五个问题,要是我们已经无法从Windows中挖出有用的信息,研究岂不停滞?
不会的,天无绝人之路。这时候,“开源”该登场了。开源代码,比如Linux源码(见图1-20),其中包含了几十种文件系统的支持代码。此外,TestDisk也是开源数据恢复工具的典范。
第六个问题,除了阅读开发文档和源代码,是否还有其他捷径?
搞研究永远没有捷径。还有条更难走的路,那就是调试和反汇编。WinDbg 是个不错的调试工具,而 IDAPro 是当仁不让的反汇编大师。当然调试、反汇编都并非破解牟利,而是了解他人设计思路的途径之一。必须指出,任何有法律保护的知识产权都不容侵犯,所以要慎之又慎。
第七个问题,还有更高的研究层次吗?
有,欢迎大家进入学术研究领域。大学生是幸运的,可以通过学校的IP自由下载各大引擎的论文,所以年轻人要抓住这些来之不易的研究环境。最新的数据恢复学术成果往往来自于论文,当然,阅读论文需要扎实的基本功。
以上几个问题,说明了数据恢复研究的基本步骤,其实就是收集研究资料的方法。剩下的就靠我们自己的聪明才智了。当然,这些步骤并非一定按照顺序严格执行,所谓“因人而异”即可随机应变矣。
最近常常听到这样一句自负又略带轻蔑色彩的话:“文件系统我已经掌握了,没什么难的。”乍听起来似乎有那么点道理,文件系统的确影响着数据恢复学科的大多数方面,可当我们真的去向这些所谓的“文件系统专家”求教时,他们可能除了几个数据结构的英文缩写,就什么也说不出来了。即便有不少认真刻苦的朋友通过反复记忆和实战,熟记了 Layout(就是那一张张的表格),也不能说取得了什么非凡的学习成果。
可以说,当前数据恢复行业中流传的文件系统资料仅仅是冰山一角而已,而我们对文件系统的理解,恐怕连这一角都不及。
就拿下面这个结构来说,它有可能成为一个衡量文件系统活跃度的技术核心,从而帮助我们对数据恢复成功率做出预测。可是到今天,作者还不能完全理解每一个结构成员所传递的信息。作者仅仅知道,它是NTFS信息的统计表,记录着自文件系统激活后,其缓存命中、簇分配、元数据访问状况和各种性能计数,至于这些数据究竟反映了文件系统的哪些特性或状态,仍需要长时间大量的研究。其实,光获取这些数据,就费了不少周折,我们不妨加工成一个小小的研究案例。
1.懵懂
根本不知道有文件系统统计信息这个东西。
2.好奇
从fsutil中得知还有文件系统统计信息这个东西,从而产生强烈的好奇。
3.上网去搜
好像没有多少人谈及这个话题。
4.MSDN
找到了这几个结构,具体如下。
typedef struct _FILESYSTEM_STATISTICS {
WORD FileSystemType;
WORD Version;
DWORD SizeOfCompleteStructure;
DWORD UserFileReads;
DWORD UserFileReadBytes;
DWORD UserDiskReads;
DWORD UserFileWrites;
DWORD UserFileWriteBytes;
DWORD UserDiskWrites;
DWORD MetaDataReads;
DWORD MetaDataReadBytes;
DWORD MetaDataDiskReads;
DWORD MetaDataWrites;
DWORD MetaDataWriteBytes;
DWORD MetaDataDiskWrites;
} FILESYSTEM_STATISTICS.
*PFILESYSTEM_STATISTICS;
typedef struct _NTFS_STATISTICS {
DWORD LogFileFullExceptions;
DWORD OtherExceptions;
DWORD MftReads;
DWORD MftReadBytes;
DWORD MftWrites;
DWORD MftWriteBytes;
struct {
WORD Write;
WORD Create;
WORD SetInfo;
WORD Flush;
} MftWritesUserLevel;
……
DWORD UserIndexReads;
DWORD UserIndexReadBytes;
DWORD UserIndexWrites;
DWORD UserIndexWriteBytes;
DWORD LogFileReads;
DWORD LogFileReadBytes;
DWORD LogFileWrites;
DWORD LogFileWriteBytes;
struct {
DWORD Calls;
DWORD Clusters;
DWORD Hints;
DWORD RunsReturned;
DWORD HintsHonored;
DWORD HintsClusters;
DWORD Cache;
DWORD CacheClusters;
DWORD CacheMiss;
DWORD CacheMissClusters;
} Allocate;
} NTFS_STATISTICS,
*PNTFS_STATISTICS;
从文档的链接中发现了FSCTL_FILESYSTEM_GET_STATISTICS这个IOCTL码。
BOOL DeviceIoControl(
(HANDLE) hDevice, // handle to device
FSCTL_FILESYSTEM_GET_STATISTICS, // dwIoControlCode
NULL, // lpInBuffer
0, // nInBufferSize
(LPVOID) lpOutBuffer, // output buffer
(DWORD) nOutBufferSize, // size of output buffer
(LPDWORD) lpBytesReturned, // number of bytes returned
(LPOVERLAPPED) lpOverlapped // OVERLAPPED structure
);
从文档中可得到如下关键信息。
• 要得到具体某一类的文件系统统计信息,必须将文件系统公用统计信息结构和私有结构联合起来使用。
• 为它们分配的缓冲区大小数值必须是64的倍数。如果平台为多核处理器,该数值必须乘以核心数目而产生最终缓冲大小。
5.WDK
前面提到,必须两种结构联合使用,但文档中似乎谈到的细节很少,于是我们打开 WDK,在FILESYSTEM_STATISTICS中看到了这样的话:
The file system’s private structure is appended here.
这是让我们把另一个结构声明在此处,这里选择NTFS_STATISTICS(见图1-21)。
6.编程
似乎所有的障碍都被扫清了,我们尽快开始编码。
PFILESYSTEM_STATISTICS LpFs=
(PFILESYSTEM_STATISTICS)calloc(0x140*4,sizeof(UCHAR));
if (LpFs){
DWORD BackBytesCount=0;
BOOL Ret=FALSE;
Ret=DeviceIoControl(DiskObject,
FSCTL_FILESYSTEM_GET_STATISTICS,
NULL,
0,
LpFs,
//四核,0x140为64的倍数
0x140*4,
&BackBytesCount,
NULL);
if (Ret){
printf("%lu",LpFs->NtfsStatistics.MftReadBytes);
}else{
printf("%d\n",GetLastError());
}
}
当熟悉的控制台窗体弹出便成功了,屏幕上显示出如图1-22所示的结果。
这算是个正常的数值,下面调用fsutil来查看,如图1-23所示。
结果却大相径庭。
7.反汇编
通过反汇编我们发现类似下面的循环。
……
do
{
result += *(_DWORD *)v6;
v6 += a2;
--v4;
}
while ( v4 );
……
该循环往result上累加v6指向的整型数据,而且不断增加v6指针的值。这时我们应当可以想到,该循环在某片连续的内存中按照固定距离间隔依次提取某种数据,而这种数据的和就是结果。我们在编写代码时,曾声明结构指针,并按照CPU的核心数目分配了结构大小4倍之多的内存,这是不是意味着内存中会有4个结构被赋值呢?
于是我们抱着尝试的态度调整代码,具体如下。
PFILESYSTEM_STATISTICS LpFs=
(PFILESYSTEM_STATISTICS)calloc(0x140*4,sizeof(UCHAR));
if (LpFs){
DWORD BackBytesCount=0;
BOOL Ret=FALSE;
Ret=DeviceIoControl(DiskObject,
FSCTL_FILESYSTEM_GET_STATISTICS,
NULL,
0,
LpFs,
0x140*4,
&BackBytesCount,
NULL);
if (Ret){
for (int i =1;i<=3;i++){
(LpFs->NtfsStatistics).MftReadBytes+=
((PFILESYSTEM_STATISTICS)((UCHAR*)LpFs+320*i))->
NtfsStatistics.MftReadBytes;
}
printf("%lu",LpFs->NtfsStatistics.MftReadBytes);
}else{
printf("%d\n",GetLastError());
}
}
功夫不负有心人,终于得到了像样的答案(见图1-24)。
至此,本研究案例介绍完毕。处于教学目的,作者力求将每一个环节都穿插进来,做了一点“人为设计”。
此处,讲文件系统似乎显得有些不伦不类。但是,大家应该仔细看看小节标题。要是大家感到了某种诱惑,请侧耳聆听。
作者曾听到这样的观点:“要是文件系统改变现有模式,默认在删除时彻底销毁文件,数据恢复行业就没有生存空间了。”这里,给大家吃粒定心丸,是永远不会出现那一天的。道理很简单:删除一个200GB的数据库的速度将是不可忍受的。当然,某些文件系统提供了类似的高级功能,但应用起来需要较为专业的知识,不能代表主流。可以说,除非存储器发生革命性的变化,只要文件系统还存在,数据恢复就有其存在价值。
为了进一步勾起大家对数据恢复研究的兴趣,我们以Ext2为饵,完成一次文件系统知识提炼。
1.文件系统存在的意义
有首歌叫《我飞故我在》,翻译成俗语就是“我因为在飞所以我活着”,但是我们不能这样直白地揣测歌曲命名的缘由,应该用广袤的心胸去窥探歌名背后隐藏的意境:似九天之鸿雁,高度方显价值。
文件系统也有其生存意义,我们也不能简单地将文件系统看成是一个存储文件的工具。设计文件系统是浩大复杂的工程,需要考虑到方方面面的影响。
• 尽可能地减少碎片。移动和创建新文件都会导致支离破碎的存储状况,这也为数据恢复带来了不少契机。
• 最有效地利用存储空间。软件的职责之一就是尽可能分担硬件的工作并减少硬件带来的成本。
• 维护文件的健康。这主要体现在,维护文件的一致性。我们不能容忍自己的文件被随意篡改、破坏、毁灭。
• 充分利用磁盘的寻道机制,减少磁头访问时间,提高读写效率。
总之,优秀的文件系统能提升I/O性能,使操作系统运行更加流畅。蹩脚的文件系统会成为计算机的沉重负担。
2.Ext2文件系统的设计目标
Ext2的设计目标如下。
• 专注于高性能,尽可能地为用户带来高速体验。如可变块长特性、快速符号链接技术、无损扩展能力。
• 可恢复性。我们知道,意外断电和系统崩溃都会给文件系统带来影响,有时甚至是破坏性的,故文件系统的自我恢复能力就显得格外重要。可恢复性往往对数据恢复大有裨益。
• 紧凑的代码特征。Ext2的代码总量尚不足10 000行。
3.Ext2的宏观结构
文件系统由大量的数据结构组成。最初设计者希望这些数据结构能存储在内存中,以最大限度地减少磁盘带来的性能损耗。很快,他们发现这并不现实。随着硬盘容量的增大,个人数据量也进入TB时代,如果文件数目众多,就需要大量的数据结构存储其管理信息,这是内存所无法胜任的。所以,硬盘充当了存储文件系统数据结构的重任。Ext2的宏观结构如下。
• 块是Ext2 的基本存储管理单位,大概是因为UNIX 体系中有“块设备”的概念。其实块与NTFS的簇没有本质的区别。
• 块组是文件系统的基本成分。每个块组都包括超级块、组描述符、数据位图、inode位图、inode 表、数据块等其他数据结构。这就引出了一个疑问,如果每个块组都存放如此多的文件系统信息,那岂不是冗余?实际上,这是Ext2的一种安全机制,而且Ext2也并非在所有块组中都保留相同的信息。例如,超级块有可能被破坏,Ext2 就采用了“稀疏超级块技术”,有选择地在某些空余块组中备份超级块的内容。
4.块组的微观结构
• 超级块用于存放Ext2文件系统核心元数据。包括空间使用情况、块大小、文件系统状态、文件系统时间戳、魔数等。
• 组描述符包含了文件系统中各个块组的状态,如空闲块、i节点数目等。
• 数据块位图和 inode 位图。是 bit 位串,类似 NTFS 的位图,用以指示数据块的状态是使用还是闲置。
• inode表包含了块组中所有的inode。
• inode,就是i节点,保存文件和目录的元数据,类似NTFS中的MFT记录。
• 数据块保存文件的数据。
5.Inode
这是我们理解Ext2数据恢复技术的关键。我们知道,inode通常占用128个字节。但是文件大小千差万别,其碎片数量变化莫测,无法保证inode有足够的空间存储其碎片的块号(地址)。所以,inode采用间接块指针技术解决此类问题。
间接块指针技术的核心内容:对于较小的文件,直接存储其碎片的块号。因此inode中有12个“直接块指针”。对于较大的文件,inode只能存储“x次间接块指针”,指向磁盘中的某个区域,而这个区域用于存储文件碎片的块号或下一级间接块的指针。Ext2一共定义了3种间接块指针,每一种都比上一种加深了间接访问的层次。具体可以参考ext2_inode结构,如下所示。
struct ext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
__le32 i_flags; /* File flags */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr; /* Fragment address */
union {
struct {
__u8 l_i_frag; /* Fragment number */
__u8 l_i_fsize; /* Fragment size */
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; /* Fragment number */
__u8 h_i_fsize; /* Fragment size */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; /* Fragment number */
__u8 m_i_fsize; /* Fragment size */
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* OS dependent 2 */
};
我们可以判断出,间接块指针技术与NTFS的“簇流项”技术有所区别。对于较小的文件, MFT记录直接存储文件内容,而对于较大文件,簇流列表直接存储其碎片地址,类似于直接块指针。
6.数据恢复可能性分析
Ext2在删除文件时,调用ext2_delete_inode函数来释放与该inode相关的数据,其中又调用了ext2_free_inode函数,其源码如下。
void ext2_free_inode (struct inode * inode)
{
struct super_block * sb = inode->i_sb;
//是否目录
int is_directory;
//i节点号
unsigned long ino;
struct buffer_head *bitmap_bh;
unsigned long block_group;
unsigned long bit;
struct ext2_super_block * es;
//i节点号从inode结构中获得
ino = inode->i_ino;
ext2_debug ("freeing inode %lu\n", ino);
/*
* Note: we must free any quota before locking the superblock,
* as writing the quota to disk may need the lock as well.
*/
if (!is_bad_inode(inode)) {
/* Quota is already initialized in iput() */
ext2_xattr_delete_inode(inode);
dquot_free_inode(inode);
dquot_drop(inode);
}
es = EXT2_SB(sb)->s_es;
is_directory = S_ISDIR(inode->i_mode);
/* Do this BEFORE marking the inode not in use or returning an error */
clear_inode (inode);
if (ino < EXT2_FIRST_INO(sb) ||
ino > le32_to_cpu(es->s_inodes_count)) {
ext2_error (sb, "ext2_free_inode",
"reserved or nonexistent inode %lu", ino);
return;
}
//得到i节点所在的块组号
block_group = (ino - 1) / EXT2_INODES_PER_GROUP(sb);
//得到i节点位图中相应的bit序号
bit = (ino - 1) % EXT2_INODES_PER_GROUP(sb);
bitmap_bh = read_inode_bitmap(sb, block_group);
if (!bitmap_bh)
return;
/* Ok, now we can actually update the inode bitmaps.. */
//清除i节点位图中相应的bit
if (!ext2_clear_bit_atomic(sb_bgl_lock(EXT2_SB(sb), block_group),
bit, (void *) bitmap_bh->b_data))
ext2_error (sb, "ext2_free_inode",
"bit already cleared for inode %lu", ino);
else
ext2_release_inode(sb, block_group, is_directory);
mark_buffer_dirty(bitmap_bh);
if (sb->s_flags & MS_SYNCHRONOUS)
sync_dirty_buffer(bitmap_bh);
brelse(bitmap_bh);
}
从代码中我们看出,清除inode仅仅是清除i节点位图中相应的bit而已。对inode中块指针等关键数据秋毫无犯。而块位图中也仅仅是清除了相应块对应的bit而已,行为相似。综上所述,我们得出,Ext2的删除文件完全可以被恢复,其难度并未比NTFS大多少。
至于Ext3就没那么容易了,inode中块指针会被清除,但是Ext3多了日志这个重要的“器官”,里面有可能保留着i节点的关键数据。此外,还流传着一些模棱两可的恢复方案,但每一种方案的实施似乎都需要相当好的运气,所以仍需要我们去深入研究,总结出更好的恢复方法。
7.食饵
现在,我们可以闭目静思各个文件系统之间的差异了。
• 在NTFS中发现了位图,在Ext2中发现了i节点位图和块位图,似乎HFS+中也有个“分配文件”在碍事。
• 我们在NTFS中学会了“簇流项”,在Ext中掌握了块指针和间接块指针,仔细翻翻资料,发现HFS+中有“数据叉结构”。
这说明了什么?继续思考吧!相信大家已经上钩食饵,不仅有所回味,更打开了一扇思维之门。
现成的数据恢复源码对我们来说真如久旱之甘霖。的确,源码可以最直观、清晰地反映开发者的设计思路。
Kickass Undelete 是一个少见的开放源代码的数据恢复软件,用C#语言编写而成,支持FAT和NTFS两大文件系统,目前已经更新到Kickass Undelete_1.3_beta版本(见图1-25)。
获取它的源代码需要登录sourceforge.net网站所属的SVN服务器。最简单的方案就是,下载SVN客户端工具,然后打开Checkout界面。这里推荐使用TortoiseSVN。
按照图1-26所示输入网址链接和Checkout目录,单击OK按钮开始获取过程,如图1-27所示。
下载完成后,大家就可以从中攫取养分了。不过,阅读源码是枯燥而繁琐的工作,需要有正确的阅读思路,不然很容易心浮气躁,这里提供一些阅读经验。
首先,如果可以编译并生成软件,应当首先熟悉其使用方法。只有这样,我们才能在脑海中构造出大概的模块组织情况。
其次,应该阅读它的 README 文件,了解软件的设计目的、详细功能、历史版本、安装方式等。
再次,阅读源代码提供的使用示例或API文档。
第四,如果发现MakeFile之类的编译脚本文件,应该首先阅读。了解源代码的编译、链接顺序,以及代码文件的组织结构。
第五,阅读应从程序入口处开始(如main函数),从上到下逐行阅读。遇到注释千万不可走马观花,要仔细研读。对命名较为清晰的变量,要大致判断其任务和生存期。有的函数凭借其名称就可以了解其大致功能,有的函数则需要深入跟踪,分析调用层次。有很多专业的IDE环境或代码阅读工具可以辅助我们有效地完成阅读任务。
第六,对于完整的代码工程,我们应当利用开发环境加载整个项目,然后依照调试的方式跟踪软件行为。
以 Kickass Undelete 为例,我们不妨一起追踪其执行流程,但因篇幅有限,不可能讲解整个项目纳入讲解范围,只能提供思路,将大家的思维引入正轨。
1.MainForm_Load
既然我们已经知道Kickass Undelete由C#语言开发,而且拥有WinForm界面,并在启动时就获取了分区卷标、分区类型等卷信息,那么按照常理,其初始化操作应当位于Main函数、Form类的构造函数或主界面的Load事件中。经过跟踪,我们终于发现了感兴趣的内容:MainForm_Load作为Load事件,调用了函数LoadLogicalDisks。
private void MainForm_Load(object sender, EventArgs e) {
LoadLogicalDisks();
}
2.LoadLogicalDisks
从字面理解,该函数必定与打开逻辑分区行为有某种关联。从代码中我们看到一个 foreach循环,它从DiskLoader.LoadLogicalVolumes()返回的结果中获取LogicalDisk类型的引用,然后将其纳入树形控件中。
private void LoadLogicalDisks() {
foreach (LogicalDisk disk in DiskLoader.LoadLogicalVolumes()) {
TreeNode node = new TreeNode(disk.ToString());
node.Tag = disk;
node.ImageKey = "HDD";
if (disk.FS == null) {
node.ForeColor = Color.Gray;
}
diskTree.Nodes.Add(node);
}
}
3.LoadLogicalVolumes
LoadLogicalVolumes 函数是软件获取分区信息的关键所在。可以看出,它利用了 WMI 查询技术,并将查询结果作为LogicalDisk类型构造函数的参数传入。
public static List<LogicalDisk> LoadLogicalVolumes() {
List<LogicalDisk> res = new List<LogicalDisk>();
try {
ManagementScope ms = new ManagementScope();
ObjectQuery oq = new ObjectQuery("SELECT * FROM Win32_LogicalDisk");
ManagementObjectSearchermos=newManagementObjectSearcher(ms,oq);
ManagementObjectCollection moc = mos.Get();
foreach (ManagementObject mo in moc) {
try {
LogicalDisk disk = new LogicalDisk(mo);
res.Add(disk);
} catch { }
}
} catch { }
return res;
}
4.LogicalDisk
LogicalDisk 函数根据 WMI 返回的信息获取分区句柄和分区大小,并将自身类的引用传入FileSystem.TryLoad函数的参数中。
public LogicalDisk(ManagementObject mo) {
Attributes = new LogicalDiskAttributes(mo);
Handle = Win32.CreateFile(@"\\.\" + Attributes.DeviceID, EFileAccess.GenericRead, EFileShare.Read | EFileShare.Write | EFileShare.Delete, IntPtr.Zero,ECreationDisposition.OpenExisting, EFileAttributes.None, IntPtr.Zero);
if (Handle.IsInvalid) {
throw new Exception("Failed to get a handle to the logical volume." + Marshal.GetLastWin32Error());
}
m_Size = Util.GetDiskSize(Handle);
m_fileSystem = FileSystem.TryLoad(this as IFileSystemStore);
}
5.FileSystem.TryLoad
FileSystem.TryLoad函数有两个switch,在按照分区类型声明相应的引用类型。这里我们只关注FileSystemNTFS。
public static FileSystem TryLoad(IFileSystemStore store) {
if (store == null || store.StreamLength == 0) return null;
switch (store.StorageType) {
case StorageType.PhysicalDiskPartition: {
PhysicalDiskPartitionAttributes attributes =
(PhysicalDiskPartitionAttributes)store.Attributes;
if (attributes.PartitionType == PartitionType.NTFS) {
return new FileSystemNTFS(store);
} else if(attributes.PartitionType == PartitionType.FAT16
|| attributes.PartitionType == PartitionType.FAT32) {
return new FileSystemFAT(store, attributes.
PartitionType);
} else if (attributes.PartitionType == PartitionType.
FAT32WithInt13Support) {
return new FileSystemFAT(store, PartitionType.FAT32);
} else {
return null;
}
}
case StorageType.LogicalVolume: {
LogicalDiskAttributes attributes = (LogicalDiskAttributes)
store.Attributes;
if (attributes.FileSystem == "NTFS") {
return new FileSystemNTFS(store);
} else if (attributes.FileSystem == "FAT16") {
return new FileSystemFAT(store, PartitionType.FAT16);
} else if (attributes.FileSystem == "FAT32") {
return new FileSystemFAT(store, PartitionType.FAT32);
} else {
return null;
}
}
default:
return null;
}
}
6.FileSystemNTFS
FileSystemNTFS 函数比较重要,它首先将 IFileSystemStore 类型的引用传到本类的字段中,然后执行LoadBPB函数,从名字就看出来它是在访问DBR的本分区参数记录表。得到相关数据后,它用MFT起始簇号和每簇扇区数相乘,得到MFT的起始扇区号。最后,将本类自身引用传入MFTRecord.Create中。这里要注意,程序一共执行了3次MFTRecord.Create,传入的记录号分别为0、5、6,分别代表$MFT、$Root、$Bitmap的MFT记录。
public FileSystemNTFS(IFileSystemStore store) {
Store = store;
LoadBPB();
m_mftSector = (BPB_MFTStartCluster64 * BPB_SecPerClus);
m_MFT = new FileNTFS(MFTRecord.Create(0, this), "");
m_Root = new FolderNTFS(MFTRecord.Create(5, this), "");
m_bitmapFile = new FileNTFS(MFTRecord.Create(6, this), "");
}
LoadBPB函数如下。
private void LoadBPB() {
byte[] bpb = Store.GetBytes((ulong)0x0B, (ulong)BPB_SIZE);
BPB_BytsPerSec = BitConverter.ToUInt16(bpb, 0);
BPB_SecPerClus = bpb[2];
BPB_SecPerTrk = bpb[13];
BPB_NumHeads = bpb[15];
BPB_HiddSec = BitConverter.ToUInt32(bpb, 17);
BPB_TotSec64 = BitConverter.ToUInt64(bpb, 29);
BPB_MFTStartCluster64 = BitConverter.ToUInt64(bpb, 37);
BPB_MFTMirrorStartCluster64 = BitConverter.ToUInt64(bpb, 45);
byte b = bpb[53];
if (b > 0x80) {
BPB_SectorsPerMFTRecord = (ushort)(Math.Pow(2, Math.Abs(256 -b)) / BPB_BytsPerSec);
} else {
BPB_SectorsPerMFTRecord = (ushort)(BPB_SecPerClus * b);
}
BPB_SerialNumber = BitConverter.ToUInt64(bpb, 57);
}
7.MFTRecord.Create
MFTRecord.Create函数先按照MFT编号得出MFT记录的具体地址。当编号为0或其他数值时(也就是MFT本身的记录),分别得到两种SubStream类型的引用(它的作用是保留MFT记录的数据流)。该函数似乎还检查了NFT记录标识。最后,返回了MFTRecord类型的引用。
public static MFTRecord Create(ulong recordNum, FileSystemNTFS fileSystem, bool
loadData) {
ulong startOffset = recordNum * (ulong)fileSystem.SectorsPerMFTRecord* (ulong)fileSystem.BytesPerSector;
IDataStream stream;
//Special case for MFT - can't read itself
if (recordNum == 0) {
stream = new SubStream (fileSystem.Store, fileSystem.MFTSector* (ulong)fileSystem.BytesPerSector, (ulong)(fileSystem.SectorsPerMFTRecord *fileSystem.BytesPerSector));
} else {
stream = new SubStream(fileSystem.MFT, startOffset, (ulong)(fileSystem.SectorsPerMFTRecord * fileSystem.BytesPerSector));
}
string Magic = Util.GetASCIIString(stream, 0, 4);
if (!Magic.Equals("FILE")) {
return null;
}
return new MFTRecord(recordNum, fileSystem, stream, loadData);
}
8.MFTRecord
MFTRecord 函数向 MFTRecord 的字段传入记录编号、每扇区字节数等参数。最重要的是,它保留MFT记录的流的引用变量m_Stream,然后调用了LoadData函数。
private MFTRecord(ulong recordNum, FileSystemNTFS fileSystem, IDataStream stream,
bool loadData){
this.RecordNum = recordNum;
this.FileSystem = fileSystem;
this.BytesPerSector = fileSystem.BytesPerSector;
this.SectorsPerCluster = fileSystem.SectorsPerCluster;
this.PartitionStream = fileSystem.Store;
m_Stream = stream;
Flags = Util.GetUInt16(m_Stream, 22);
if (loadData) {
LoadData();
}
}
9.LoadData
LoadData函数首先从MFT记录流中获取了更新序列号数组,然后声明了FixupStream类型的实例。最后它调用了LoadHeader和LoadAttributes两个函数。
private void LoadData() {
if (m_DataLoaded) return;
m_DataLoaded = true;
ushort updateSequenceOffset = Util.GetUInt16(m_Stream, 0x04);
ushort updateSequenceLength = Util.GetUInt16(m_Stream, 0x06);
ushortupdateSequenceNumber=Util.GetUInt16(m_Stream,updateSequenceOffset);
ushort[] updateSequenceArray = new ushort[updateSequenceLength - 1];
ushort read = 1;
while (read < updateSequenceLength) {
updateSequenceArray[read - 1] = Util.GetUInt16(m_Stream, (ushort)(updateSequenceOffset + read * 2));
read++;
}
FixupStreamfixedStream=newFixupStream(m_Stream,0,m_Stream.StreamLength,updateSequenceNumber, updateSequenceArray, (ulong)BytesPerSector);
LoadHeader(fixedStream);
LoadAttributes(fixedStream, AttributeOffset);
if (Attributes.Count == 0) {
//throw new InvalidFILERecordException(FileSystem, fixedStream.DeviceOffset, "MFT record had no attributes.");
}
}
LoadHeader函数用于获取MFT记录头部的数据。
private void LoadHeader(IDataStream stream) {
record_Magic = stream.GetBytes(0, 4);
record_Ofs = Util.GetUInt16(stream, 4);
record_Count = Util.GetUInt16(stream, 6);
LogSequenceNumber = Util.GetUInt64(stream, 8);
SequenceNumber = Util.GetUInt16(stream, 16);
record_NumHardLinks = Util.GetUInt16(stream, 18);
AttributeOffset = Util.GetUInt16(stream, 20);
Flags = Util.GetUInt16(stream, 22);
BytesInUse = Util.GetUInt32(stream, 24);
BytesAllocated = Util.GetUInt32(stream, 28);
BaseMFTRecord = Util.GetUInt64(stream, 32);
NextAttrInstance = Util.GetUInt16(stream, 40);
Reserved = Util.GetUInt16(stream, 42);
MFTRecordNumber = Util.GetUInt32(stream, 44);
}
LoadAttributes函数用于遍历属性列表,其内容比较复杂,请大家自行查阅。
为我们的发现做个总结:Kickass Undelete的启动过程并非弹出一个窗体那样单纯。它承担了获取介质、分区信息、获取主要元文件重要属性的大量工作。别小看这句话,以后我们自己设计数据恢复软件的时候,就可以借鉴这种模式。
对于本书,Kickass Undelete的任务已经完成,但是,源码迷宫中的一条路尚未走完,对于理解整个项目还远远不够。如果大家感到吃力,就应该采用代码调试的方法,逐步执行并跟踪每一条语句。
图书在版编目(CIP)数据
识数寻踪:WinHex应用与数据恢复开发秘籍/高志鹏,张志伟,孙云峰编著.--北京:人民邮电出版社,2013.1
ISBN 978-7-115-29721-1
Ⅰ.①识… Ⅱ.①高…②张…③孙… Ⅲ.①数据管理—安全技术—软件工具 Ⅳ.①TP309.3
中国版本图书馆CIP数据核字(2012)第248198号
内容提要
全书根据WinHex菜单来划分章节,详细描述了WinHex的全部功能和使用方法,对于那些晦涩难懂的知识点,利用编写实验代码的方式直观地展示其原理。本书还揭示了WinHex 脚本编程及WinHex API函数的秘密,这在相关图书中是很难得的。最后,本书以SQL Server 数据库页组合技术为案例回顾了部分所学内容。
本书以WinHex的功能模块为线索,看似讲述操作技法,实则探讨数据恢复技术的研究思路,旨在拓宽读者视野,引发读者的兴趣,适合数据恢复工程师、数据恢复程序员、数据恢复研究人员、高校教师、电子取证工程师、技术支持工程师等读者阅读。
识数寻踪:WinHex应用与数据恢复开发秘籍
◆编著 高志鹏 张志伟 孙云峰
责任编辑 王峰松
◆人民邮电出版社出版发行 北京市崇文区夕照寺街14号
邮编 100061 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
北京昌平百善印刷厂印刷
◆开本:787×1092 1/16
印张:20.25
字数:531千字 2013年1月第1版
印数:1-3000册 2013年1月北京第1次印刷
ISBN 978-7-115-29721-1
定价:49.00元
读者服务热线:(010)67132692 印装质量热线:(010)67129223
反盗版热线:(010)67171154