Skip to content

Instantly share code, notes, and snippets.

@xennygrimmato
Last active March 9, 2024 19:06
Show Gist options
  • Save xennygrimmato/42a2f216de3e327f8148c6a306cfdf40 to your computer and use it in GitHub Desktop.
Save xennygrimmato/42a2f216de3e327f8148c6a306cfdf40 to your computer and use it in GitHub Desktop.
Generating GIFs for fuzzing a GIF Parser
File 'gifdec.c'
Lines executed:36.91% of 317
Creating 'gifdec.c.gcov'
File 'gifread.c'
Lines executed:62.50% of 56
Creating 'gifread.c.gcov'
# gemini 1.5
# temperature = 2.0
# top_p = 0.4
import random
import struct
import io
from typing import BinaryIO
RANDOM_HEADER =\
b'GIF89a'\
b'\x00\x01'\
b'\x00\x01\x00\x00'
GIF_EXT_LABELS = {
0x01: b'text',
0xf9: b'graphic control',
0xfe: b'comment',
0xff: b'application',
}
RANDOM_TEXT_EXT =\
b'\x01'\
b'\x01\x01'\
b'\x01\x01'\
b'\x01\x01'\
b'\x01\x01\x01\x01'\
b'\x01\x01\x01\x01\x01\x01\x01\x01\x01'\
b'\x01'
RANDOM_GCE_EXT =\
b'\xf9'\
b'\x04'\
b'\x00'
RANDOM_COMMENT_EXT =\
b'\xfe'\
b'Comment!'
RANDOM_APPLICATION_EXT =\
b'\xff'\
b'NETSCAPE2.0'\
b'\x03'\
b'\x00\x00'
RANDOM_TERMINATOR =\
b'\x00'
def generate_random_input(out: BinaryIO):
"""Generates a random file into `out`.
The generated input will fully exercise the parsing code.
Generates a random header with a random width and height, then
generates a random color table and a random image. Finally, generates
some random extensions, including the text extension, the graphic control
extension, the comment extension, and the application extension.
"""
out.write(RANDOM_HEADER)
width = random.randint(1, 1000)
height = random.randint(1, 1000)
depth = random.randint(1, 8)
gct_size = 1 << (depth + 1)
out.write(struct.pack('>H', width))
out.write(struct.pack('>H', height))
flags = depth | (1 << 4) | (1 << 7)
out.write(struct.pack('B', flags))
out.write(struct.pack('B', gct_size))
out.write(struct.pack('B', 0)) # background color index
out.write(struct.pack('B', 0)) # aspect ratio
# Generate a random color table.
for _ in range(gct_size):
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
out.write(b"%c%c%c" % (r, g, b))
# Generate a random image.
for _ in range(width * height):
index = random.randint(0, gct_size - 1)
color = struct.pack('B', index)
out.write(color)
# Generate some random extensions.
for label in GIF_EXT_LABELS.keys():
if random.random() < 0.5:
out.write(struct.pack('B', label))
if label == 0x01:
out.write(RANDOM_TEXT_EXT)
elif label == 0xf9:
out.write(RANDOM_GCE_EXT)
elif label == 0xfe:
out.write(RANDOM_COMMENT_EXT)
elif label == 0xff:
out.write(RANDOM_APPLICATION_EXT)
out.write(RANDOM_TERMINATOR)
out.write(RANDOM_TERMINATOR)
for i in range(10000):
with open(f"gifs/random_gif_{i}.gif", "wb") as f:
generate_random_input(f)
from PIL import Image, ImageDraw, ImageSequence
import os
from io import BytesIO
# Utility function to save GIF to a BytesIO object
def save_gif(images, **kwargs):
bio = BytesIO()
images[0].save(bio, format='GIF', save_all=True, append_images=images[1:], **kwargs)
bio.seek(0)
return bio
# Generates GIFs to test the C GIF parsing code
def generate_gif_inputs():
# Ensure the gifs directory exists
os.makedirs('gifs', exist_ok=True)
files = []
# 1. Valid GIF with a simple global color table
img1 = Image.new("RGB", (100, 100), "white")
draw = ImageDraw.Draw(img1)
draw.rectangle([10, 10, 90, 90], fill="blue")
files.append(save_gif([img1], loop=0))
# 2. GIF with no global color table (invalid per parser's expectations)
img2 = Image.new("RGB", (100, 100), "red")
files.append(save_gif([img2], loop=0, global_palette=False))
# 3. GIF with a NETSCAPE looping extension
img3 = Image.new("RGB", (50, 50), "green")
img3.info['loop'] = 1 # Loop once
files.append(save_gif([img3], loop=1))
# 4. GIF with local color tables and different frames
img4_1 = Image.new("RGB", (100, 100), "yellow")
img4_2 = Image.new("RGB", (100, 100), "purple")
img4_2.info['local_palette'] = True
files.append(save_gif([img4_1, img4_2], loop=0))
# 5. Interlaced GIF
img5 = Image.new("RGB", (100, 100), "black")
draw = ImageDraw.Draw(img5)
for y in range(0, 100, 10):
draw.line([(0, y), (99, y)], fill="white")
files.append(save_gif([img5], interlace=True))
# 6. GIF with a comment extension
img6 = Image.new("RGB", (100, 100), "orange")
img6.info['comment'] = b"This is a test comment."
files.append(save_gif([img6]))
# Save all BytesIO objects to files for inspection
for i, file in enumerate(files):
with open(f'gifs/generated_gif_{i}.gif', 'wb') as f:
f.write(file.getvalue())
generate_gif_inputs()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment