Skip to content
Snippets Groups Projects
Commit cbee69b4 authored by Yang Liu's avatar Yang Liu
Browse files

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.
parent 332580b8
No related branches found
No related tags found
1 merge request!8Yuqiang vulnerabilities
......@@ -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
......@@ -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);
......
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};
// 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};
// 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);
}
......@@ -130,6 +245,7 @@ public class InputGenerator {
* generate multiple random commands based on url as the first argument, so stored url
* 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 noMutateRatio see mutateRandomCmdWithValidUrl()
......@@ -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);
......@@ -176,6 +293,7 @@ public class InputGenerator {
* 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
......@@ -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,6 +363,7 @@ public class InputGenerator {
/**
* generate multiple random given commands, with randomness in number of arguments and input length
*
* @param command the command
* @param numCommands number of commands to generate
* @return all the commands generated
......@@ -253,6 +381,7 @@ public class InputGenerator {
/**
* generate random number of random arguments, then attach those after a 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);
}
// 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,6 +488,7 @@ 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 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);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment