├── LICENSE.txt ├── Makefile ├── README.md ├── TODO ├── diffs ├── _vorbis.py.diff ├── fuzzaifc.py.diff ├── fuzzwave.py.diff ├── id3.py.diff └── mp4.py.diff ├── formats.py ├── fuzzbox.py ├── oggcrc.py ├── randjunk.py └── utils.py /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007, iSEC Partners, Inc. 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the iSEC Partners, Inc. nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 26 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FETCH_CMD= fetch 2 | PY_LIB= /usr/local/lib/python2.7 3 | MUTAGEN_SITE= http://www.sacredchao.net/~piman/software/ 4 | MUTAGEN_DIST= mutagen-1.11 5 | 6 | all: fetch extract patch 7 | 8 | fetch: 9 | ${FETCH_CMD} ${MUTAGEN_SITE}${MUTAGEN_DIST}.tar.gz 10 | 11 | extract: fetch 12 | tar -xvzf ${MUTAGEN_DIST}.tar.gz 13 | mkdir mutagen 14 | cp -r ${MUTAGEN_DIST}/mutagen/* mutagen/ 15 | 16 | patch: extract 17 | patch -o fuzzwave.py ${PY_LIB}/wave.py < diffs/fuzzwave.py.diff 18 | patch -o fuzzaifc.py ${PY_LIB}/aifc.py < diffs/fuzzaifc.py.diff 19 | patch mutagen/_vorbis.py < diffs/_vorbis.py.diff 20 | patch mutagen/id3.py < diffs/id3.py.diff 21 | patch mutagen/mp4.py < diffs/mp4.py.diff 22 | 23 | clean: 24 | rm -rf aifc.* wave.* mutagen* *.orig *.pyc output* fuzzwave.py \ 25 | fuzzaifc.py 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fuzzbox 2 | ======= 3 | Fuzzbox 0.3.1 4 | 5 | A multi-codec media fuzzing tool. 6 | 7 | https://www.isecpartners.com/storage/docs/presentations/iSEC-Thiel-Exposing-Vulnerabilities-Media-Software-Presentation.pdf 8 | 9 | _Note: This tool is provided for historical reference, and is not being 10 | actively maintained. Feel free to fork and/or provide pull requests._ 11 | 12 | Fuzzbox creates corrupt but structurally valid sound files and 13 | optionally launches them in a player, gathering backtraces and 14 | register information. Also included is a standalone tool to reset 15 | the CRCs of Ogg-contained files after manual corruption. 16 | 17 | __NOTICE:__ One of the fuzzing tests tries to insert an HTTP URL to check 18 | for programs attempting to make web requests when processing files. 19 | This goes to labs.isecpartners.com by default. Please change this 20 | if you have privacy concerns. 21 | 22 | The spawning/killing of the player will only work on UNIX/OSX or 23 | possibly cygwin, as there's unfortunately no simple cross-platform way 24 | to do it. It shouldn't be hard to modify for Windows, though. 25 | 26 | For the vorbis comment header, py-vorbis is required. You will have to 27 | increase the max tag buffer size (tag_buff) in pyvorbisinfo.c before 28 | install for this to work right. 29 | 30 | For AIFFs, WAVs, MP3s and MP4s, the included Makefile should auto-fetch 31 | and patch the appropriate files. It will need to be edited to know about 32 | your system layout and file transfer program of choice. 33 | 34 | You should verify that the mutagen distfile matches this SHA256: 35 | 36 | SHA256 (./mutagen-1.11.tar.gz) = 37 | f22d0570a0d7d1b3d7a54bc70471fe212bd84aaabe5ab1d0c685f2b92a85b11a 38 | 39 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - Should probably fuzz the filename too - good opportunity for 2 | turning up bugs in shells/file browsers as well as players. 3 | 4 | - Option to use WinDbg as a default exception handler. 5 | 6 | - Use a seed for random functions to reproduce fuzzer runs 7 | -------------------------------------------------------------------------------- /diffs/_vorbis.py.diff: -------------------------------------------------------------------------------- 1 | --- _vorbis.py Wed May 30 19:37:13 2007 2 | +++ /crypt/usr/local/lib/python2.4/site-packages/mutagen/_vorbis.py Tue Jun 5 11:17:43 2007 3 | @@ -140,14 +140,17 @@ 4 | framing -- if true, append a framing bit (see load) 5 | """ 6 | 7 | - self.validate() 8 | + #self.validate() 9 | 10 | f = StringIO() 11 | f.write(cdata.to_uint_le(len(self.vendor.encode('utf-8')))) 12 | f.write(self.vendor.encode('utf-8')) 13 | f.write(cdata.to_uint_le(len(self))) 14 | for tag, value in self: 15 | - comment = "%s=%s" % (tag, value.encode('utf-8')) 16 | + try: 17 | + comment = "%s=%s" % (tag, value.encode('utf-8')) 18 | + except: 19 | + comment = "%s=%s" % (value, tag) 20 | f.write(cdata.to_uint_le(len(comment))) 21 | f.write(comment) 22 | if framing: f.write("\x01") 23 | -------------------------------------------------------------------------------- /diffs/fuzzaifc.py.diff: -------------------------------------------------------------------------------- 1 | --- aifc.py Wed Feb 14 10:45:39 2007 2 | +++ fuzzaifc.py Mon Mar 5 15:04:27 2007 3 | @@ -494,7 +494,8 @@ 4 | scheme = cl.G711_ALAW 5 | self._framesize = self._framesize / 2 6 | else: 7 | - raise Error, 'unsupported compression type' 8 | + #raise Error, 'unsupported compression type' 9 | + pass 10 | self._decomp = cl.OpenDecompressor(scheme) 11 | self._convert = self._decomp_data 12 | else: 13 | @@ -604,7 +605,8 @@ 14 | if self._nframeswritten: 15 | raise Error, 'cannot change parameters after starting to write' 16 | if nchannels < 1: 17 | - raise Error, 'bad # of channels' 18 | + #raise Error, 'bad # of channels' 19 | + pass 20 | self._nchannels = nchannels 21 | 22 | def getnchannels(self): 23 | @@ -616,7 +618,8 @@ 24 | if self._nframeswritten: 25 | raise Error, 'cannot change parameters after starting to write' 26 | if sampwidth < 1 or sampwidth > 4: 27 | - raise Error, 'bad sample width' 28 | + #raise Error, 'bad sample width' 29 | + pass 30 | self._sampwidth = sampwidth 31 | 32 | def getsampwidth(self): 33 | @@ -628,7 +631,8 @@ 34 | if self._nframeswritten: 35 | raise Error, 'cannot change parameters after starting to write' 36 | if framerate <= 0: 37 | - raise Error, 'bad frame rate' 38 | + #raise Error, 'bad frame rate' 39 | + pass 40 | self._framerate = framerate 41 | 42 | def getframerate(self): 43 | @@ -648,7 +652,8 @@ 44 | if self._nframeswritten: 45 | raise Error, 'cannot change parameters after starting to write' 46 | if comptype not in ('NONE', 'ULAW', 'ALAW', 'G722'): 47 | - raise Error, 'unsupported compression type' 48 | + #raise Error, 'unsupported compression type' 49 | + pass 50 | self._comptype = comptype 51 | self._compname = compname 52 | 53 | @@ -774,11 +779,14 @@ 54 | if self._sampwidth != 2: 55 | raise Error, 'sample width must be 2 when compressing with G7.22 (ADPCM)' 56 | if not self._nchannels: 57 | - raise Error, '# channels not specified' 58 | + #raise Error, '# channels not specified' 59 | + pass 60 | if not self._sampwidth: 61 | - raise Error, 'sample width not specified' 62 | + #raise Error, 'sample width not specified' 63 | + pass 64 | if not self._framerate: 65 | - raise Error, 'sampling rate not specified' 66 | + #raise Error, 'sampling rate not specified' 67 | + pass 68 | self._write_header(datasize) 69 | 70 | def _init_compression(self): 71 | -------------------------------------------------------------------------------- /diffs/fuzzwave.py.diff: -------------------------------------------------------------------------------- 1 | --- /usr/local/lib/python2.7/wave.py 2012-07-23 10:45:22.000000000 -0700 2 | +++ /tmp/wave.py 2012-10-09 11:05:35.000000000 -0700 3 | @@ -330,8 +330,6 @@ 4 | def setnchannels(self, nchannels): 5 | if self._datawritten: 6 | raise Error, 'cannot change parameters after starting to write' 7 | - if nchannels < 1: 8 | - raise Error, 'bad # of channels' 9 | self._nchannels = nchannels 10 | 11 | def getnchannels(self): 12 | @@ -342,8 +340,6 @@ 13 | def setsampwidth(self, sampwidth): 14 | if self._datawritten: 15 | raise Error, 'cannot change parameters after starting to write' 16 | - if sampwidth < 1 or sampwidth > 4: 17 | - raise Error, 'bad sample width' 18 | self._sampwidth = sampwidth 19 | 20 | def getsampwidth(self): 21 | @@ -354,8 +350,6 @@ 22 | def setframerate(self, framerate): 23 | if self._datawritten: 24 | raise Error, 'cannot change parameters after starting to write' 25 | - if framerate <= 0: 26 | - raise Error, 'bad frame rate' 27 | self._framerate = framerate 28 | 29 | def getframerate(self): 30 | @@ -374,8 +368,6 @@ 31 | def setcomptype(self, comptype, compname): 32 | if self._datawritten: 33 | raise Error, 'cannot change parameters after starting to write' 34 | - if comptype not in ('NONE',): 35 | - raise Error, 'unsupported compression type' 36 | self._comptype = comptype 37 | self._compname = compname 38 | 39 | @@ -451,12 +443,6 @@ 40 | 41 | def _ensure_header_written(self, datasize): 42 | if not self._headerwritten: 43 | - if not self._nchannels: 44 | - raise Error, '# channels not specified' 45 | - if not self._sampwidth: 46 | - raise Error, 'sample width not specified' 47 | - if not self._framerate: 48 | - raise Error, 'sampling rate not specified' 49 | self._write_header(datasize) 50 | 51 | def _write_header(self, initlength): 52 | -------------------------------------------------------------------------------- /diffs/id3.py.diff: -------------------------------------------------------------------------------- 1 | --- id3.py Sun Jan 21 21:19:43 2007 2 | +++ /usr/local/lib/python2.4/site-packages/mutagen/id3.py Tue Jan 23 15:53:42 2007 3 | @@ -397,9 +397,9 @@ 4 | if len(str(frame)) == 0: return '' 5 | framedata = frame._writeData() 6 | usize = len(framedata) 7 | - if usize > 2048: 8 | - framedata = BitPaddedInt.to_str(usize) + framedata.encode('zlib') 9 | - flags |= Frame.FLAG24_COMPRESS | Frame.FLAG24_DATALEN 10 | + #if usize > 2048: 11 | + # framedata = BitPaddedInt.to_str(usize) + framedata.encode('zlib') 12 | + # flags |= Frame.FLAG24_COMPRESS | Frame.FLAG24_DATALEN 13 | datasize = BitPaddedInt.to_str(len(framedata), width=4) 14 | header = pack('>4s4sH', type(frame).__name__, datasize, flags) 15 | return header + framedata 16 | @@ -629,8 +629,11 @@ 17 | # Okay, seriously. This is private and defined explicitly and 18 | # completely by the ID3 specification. You can't just add 19 | # encodings here however you want. 20 | + # 21 | + # Oh yes I can. 22 | _encodings = ( ('latin1', '\x00'), ('utf16', '\x00\x00'), 23 | - ('utf_16_be', '\x00\x00'), ('utf8', '\x00') ) 24 | + ('utf_16_be', '\x00\x00'), ('utf8', '\x00'), 25 | + ('ascii', '\x00') ) 26 | 27 | def read(self, frame, data): 28 | enc, term = self._encodings[frame.encoding] 29 | @@ -652,7 +655,12 @@ 30 | 31 | def write(self, frame, value): 32 | enc, term = self._encodings[frame.encoding] 33 | - return value.encode(enc) + term 34 | + #return value.encode(enc) + term 35 | + try: 36 | + moo = value.encode(enc) + term 37 | + except: 38 | + moo = value 39 | + return moo 40 | 41 | def validate(self, frame, value): return unicode(value) 42 | 43 | @@ -1163,7 +1171,7 @@ 44 | HashKey = property(lambda s: '%s:%s' % (s.FrameID, s.url)) 45 | 46 | class TALB(TextFrame): "Album" 47 | -class TBPM(NumericTextFrame): "Beats per minute" 48 | +class TBPM(TextFrame): "Beats per minute" 49 | class TCOM(TextFrame): "Composer" 50 | 51 | class TCON(TextFrame): 52 | @@ -1227,7 +1235,7 @@ 53 | class TDAT(TextFrame): "Date of recording (DDMM)" 54 | class TDEN(TimeStampTextFrame): "Encoding Time" 55 | class TDOR(TimeStampTextFrame): "Original Release Time" 56 | -class TDLY(NumericTextFrame): "Audio Delay (ms)" 57 | +class TDLY(TextFrame): "Audio Delay (ms)" 58 | class TDRC(TimeStampTextFrame): "Recording Time" 59 | class TDRL(TimeStampTextFrame): "Release Time" 60 | class TDTG(TimeStampTextFrame): "Tagging Time" 61 | @@ -1240,14 +1248,14 @@ 62 | class TIT3(TextFrame): "Subtitle/Description refinement" 63 | class TKEY(TextFrame): "Starting Key" 64 | class TLAN(TextFrame): "Audio Languages" 65 | -class TLEN(NumericTextFrame): "Audio Length (ms)" 66 | +class TLEN(TextFrame): "Audio Length (ms)" 67 | class TMED(TextFrame): "Source Media Type" 68 | class TMOO(TextFrame): "Mood" 69 | class TOAL(TextFrame): "Original Album" 70 | class TOFN(TextFrame): "Original Filename" 71 | class TOLY(TextFrame): "Original Lyricist" 72 | class TOPE(TextFrame): "Original Artist/Performer" 73 | -class TORY(NumericTextFrame): "Original Release Year" 74 | +class TORY(TextFrame): "Original Release Year" 75 | class TOWN(TextFrame): "Owner/Licensee" 76 | class TPE1(TextFrame): "Lead Artist/Performer/Soloist/Group" 77 | class TPE2(TextFrame): "Band/Orchestra/Accompaniment" 78 | @@ -1260,14 +1268,14 @@ 79 | class TRDA(TextFrame): "Recording Dates" 80 | class TRSN(TextFrame): "Internet Radio Station Name" 81 | class TRSO(TextFrame): "Internet Radio Station Owner" 82 | -class TSIZ(NumericTextFrame): "Size of audio data (bytes)" 83 | +class TSIZ(TextFrame): "Size of audio data (bytes)" 84 | class TSOA(TextFrame): "Album Sort Order key" 85 | class TSOP(TextFrame): "Perfomer Sort Order key" 86 | class TSOT(TextFrame): "Title Sort Order key" 87 | class TSRC(TextFrame): "International Standard Recording Code (ISRC)" 88 | class TSSE(TextFrame): "Encoder settings" 89 | class TSST(TextFrame): "Set Subtitle" 90 | -class TYER(NumericTextFrame): "Year of recording" 91 | +class TYER(TextFrame): "Year of recording" 92 | 93 | class TXXX(TextFrame): 94 | """User-defined text data. 95 | -------------------------------------------------------------------------------- /diffs/mp4.py.diff: -------------------------------------------------------------------------------- 1 | --- mutagen-1.11/mutagen/mp4.py 2007-04-26 19:12:50.000000000 -0700 2 | +++ mutagen/mp4.py 2007-07-24 22:48:52.000000000 -0700 3 | @@ -265,7 +265,7 @@ 4 | """Save the metadata to the given filename.""" 5 | values = [] 6 | items = self.items() 7 | - items.sort(self.__key_sort) 8 | + #items.sort(self.__key_sort) 9 | for key, value in items: 10 | info = self.__atoms.get(key[:4], (None, MP4Tags.__render_text)) 11 | try: 12 | -------------------------------------------------------------------------------- /formats.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import ogg.vorbis 5 | 6 | # The ogg crc lookup table, taken from framing.c 7 | crc_lookup = [ 8 | 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, 9 | 0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005, 10 | 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61, 11 | 0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd, 12 | 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9, 13 | 0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75, 14 | 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011, 15 | 0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd, 16 | 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039, 17 | 0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5, 18 | 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81, 19 | 0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d, 20 | 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49, 21 | 0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95, 22 | 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1, 23 | 0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d, 24 | 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae, 25 | 0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072, 26 | 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16, 27 | 0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca, 28 | 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde, 29 | 0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02, 30 | 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066, 31 | 0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba, 32 | 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e, 33 | 0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692, 34 | 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6, 35 | 0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a, 36 | 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e, 37 | 0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2, 38 | 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686, 39 | 0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a, 40 | 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637, 41 | 0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb, 42 | 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f, 43 | 0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53, 44 | 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47, 45 | 0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b, 46 | 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff, 47 | 0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623, 48 | 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7, 49 | 0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b, 50 | 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f, 51 | 0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3, 52 | 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7, 53 | 0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b, 54 | 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f, 55 | 0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3, 56 | 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640, 57 | 0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c, 58 | 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8, 59 | 0x68860bfd,0x6c47164a,0x61043093,0x65c52d24, 60 | 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30, 61 | 0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec, 62 | 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088, 63 | 0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654, 64 | 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0, 65 | 0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c, 66 | 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18, 67 | 0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4, 68 | 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0, 69 | 0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c, 70 | 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, 71 | 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4 72 | ] 73 | 74 | comments = {} 75 | 76 | # these are the most commonly used tags by vorbis apps. 77 | comments['COMMENT'] = 'leetleet' 78 | comments['TITLE'] = 'safety short' 79 | comments['ARTIST'] = 'Various' 80 | comments['ALBUM'] = 'Touch25' 81 | comments['TRACKNUMBER'] = '1' 82 | comments['DISCNUMBER'] = '1' 83 | comments['GENRE'] = 'Experimental' 84 | comments['DATE'] = '2006' 85 | comments['REPLAYGAIN_TRACK_GAIN'] = 'trackgain' 86 | comments['REPLAYGAIN_ALBUM_GAIN'] = 'albumgain' 87 | comments['REPLAYGAIN_TRACK_PEAK'] = 'trackpeak' 88 | comments['REPLAYGAIN_ALBUM_PEAK'] = 'albumpeak' 89 | comments['LICENSE'] = 'Free as in beer' 90 | comments['ORGANIZATION'] = 'iSEC' 91 | comments['DESCRIPTION'] = 'A test file' 92 | comments['LOCATION'] = 'SF' 93 | comments['CONTACT'] = 'david@isecpartners.com' 94 | comments['ISRC'] = '12345' 95 | comments['COMPOSER'] = 'Unknown' 96 | 97 | #vcomments = ogg.vorbis.VorbisComment(comments) 98 | # 99 | #totaltags = len(vcomments) 100 | 101 | # these are the base text tags that we want to work with. 102 | mp3tags = ['TEXT', 'WOAS', 'WOAR', 'POPM', 'SYLT', 'ENCR', 'SYTC', 'TMOO', 'TDAT', 'TXXX', 'TSOA', 'SIGN', 'TSOP', 'TSOT', 'TOWN', 'TFLT', 'TLAN', 'TOAL', 'TRSN', 'TMED', 'WCOP', 'TPE4', 'AENC', 'USER', 'TOPE', 'EQU2', 'WCOM', 'OWNE', 'TDRL', 'TDLY', 'TDRC', 'PRIV', 'TPUB', 'TCOP', 'USLT', 'TPRO', 'IPLS', 'TSIZ', 'TIT1', 'TIT3', 'TOFN', 'WOAF', 'TCON', 'POSS', 'TCOM', 'UFID', 'TENC', 'MLLT', 'TRCK', 'TRSO', 'TSST', 'TIT2', 'APIC', 'TSSE', 'ASPI', 'WORS', 'TPE3', 'TPE2', 'TPE1', 'TDTG', 'TORY', 'TALB', 'RVRB', 'COMM', 'WPAY', 'WXXX', 'TLEN', 'TBPM', 'TMCL', 'RVA2', 'SEEK', 'TOLY', 'TDOR', 'TIPL', 'TRDA', 'WPUB', 'GEOB', 'TKEY', 'COMR', 'TIME', 'TDEN', 'GRID', 'PCNT', 'RBUF', 'TSRC', 'TYER', 'LINK', 'MCDI', 'TPOS', 'ETCO'] 103 | 104 | # Don't forget to change numerictextframes to plain textframes in mutagen 105 | texttags = ['TALB', 'TCOM', 'TCON', 'TCOP', 'TDAT', 'TBPM', 'TDLY', 'TENC', 'TEXT', 'TFLT', 'TIME', 'TIT1', 'TIT2', 'TIT3', 'TKEY', 'TLAN', 'TLEN', 'TMED', 'TMOO', 'TOAL', 'TOFN', 'TOLY', 'TOPE', 'TORY', 'TOWN', 'TPE1', 'TPE2', 'TPE3', 'TPE4', 'TPRO', 'TPUB', 'TRDA', 'TRSN', 'TRSO', 'TSIZ', 'TSOA', 'TSOP', 'TSOT', 'TSRC', 'TSSE', 'TSST', 'TYER', 'COMM' ] 106 | 107 | timetags = ['TDEN', 'TDOR', 'TDRC', 'TDRL', 'TDTG', 'TPOS'] 108 | 109 | flactags = ['artist', 'title', 'comment', 'genre', 'album'] 110 | 111 | # iTunes-specific atoms can be found in mutagen.mp4.MP4Tags 112 | qtatoms = ['cmov', 'cmvd', 'co64', 'ctts', 'dcom', 'edts', 'elst', 'esds', 'free', 'ftyp', 'gmhd', 'iods', 'junk', 'mdat', 'mdhd', 'mdia', 'minf', 'moov', 'mvhd', 'pict', 'pnot', 'rdrf', 'rmcd', 'rmcs', 'rmda', 'rmdr', 'rmqu', 'rmra', 'rmvc', 'skip', 'smhd', 'stbl', 'stco', 'stsc', 'stsd', 'stss', 'stsz', 'stts', 'tkhd', 'trak', 'uuid', 'vmhd', 'wide', 'schi', 'user', 'song', 'name', '\xa9url', 'soun', 'dinf', 'dref', 'schm', 'itun', 'pinf', 'hdlr', '\xa9alb', '\xa9ART', '\xa9alb', 'aART', '\xa9wrt', '\xa9day', '\xa9cmt', 'desc', 'purd', '\xa9grp', '\xa9gen', '\xa9lyr', 'purl', 'egid', 'catg', 'keyw', '\xa9too', 'cprt', 'soal', 'soaa', 'soar', 'sonm', 'soco', 'sosn', 'tvsh', 'pcst', 'tmpo', 'smhd', 'dinf', 'dref', 'traf', 'meta', 'moov.udta', 'moov.mvhd', 'moov.trak', 'moov.trak.tkhd', 'moov.trak.tkhd.edts', 'moov.trak.tkhd.edts.elst', 'moov.trak.tkhd.mdia', 'moov.trak.tkhd.hdlr', 'moov.trak.tkhd.minf', 'moov.trak.tkhd.mdhd', 'moov.udta' ] 113 | -------------------------------------------------------------------------------- /fuzzbox.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Fuzzbox 0.3.1 6 | (C)2007, iSEC Partners Inc. 7 | See LICENSE.txt for details. 8 | 9 | https://www.isecpartners.com 10 | """ 11 | 12 | import random, shutil, struct, os, time, resource, sys 13 | sys.path.append(os.getcwd() + "/mutagen") 14 | import ogg.vorbis, fuzzwave, mutagen.id3, fuzzaifc, mutagen.flac, mutagen.mp4 15 | from randjunk import randstring 16 | from utils import * 17 | from formats import * 18 | from subprocess import * 19 | from optparse import OptionParser 20 | 21 | vcomments = ogg.vorbis.VorbisComment(comments) 22 | 23 | totaltags = len(vcomments) 24 | 25 | # this is to reset the CRC after mangling of the header. 26 | def ogg_page_checksum_set(page): 27 | 28 | crc_reg = 0 29 | 30 | # This excludes the CRC from being part of the new CRC. 31 | page = page[0:22] + "\x00\x00\x00\x00" + page[26:] 32 | 33 | for i in range(len(page)): 34 | crc_reg = ((crc_reg<<8) & 0xffffffff) ^ crc_lookup[((crc_reg >> 24) & 0xff) ^ ord(page[i])] 35 | 36 | # Install the CRC. 37 | page = page[0:22] + struct.pack('I', crc_reg) + page[26:] 38 | return page 39 | 40 | def fuzz_ogg_tags(vcomments, numtofuzz): 41 | newtags = vcomments.as_dict() 42 | 43 | for i in range(numtofuzz): 44 | manglekey = random.choice(newtags.keys()) 45 | chance = random.randint(0,5) 46 | if chance == 0: 47 | print "generating garbage tag" 48 | garbagetag = randstring() 49 | newtags[str(garbagetag)] = randstring() 50 | else: 51 | print "mangling %d tags"%numtofuzz 52 | newtags[manglekey] = randstring() 53 | return newtags 54 | 55 | def fuzz_mp3_tags(oldtags, texttags, numtofuzz): 56 | 57 | newtags = oldtags 58 | 59 | print "mangling %d tags"%numtofuzz 60 | for i in range(numtofuzz): 61 | manglekey = random.choice(texttags) 62 | # pick a random charset encoding 63 | enc = random.randint(0,5) 64 | 65 | print manglekey 66 | try: 67 | newtags.add(eval("mutagen.id3.%s"%manglekey)(encoding=enc, text=randstring())) 68 | except: 69 | print "failed %s"%manglekey 70 | return newtags 71 | 72 | def fuzz_mp3_frame(sourcefile): 73 | try: 74 | f=open(sourcefile, 'rb') 75 | except IOError: 76 | print "Can't open source mp3 file." 77 | 78 | # taking a cue from mutagen 79 | data = f.read(32768) 80 | frame = data.find("\xff") 81 | frame_data = struct.unpack(">I", data[frame:frame + 4])[0] 82 | 83 | y = {} 84 | #### MP3 frame structure 85 | # sync will always be 11 set bits 86 | y['01version'] = (frame_data >> 19) & 0x3 87 | y['02layer'] = (frame_data >> 17) & 0x3 88 | # we'll always change this to off 89 | y['03protection'] = (frame_data >> 16) & 0x1 90 | y['04bitrateindex'] = (frame_data >> 12) & 0xF 91 | y['05sampfreq'] = (frame_data >> 10) & 0x3 92 | y['06padding'] = (frame_data >> 9) & 0x1 93 | y['07private'] = (frame_data >> 8) & 0x1 94 | y['08channelmode'] = (frame_data >> 6) & 0x3 95 | y['09modeext'] = (frame_data >> 4) & 0x3 96 | y['10copyright'] = (frame_data >> 3) & 0x1 97 | # this is pretty pointless 98 | y['11original'] = (frame_data >> 2) & 0x1 99 | y['12emphasis'] = (frame_data >> 0) & 0x3 100 | 101 | headerlength = f.tell() 102 | restoffile = f.read() 103 | filelength = len(restoffile) 104 | f.close() 105 | thestring = "" 106 | letsfuzz = random.choice(y.keys()) 107 | print "fuzzing %s"%letsfuzz 108 | 109 | thestring = "%X" % random.randint(0,15) 110 | y[letsfuzz] = thestring 111 | print y 112 | 113 | return y, restoffile 114 | 115 | def fuzz_ogg_frame(sourcefile): 116 | try: 117 | f=open(sourcefile, 'rb') 118 | except IOError: 119 | print "Can't open source ogg file." 120 | 121 | y = {} 122 | #### Ogg structure 123 | # the magic number 124 | y['01magic'] = f.read(4) 125 | # should always be 0x00 126 | y['02version'] = f.read(1) 127 | # 0x01 is cont 128 | # 0x02 is BOS 129 | # 0x04 is EOS 130 | y['03headertype'] = f.read(1) 131 | # a time marker 132 | y['04granulepos'] = f.read(8) 133 | # this is stored backwards?? 134 | y['05serial'] = f.read(4) 135 | # just the number of the page 136 | y['06pageseq'] = f.read(4) 137 | # CRC 138 | y['07crc'] = f.read(4) 139 | # number of segs in a page 140 | y['08numsegments'] = f.read(1) 141 | # umm, not sure 142 | y['09segtable'] = f.read(1) 143 | #### Vorbis structure 144 | # 0x00 is audio packet 145 | # 0x01 is id packet 146 | # 0x03 is comment packet 147 | # 0x05 is setup packet 148 | y['10packettype'] = f.read(1) 149 | # always "vorbis" 150 | y['11streamtype'] = f.read(6) 151 | # version of vorbis 152 | y['12version'] = f.read(4) 153 | # number of audio channels 154 | y['13channels'] = f.read(1) 155 | # self explanatory 156 | y['14samplerate'] = f.read(4) 157 | y['15maxbitrate'] = f.read(4) 158 | y['16nominalbitrate'] = f.read(4) 159 | y['17minbitrate'] = f.read(4) 160 | # first 4 bits are blocksize_0, next are blocksize_1 161 | y['18blocksize'] = f.read(1) 162 | # the framing byte 163 | y['19framing'] = f.read(1) 164 | 165 | # should be 58 bytes 166 | headerlength = f.tell() 167 | restoffile = f.read() 168 | filelength = len(restoffile) 169 | f.close() 170 | 171 | thestring = "" 172 | letsfuzz = random.choice(y.keys()) 173 | print "fuzzing %s"%letsfuzz 174 | 175 | thestring = randstring() 176 | stringtype = type(thestring) 177 | length = len(y[letsfuzz]) 178 | if str(stringtype) == "": 179 | y[letsfuzz] = struct.pack('s', thestring[:length]) 180 | elif str(stringtype) == "": 181 | y[letsfuzz] = struct.pack('i', thestring) 182 | else: 183 | thestring = "" 184 | for i in range(len(y[letsfuzz])): 185 | thestring += "%X" % random.randint(0,15) 186 | 187 | return y,restoffile 188 | 189 | def fuzz_flac_frame(sourcefile): 190 | try: 191 | f=open(sourcefile, 'rb') 192 | except IOError: 193 | print "Can't open source flac file." 194 | 195 | y = {} 196 | 197 | ### FLAC structure 198 | y['01magic'] = f.read(4) 199 | y['02header'] = f.read(1) 200 | y['03length'] = f.read(3) 201 | y['04minblocksize'] = f.read(2) 202 | y['05maxblocksize'] = f.read(2) 203 | y['06minframesize'] = f.read(3) 204 | y['07maxframesize'] = f.read(3) 205 | y['08samplerate_n_channels'] = f.read(3) 206 | 207 | restoffile = f.read() 208 | filelength = len(restoffile) 209 | f.close() 210 | 211 | thestring = "" 212 | letsfuzz = random.choice(y.keys()) 213 | print "fuzzing %s"%letsfuzz 214 | 215 | thestring = randstring() 216 | stringtype = type(thestring) 217 | length = len(y[letsfuzz]) 218 | if str(stringtype) == "": 219 | y[letsfuzz] = struct.pack('s', thestring[:length]) 220 | elif str(stringtype) == "": 221 | y[letsfuzz] = struct.pack('i', thestring) 222 | else: 223 | thestring = "" 224 | for i in range(len(y[letsfuzz])): 225 | thestring += "%X" % random.randint(0,15) 226 | #untested 227 | #y[letsfuzz] = thestring 228 | 229 | def fuzz_speex_frame(sourcefile): 230 | try: 231 | f=open(sourcefile, 'rb') 232 | except IOError: 233 | print "Can't open source flac file." 234 | 235 | y = {} 236 | 237 | y['00beginning'] = f.read(28) 238 | y['01sync'] = f.read(8) 239 | y['02version'] = f.read(20) 240 | y['03versionid'] = f.read(4) 241 | y['04headersize'] = f.read(4) 242 | y['05rate'] = f.read(4) 243 | y['06mode'] = f.read(4) 244 | y['07modebitstreamversion'] = f.read(4) 245 | y['08nbchannels'] = f.read(4) 246 | y['09bitrate'] = f.read(4) 247 | y['10framesize'] = f.read(4) 248 | y['11vbr'] = f.read(4) 249 | y['12framesperpacket'] = f.read(4) 250 | y['13extraheaders'] = f.read(4) 251 | y['14reserved1'] = f.read(4) 252 | y['15reserved2'] = f.read(4) 253 | 254 | restoffile = f.read() 255 | filelength = len(restoffile) 256 | f.close() 257 | 258 | thestring = "" 259 | letsfuzz = random.choice(y.keys()) 260 | print "fuzzing %s"%letsfuzz 261 | 262 | thestring = randstring() 263 | stringtype = type(thestring) 264 | length = len(y[letsfuzz]) 265 | if str(stringtype) == "": 266 | y[letsfuzz] = struct.pack('s', thestring[:length]) 267 | elif str(stringtype) == "": 268 | y[letsfuzz] = struct.pack('i', thestring) 269 | else: 270 | thestring = "" 271 | for i in range(len(y[letsfuzz])): 272 | thestring += "%X" % random.randint(0,15) 273 | #untested 274 | #y[letsfuzz] = thestring 275 | 276 | return y,restoffile 277 | 278 | def fuzz_flac_tags(comments, fh, numtofuzz): 279 | 280 | for i in range(numtofuzz): 281 | manglekey = random.choice(comments.keys()) 282 | print manglekey 283 | chance = random.randint(0,1) 284 | if chance == 0: 285 | print "generating garbage tag" 286 | garbagetag = randstring() 287 | try: 288 | fh[str(garbagetag)] = randstring() 289 | except UnicodeEncodeError: pass 290 | else: 291 | print "mangling %d tags"%numtofuzz 292 | fh[manglekey] = randstring() 293 | return fh 294 | 295 | def fuzz_qt_atoms(oldatoms, numtofuzz): 296 | 297 | newatoms = oldatoms 298 | 299 | print "mangling %d tags"%numtofuzz 300 | for i in range(numtofuzz): 301 | manglekey = random.choice(qtatoms) 302 | # pick a random charset encoding 303 | enc = random.randint(0,5) 304 | 305 | print manglekey 306 | #try: 307 | newatoms[manglekey] = randstring() 308 | #except: 309 | # print "failed %s"%manglekey 310 | return newatoms 311 | 312 | def playit(filename, timeout): 313 | log = open(logfile, "a") 314 | gdbfile = open("/tmp/gdbparams", "w") 315 | gdbfile.write("set args %s\n"%filename) 316 | if itunes == True: 317 | gdbfile.write("break ptrace if $r3 == 31\n") 318 | gdbfile.write("run\n") 319 | gdbfile.write("bt\n") 320 | if itunes == True: 321 | gdbfile.write("return\n") 322 | gdbfile.write("cont\n") 323 | gdbfile.write("bt\n") 324 | gdbfile.write("info reg\n") 325 | gdbfile.write("quit\n") 326 | gdbfile.close() 327 | # this is stupid. stdin=None causes the program to suspend 328 | # when gdb is killed. 329 | devnull = open("/dev/null", "r") 330 | log.write("===> Playing %s\n"%filename) 331 | gdb = Popen(["gdb", "-batch", "-x", "/tmp/gdbparams", progname], stdin=devnull, stdout=log, stderr=log) 332 | if itunes == True: 333 | # give a little time for gdb and iTunes to start up 334 | time.sleep(10) 335 | os.system("/usr/bin/open -a iTunes %s"%filename) 336 | x = 0 337 | # Watch the process to see if it gets stuck - either because 338 | # of an infinite loop, or because the player doesn't exit when 339 | # the file is complete. You'll get better performance if you 340 | # can make the player exit when done. 341 | while 1: 342 | try: 343 | if os.waitpid(gdb.pid,os.WNOHANG)[0]==0: 344 | time.sleep(1) 345 | x = x + 1 346 | if x >= timeout: 347 | print "process still running, killing it" 348 | log.write("===> Resources: " + str(resource.getrusage(resource.RUSAGE_CHILDREN)) + "\n") 349 | # Yeah, sorry. Replace with whatever 350 | # works for you. Hope you weren't listening 351 | # to something in another instance. :) 352 | os.system("killall -9 `basename %s`"%progname) 353 | break 354 | except OSError: 355 | log.write("process died playing %s\n."%filename) 356 | break 357 | 358 | log.close() 359 | 360 | count = 0 361 | 362 | def get_options(): 363 | parser = OptionParser(version='%prog version 0.1') 364 | 365 | parser.add_option('-r', '--reps', action='store', dest='reps', 366 | help='Number of files to generate/play', 367 | default = 10, type = int) 368 | 369 | parser.add_option('-p', '--program', action='store', dest='progname', 370 | default = None, 371 | help='Path to the player you\'d like to test') 372 | 373 | parser.add_option('-l', '--logfile', action='store', dest='logfile', 374 | help='Path to the logfile to record results', 375 | default = "playlog.log") 376 | 377 | parser.add_option('-s', '--source', action='store', dest='sourcefile', 378 | default = None, 379 | help='Path to a source file to fuzz') 380 | 381 | parser.add_option('-t', '--timeout', action='store', dest='timeout', 382 | default = 20, type = int, 383 | help='How long to wait for the player to crash') 384 | 385 | parser.add_option('-n', '--fuzzmax', action='store', dest='fuzzmax', 386 | default = 5, type = int, 387 | help='Maximum number of file elements to fuzz') 388 | 389 | parser.add_option('--itunes', action='store_true', dest='itunes', 390 | default = False, 391 | help='Work around iTunes anti-debugging') 392 | 393 | parser.add_option('--filetype', action='store', dest='filetype', 394 | default = "ogg", 395 | help='Type of file to fuzz: wav, aiff, spx, mp3, mp4 or ogg') 396 | 397 | 398 | return parser 399 | 400 | # main stuff starts here. 401 | parser = get_options() 402 | (ops, args) = parser.parse_args() 403 | reps = ops.reps 404 | progname = ops.progname 405 | logfile = ops.logfile 406 | sourcefile = ops.sourcefile 407 | timeout = ops.timeout 408 | itunes = ops.itunes 409 | filetype = ops.filetype 410 | fuzzmax = ops.fuzzmax 411 | 412 | if sourcefile == None: 413 | print "ERROR: You need to define at least the source file." 414 | print 415 | parser.print_help() 416 | sys.exit(1) 417 | 418 | if filetype == "ogg": 419 | 420 | for i in range(reps): 421 | check = random.randint(0,2) 422 | if check == 0: 423 | print "fuzzing tags." 424 | numtofuzz = random.randint(1,fuzzmax) 425 | print "fuzzing %d tags"%numtofuzz 426 | 427 | newfile = 'output' + str(count) + '.ogg' 428 | shutil.copyfile(sourcefile, newfile) 429 | try: 430 | newtags = ogg.vorbis.VorbisComment(fuzz_ogg_tags(vcomments, numtofuzz)) 431 | newtags.write_to(newfile) 432 | # ignore conversion breakage 433 | except (UnicodeEncodeError, ValueError): pass 434 | count = count + 1 435 | 436 | else: 437 | 438 | print "fuzzing frame." 439 | newfile = 'output' + str(count) + '.ogg' 440 | shutil.copyfile(sourcefile, newfile) 441 | fout = open(newfile, 'wb') 442 | newheader,restoffile = fuzz_ogg_frame(sourcefile) 443 | # keys() results are unsorted, so put them back in order 444 | page = "" 445 | for key in sorted(newheader.keys()): 446 | page += str(newheader[key]) 447 | 448 | page_with_crc = ogg_page_checksum_set(page) 449 | fout.write(page_with_crc) 450 | fout.close() 451 | fout = open(newfile, 'a') 452 | fout.write(restoffile) 453 | fout.close() 454 | if progname != None: 455 | print "Playing %s..."%newfile 456 | try: 457 | playit(newfile, timeout) 458 | except KeyboardInterrupt: 459 | print "User interrupted, cleaning up." 460 | os.system("killall -9 %s"%progname) 461 | sys.exit() 462 | count = count + 1 463 | 464 | elif filetype == "flac": 465 | numtags = len(comments) 466 | for i in range(reps): 467 | check = random.randint(0,2) 468 | if check == 0: 469 | print "fuzzing tags." 470 | numtofuzz = random.randint(1,fuzzmax) 471 | print "fuzzing %d tags"%numtofuzz 472 | 473 | newfile = 'output' + str(count) + '.flac' 474 | shutil.copyfile(sourcefile, newfile) 475 | 476 | fh = mutagen.flac.FLAC(newfile) 477 | newfh = fuzz_flac_tags(comments, fh, numtofuzz) 478 | 479 | try: 480 | newfh.save() 481 | if progname != None: 482 | print "Playing %s..."%newfile 483 | try: 484 | playit(newfile, timeout) 485 | except KeyboardInterrupt: 486 | print "User interrupted, cleaning up." 487 | os.system("killall -9 %s"%progname) 488 | sys.exit() 489 | except: 490 | failed = True 491 | os.remove(newfile) 492 | count = count + 1 493 | 494 | elif filetype == "mp3": 495 | numtags = len(texttags) 496 | for i in range(reps): 497 | check = random.randint(0,2) 498 | if check == 20: 499 | print "fuzzing tags." 500 | numtofuzz = random.randint(1,fuzzmax) 501 | newfile = 'output' + str(count) + '.mp3' 502 | shutil.copyfile(sourcefile, newfile) 503 | 504 | oldtags = mutagen.id3.ID3(newfile) 505 | 506 | newtags = fuzz_mp3_tags(oldtags, texttags, numtofuzz) 507 | failed = False 508 | try: 509 | newtags.save(newfile ,2) 510 | except: 511 | print "Failed to save %s"%newfile 512 | failed = True 513 | os.remove(newfile) 514 | 515 | count = count + 1 516 | else: 517 | print "fuzzing frame." 518 | newfile = 'output' + str(count) + '.mp3' 519 | shutil.copyfile(sourcefile, newfile) 520 | fout = open(newfile, 'wb') 521 | newheader,restoffile = fuzz_mp3_frame(sourcefile) 522 | page = "" 523 | for key in sorted(newheader.keys()): 524 | page += str(newheader[key]) 525 | 526 | fout.write(page) 527 | fout.close() 528 | fout = open(newfile, 'a') 529 | fout.write(restoffile) 530 | fout.close() 531 | if progname != None: 532 | print "Playing %s..."%newfile 533 | try: 534 | playit(newfile, timeout) 535 | except KeyboardInterrupt: 536 | print "User interrupted, cleaning up." 537 | os.system("killall -9 %s"%progname) 538 | sys.exit() 539 | count = count + 1 540 | 541 | elif filetype == "wav": 542 | 543 | oldwav = fuzzwave.open(sourcefile, 'rb') 544 | oldparams = oldwav.getparams() 545 | print oldparams 546 | numframes = oldwav.getnframes() 547 | # shouldn't be a problem given a small wav file 548 | data = oldwav.readframes(numframes) 549 | oldwav.close() 550 | 551 | for i in range(reps): 552 | newfile = 'output' + str(count) + '.wav' 553 | newwav = fuzzwave.open(newfile, 'wb') 554 | # this mostly tends to just make things not play. 555 | # if random.randint(0,2): 556 | # print "Fuzzing channels" 557 | # newwav.setnchannels(random.randint(-10,10)) 558 | # else: 559 | newwav.setnchannels(oldwav.getnchannels()) 560 | if random.randint(0,2): 561 | print "Fuzzing sampwidth" 562 | newwav.setsampwidth(random.randint(-1024,1024)) 563 | else: 564 | newwav.setsampwidth(oldwav.getsampwidth()) 565 | if random.randint(0,2): 566 | print "Fuzzing framerate" 567 | newwav.setframerate(random.randint(-1024,50000)) 568 | else: 569 | newwav.setframerate(oldwav.getframerate()) 570 | if random.randint(0,2): 571 | print "Fuzzing frame number" 572 | newwav.setnframes(random.randint(-1024,50000)) 573 | else: 574 | newwav.setnframes(oldwav.getnframes()) 575 | if random.randint(0,10): 576 | print "Fuzzing compression type" 577 | newwav.setcomptype(randstring(), randstring()) 578 | else: 579 | newwav.setcomptype(oldwav.getcomptype(), "lalala") 580 | print "Writing out data" 581 | try: 582 | newwav.writeframesraw(data) 583 | # There's potential for some divide-by-zeroes here 584 | except: 585 | print "Failed to write that one out" 586 | newwav.close() 587 | if progname != None: 588 | print "Playing %s..."%newfile 589 | try: 590 | playit(newfile, timeout) 591 | except KeyboardInterrupt: 592 | print "User interrupted, cleaning up." 593 | os.system("killall -9 %s"%progname) 594 | sys.exit() 595 | count = count + 1 596 | 597 | elif filetype == "aiff": 598 | 599 | oldaiff = fuzzaifc.open(sourcefile, 'rb') 600 | oldparams = oldaiff.getparams() 601 | print oldparams 602 | numframes = oldaiff.getnframes() 603 | # shouldn't be a problem given a small file 604 | data = oldaiff.readframes(numframes) 605 | oldaiff.close() 606 | 607 | for i in range(reps): 608 | newfile = 'output' + str(count) + '.aiff' 609 | newaiff = fuzzaifc.open(newfile, 'wb') 610 | if random.randint(0,2): 611 | print "Fuzzing channels" 612 | newaiff.setnchannels(random.randint(-10,10)) 613 | else: 614 | newaiff.setnchannels(oldaiff.getnchannels()) 615 | if random.randint(0,2): 616 | print "Fuzzing sampwidth" 617 | newaiff.setsampwidth(random.randint(-1024,1024)) 618 | else: 619 | newaiff.setsampwidth(oldaiff.getsampwidth()) 620 | if random.randint(0,2): 621 | print "Fuzzing framerate" 622 | newaiff.setframerate(random.randint(-1024,50000)) 623 | else: 624 | newaiff.setframerate(oldaiff.getframerate()) 625 | if random.randint(0,2): 626 | print "Fuzzing frame number" 627 | newaiff.setnframes(random.randint(-1024,50000)) 628 | else: 629 | newaiff.setnframes(oldaiff.getnframes()) 630 | if random.randint(0,10): 631 | print "Fuzzing compression type" 632 | newaiff.setcomptype(randstring(), randstring()) 633 | else: 634 | newaiff.setcomptype(oldaiff.getcomptype(), "lalala") 635 | print "Writing out data" 636 | try: 637 | newaiff.writeframesraw(data) 638 | newaiff.close() 639 | # There's potential for some divide-by-zeroes here 640 | except: 641 | print "Failed to write that one out" 642 | if progname != None: 643 | print "Playing %s..."%newfile 644 | try: 645 | playit(newfile, timeout) 646 | except KeyboardInterrupt: 647 | print "User interrupted, cleaning up." 648 | os.system("killall -9 %s"%progname) 649 | sys.exit() 650 | count = count + 1 651 | 652 | elif filetype == "spx": 653 | 654 | for i in range(reps): 655 | print "fuzzing frame." 656 | newfile = 'output' + str(count) + '.spx' 657 | shutil.copyfile(sourcefile, newfile) 658 | fout = open(newfile, 'wb') 659 | newheader,restoffile = fuzz_speex_frame(sourcefile) 660 | page = "" 661 | for key in sorted(newheader.keys()): 662 | page += str(newheader[key]) 663 | 664 | page_with_crc = ogg_page_checksum_set(page) 665 | fout.write(page_with_crc) 666 | fout.close() 667 | fout = open(newfile, 'a') 668 | fout.write(restoffile) 669 | fout.close() 670 | if progname != None: 671 | print "Playing %s..."%newfile 672 | try: 673 | playit(newfile, timeout) 674 | except KeyboardInterrupt: 675 | print "User interrupted, cleaning up." 676 | os.system("killall -9 %s"%progname) 677 | sys.exit() 678 | count = count + 1 679 | 680 | elif filetype == "mp4": 681 | numtags = len(qtatoms) 682 | for i in range(reps): 683 | check = random.randint(0,2) 684 | if check == 0: 685 | print "fuzzing tags." 686 | numtofuzz = random.randint(1,fuzzmax) 687 | newfile = 'output' + str(count) + '.mp4' 688 | shutil.copyfile(sourcefile, newfile) 689 | 690 | oldatoms = mutagen.mp4.MP4(newfile) 691 | 692 | newatoms = fuzz_qt_atoms(oldatoms, numtofuzz) 693 | failed = False 694 | try: 695 | newatoms.save() 696 | if progname != None: 697 | print "Playing %s..."%newfile 698 | try: 699 | playit(newfile, timeout) 700 | except KeyboardInterrupt: 701 | print "User interrupted, cleaning up." 702 | os.system("killall -9 `basename %s`"%progname) 703 | sys.exit() 704 | except: 705 | print "Failed to save %s"%newfile 706 | failed = True 707 | os.remove(newfile) 708 | 709 | count = count + 1 710 | -------------------------------------------------------------------------------- /oggcrc.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This is the CRC-setting logic stripped out from fuzzbox, for resetting 5 | # the CRC on manually altered files. 6 | 7 | import random, shutil, struct, os, time, resource, sys 8 | import ogg.vorbis 9 | from formats import * 10 | from subprocess import * 11 | from optparse import OptionParser 12 | 13 | vcomments = ogg.vorbis.VorbisComment(comments) 14 | 15 | totaltags = len(vcomments) 16 | 17 | # this is to reset the CRC after mangling of the header. 18 | def ogg_page_checksum_set(page): 19 | 20 | crc_reg = 0 21 | 22 | # This excludes the CRC from being part of the new CRC. 23 | page = page[0:22] + "\x00\x00\x00\x00" + page[26:] 24 | 25 | for i in range(len(page)): 26 | crc_reg = ((crc_reg<<8) & 0xffffffff) ^ crc_lookup[((crc_reg >> 24) & 0xff) ^ ord(page[i])] 27 | 28 | # Install the CRC. 29 | page = page[0:22] + struct.pack('I', crc_reg) + page[26:] 30 | return page 31 | 32 | def read_ogg_frame(sourcefile): 33 | try: 34 | f=open(sourcefile, 'rb') 35 | except IOError: 36 | print "Can't open source ogg file." 37 | 38 | y = {} 39 | #### Ogg structure 40 | # the magic number 41 | y['01magic'] = f.read(4) 42 | # should always be 0x00 43 | y['02version'] = f.read(1) 44 | # 0x01 is cont 45 | # 0x02 is BOS 46 | # 0x04 is EOS 47 | y['03headertype'] = f.read(1) 48 | # a time marker 49 | y['04granulepos'] = f.read(8) 50 | # this is stored backwards?? 51 | y['05serial'] = f.read(4) 52 | # just the number of the page 53 | y['06pageseq'] = f.read(4) 54 | # CRC 55 | y['07crc'] = f.read(4) 56 | # number of segs in a page 57 | y['08numsegments'] = f.read(1) 58 | # umm, not sure 59 | y['09segtable'] = f.read(1) 60 | #### Vorbis structure 61 | # 0x00 is audio packet 62 | # 0x01 is id packet 63 | # 0x03 is comment packet 64 | # 0x05 is setup packet 65 | y['10packettype'] = f.read(1) 66 | # always "vorbis" 67 | y['11streamtype'] = f.read(6) 68 | # version of vorbis 69 | y['12version'] = f.read(4) 70 | # number of audio channels 71 | y['13channels'] = f.read(1) 72 | # self explanatory 73 | y['14samplerate'] = f.read(4) 74 | y['15maxbitrate'] = f.read(4) 75 | y['16nominalbitrate'] = f.read(4) 76 | y['17minbitrate'] = f.read(4) 77 | # TODO - I messed up here, blocksize0 and blocksize1 are 4 bits each, 78 | # not one byte each 79 | y['18blocksize'] = f.read(1) 80 | y['19blocksize'] = f.read(1) 81 | # then there's the framing bit - but we don't need to touch it 82 | 83 | # should be 58 bytes 84 | headerlength = f.tell() 85 | restoffile = f.read() 86 | filelength = len(restoffile) 87 | f.close() 88 | 89 | return y,restoffile 90 | 91 | count = 0 92 | 93 | def get_options(): 94 | parser = OptionParser(version='%prog version 0.1') 95 | 96 | parser.add_option('-s', '--source', action='store', dest='sourcefile', 97 | default = None, 98 | help='Path to a source file to fuzz') 99 | 100 | parser.add_option('--filetype', action='store', dest='filetype', 101 | default = "ogg", 102 | help='Type of file to fuzz: wav, aiff, spx, mp3, mp4 or ogg') 103 | 104 | 105 | return parser 106 | 107 | # main stuff starts here. 108 | parser = get_options() 109 | (ops, args) = parser.parse_args() 110 | sourcefile = ops.sourcefile 111 | filetype = ops.filetype 112 | 113 | if sourcefile == None: 114 | print "ERROR: You need to define at least the source file." 115 | print 116 | parser.print_help() 117 | sys.exit(1) 118 | 119 | if filetype == "ogg": 120 | 121 | print "writing frame." 122 | newfile = 'output.ogg' 123 | shutil.copyfile(sourcefile, newfile) 124 | fout = open(newfile, 'wb') 125 | newheader,restoffile = read_ogg_frame(sourcefile) 126 | # keys() results are unsorted, so put them back in order 127 | page = "" 128 | for key in sorted(newheader.keys()): 129 | page += str(newheader[key]) 130 | 131 | page_with_crc = ogg_page_checksum_set(page) 132 | fout.write(page_with_crc) 133 | fout.close() 134 | fout = open(newfile, 'a') 135 | fout.write(restoffile) 136 | fout.close() 137 | -------------------------------------------------------------------------------- /randjunk.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import random 5 | 6 | # parts of the logic here taken from the DDD fuzzers: 7 | # http://www.digitaldwarf.be/products.html 8 | 9 | def randstring(): 10 | thestring = "" 11 | chance = random.randint(0,8) 12 | print "using method " + str(chance) 13 | if chance == 0: 14 | # try a random length of one random char 15 | char = chr(random.randint(0,255)) 16 | length = random.randint(0,3000) 17 | thestring = char * length 18 | # or a format string 19 | elif chance == 1: 20 | thestring = "%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n" 21 | elif chance == 2: 22 | # some garbage ascii 23 | for i in range(random.randint(0,3000)): 24 | char = '\n' 25 | while char == '\n': 26 | char = chr(random.randint(0,127)) 27 | thestring += char 28 | elif chance == 3: 29 | # build up a random string of alphanumerics 30 | chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 31 | for i in range(random.randint(0,3000)): 32 | thestring += random.choice(chars) 33 | elif chance == 4: 34 | # maybe something with unicode? 35 | for i in range(random.randint(0,3000)): 36 | thestring += unichr(i) 37 | # encoded or raw, 50/50 38 | if random.randint(0,1) == 0: 39 | thestring.encode('utf-8') 40 | elif chance == 5: 41 | # check for fencepost errors 42 | thestring = random.randint(-1,1) 43 | elif chance == 6: 44 | # try NULLs 45 | thestring = random.choice(["\x00", "\0"]) 46 | elif chance == 7: 47 | # Perhaps HTML will show up somewhere, could be 48 | # interesting 49 | thestring = "

big and noticeable

" 50 | elif chance == 8: 51 | # Some tags contain urls. If they're automatically 52 | # fetched, that could be of note. Insert your 53 | # webserver here, and watch for fetches for 54 | # that file in the logs. 55 | thestring = "http://labs.isecpartners.com/fuzzboxtest" 56 | else: 57 | thestring += str(random.randint(-5000,5000)) 58 | 59 | return thestring 60 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | def readint(self): 4 | data = self.file.read(4) 5 | return unpack('>I', data)[0] 6 | 7 | def readshort(self): 8 | data = self.file.read(2) 9 | return unpack('>H', data)[0] 10 | 11 | def readbyte(self): 12 | data = self.file.read(1) 13 | return unpack('B', data)[0] 14 | 15 | def read24bit(self): 16 | b1, b2, b3 = unpack('3B', self.file.read(3)) 17 | return (b1 << 16) + (b2 << 8) + b3 18 | --------------------------------------------------------------------------------