diff --git a/README.md b/README.md index db8a2f4ce778bad4a9c24e4af63eceb89187b327..f5135b73a923e4e33f2f855c5d395281a0e6bfb9 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,27 @@ Proofs of Concept (PoCs that you should provide for each vulnerability): * poc/vuln-1.poc -- poc/vuln-5.poc +<hr> +Commands for testing (by Yang Liu):<br> +<ol> +<li> +Generate 100 fuzz.txt<br> +<code> +bash ./run_fuzzer.sh +</code> +</li> +<li> +Run the generated 100 fuzz.txt above:<br> +<code> +bash ./run_tests.sh original +</code><br> +This is to run all the txt inputs on original. Replace 'original' with 'vuln-1' to 'vuln-5' as needed. +</li> +<li> +Calculate coverage rate:<br> +<code> +bash ./get_coverage.sh ./fuzzer/fuzz.txt +</code><br> +Replace the './fuzzer/fuzz.txt' with the txt file used. +</li> +</ol> \ No newline at end of file diff --git a/fuzzer/Fuzzer.java b/fuzzer/Fuzzer.java index 9394bf8fd81a260984d87d9fe1aa3f3ef35fd4de..b1ecf092bafa212a91c9debe27ee78bb441c5809 100644 --- a/fuzzer/Fuzzer.java +++ b/fuzzer/Fuzzer.java @@ -13,14 +13,11 @@ public class Fuzzer { FileOutputStream out = null; PrintWriter pw = null; try { - out = new FileOutputStream(OUTPUT_FILE); - pw = new PrintWriter(out); - - /* We just print one instruction. - Hint: you might want to make use of the instruction - grammar which is effectively encoded in Instruction.java */ - pw.println("list"); - + InputGenerator inputGenerator = new InputGenerator( + 1022, + 1024, + "fuzz.txt"); + inputGenerator.generateFuzz(InputGenerator.MODE.TOTAL_RANDOM); }catch (Exception e){ e.printStackTrace(System.err); System.exit(1); diff --git a/fuzzer/InputGenerator.java b/fuzzer/InputGenerator.java index 111c459fe5275fb45448e69812cff4e72f3c9f52..7b02c6fcecba3e8fe8fbc57d1bb2ca9e49dab67b 100644 --- a/fuzzer/InputGenerator.java +++ b/fuzzer/InputGenerator.java @@ -1,38 +1,58 @@ +import java.io.*; import java.util.*; +import java.util.stream.Collectors; /** * A string generator to generate various inputs for the Fuzzer */ public class InputGenerator { + // modes of the generation pattern + public enum MODE { + TOTAL_RANDOM, + } + + // filename for the config file + private static final String CFG_FILE = "config.cfg"; + // a key in the config file: how many times the fuzzer has been run already + private static final String keyRunCounter = "Number of runs done"; // the chars range used to generate inputs, left inclusive, right exclusive // 0 is skipped since it will terminate a c string - private static final int[] ASCII_RANGE = new int[] {33, 127}; - + private static final int[] ASCII_RANGE = new int[]{33, 127}; // how many args for the put command private static final int NUM_PUT_ARGS = 3; - // how many times the total length of an invalid string (due to being too long) should be, relative to // the max length of a valid string private static final int INVALID_MAX_LENGTH_MULTIPLIER = 5; - // same as above, but relative to the number of arguments for a command private static final int INVALID_NUM_ARGUMENTS_MULTIPLIER = 10; - // how likely the generated commands will have valid number of arguments // it will be a chance of: when random.newInt(rate) != 0 for all nun-valid arg num commands to reroll private static final int VALID_NUM_ARGUMENT_SKEW_RATE = 3; - // how likely the generated GET commands will be asking for existing entries generated by PUT // the probability will be rate/(rate+1) private static final int GET_EXISTING_ENTRY_SKEW_RATE = 1; - // avoid these chars in the randomly generated strings - private static final int[] AVOIDED_ASCII_IDX = new int[] {9, 10, 13, 32}; + private static final int[] AVOIDED_ASCII_IDX = new int[]{9, 10, 13, 32}; + // the fuzzer is assumed to run at least n times. according to the assignment description, n=10. + private static final int ASSUMED_MIN_TIMES_TO_RUN = 10; + // if set to true, not generating masterpw commands + // it seems currently there is no way to do the password check via fuzz.txt, so this is set to true + private static final boolean IGNORE_MASTERPW = true; // if set to true, will only generate commands with valid number of arguments // currently it seems incorrect number of arguments will end the passbook, so this is set to true private static final boolean ALWAYS_USE_CORRECT_NUM_ARGS = true; + // if set to true, will only generate commands within valid line length of given constructor arguments + // when set to false, will generate commands within valid line length of the original passbook code + private static final boolean USE_MAX_LINE_LENGTH_IN_ARGS = true; + // if set to true, will only generate number of commands according to the given constructor arguments + // when set to false, will generate number of commands according to the original passbook code + private static final boolean USE_MAX_NUM_LINES_IN_ARGS = true; + // the max line length of original passbook + private static final int ORIGINAL_MAX_LINE_LENGTH = 1022; + // the max line length of original passbook + private static final int ORIGINAL_MAX_NUM_LINES = 1024; // max length of an input line private int maxInputLineLength; @@ -42,23 +62,113 @@ public class InputGenerator { private Map<String, Pair<String, String>> validPassbookEntries; // skipped ascii index for symbol not included in the random generated strings private Set<Integer> avoidedAsciiIndexSet; + // store config + private Properties configBase = new Properties(); + // name of the output fuzz file + private String fuzzFileName; + // max number of commands in the fuzz file + private int maxNumLines; /** * base constructor + * * @param maxInputLineLength the size of input buffer for target program + * @param maxNumLines max number of commands in the fuzz file + * @param fuzzFileName name of the output fuzz file */ - public InputGenerator(int maxInputLineLength) { - this.maxInputLineLength = maxInputLineLength; + public InputGenerator(int maxInputLineLength, int maxNumLines, String fuzzFileName) { + this.maxInputLineLength = USE_MAX_LINE_LENGTH_IN_ARGS ? maxInputLineLength : ORIGINAL_MAX_LINE_LENGTH; + this.maxNumLines = USE_MAX_NUM_LINES_IN_ARGS ? maxNumLines : ORIGINAL_MAX_NUM_LINES; + this.fuzzFileName = fuzzFileName; this.random = new Random(); this.validPassbookEntries = new HashMap<>(); this.avoidedAsciiIndexSet = new HashSet<>(); for (int i : AVOIDED_ASCII_IDX) { this.avoidedAsciiIndexSet.add(i); } + readConfig(); + } + + /** + * generate the fuzz file according to required generation mode + * @param mode the generation mode + */ + public void generateFuzz(MODE mode) { + String output = ""; + switch (mode) { + case TOTAL_RANDOM: + output = generateTotalRandom(); + default: + output = generateTotalRandom(); + } + + // write to fuzz file + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(this.fuzzFileName)); + writer.write(output); + writer.close(); + // update cfg to record times run + increaseRunCounter(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * generate random lines, chosen from all commands + * + * @return output + */ + public String generateTotalRandom() { + List<String> commands = new ArrayList<>(); + ArrayList<Instruction> instructions = new ArrayList<>(); + for (Instruction instruction : Instruction.class.getEnumConstants()) { + if (IGNORE_MASTERPW && instruction == Instruction.MASTERPW) { + continue; + } + instructions.add(instruction); + } + + for (int i = 0; i < this.maxNumLines; i++) { + int cmdIdx = random.nextInt(instructions.size()); + switch (instructions.get(cmdIdx)) { + case PUT: + commands.add(generatePut(1).get(0)); + break; + case GET: + commands.add(generateGet(1).get(0)); + break; + case REM: + commands.add(generateRem(1).get(0)); + break; + case LIST: + commands.add(generateList(1).get(0)); + break; + case SAVE: + commands.add(generateSave(1).get(0)); + break; + case MASTERPW: + // since each masterpw has 2 lines + if (commands.size() <= this.maxNumLines - 2) { + ArrayList<String> masterpwCmds = generateMasterpw(1); + commands.addAll(masterpwCmds); + i++; + } + break; + } + } + + // make sure the output does not crash the program by having too many lines + if (commands.size() > this.maxNumLines) { + commands = commands.subList(0, this.maxNumLines - 1); + } + String output = commands.stream().map(Objects::toString).collect(Collectors.joining("\n")); + return output; } /** * generate multiple random PUT commands + * * @param numCommands number of commands to generate * @return all the commands generated */ @@ -68,6 +178,7 @@ public class InputGenerator { /** * generate multiple random REM commands + * * @param numCommands number of commands to generate * @return all the commands generated */ @@ -77,6 +188,7 @@ public class InputGenerator { /** * generate multiple random GET commands + * * @param numCommands number of commands to generate * @return all the commands generated */ @@ -86,6 +198,7 @@ public class InputGenerator { /** * generate multiple random LIST commands + * * @param numCommands number of commands to generate * @return all the commands generated */ @@ -95,6 +208,7 @@ public class InputGenerator { /** * generate multiple random SAVE commands + * * @param numCommands number of commands to generate * @return all the commands generated */ @@ -105,6 +219,7 @@ public class InputGenerator { /** * generate multiple random MASTERPW commands * each 'masterpw password' will be followed by the password itself. + * * @param numCommands number of commands to generate * @return all the commands generated */ @@ -116,7 +231,7 @@ public class InputGenerator { String pw = prompt.split(" ")[1]; // trim a bit in case the pw is too long and only part of it is registered in the buffer // this has to take into account of the length of 'masterpw ' part in the front - int maxPwLength = maxInputLineLength - Instruction.MASTERPW.getOpcode().length() - 2; + int maxPwLength = maxInputLineLength - Instruction.MASTERPW.getOpcode().length() - 1; if (pw.length() > maxPwLength) { pw = pw.substring(0, maxPwLength); } @@ -128,19 +243,20 @@ public class InputGenerator { /** * generate multiple random commands based on url as the first argument, so stored url - * generated by 'put' can be tested + * generated by 'put' can be tested * currently these commands are just 'rem' and 'get' - * @param command the Instruction - * @param numCommands number of commands to generate + * + * @param command the Instruction + * @param numCommands number of commands to generate * @param noMutateRatio see mutateRandomCmdWithValidUrl() - * @param attachRatio see mutateRandomCmdWithValidUrl() - * @param replaceRatio see mutateRandomCmdWithValidUrl() + * @param attachRatio see mutateRandomCmdWithValidUrl() + * @param replaceRatio see mutateRandomCmdWithValidUrl() * @return generated commands */ private ArrayList<String> generateUrlBasedCommands(Instruction command, int numCommands, - int noMutateRatio, - int attachRatio, - int replaceRatio) { + int noMutateRatio, + int attachRatio, + int replaceRatio) { // if no stored entries, generate random ones if (validPassbookEntries.isEmpty()) { return generateCommands(command, numCommands); @@ -162,6 +278,7 @@ public class InputGenerator { return commands; } } + // overload with default arguments private ArrayList<String> generateUrlBasedCommands(Instruction command, int numCommands) { return generateUrlBasedCommands(command, numCommands, 1, 1, 1); @@ -170,16 +287,17 @@ public class InputGenerator { /** * mutate the random commands, make some of them to have part of a stored url * currently have 3 random cases: - * 0. no change - * 1. attach a stored url to the first argument, e.g. we have a url of --url1--, and the - * random command is 'get abcde ff', the result will be 'get --url1--abcde ff' - * 2. replace the first argument with a stored url. the result of the above example will - * become 'get --url1-- ff' + * 0. no change + * 1. attach a stored url to the first argument, e.g. we have a url of --url1--, and the + * random command is 'get abcde ff', the result will be 'get --url1--abcde ff' + * 2. replace the first argument with a stored url. the result of the above example will + * become 'get --url1-- ff' * used by commands that are based on urls generated by PUT + * * @param randomCommands total random commands - * @param noMutateRatio ratio of case 0 - * @param attachRatio ratio of case 1 - * @param replaceRatio ratio of case 2 + * @param noMutateRatio ratio of case 0 + * @param attachRatio ratio of case 1 + * @param replaceRatio ratio of case 2 * @return the commands, with some of them mutated */ private ArrayList<String> mutateRandomCmdWithValidUrl(ArrayList<String> randomCommands, @@ -188,7 +306,7 @@ public class InputGenerator { int replaceRatio) { int totalRatio = noMutateRatio + attachRatio + replaceRatio; if (totalRatio == 0) { - return randomCommands; + return randomCommands; } ArrayList<String> mutatedCommands = new ArrayList<>(); @@ -206,6 +324,10 @@ public class InputGenerator { cmdArray[1] = getRandomUrlFromStored() + cmdArray[1]; mutatedCmd = String.join(" ", cmdArray); } + // make sure the mutation will not make the line too long + if (mutatedCmd.length() > this.maxInputLineLength) { + mutatedCmd = mutatedCmd.substring(0, this.maxInputLineLength); + } mutatedCommands.add(mutatedCmd); } else { // case 2: replace the first argument with valid url @@ -215,6 +337,10 @@ public class InputGenerator { cmdArray[1] = getRandomUrlFromStored(); mutatedCmd = String.join(" ", cmdArray); } + // make sure the mutation will not make the line too long + if (mutatedCmd.length() > this.maxInputLineLength) { + mutatedCmd = mutatedCmd.substring(0, this.maxInputLineLength); + } mutatedCommands.add(mutatedCmd); } } @@ -223,6 +349,7 @@ public class InputGenerator { /** * obtain a random stored url form recorded validPassbookEntries + * * @return the url */ private String getRandomUrlFromStored() { @@ -236,7 +363,8 @@ public class InputGenerator { /** * generate multiple random given commands, with randomness in number of arguments and input length - * @param command the command + * + * @param command the command * @param numCommands number of commands to generate * @return all the commands generated */ @@ -253,7 +381,8 @@ public class InputGenerator { /** * generate random number of random arguments, then attach those after a command - * @param command the command + * + * @param command the command * @param numValidArgs valid number of args for the command * @return a string, showing the command with arguments */ @@ -288,7 +417,10 @@ public class InputGenerator { if (mode == numValidArgs + 2) { mode = numValidArgs * INVALID_NUM_ARGUMENTS_MULTIPLIER; } - String randomLengthString = generateRandomLengthString(mode, maxInputLineLength); + + // take into account of the length of instruction itself and spaces between args + int maxLineLengthToUse = this.maxInputLineLength - (command.length() + mode); + String randomLengthString = generateRandomLengthString(mode, maxLineLengthToUse); ArrayList<String> argumentsList = stringSeparator(randomLengthString, mode); // retry if failed for some reason if (argumentsList == null) { @@ -307,23 +439,29 @@ public class InputGenerator { } /** - * generate a string with length of the given length, or more than the given length, randomly + * generate a string with length of the given length + * can also randomly generate lines with greater length, but currently not in use, + * since currently line length greater than the max line length will crash the program + * * @param minLength valid min length * @param maxLength valid max length * @return a string with valid length or more than valid length */ private String generateRandomLengthString(int minLength, int maxLength) { - int mode = random.nextInt(2); - if (mode == 0) { - return generateRandomString(minLength, maxLength); - } else { - return generateRandomString(maxLength + 1, (maxLength + 1) * INVALID_MAX_LENGTH_MULTIPLIER); - } + return generateRandomString(minLength, maxLength); + // commended out: since currently line length greater than the max line length will crash the program +// int mode = random.nextInt(2); +// if (mode == 0) { +// return generateRandomString(minLength, maxLength); +// } else { +// return generateRandomString(maxLength + 1, (maxLength + 1) * INVALID_MAX_LENGTH_MULTIPLIER); +// } } /** * generate a random string with ascii symbols in the ASCII_RANGE, of given length range * including both minLength and maxLength + * * @param minLength min length of the string * @param maxLength man length of the string * @return a random string. an empty one if minLength and maxLength are not matched correctly @@ -350,7 +488,8 @@ public class InputGenerator { /** * separate a string into numSections parts, each with random length * will not output empty strings as parts - * @param input input string + * + * @param input input string * @param numSections number of output sections * @return a list of string sections if possible, null otherwise */ @@ -394,10 +533,57 @@ public class InputGenerator { /** * get the stored valid entries + * * @return this.validPassbookEntries */ Map<String, Pair<String, String>> getValidPassbookEntries() { return validPassbookEntries; } + /** + * read config file. initialize a new one if it doesn't exit + * if for some reason an IO error occurred, use random values instead + */ + private void readConfig() { + File cfgFile = new File(CFG_FILE); + try { + if (cfgFile.exists()) { + this.configBase.load(new FileInputStream(CFG_FILE)); + } else { + setDefaultConfig(false); + this.configBase.store(new FileOutputStream(CFG_FILE), null); + } + } catch (IOException e) { + setDefaultConfig(true); + } + } + + /** + * setup default config values in self.configBase + * + * @param useRandom whether to use random value for default + */ + private void setDefaultConfig(boolean useRandom) { + int timesRun = 0; + if (useRandom) { + timesRun = random.nextInt(ASSUMED_MIN_TIMES_TO_RUN); + } + this.configBase.setProperty(keyRunCounter, Integer.toString(timesRun)); + } + + /** + * increase the run counter, write to cfg file + * + * @param numChange change of run counter + */ + private void increaseRunCounter(int numChange) throws IOException { + int runCounter = Integer.parseInt(this.configBase.getProperty(keyRunCounter, "0")); + runCounter += numChange; + this.configBase.setProperty(keyRunCounter, Integer.toString(runCounter)); + this.configBase.store(new FileWriter(CFG_FILE), null); + } + // overload with default args + private void increaseRunCounter() throws IOException { + increaseRunCounter(1); + } }