2009-11-30 44 views
11

组合问题希望你听说过neat hack,可以让你把一个JPG和Zip文件到一个单一的文件,它是两种格式的有效(或至少可读)文件。那么,我意识到,由于JPG允许最终使用任意东西,并且在开始时使用ZIP,所以您可以在中间再粘贴一种格式。对于这个问题,假设中间数据是任意的二进制数据,保证不与JPG或ZIP格式冲突(意味着它不包含魔术zip头0x04034b50)。插图:JPG + Zip文件用Zip格式

0xFFD8 <- start jpg data end -> 0xFFD9 ... ARBITRARY BINARY DATA ... 0x04034b50 <- start zip file ... EOF 

我catting这样的:

猫 “mss_1600.jpg” FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB “null.bytes” “randomzipfile.zip”> temp.zip

这产生一个6,318 KB文件。它不是在7-Zip中打开。然而,当我的猫少了一个 '双'(所以不是13 FILEA和B,12):

猫 “mss_1600.jpg” FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB FILEA FILEB “null.bytes” “randomzipfile.zip”> temp.zip

它产生一个5996 KB文件确实在7-Zip的开放。

所以我知道我的任意二进制数据没有神奇的Zip文件头来搞砸了。我有参考文件working jpg+data+zipnon-working jpg+data+zip(保存 - 因为浏览器认为它们是图像,并自己添加zip扩展名)。

我想知道为什么它失败了13个组合,而不是12。对于奖励点,我需要绕过这个莫名其妙。

+1

只是想指出,这可能是7Zip算法的问题,因为File Roller也设法打开非工作示例。 – laginimaineb 2009-12-02 15:39:49

+1

巧妙的技巧 - 从现在开始,我将使用这种技术将我自己的图片插入到我的所有java .jar(可执行的jar-pegs :) – Seth 2009-12-06 20:50:54

回答

10

其实这是一个两部分的答案真的:)

首先不管别人说什么的zip文件不能技术上被逐字在文件的结尾放。中央目录记录的末尾有一个值,表示从当前磁盘开始的字节偏移量(如果只有一个.zip文件,则表示当前文件)。现在很多处理器都忽略了这一点,虽然Windows的zip文件夹并没有,所以你需要改正这个值才能使它在Windows资源管理器中工作(不是你可能会关心的; P)关于文件格式的信息,请参阅Zip APPNOTE。基本上你可以在一个十六进制编辑器中找到(或者写一个工具)来找到“中央目录开始偏移量相对于起始磁盘编号”的值。然后找到第一个“中央文件头签名”(十六进制504b0102)并将该值设置为该偏移量。

现在唉,不能解决7zip,但这是由于7zip试图猜测文件格式的方式。基本上它只会搜索第一个〜4MiB的二进制序列504b0304,如果它没有找到它,它会假定它不是Zip,并尝试其他存档格式。这显然是为什么增加一个文件会使事情发生中断,并将其推到搜索限制之上。

现在来解决它,你需要做的是将该十六进制字符串添加到JPEG而不会打破它。这样做的一种方法是在FFD8 JPEG SOI头后面添加以下十六进制数据FFEF0005504B030400。这就为你的序列添加了一个自定义块,并且是正确的,所以jpeg头文件应该忽略它。

+0

这得到了我有60%的路在那里。我还必须修改504b0102条目以更改THEIR偏移量,否则它会打开但不允许您提取文件。我认为**我在Windows资源管理器和7-Zip中使用了jpg/zip,但我明天需要做更多的测试。 – 2009-12-03 03:50:10

20

我下载了7-Zip的源代码并找出了导致这种情况发生的原因。

在CPP/7zip的/ UI /通用/ OpenArchive.cpp,你会看到以下内容:

// Static-SFX (for Linux) can be big. 
const UInt64 kMaxCheckStartPosition = 1 << 22; 

这意味着,只有第4194304个字节的文件中搜索标题。如果没有找到,7-Zip认为它是一个无效的文件。

您可以通过将1 << 22更改为1 << 23将此限制加倍。我通过重建7-Zip测试了这一变化,它起作用。

编辑:要解决此问题,您可以download the source,进行上述更改并构建它。我建立它使用VS 2008打开VS命令提示,导航到提取 - 源位置 \ CPP \ 7zip的\包和类型“NMAKE”。然后在Alone目录中运行'7za t非工作。jpg',你应该看到'一切正常'。

+0

令人难以置信的好先生。我不知道我是否可以在第一个字节范围内放置一个正确形式的假文件并把戏7-Zip ......我打算玩一下(在接受之前也等一下,不要冒犯) – 2009-12-03 01:57:56

4

所以对大家有没有发现这个问题,这里的故事:

是,安迪,为什么7-Zip是在文件上失败字面上是正确的,但因为我可以不帮我的问题”正好让人们使用我的7-Zip版本。

然而,tyranid给了我解决方案。

  • 首先,他建议将一个小字节串添加到JPG中,让7-Zip打开它。但是,它与有效的JPG片段略有不同,它需要为FFEF00 504B030400 - 长度已减去2个字节。
  • 这让7-Zip打开它,但不提取文件,它会自动失败。这是因为中央目录中的条目具有指向文件条目的内部指针/偏移量。由于您在此之前放置了一堆东西,因此您需要纠正所有这些指针!
  • 要使用zip内置的zip支持打开压缩文件,您需要像tyranid所说的那样,纠正“中央目录的起始位置相对于起始磁盘编号的偏移量”。这里是一个python脚本做最后两个,虽然这是一个片段,而不是copypasta准备使用的

#Now we need to read the file and rewrite all the zip headers. Fun! 
torewrite = open(magicfilename, 'rb') 
magicdata = torewrite.read() 
torewrite.close() 

#Change the Central Repository's Offset 
offsetOfCentralRepro = magicdata.find('\x50\x4B\x01\x02') #this is the beginning of the central repo 
start = len(magicdata) - 6 #it so happens, that on my files, the point is stored 2 bytes from the end. so datadatadatdaata OF FS ET !! 00 00 EOF where OFFSET!! is the 4 bytes 00 00 are the last two bytes, then EOF 
magicdata = magicdata[:start] + pack('I', offsetOfCentralRepro) + magicdata[start+4:] 

#Now change the individual offsets in the central directory files 
startOfCentralDirectoryEntry = magicdata.find('\x50\x4B\x01\x02', 0) #find the first central directory entry 
startOfFileDirectoryEntry = magicdata.find('\x50\x4B\x03\x04', 10) #find the first file entry (we start at 10 because we have to skip past the first fake entry in the jpg) 
while startOfCentralDirectoryEntry > 0: 
    #Now I move a magic number of bytes past the entry (really! It's 42!) 
    startOfCentralDirectoryEntry = startOfCentralDirectoryEntry + 42 

    #get the current offset just to output something to the terminal 
    (oldoffset,) = unpack('I', magicdata[startOfCentralDirectoryEntry : startOfCentralDirectoryEntry+4]) 
    print "Old Offset: ", oldoffset, " New Offset: ", startOfFileDirectoryEntry , " at ", startOfCentralDirectoryEntry 
    #now replace it 
    magicdata = magicdata[:startOfCentralDirectoryEntry] + pack('I', startOfFileDirectoryEntry) + magicdata[startOfCentralDirectoryEntry+4:] 

    #now I move to the next central directory entry, and the next file entry 
    startOfCentralDirectoryEntry = magicdata.find('\x50\x4B\x01\x02', startOfCentralDirectoryEntry) 
    startOfFileDirectoryEntry = magicdata.find('\x50\x4B\x03\x04', startOfFileDirectoryEntry+1) 

#Finally write the rewritten headers' data 
towrite = open(magicfilename, 'wb') 
towrite.write(magicdata) 
towrite.close() 
+0

感谢您分享您的代码(并揭示其含义为42;))。无需解释 - 我学到了很多东西,无论如何都很有趣。 – 2009-12-08 20:04:15

+0

对不起,如果我有几件事情。虽然谢谢:) – tyranid 2009-12-08 20:46:51

2

可以产生杂交JPG +使用DotNetZip ZIP文件。 DotNetZip可以保存到流中,并且它足够智能,可以在开始将zip内容写入其中之前识别预先存在的流的原始偏移量。因此,在伪代码,你可以得到一个JPG + ZIP这样:

open stream on an existing JPG file for update 
seek to the end of that stream 
open or create a zip file 
call ZipFile.Save to write zip content to the JPG stream 
close 

所有偏移正确想通。使用相同的技术来生成自解压存档。您可以在EXE上打开流,然后查找结尾,然后将ZIP内容写入该流。如果你这样做,所有偏移量都可以正确计算。

另一件事 - 关于另一篇文章中的评论之一...... ZIP可以在文件末尾有的任意数据。就我所知,zip中央目录需要位于文件末尾,没有任何要求,尽管这很典型。