前一节《EBT 道客巴巴的加密与破解 序章》粗略讲了一些关系DOC88文档下载的一些枝节,主要是通过Chrome等工具来开启VIP模式,打开道客巴巴文档内容复制和打印功能,这一篇开始将进行更深入的研究。
还是以《Soilless Culture- Theory and Practice》为例:http://www.doc88.com/p-362142082976.html
DOC88文档页面中的每一页内容都由JavaScript代码和ActionScript代码两部分功能结合显现的,JavaScript部分主要是由一个称为Viewer的类来实现DOC88档案的加密解密,并产生页面的FLASH嵌入代码,包括了页面内容ebt文件的服务器端地址。而ActionScript部分则主要是通过pv.swf这个FLASH文件来实现页面内容的展示,同时它还需要为文档页的内容进行解密,这就是本文的重点内容。
先通过Chrom浏览器,FireFox也可以,取得网页的一断代码:
<object type="application/x-shockwave-flash" data="http://assets.doc88.com/assets/swf/pv.swf?v=1.7" width="100%" height="100%" id="pageflash_0" style="visibility: visible;">
<param name="hasPriority" value="true">
<param name="wmode" value="transparent">
<param name="swliveconnect" value="true">
<param name="FlashVars" value="hn=1&ph=http://ebt246.doc88.com/getebt-0rUXzqMX2LkT0qEd1qEd0jPR0jP50qEd0jPR0jP50qEX2q3V0jBApIlVpTsTsqs=.ebt&pk=http://ebt246.doc88.com/getebt-0rUR0Ln50TMQzq3R0jvS1SUV1SUS0LMS0LkR1SUS0LMS0LkR1TP50jsS1l9MGotI1q1p1v==.ebt&ptm=GotoPage&hlm=HeaderLoaded&fn=0&e404m=ViewerError&st=GetSURL&v=0&sp=false">
<param name="allowScriptAccess" value="always">
</object>
这里取得的代码就是《Soilless Culture- Theory and Practice》的封面页,是一张大图片。这里重要的内容是FlashVars对应的参数,其中包含了两个ebt文件的URL地址,通过这个地址就可以下载到想要的页面了。这只是第一步,后头才是关键。这两个文件分别标记ph和pk,ph指向的这个文件是共用的,每一页内容都会需要它。URL地址中,在getebt-到扩展名之间有一段编码内容,明眼人一看就知道是BASE64编码内容,但其实只对了一半,它是BASE64的一种变体,我在破解中称其为BASE64DOC88编码。现在就通过链接下载这两个ebt 文件:
PH EBT:http://ebt246.doc88.com/getebt-0rUXzqMX2LkT0qEd1qEd0jPR0jP50qEd0jPR0jP50qEX2q3V0jBApIlVpTsTsqs=.ebt
接下来就是要破解它们,让它们变成可以看的书封。在stackoverflow.com上我找到了一段代码,省了我不少时间,这里贴上修改后的代码,使用FlashDevelop编译后可以得到一个DOC88 EBT破解工具,运行后,会弹出文件选择框,就选择前面下载的两个ebt文件,DOC88破解工具会将生成最后直接可用的SWF页面内容:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.net.FileFilter;
import flash.net.FileReference;
import flash.text.TextField;
import flash.utils.*;
/**
* ...
* @author Ace
* Modify by Jimbowhy
*
* http://stackoverflow.com/questions/12121062/how-to-decompress-a-lzma-compressed-file-using-bytearray-method-in-as3
* This is the code that I'am using to compress/decompress files.
*
* My problem : This method doesn't decompress LZMA compressed files :(
* Can anyone please tell me how to restructure the above code to achieve LZMA decompression
* and whether the above compression code is good enough for LZMA-compression?If not,
* please do give an example of it.
*
* EDIT : After long hours of searching,I got this but I can't quite understand the
* example code in it :( Some help,anyone?
* https://helpx.adobe.com/flash-player/kb/exception-thrown-you-decompress-lzma-compressed.html
*/
public class Compressor extends Sprite
{
private var ref:FileReference;
private var txf:TextField;
private var buffer:ByteArray;
private var glue:String = "";
private var dos:String = "Nothing";
private var MAGIC_ZWS:String = "ZWS";
private var MAGIC_CWS:String = "CWS";
private var MAGIC_FWS:String = "FWS";
private var MAGIC_EBT:String = "YBD";
private var MAGIC_EBK:String = "EBT_PK";
private var MAKEUP:Boolean = true;
public function Compressor()
{
txf = new TextField();
txf.text = "SWF Compressor and Decompressor";
txf.width = txf.textWidth + 200;
txf.x = stage.stageWidth / 2 - txf.width / 2;
txf.y = stage.stageHeight / 2 - txf.height / 2;
parent.addChild(txf);
open();
}
private function open():void
{
ref = new FileReference();
ref.addEventListener(Event.SELECT, load);
ref.browse([new FileFilter("SWF Files", "*.swf;*.ebt")]);
}
private function load(e:Event):void
{
ref.addEventListener(Event.COMPLETE, processSWF);
ref.load();
}
private function processSWF(e:Event):void
{
var swf:ByteArray;
dos = ref.data.readMultiByte(3, "us-ascii");
switch(dos)
{
case MAGIC_CWS:
swf = decompress(ref.data);
break;
case MAGIC_ZWS:
txf.text = "ZWS detected, donothing. SWF 13 and later use LZMA.";
break;
case MAGIC_FWS:
swf = compress(ref.data);
break;
case MAGIC_EBT:
swf = decompressEBT_PH(ref.data);
break;
default:
//throw Error("Not SWF...");
dos = MAGIC_EBK;
swf = decompressEBT_PK(ref.data); // ebt is a compress file and light encrypt
break;
}
if ( swf && glue && MAKEUP) {
txf.text = "Need the 2nd part of ebt to makeup a page.";
open(); // deal the 2nd part of ebt.
MAKEUP = false
}else if (swf && glue && dos!=glue) {
var b:Boolean = glue == MAGIC_EBT;
swf = makeup(b?buffer:swf, b?swf:buffer);
glue = null;
}
if( (swf && !glue) || dos==glue ){
txf.text = dos + " Dectected.";
new FileReference().save(swf);
}
}
private function makeup(ebt_ph:ByteArray, ebt_pk:ByteArray):ByteArray
{
var buff:ByteArray = new ByteArray();
ebt_ph.position = 0;
ebt_pk.position = 0;
buff.endian = Endian.LITTLE_ENDIAN;
buff.writeBytes(ebt_ph, 0, ebt_ph.bytesAvailable);
buff.writeBytes(ebt_pk, 0, ebt_pk.bytesAvailable);
buff.writeByte(64); // make 4 bytes ending
buff.writeByte(0);
buff.writeByte(0);
buff.writeByte(0);
buff.position = 4;
buff.writeUnsignedInt(buff.length); // write file size back to header
buff.position = 0;
return buff;
}
private function decompressEBT_PH(data:ByteArray):ByteArray
{
data.position = 40; // 40 bytes bypass
var buff:ByteArray = new ByteArray();
buff.endian = Endian.LITTLE_ENDIAN;
var ebt:ByteArray = new ByteArray();
ebt.endian = Endian.LITTLE_ENDIAN;
//ebt.writeBytes(data, 0, data.bytesAvailable); // different below
data.readBytes(ebt, 0, data.bytesAvailable);
try {
ebt.uncompress();
buff.writeBytes(ebt, 0, ebt.length);
buff.position = 4;
buff.writeUnsignedInt(buff.length);
}catch (e:Error) {
txf.text = e.message + " at line:" + /\\.+\.as:([0-9]+)]/.exec(e.getStackTrace())[1];
return null;
}
if (!buffer) {
buffer = buff;
glue = MAGIC_EBT;
}
return buff;
}
private function decompressEBT_PK(data:ByteArray):ByteArray
{
data.position = 32; // 32bytes bypass in pk ebt
var ebt:ByteArray = new ByteArray();
var buff:ByteArray = new ByteArray();
ebt.endian = Endian.LITTLE_ENDIAN;
buff.endian = Endian.LITTLE_ENDIAN;
data.readBytes(buff, 0, data.bytesAvailable);
try {
buff.uncompress();
ebt.writeMultiByte(MAGIC_FWS, "ANSI");
//ebt.position = 4;
//ebt.writeUnsignedInt(buff.length+8);
ebt.writeBytes(buff);
} catch (e:Error) {
txf.text = e.message + " at line:" + /\\.+\.as:([0-9]+)]/.exec(e.getStackTrace())[1];
return null;
}
if (!buffer) {
buffer = buff;
glue = MAGIC_EBK;
}
return ebt;
}
private function compress(data:ByteArray):ByteArray
{
var header:ByteArray = new ByteArray();
var decompressed:ByteArray = new ByteArray();
var compressed:ByteArray = new ByteArray();
header.writeBytes(data, 3, 5); //read the header, excluding the signature
decompressed.writeBytes(data, 8); //read the rest
decompressed.compress();
compressed.writeMultiByte("CWS", "us-ascii"); //mark as compressed
compressed.writeBytes(header);
compressed.writeBytes(decompressed);
return compressed;
}
private function decompress(data:ByteArray):ByteArray
{
var header:ByteArray = new ByteArray();
var compressed:ByteArray = new ByteArray();
var decompressed:ByteArray = new ByteArray();
header.writeBytes(data, 3, 5); //read the uncompressed header, excluding the signature
compressed.writeBytes(data, 8); //read the rest, compressed
compressed.uncompress();
decompressed.writeMultiByte("FWS", "us-ascii"); //mark as uncompressed
decompressed.writeBytes(header); //write the header back
decompressed.writeBytes(compressed); //write the now uncompressed content
return decompressed;
}
}
}
编译好的工具可以在CSDN下载中心找到,搜索DOC88_CRACKER_JIMBOWHY就可以得到。工具使用截图如下,得到的SWF文件内容见下图:
注:本人的破解工作中,已经可以直接完成ebt文件的导出,而不必借助工具:
来张《Soilless Culture: Theory and Practice》by Michael Raviv J. Heinrich Lieth超清的封面图,只有PDF电子书才看得到这700*1025这个SIZE的。
刚去 wenku.baidu.com 百度文库去探了下环境,没想到立刻给拉到招聘现场一样!“百度文库招聘靠谱前端开发工程师,有意者请发简历到cuihongpeng#baidu.com。(邮件请注明:来自console)”,看客们用这段内容去百度文库吧,如果在找工作的话,会加分的:)。
阅读器完工,先鉴赏图,后再发贴。有了阅读器《Soilless Culture: Theory and Practice》才能真正称为电子版,唯一的差别就是非PDF,但可以是CHM。阅读器有单页、对开、阵列三种显示模式,还有页面跳转G:
《花卉无土栽培技术DOC》DOC88下载破解示范已经上传: http://download.csdn.net/detail/winsenjiansbomber/8938317
破解DOC88文档下载系列未完待续。。。