常见电子书格式及其反编译思路分析第2/3页

2 2 2 1 Web Compiler 1 67 这种格式的电子书,因为其制作工具在国内出现得比较早,而且有非常彻底的汉化解密版,所以曾经比较流行,E书时空

2.2.2.1 Web Compiler 1.67 
这种格式的电子书,因为其制作工具在国内出现得比较早,而且有非常彻底的汉化解密版,所以曾经比较流行,E书时空提供的很多电子书都是这种格式。不过也正因为它的流行,导致想反编译它的人也多,引出了各种反编译工具,所以现在用的人似乎已经不多了。 

反编译工具里,收费的就不去说它了,国内RMH和Fbilo还联合推出过免费的unwebcompiler,并且提供全套的Delphi源代码,有需要的到google或百度搜索一下unwebcompiler就有了。不过可能国内大多数软件网站的管理员都不是开发人员出身,对源代码不感兴趣,所以收藏的都是212 KB的EXE,有源代码的不多,需要仔细找一下。 

在unwebcompiler的源代码里,RMH和Fbilo对Web Compiler 1.67生成的电子书的文件格式进行了详细描述,在这里我就不做无聊的重复,有兴趣就自己去看吧。我做的UnEBook也使用了他们提供的源代码,实现对Web Compiler 1.67生成的电子书的批量反编译,不过被我将代码从Delphi改成了C,似乎长度缩短了一些(原代码中有一段在字符串和十六进制数之间转换来、转换去,看起来比较怪异,被我省了),不过LHA解压缩部分改起来实在太麻烦,我直接在网上找了一段现成的C代码来用。 

2.2.2.2 Caislabs eBook Pack Express 1.6 
这个电子书制作工具也出过汉化版,所以在国内也有一定影响,不过这种影响似乎还没有大到足以使反编译工具满天飞的程度,嘿嘿…… 

在分析这种格式的电子书的时候,我没有使用任何反汇编工具,用UltraEdit32和系统监视工具就猜出来了: 

文件标识:以十六进制串 00 F8 03 00 结尾。这个似乎是一种惯例,差不多所有EXE格式的电子书都有自己特殊的文件结尾。  
目录块起始地址指针:0003F81C  
目录块中目录项结构:以0字符结尾的文件名+4字节起始地址,文件名起始字节为FF则目录块结束。  
如果文件存放在子目录里,则文件名首字符:02=../,01:第一个00变成/,直到遇到02。  
文件内容实际起始地址:目录项里的4字节起始地址+9  
文件内容长度:目录项里4字节起始地址所指内容,DWORD。  
在分析出目录结构后,我曾经想通过调试工具,分析文件加密算法,再反编译出具体的文件内容,但是很快我就发现那样干太累了,实在是得不偿失。 

不过在经过几次尝试后,我还是找到了一个偷懒的办法: 

通过安装hook的方法,往电子书的进程空间注入一个DLL。  
在这个DLL里,用Windows标准的API函数URLDownloadToFile,就可以下载到指定的文件。文件的URL可以按前面说的方法,从目录项得到相对路径,再加上一个固定前缀("file://Z:\\com_caislabs_ebk\\")构成绝对路径。  
UnEbook在批量反编译这种格式的电子书的时候,就是按照上面的分析结果实现的。 

不过到了更高版本的Caislabs eBook Pack Express的时候,似乎Caislabs公司也开始意识到文件内容保护的重要性,因此不仅对文件内容采用更强的加密算法,杜绝了可以用URLDownloadToFile下载的漏洞,连目录块的加密强度都强到足够使我不想去分析了。幸好这个时候我已经有了更好的反编译思路--与具体文件格式无关的,专门针对使用IE内核的电子书的通用反编译思想。 

2.2.2.3 通用反编译思路 
在分析过几种电子书格式后,我开始领悟到一个真理:电子书内部文件结构的变化是无穷的,而我的时间和精力是有限的;把有限的时间和精力投入到对抗无穷的变数中去,早晚会有累死的一天。 

有此认识后,我开始思考有没有什么通用的方法,可以解决大部分电子书的反编译问题(我还没有幼稚到相信这世上会有万能药的程度)。按照惯例(不可救药的职业病),第一步当然是市场调查、产品定位,结论是目前大多数电子书都是基于IE内核的,但是根据我在开发MyReader时对IE内核的了解,这里面明显存在一个误区:微软以控件的形式提供IE内核,其目的就是希望通过控件接口的开放性、方便性,吸引更多的人加入微软的标准阵营,如果想在此基础上添加加密、保护等等内容,恐怕与微软的初衷不合(我说的是当时,以后微软改主意了也说不定)。因此我相信IE内核一定有后门可走!经过一番努力,果然没有令我失望。 

1、基本原理 

针对IE内核电子书的通用破解技术实现起来可能需要一些技术和技巧,但是原理却很简单,几句话就可以说清楚:不论电子书在存储的时候如何对内容进行加密,在将内容传递给IE内核进行显示的时候,一定要将内容转换成IE内核能够识别的标准格式--HTML格式。而IE内核为了便于显示、刷新,在对HTML代码进行解析后,并不是立刻就把这些HTML代码抛弃,而是在内存里保存了一份备份。因此只要将这份备份从IE内核里搞出来,就得到了解码后的内容,也就是反编译想得到的内容。 

至于网页中的其它内容,包括图片、css、js、Flash文件等,就更简单了:模拟IE内核,直接找电子书要就好。如果电子书分辨不出请求是来自IE内核还是来自其它地方,自然会乖乖把我们需要的东西双手奉上! 

虽然反编译的原理几句话就可以说清,但是要加以实现,还需要经过艰苦的探索和试验,我自己就经过了长期的努力,IE内核的源代码都翻来覆去看了好几遍(吹的,别当真!)。而我思想的发展也大概经历了两个阶段:第一个阶段是在得到某份传说中的源代码(没错,就是那份展开后近700MB,被国内主流媒体形容为噱头、无足轻重、充满无聊垃圾的东西)之前,完全立足于微软公开的IE内核接口。当时我考虑将电子书内容按照HTML、图像等分类,分别解决获取问题。第二个阶段是在得到那份源代码之后,我突然发现其实对于所有文件,我都可以直接找电子书要,只要假装是IE内核在要就行了。 

由于某些东西比较敏感,因此下面叙述的主要是我第一个阶段的想法,其中有些属于基础性的东西。第二个阶段的实现恕我不便奉告。 

2、获取HTML源代码的方法 

从IE内核获取HTML源代码的方法不仅我一个人在想,从国内到国外,从CSDN(CSDN的VC/MFC区有一个栏目专门讨论IE内核编程)到MSDN,早就有很多人讨论过了,归纳起来,一般认为可以通过下列步骤实现: 

不管是通过鼠标点击也好,通过EnumChildWindow也好,总之先找到IE内核的显示窗口,也就是电子书显示网页内容的那个窗口。  
通过这个窗口的句柄(HWND),取得这个窗口对应的IE内核文档接口IHTMLDocument2的接口指针。取得的方法目前认为有两种,我个人认为这两种需要结合使用,否则总有一些电子书会搞不定:一个是通过MSAA,一个是通过WM_HTML_GETOBJECT消息。至于具体的实现代码,在CSDN上都快被讨论烂了,因此此处从略,有需要的自己到CSDN上找。不过这两种方法都对平台有要求:XP下是完全没有问题,2000下可能需要装IE 6,98/Me/NT就不要想了。  
在得到IHTMLDocument2接口指针后,按照这个接口提供的标准方法,即可获得文档的HTML代码。具体实现代码见CSDN中的例子。  
除了上面这种方法外,我自己还尝试过一种方法:使用MIME Filter。 

对于搞过网页在线翻译、网页内容过滤的人来说,MIME Filter可是吃饭的本钱,它的作用和实现机理应该早就烂熟于心,但是对于其它人来说,可能还不是很熟,所以这里简单介绍一下:为了便于对IE内核的功能进行扩展,微软规定在IE内核显示某种标准格式(HTML、TEXT等)的内容之前,会先将要显示的内容传递给这种格式的过滤器,即MIME Filter,由它先对内容进行预处理(如将英文翻译成中文,将下流文字替换成星号等),然后再显示。 

按照这个原理,如果实现一个针对HTML格式的MIME Filter,即可拦截到最原汁原味的HTML代码。可惜,经过我的尝试,这招对IE本身是灵的,对某些电子书也有效,但是对另一些无效。再加上使用IHTMLDocument2接口指针的方法要比这种方法简单得多,也可靠得多,所以后来在我开发的反编译工具KillEBook、IECracker和CtrlN里就没有使用这种方法。不过这种方法也有一个好处:与平台无关,我在98/Me/2000/XP下都试过,当然都是在虚拟机下试的啦。 

MIME Filter的作用机理、实现方法在MSDN里有详细说明,并提供了详细的实例代码,有需要的可以到MSDN上搜“MIME Filter”。 

3、获取图像的方法 

与HTML代码相似,IE内核对图像的处理也有一个“下载->解码->显示”的过程。考虑到显示代码的抽象性,原来各种各样的图像格式,包括JPG、GIF、PNG、TIFF等,在解码后都被统一表示成位图格式,而原有格式数据在解码后即被从内存中释放,只在IE的cache中留有文件备份。如果指定不允许保存本地cache,则连这个备份都没有。在IE中通过右键菜单选“图片另存为...”的时候,其实就是将cache中的文件备份拷贝一份出来,如果cache中已经没有备份,就只能保存内存中的位图(*.bmp)了。现在明白为什么有些图片明明是jpg格式,但是用IE却只能保存为“无标题.bmp”了吧? 

因此,获取图像文件要比获取HTML文件难得多。而且在MSDN里说得很清楚,用IHTMLDocument2接口只能得到图像的链接,用MIME Filter也不能搞到网页里的图像数据,因此需要另想办法。我想过、试过的包括: 

先将图像复制到剪贴板,再从剪贴板里获取图像数据,然后根据图像文件扩展名(可以从图像元素的URL里解析),编码成原始图像格式,包括jpg、png、gif、tiff等。这个方法实现比较简单,到MSDN KB里搜索Q293125,拷贝图像到剪贴板的现成源代码就有了,图像编码的源代码则可以参考cximage,这个也是google一下就有的。不过这个方法远非完美无缺:a). 对于png、gif等允许带透明背景的格式,用这种方法处理后就不透明了。b). gif动画处理后就动不起来了,只能显示其中的某一帧。c). 对于jpg这样的有损压缩格式来说,每压缩一次就损失一次,多压缩几次可能就没法看了。d). 在电子书里,可以通过标准的Windows API函数,使剪贴板失效。  
将IE内核导航到图片,然后通过IViewObject接口获取图片的拷贝。这个方法与上面的方法基本相同,不过不通过剪贴板,可以防止因为剪贴板被封锁而搞不到图像。  
使用IE图像解码插件。IE内核在下载到某种格式的图像文件后,会调用对应的解码器,对图像进行解码(类似于MIME Filter)。为了便于扩充,解码器是做成插件形式的。如果自己做一个图像解码器插件,对解码请求进行拦截,即可获得解码前的原始图像格式数据。解码器的接口、实现方法在微软公开文档中没有任何蛛丝马迹,但是在那份传说中的源代码里,不仅有详细的接口规范,而且有好几个内嵌图像解码器的实现代码,可供借鉴。奇怪的是,虽然在MSDN中找不到,但是我在google上搜的时候,却发现有一个日本人在自己的个人网站上,早就给出了详细的图像解码器插件实现方法,一步一步说得很清楚,而且落款时间是2002年12月!看来这份源代码的泄漏时间可能比想象的要早。当然这个日本人也可能本来就在微软工作,或与微软有合作关系,可以光明正大地查看解码器源代码也说不定。  
4、通用反编译器的实现 

在解决了HTML、页面元素等的获取方法后,通用反编译器KillEBook的实现就很简单了,其算法可以描述如下: 

打开电子书。  
定位电子书的显示窗口。  
获取当前显示页面的HTML代码。  
解析页面HTML代码,得到其中的所有链接。  
获取页面上的所有元素内容,包括图片等。  
引导IE内核依次加载HTML链接页面。  
重复步骤3~6,直到所有页面及其中的元素都已获取到。  
5、进一步讨论 

在完成KillEBook后,我发现其实对它扩展一下,就可以成为一种新的离线浏览器,解决传统离线浏览器(Offline Explorer Pro、Webzip等)面临的一个问题:传统离线浏览器多半与IE内核没什么瓜葛,因此在抓静态网页的时候都没有什么问题,但是在抓用session维持的动态网页时,都有点问题,更不用说抓需要PKI证书验证的HTTPS网站。 

因此我考虑可以实现这样一个离线浏览器: 

提供一个地址栏供用户输入起始URL。  
内嵌一个微软web browser控件(IE内核),供用户交互,包括在网页上输入用户名/密码、从IE证书库中选择证书。  
用户登录成功、进入需要开始抓取的网页后,设定递归深度、URL过滤条件,点“开始”按钮开始抓取。  
离线浏览器自动引导web browser进入每个页面,每进入一个页面,都通过web browser控件获取客户端HTML源代码及页面元素,包括图片、css、js、flash等。  
采用这种方法实现的离线浏览器,由于使用web browser控件,因此可以维持客户端session,抓取到动态网页。虽然网页抓取下来就成了静态的,但是对离线浏览来说应该不成问题,对付收费的网上教育等网站正好合适。 

2.3 HLP格式 
这种格式出现得比较早,在16位Windows(Windows 95以前的各Windows版本)下曾是标准的帮助文件格式,因此大概也算是Windows下出现得最早的电子书格式之一了。 

由于这种格式比较流行,国外研究的也比较多,不过公开源代码的我似乎只见过一个HELPDECO v2.1。这个软件是一个控制台程序,因此有人做了一个GUI外壳DuffOS对它进行封装。国内有人对HELPDECO进行过汉化,到汉化新世纪搜索一下就可以找到,包括全部源代码。 

在UnEBook中使用了HELPDECO的源代码,实现对HLP文件的批量反编译。不过从我使用的情况看,原版HELPDECO有一个小小的不足:反编译出来的RTF文件没有指定字符集。这对英文RTF来说没有任何影响,但是对中文RTF来说,其影响足够强到使您打开RTF后看到的是一堆乱码。它的修正方法有两个: 

用文本编辑器打开反编译出来的RTF文件,手工指定中文字符集。这个是一种比较累的方法。  
修改HELPDECO源代码,加上字符集修正,这个是一劳永逸的办法。但是不知道为什么,在汉化新世纪推出的汉化版上,我看到的还是原版的HELPDECO。看来汉化者只是用它反编译过英文HLP,没有反编译过中文HLP。  
另外这份源代码还有一个不知道算不算是严重的问题:变量没有统一初始化、释放,因此不仅在程序退出的时候,VC++会报告有内存漏洞,而且就象当年的DOS内核一样,几乎没有可重入性。我曾经试图修复这个bug,但是在经过一个下午的奋斗后,有两处泄漏死活找不到。最后我还是决定向DuffOS学习:将HELPDECO代码封装成一个独立的DLL,每反编译一个HLP文件,都动态加载、释放一次DLL。这样一方面可以利用Windows本身的DLL管理机制,弥补HELPDECO产生的内存漏洞,一方面解决不可重入问题。收费的“耶书制造”软件提供的HLP反编译功能也是用DLL文件实现的,因此我严重怀疑它的作者可能也曾遇到过相同的烦恼,嘿嘿嘿…… 
标签: 电子书 反编译