/** * File format fuzzer for detecting integer overflow, and unchecked input * bugs in file formats. Takes a file, and extension and a mask and produces new files, * each is a one-byte modification of the original. If masks from 1-255 are used, * this generates every permutation of single-byte modification to the original file * (every byte value for every position within the file. Unless limits are given, * you'll end up with N files, where N is the number of bytes in the original file. * * Filenames of created files are "shifter_B_M.E" where: * B = byte offset (0-based) * M = mask value * E = extension * * This means if you get a core dump from a particular input file, when you load it * into gdb, you can see exactly which offset and mask triggered the core, and * recreate the input file. * * This tool's real power comes when it is combined with scripting - it can be left * alone to generate bad input, test it against the program being checked, all while * the researcher sleeps. Simply script running this tool with all masks from 1-255, * keep all core files and remove all input files. You can always generate the input * again: * * gdb says input "run --debug shifter_1754_-122.rar" caused a core, so: * * 256 - 122 = 134 (convert to mask from ones complement) * $ java BitMasker sample.rar rar 134 1754 1755 * * This generates our input file again "shifter_1754_-122.rar" and we can run the * input within gdb or valgrind to help find the bug. * * Enjoy - yes, it's a simple tool, but it works while you sleep! */ import java.io.*; public class BitMasker { public static void main( String [] args ) { try { if( args.length < 3 ) { System.out.println( "You must supply a sample file, extension and mask.\n" ); System.exit( 1 ); } File original = new File( args[ 0 ] ); String extension = args[ 1 ]; byte mask = (byte)( Integer.parseInt( args[ 2 ] ) & 0xFF ); if( mask == 0 ) { System.err.println( "Mask of 0 results in no changes to the input file.\n" ); System.exit( 2 ); } if( !original.exists() || !original.canRead() || !original.isFile() || ( original.length() < 1 ) ) { System.err.println( "File " + args[ 0 ] + " can't be read.\n" ); System.exit( 2 ); } long totalLen = original.length(); int from = 0; int to = (int)totalLen; if( args.length >= 4 ) { from = Integer.parseInt( args[ 3 ] ); } if( args.length >= 5 ) { to = Integer.parseInt( args[ 4 ] ); } if( from < 0 || to < 0 || from > totalLen || to > totalLen ) { System.err.println( "You specified from/to outside file boundaries." ); System.exit( 4 ); } byte [] workingBuffer = new byte[ (int)totalLen ]; BufferedInputStream bi = new BufferedInputStream( new FileInputStream( original ) ); bi.read( workingBuffer, 0, (int)totalLen ); bi.close(); System.out.println( "Running from " + from + " to " + to ); for( int i = from; i < to; i++ ) { byte save = workingBuffer[ i ]; workingBuffer[ i ] ^= mask; String filename = "shifter_" + Integer.toString( i ) + "_" + mask + "." + extension; BufferedOutputStream bo = new BufferedOutputStream( new FileOutputStream( filename ) ); bo.write( workingBuffer, 0, workingBuffer.length ); bo.close(); workingBuffer[ i ] = save; // Put it back } } catch( Exception e ) { e.printStackTrace(); } } }