From cbee69b44f7abe574cddffbfea89cfeb15259195 Mon Sep 17 00:00:00 2001
From: Yang Liu <yangl18@student.unimelb.edu.au>
Date: Sat, 28 Sep 2019 21:35:57 +1000
Subject: [PATCH] Done: 1. Finished fuzzer with random commands generation and
 run counter. 2. Fixed some bugs which results in crash. 3. Updated README.md,
 attached commands to run tests at the end of the file.

---
 README.md                  |  24 ++++
 fuzzer/Fuzzer.java         |  13 +-
 fuzzer/InputGenerator.java | 264 +++++++++++++++++++++++++++++++------
 3 files changed, 254 insertions(+), 47 deletions(-)

diff --git a/README.md b/README.md
index db8a2f4..f5135b7 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 9394bf8..b1ecf09 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 111c459..7b02c6f 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);
+    }
 }
-- 
GitLab