Binfuzz.js: A Binary Fuzzer in JavaScript

This live example uses Binfuzz.js to fuzz Windows ICO files in your browser. The fuzzing (eventually) crashes Safari, and makes Mobile Safari (the iPhone browser) close due to low memory. Starting fuzzing will definitely result in slowdown. The commented code for this example is at the end of the page.
Warning: Clicking 'Start Fuzzing' may crash your browser. It will definitely make your browser gobble up gigabytes of memory.
But you should still do it, because breaking things is awesome.


The Currently Fuzzing Icon:

(note: this may be blank for a while, most fuzzed icons are invalid by definition)




About Binfuzz.js

Binfuzz.js is a library for fuzzing structured binary data in JavaScript. Structured binary data is data that can be easily represented by one or more C structures. That is, the data is composed of fixed size fields, and any variable length fields are counted by another structure member. Examples of structured binary data include SSL, DNS, and most image formats. Things that aren't structured binary data include languages (such as HTML or JavaScript) or text-based protocols (such as HTTP) or text-based file formats (such as PDF).

Binfuzz.js uses the definition of a structure to create instances of the structure with invalid or edge-case values. The example on this page uses Binfuzz.js to generate Windows ICO files to stress your browser's icon parsing and display code. All icon files are generated from the structural definition of an ICO file. Each ICO file contains many images of different sizes (for optimal display on different resolutions). Binfuzz.js will generate ICO files that try to break your browser's icon drawing code. Examples of invalid icons include icons with a size of -1 by -1, icons that claim they are composed of 64 images but ony provide data for one, icons that claim they have 16-bit color in one field but specify a 32-bit color in another field, and so on.

A Crash Course in Fuzzing

Imagine there was a program that rendered pictures. To exhaustively test the program, one would have to render every possible picture, which is impossible. Exhaustive testing could be approximated by picking a random set of pictures and rendering those. This approximation is possible, and might find some bugs. The more random pictures are selected, the closer the randomized test approximates the exhaustive test. This randomized input testing is called fuzzing. Fuzzing sidesteps the 'too many possibilities' problem by assuming that a random sample of possible inputs is sufficient to find faults in software.

A fuzzer is a program that generates the random inputs. There are two main types: generative fuzzers and mutative fuzzer. A generative fuzzer will create new input, while a mutative fuzzer will mutate existing input. Binfuzz.js is a generative fuzzer. Most program will have checks on their inputs, such as magic numbers, length limits, and value restrictions. Modern fuzzers try to create inputs that will pass these checks to increase code coverage, that is, stress more of the program under test. Binfuzz.js generates program inputs that are likely to be partially valid by using the supplied structure definition. Additionally, binfuzz.js supports custom calculation functions to accurately populate checksums or other values dependent on multiple fields or custom calculations.

How does Binfuzz.js Work?

Binfuzz.js instantiates a new structure from a structure definition. It was created for generating ICO files, but should operate on any structured data.

Structure definitions are designed to be simple to specify. Below is a comparison of declaring a C structure (left), and declaring the same structure in Binfuzz.js (right).

C Representation
struct PDU {
    uint32_t      magic;
    uint8_t       type;
    uint16_t      size;
    char          data[1];
};
Binfuzz.js Representation
var PDU = new Container({
    'name': 'PDU'
});
PDU.addChild( new Int32(
{
    'root': PDU,
    'name': 'magic'
}));
PDU.addChild( new Int8(
{
    'root': PDU,
    'name': 'type'
}));
PDU.addChild( new IntSize(
{
    'root': PDU,
    'name': 'size',
    'bytesize': 2,
    'target': 'PDU.data'
}));
PDU.addChild(new Blob(
{
    'root': PDU,
    'name': 'data',
    'generator': makeRandomString
}));


Binfuzz.js includes support for:
  • Several types of elements (Int8, Int16, Int32 and Blob are already defined)
  • Nested structures
  • Arrays
  • Counter Fields (e.g. field A = number of elements in Array B)
  • Length Fields (e.g. field A = length of Blob B)
  • File Offsets (e.g. field A = how far from the start of the file is Blob B?
  • Custom population functions (e.g. field A = fieldB.length + fieldC.length)
The ICO fuzzing example includes uses of all of these because I needed them to implement ICO file generation.

Combinations

Binfuzz.js calculates the total number of combinations based on how many possible combinations there are for each individual field. It is then possible to generate a structured data instance corresponding to a specific combination number. It is not necessary to generate prior combinations. This way random combinations can be selected when fuzzing run time is limited.

Why?

The best way to learn is by doing, and I wanted to learn JavaScript. So I decided to create an ICO file fuzzer in JavaScript. I chose ICO files because of favicon.ico, a file browsers automatically request when navigating to a new page.

Favicons are an especially interesting fuzzing target because they can expose vulnerabilities in the browser and the OS. Favicons can expose browser vulnerabilities when drawn via an IMG tag or in a CANVAS element, and favicons can expose OS vulnerabilities when set as a tab icon or menu icon. Loading favicons requires no user iteraction, because favicons are automatically fetched and drawn by your browser. Another benefit to fuzzing icons, and one that I had not considered before starting the project is that when fuzzing images, you can actually see the fuzzing working.

After starting the project, I realized I got a lot more than I bargained for. Icons are a surprisingly complex format that has evolved over time. There are several images in one file, each image has corresponding metadata, there are internal fields that refer to offsets in the file, and the size of the icon data for each image depends the metadata. All of these interlinked reationships need to be described and processed by Binfuzz.js.

Where?

All code is available at http://github.com/artemdinaburg/binfuzz

ICO Fuzzing Example

Icons are a complex format, involving nested structures, arrays, and binary blobs. Some of the fields are derived from other fields, and a few fields of nested structures are linked together. The Microsoft documentation on ICO files describes the interaction pretty well. Below is the documented code of the example on this page, to show how to use Binfuzz.js.

<!-- Base64 conversion for browsers that don't support it -->
<script language="JavaScript" src="base64.js"></script>
<!-- Convert binary values to a packed string -->
<script language="JavaScript" src="BinaryParser.js"></script>
<!-- Use a seeded random generator, but keep a reference to the builtin Math.random -->
<script language="JavaScript">
    <!--
        Math['oldrandom'] = Math.random;
              should_fuzz = true;
        -->
</script>
<script language="JavaScript" src="seedrandom-min.js"></script>
<!-- The Binfuzz.js script -->
<script language="JavaScript" src="binfuzz.js"></script>
<script language="JavaScript">
    <!--
        // If the browser doesn't support native base64
        // then use base64.js version.
        if (!window.btoa) window.btoa = base64.encode;
        if (!window.atob) window.atob = base64.decode;
        
        // Create a new link element to dynamically update the
        // favicon for this browser tab
        function changeFavicon(src) {
            document.head || (document.head = document.getElementsByTagName('head')[0]);
            var link = document.createElement('link'),
                oldLink = document.getElementById('dynamic-favicon');
            link.id = 'dynamic-favicon';
            link.rel = 'icon';
            link.href = src;
            if (oldLink) {
                document.head.removeChild(oldLink);
            }
            document.head.appendChild(link);
        }
        
        // Draw this icon file in a canvas element
        function drawPreview(datalink) {
            var canvas = document.getElementById('iconpreview');
            context = canvas.getContext('2d');
        
            var oldw = canvas.width;
            // clear the old icon in the canvas by changing the width
            // and re-setting it
            canvas.width = 1;
            canvas.width = oldw;
        
            // create an Image from the base64 data:// URL
            // that represents the icon
            icon_preview = new Image();
            icon_preview.src = datalink;
            icon_preview.onload = function() {
                console.log("drawing image");
                try {
                    // The image has been parsed, draw it on the canvas
                    context.drawImage(icon_preview, 128/2-8, 128/2-8);
                    delete this.src;
                } catch (err) {
                    // could not draw the image
                    console.log("could not draw image");
                }
                // do the next image
                setTimeout('fuzzAnIcon()', 500);    
            };
            icon_preview.onerror = function() {
                // There was a failure parsing the image; try the next
                setTimeout('fuzzAnIcon()', 500);    
            };
        }
        
        // a simple logger to write log data to a text box
        function myLog(msg)
        {
            var val = document.getElementById('textlog').value;
            document.getElementById('textlog').value =  msg + "\n" + val;
        }
        
        
        // defines the icon structure and then creates a new icon
        // based on the structure definition.
        function makeNewIcon() {
            // this is the overall container; it is needed
            // since icons include two structures that reference 
            // each other
            var ICON = new Container({'name': 'ICON'});
        
            // this is the actual bitmap information
            // as defined in a windows bitmap. 
            // See:
            // http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
            var tagBITMAPINFOHEADER = new Container(
                {'root': ICON,
                'name': 'tagBITMAPINFOHEADER'}
                );
        
            // The IntSize element is used for sizes
            // of other structures
        
            // this is the size of the tagBITMAPINFOHEADER structure
            // the 'target' is the struture itself, 
            // and its size will be automatically 
            // calculated. This element is not fuzzed
            tagBITMAPINFOHEADER.addChild( new IntSize(
                {'root': ICON,
                'name': 'biSize',
                'bytesize': 4, 
                'nofuzz': true,
                'signed': true,
                'target': tagBITMAPINFOHEADER}
                ));
        
            // the width of the bitmap; takes on one of
            // the values in 'values'
            tagBITMAPINFOHEADER.addChild( new Int32(
                {'root': ICON,
                'name': 'biWidth', 
                'values': [0, 1, 3, 16, 32, 64, 129, 0xFF, 0xFFF, 0xFFFE, 0xFFFF]}
                ));
        
            // the height of the bitmap; takes on one of
            // the values in 'values'
            tagBITMAPINFOHEADER.addChild( new Int32(
                {'root': ICON,
                 'name': 'biHeight',
                'values': [0, 1, 3, 16, 32, 64, 129, 0x101, 0x1001, 0xFFFE, 0xFFFF]}
             ));
        
            // the number of planes. This defaults to 1, but
            // other values will be selected as fuzzing proceeds
            tagBITMAPINFOHEADER.addChild( new UInt16(
                {'root': ICON,
                'name': 'biPlanes',
                'default': 1}
            ));
        
            // the choices for the color depth
            tagBITMAPINFOHEADER.addChild( new UInt16(
                {'root': ICON,
                 'name': 'biBitCount',
                 'values': [1,2,4,8,16,24,32]}
            ));
        
            // Documented compression values
            tagBITMAPINFOHEADER.addChild( new UInt32(
                {'root': ICON,
                 'name': 'biCompression',
                 'values': [0,1,2,3,6]}
            ));
        
            // This is the size of the image.
            // As we are generating images, we don't have image data a-priori
            // We need to know how much data to generate.
            // 
            // The size is calculated based on the width, height, planes, and bpp
            // There are alignment issues that make this a bit complicated
            tagBITMAPINFOHEADER.addChild( new IntSize(
                {'root': ICON,
                'name': 'biSizeImage',
                'bytesize': 4,
                'nofuzz': true,
                'signed': true,
                'target': tagBITMAPINFOHEADER,
                'calcfunc': function(target, combo) {
                    var width = target.getChild('biWidth').NativeValue();    
                    var height = target.getChild('biHeight').NativeValue();
                    var planes = target.getChild('biPlanes').NativeValue();
                    var bpp = target.getChild('biBitCount').NativeValue();
        
                    // size of OR mask
                    var tmp1 = width * planes * bpp + 31;
                    var tmp2 = (tmp1 & ~31) / 8;
                    var tmp3 = tmp2 * height / 2;
        
                    // size of AND mask
                    var and1 = width * planes * 1 + 31;
                    var and2 = (and1 & ~31) / 8;
                    var and3 = and2 * height / 2;
        
                    tmp3 += and3;
        
                    return tmp3;
                    }
                }
            ));
        
            // these are values that are ignored
            // according to the documentation, and
            // are therefore set to a constant value
            tagBITMAPINFOHEADER.addChild( new Int32(
                {'root': ICON,
                'name': 'biXPelsPerMeter',
                'constant': 0}
            ));
            tagBITMAPINFOHEADER.addChild( new Int32(
                {'root': ICON,
                'name': 'biYPelsPerMeter',
                'constant': 0}
            ));
            tagBITMAPINFOHEADER.addChild( new UInt32(
                {'root': ICON,
                'name': 'biClrUsed',
                'constant': 0}
            ));
            tagBITMAPINFOHEADER.addChild( new UInt32(
                {'root': ICON,
                'name': 'biClrImportant',
                'constant': 0}
            ));
        
            // an icon is composed of many images
            // While they can technically be all different
            // we compromise expression for ease of 
            // implementation here.
            // 
            // We use an array of the same icon, but
            // the size of the array can vary
            // 
            var ICONIMAGE = new ArrayContainer(
                {'root': ICON,
                 'name': 'ICONIMAGE',
                 'count': 'ICON.ICONDIR.idCount'}
            );
        
            // this array will have a bitmapinfoheader
            ICONIMAGE.addChild(tagBITMAPINFOHEADER);
        
            // this is an optional field that I currently
            // do not implement. This field is used in
            // some color depth icons though.
            ICONIMAGE.addChild(new Blob(
                {'root': ICON,
                 'name': 'icColors',
                 'length': 0,
                 'blob': ""}
            ));
        
            // this is the icon XOR mask.
            // Like the actual image data, we do not know it a priori
            // since we generate new icons. Also like the image data,
            // the size of the XOR mask is based on the width, height
            // planes, and color depth
            //
            // There is a sanity check to avoid generating gigabytes of
            // icon data. This script uses too much memory as is.
            ICONIMAGE.addChild(new Blob(
                {'root': ICON,
                 'name': 'icXOR',
                 'generator': makeRandomString,
                 'length': function (seed, parent, root) {
                        var width = parent.getChild('tagBITMAPINFOHEADER').getChild('biWidth').NativeValue();
                        var height = parent.getChild('tagBITMAPINFOHEADER').getChild('biHeight').NativeValue() / 2;
                        var bpp = parent.getChild('tagBITMAPINFOHEADER').getChild('biBitCount').NativeValue();
                        var planes = parent.getChild('tagBITMAPINFOHEADER').getChild('biPlanes').NativeValue();
        
                        var stride = (( width * planes * bpp + 31) & ~31) / 8;
                        var thislen = stride * height;
                        
                        var blobsize = thislen;
                        // sanity check. we are not about to generate gigabytes of
                        // icon data
                        if(blobsize < 0 || blobsize > 0xFFFF) {
                                blobsize = 0xFFFF;
                        }
                        return blobsize;
                        }
                }
            ));
        
            // this is just like the XOR mask, but the size is different
            // since there the AND mask does not depend on color depth
            ICONIMAGE.addChild(new Blob(
                {'root': ICON,
                'name': 'icAND',
                 'generator': makeRandomString,
                 'length': function (seed, parent, root)
                 {
                    var width = parent.getChild('tagBITMAPINFOHEADER').getChild('biWidth').NativeValue();
                    var height = parent.getChild('tagBITMAPINFOHEADER').getChild('biHeight').NativeValue() / 2;
                    var planes = parent.getChild('tagBITMAPINFOHEADER').getChild('biPlanes').NativeValue();
        
        
                    var stride = (( width * planes * 1 + 31) & ~31) / 8;
                    var thislen = stride * height;
        
                    // sanity check. we are not about to generate gigabytes of
                    // icon data, even if I really want to
                    if(thislen < 0 || thislen > 0xFFFF) {
                            thislen = 0xFFFF;
                    }
                    return thislen;
                 } 
                 }
            ));
        
        
            // The array of icon metadata is stored separately
            // from the bitmap data, but they need to have 
            // some kind of link with each other.
            // 
            // That is one of the reasons each entry
            // is named. Entries can be referenced by
            // name, including arrays. 
            // 
            // The 'indexer' value of the array is a token
            // that will be replaced by the array index when
            // it is used in children. 
            // 
            var ICONDIRENTRY = new ArrayContainer(
                {'root': ICON,
                 'name': 'idEntries',
                 'count': 'ICON.ICONDIR.idCount',
                 'indexer': '%n'}
            );
        
            // the width as defined by icon metadata
            // Yes, a width is specified both in the bitmapinfo
            // and here. Assume 16 by default
            ICONDIRENTRY.addChild(new Int8(
                    {'root': ICON,
                    'name': 'bWidth',
                    'default': 16 }
            ));
        
            // same as above, but height
            ICONDIRENTRY.addChild(new Int8(
                    {'root': ICON,
                    'name': 'bHeight',
                    'default': 16 }
            ));
        
            // these elements are unused and
            // hence set to constant values
            ICONDIRENTRY.addChild(new Int8(
                    {'root': ICON,
                     'name': 'bColorCount',
                     'constant': 0}
            ));
        
            ICONDIRENTRY.addChild(new Int8(
                    {'root': ICON,
                     'name': 'bReserved',
                     'constant': 0}
            ));
            
            // the number of bitmap planes
            // in this icon image
            ICONDIRENTRY.addChild(new Int16(
                    {'root': ICON,
                     'name': 'wPlanes',
                     'default': 1}
            ));
            
            // the color depth of the icon.
            // once again, this duplicates 
            // what is specified in the bitmap info
            ICONDIRENTRY.addChild(new Int16(
                    {'root': ICON,
                     'name': 'wBitCount',
                     'values': [1,2,4,8,16,24,32]}
            ));
        
            // the size of an icon metadata entry.
            // This references an array entry of the
            // icon image array.
            // The %n in the target will be expanded to the 
            // array element that we are calculating the 
            // target of
            ICONDIRENTRY.addChild(new IntSize(
                    {'root': ICON,
                     'name': 'dwBytesInRes',
                     'nofuzz': true,
                     'bytesize': 4,
                     'target': "ICON.ICONIMAGE[%n]"}
            ));
        
            // This is where the image data will be 
            // located in the file. 
            // The IntOffset type is used to calculate
            // field offsets in relation to all of the
            // other fields.
            ICONDIRENTRY.addChild(new IntOffset(
                    {'root': ICON,
                     'name': 'dwImageOffset',
                     'nofuzz': true,
                     'bytesize': 4,
                     'target': "ICON.ICONIMAGE[%n]"}
            ));
        
            // the icon directory entry
            var ICONDIR = new Container(
                {'root': ICON,
                 'name': 'ICONDIR'}
                );
        
            // a reserved magic number, set 
            // to zero
            ICONDIR.addChild( new Int16(
                {'root': ICON,
                 'name': 'idReserved',
                 'constant': 0}
            ));
        
            // the type of icon. The type 1
            // is reserved for icons
            ICONDIR.addChild( new Int16(
                    {'root': ICON,
                     'name': 'idType',
                     'constant': 1}
            ));
        
            // how many images are in this icon file?
            ICONDIR.addChild( new Int16(
                    {'root': ICON,
                     'name': 'idCount',
                     'values': [1, 64, 0xFF]}
            ));
        
            // link all the structures to the top level
            // container
            ICONDIR.addChild(ICONDIRENTRY);
            ICON.addChild(ICONDIR);
            ICON.addChild(ICONIMAGE);
        
            // icon struture is ready
            return ICON;
        } // makeNewIcon
        
        
        function fuzzAnIcon(fuzzesBeforeReload, specificCombo) {
        
            if(should_fuzz === false) {
                return;
            }
        
            // Get an icon structure definition
            var icon = makeNewIcon();
            // How many combinations are there?
            var combos = icon.Combos();
            myLog("icon Combos: " + combos);
            // pick a random combination to instantiate
            var randcombo = Math.floor(Math.oldrandom() * (combos));
        
            // this is here in case this code would be needed
            // to reproduce a specific fuzz case
            if(specificCombo === undefined)
            {
                myLog("Trying combo: " + randcombo);
                icon.Distribute(randcombo, 0);
            }
            else 
            {
                // the Distribute call populates each element of the
                // icon structure with its combination number
                myLog("Trying combo: " + specificCombo);
                icon.Distribute(specificCombo, 0);
            }
        
            myLog("Icon Size: " + icon.Size());
            console.log("About to calculate value");
            // actually generate the data that correspons
            // to the specific combination
            var ic = icon.Value();
            // and convert it to a base64 url
            var iconb64 = "data:image/x-icon;base64," + window.btoa(ic);
            ic = "";
        
            // taken from:
            // http://stackoverflow.com/questions/260857/changing-website-favicon-dynamically
            //
            drawPreview(iconb64);
            // Google Chrome will kill the renderer process if a URL 
            // length is > kMaxURLChars, which is 2MB in newer
            // Chrome builds.
            var is_chrome = /chrome/.test(navigator.userAgent.toLowerCase()); 
            var kMaxURLChars = 2 * 1024 * 1024;
            if(is_chrome && iconb64.length >= kMaxURLChars) {
                myLog("Icon Would Kill Chrome Renderer, not setting...");
            } else {
                changeFavicon(iconb64);
                myLog("Icon Set...");
            }
            iconb64 = "";
            // This was my attempt at removing references to help garbage collectors
            // make this script hog less memory
            icon.HelpGC();
        
            if(fuzzesBeforeReload === undefined) {
                return;
            }
        
            // normally a new fuzz case will be run after an icon
            // is rendered, or fails to render, but sometimes
            // none of the callbacks will be called. This ensures
            // that a new fuzz iteration restarts
            if(fuzzesBeforeReload == 0) {
                setTimeout('location.reload(true)', 2000);
            } else {
                fuzzesBeforeReload -= 1;
                setTimeout('fuzzAnIcon(' + fuzzesBeforeReload.toString() + ')', 2000);
            }
        }
        
        -->
</script>


License

Binfuzz.js is licensed under the MIT License:
Copyright (c) 2013 Artem Dinaburg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.