#!/usr/bin/python

'''
gzfuzz - Version 1.0 (2010-06-19).
Latest version at http://www.itsec.se

A GZIP file format fuzzer in python based on RFC 1952, GZIP File
Format Specification version 4.3. Uses a heavily modified version
of gzip.py.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the named License,
or any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

Copyright (C) 2010 by Gustav Nordenskjold, Sweden.
'''

import struct, sys, time
import zlib
import __builtin__

__all__ = ["GzipFile","open"]

FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16

READ, WRITE = 1, 2

#Fuzzing data
payloads = [ "%n%n%n%n%n", "%p%p%p%p%p", "%s%s%s%s%s", "%d%d%d%d%d", "%x%x%x%x%x",
"%s%p%x%d", "%.1024d", "%.1025d", "%.2048d", "%.2049d", "%.4096d", "%.4097d",
"%99999999999s", "%08x", "%%20n", "%%20p", "%%20s", "%%20d", "%%20x", 
"%#0123456x%08x%x%s%p%d%n%o%u%c%h%l%q%j%z%Z%t%i%e%g%f%a%C%S%08x%%", "\0xCD" * 50, "\0xCB" * 50,
"A" * 8200, "A" * 11000, "A" * 110000, "A" * 550000, "A" * 1100000, "A" * 2200000, "\0x99" * 1200,
"0", "-0", "1", "-1", "32767", "-32768", "2147483647", "-2147483647", "2147483648", "-2147483648",
"4294967294", "4294967295", "4294967296", "357913942", "-357913942", "536870912", "-536870912",
"1.79769313486231E+308", "3.39519326559384E-313", "99999999999", "-99999999999", "0x100", "0x1000",
"0x3fffffff", "0x7ffffffe", "0x7fffffff", "0x80000000", "0xffff", "0xfffffffe", "0xfffffff", "0xffffffff",
"0x10000", "0x100000", "0x99999999", "65535", "65536", "65537", "16777215", "16777216", "16777217", "-268435455",
"test|touch /tmp/ZfZ-PWNED|test", "test`touch /tmp/ZfZ-PWNED`test", "test'touch /tmp/ZfZ-PWNED'test", "test;touch /tmp/ZfZ-PWNED;test",
"test&&touch /tmp/ZfZ-PWNED&&test", "test|C:/WINDOWS/system32/calc.exe|test", "test`C:/WINDOWS/system32/calc.exe`test",
"test'C:/WINDOWS/system32/calc.exe'test", "test;C:/WINDOWS/system32/calc.exe;test", "/bin/sh", "C:/WINDOWS/system32/calc.exe",
"%0xa", "%u000", "//AAAA" * 250, "\\AAAA" * 250 ]

fuzz = [ "fextra", "fextra_xlen", "fname", "fcomment", "mtime", "fhcrc", "crc32", "isize" ] #xlen excluded


def U32(i):
    if i < 0:
        i += 1L << 32
    return i

def LOWU32(i):
    return i & 0xFFFFFFFFL

def write32(output, value):
    output.write(struct.pack("<l", value))

def write32u(output, value):
    output.write(struct.pack("<L", value))

def write16u(output, value):
    if value > 65535:
        value = 65535
    output.write(struct.pack("<H", value))

def open(filename, mode="rb", compresslevel=9):
    return GzipFile(filename, mode, compresslevel)

class GzipFile:
    myfileobj = None
    max_read_chunk = 10 * 1024 * 1024   # 10Mb

    def __init__(self, filename=None, mode=None,
                 compresslevel=9, fileobj=None):

        # guarantee the file is opened in binary mode on platforms
        # that care about that sort of thing

        if mode and 'b' not in mode:
            mode += 'b'
        if fileobj is None:
            fileobj = self.myfileobj = __builtin__.open(filename, mode or 'rb')
        if filename is None:
            if hasattr(fileobj, 'name'): filename = fileobj.name
            else: filename = ''
        if mode is None:
            if hasattr(fileobj, 'mode'): mode = fileobj.mode
            else: mode = 'rb'

        if mode[0:1] == 'w' or mode[0:1] == 'a':
            self.mode = WRITE
            self._init_write(filename)
            self.compress = zlib.compressobj(compresslevel,
                                             zlib.DEFLATED,
                                             -zlib.MAX_WBITS,
                                             zlib.DEF_MEM_LEVEL,
                                             0)
        else:
            raise IOError, "Mode " + mode + " not supported"

        self.fileobj = fileobj
        self.offset = 0


    def _init_write(self, filename):
        if filename[-3:] != '.gz':
            filename = filename + '.gz'
        self.filename = filename
        self.crc = zlib.crc32("")
        self.size = 0
        self.writebuf = []
        self.bufsize = 0

    def write_gzip_header(self, fuzz_variable, payload):
        fname = self.filename[:-3]
	fextra = ""
	fcomment = ""
	fhcrc = ""
        xlen = 0
        ln = 0

	if fuzz_variable == "fextra":
	    fextra = payload
            ln = len(fextra)
            xlen = ln + 4
        elif fuzz_variable == "fextra_xlen":
            fextra = payload
            ln = 16
            xlen = ln + 4
        elif fuzz_variable == "fcomment":
            fcomment = payload
        elif fuzz_variable == "fname":
            fname = payload
	elif fuzz_variable == "fhcrc":
            fhcrc = payload[:2]

        self.fileobj.write('\037\213')             		# ID1 + ID2
        self.fileobj.write('\010')                 		# CM (compression method)

	flags = 0
        if fextra:
             flags += FEXTRA 					# FLG.FEXTRA
        if fname:
            flags += FNAME 					# FLG.FNAME
        if fcomment:
            flags += FCOMMENT 					# FLG.FCOMMENT
        if fhcrc:
            flags += FHCRC 					# FLG.FHCRC

        self.fileobj.write(chr(flags))				# FLG
        
	if fuzz_variable == "mtime":
	    self.fileobj.write(payload[:4])
        else:
            write32u(self.fileobj, long(time.time()))           # MTIME
        
        self.fileobj.write('\002')				# XFL
        self.fileobj.write('\377')				# OS
        if fextra:
            write16u(self.fileobj, xlen)			# XLEN - VERIFY: xlen = xlen + 256*ord(self.fileobj.read(1))
            self.fileobj.write('\x41')				# SI1
            self.fileobj.write('\x70')				# SI2
            write16u(self.fileobj, ln)				# LEN
            self.fileobj.write(fextra)				# FEXTRA
        if fname:
            self.fileobj.write(fname + '\000')			# FNAME
        if fcomment:
            self.fileobj.write(fcomment + '\000')		# FCOMMENT
        if fhcrc:
            self.fileobj.write(fhcrc)				# FHCRC
        #    pos = self.fileobj.tell()
	#    self.fileobj.seek(0, 0)
        #    crc32 = LOWU32( zlib.crc32(self.fileobj.read(pos)) )
        #    fhcrc = struct.pack("<I", crc32)
        #    self.fileobj.seek(0, 2)
        #    self.fileobj.write(fhcrc)				# FHCRC


    def write(self,data):
        if self.mode != WRITE:
            import errno
            raise IOError(errno.EBADF, "write() on read-only GzipFile object")

        if self.fileobj is None:
            raise ValueError, "write() on closed GzipFile object"
        if len(data) > 0:
            self.size = self.size + len(data)
            self.crc = zlib.crc32(data, self.crc)
            self.fileobj.write( self.compress.compress(data) )
            self.offset += len(data)


    def close(self, fuzz_variable, payload):
        if self.mode == WRITE:
            self.fileobj.write(self.compress.flush())
            if fuzz_variable == "crc32":
	        self.fileobj.write(payload[:4])
            else:
                write32u(self.fileobj, LOWU32(self.crc))		# CRC32
            if fuzz_variable == "isize":
	        self.fileobj.write(payload[:4])
            else:
                write32u(self.fileobj, LOWU32(self.size))		# ISIZE
            self.fileobj = None
        elif self.mode == READ:
            self.fileobj = None
        if self.myfileobj:
            self.myfileobj.close()
            self.myfileobj = None


    def __del__(self):
        try:
            if (self.myfileobj is None and
                self.fileobj is None):
                return
        except AttributeError:
            return
        self.close()


if __name__ == '__main__':

    print "gzfuzz - a GZIP file format fuzzer based on RFC 1952"
    print 'Version 1.0 (2010-06-19). Latest version at http://www.itsec.se'
    print ''
    print "Usage: python " + sys.argv[0] + " [filenames]"
    print ""

    args = sys.argv[1:]

    count = 0

    for arg in args:
        print "File " + arg + ":"
        print "Fuzzing xlen: <not supported>"
 
	# Fuzz header
	for i in fuzz:
	    print "Fuzzing " + i + ": ",
            for j in payloads:
	        count += 1
	        f = __builtin__.open(arg, "rb")
	        g = open(str(count) + "-" + i + "-"+ arg + ".gz", "wb+")
		g.write_gzip_header(i, j)
                while True:
	            chunk = f.read(1024)
        	    if not chunk:
        	        break
	            g.write(chunk)
        	if g is not sys.stdout:
            	    g.close(i, j)
	        if f is not sys.stdin:
        	    f.close()
                print "*",
	    print ""