about summary refs log tree commit diff
diff options
context:
space:
mode:
authorvan Hauser <vh@thc.org>2020-01-23 10:15:33 +0100
committervan Hauser <vh@thc.org>2020-01-23 10:15:33 +0100
commite7c95ebf5a4828b662252b10052a89923dd25030 (patch)
tree8cee04ce7a4772134543a412cbe54a1a002924da
parenta58800b90122f3d612a0badb243d2c1b6fc9c742 (diff)
downloadafl++-e7c95ebf5a4828b662252b10052a89923dd25030.tar.gz
afl-cmin final touches
-rw-r--r--Makefile2
-rwxr-xr-xafl-cmin473
-rwxr-xr-xafl-cmin.awk470
-rw-r--r--docs/ChangeLog2
-rwxr-xr-xtest/test.sh2
5 files changed, 476 insertions, 473 deletions
diff --git a/Makefile b/Makefile
index 7260ee47..459cae5f 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ VERSION     = $(shell grep '^\#define VERSION ' ../config.h | cut -d '"' -f2)
 # PROGS intentionally omit afl-as, which gets installed elsewhere.
 
 PROGS       = afl-gcc afl-fuzz afl-showmap afl-tmin afl-gotcpu afl-analyze
-SH_PROGS    = afl-plot afl-cmin afl-whatsup afl-system-config
+SH_PROGS    = afl-plot afl-cmin afl-cmin.bash afl-whatsup afl-system-config
 MANPAGES=$(foreach p, $(PROGS) $(SH_PROGS), $(p).8)
 
 ifeq "$(shell echo 'int main() {return 0; }' | $(CC) -x c - -flto=full -o .test 2>/dev/null && echo 1 || echo 0 ; rm -f .test )" "1"
diff --git a/afl-cmin b/afl-cmin
index 75dc63a7..a072a62a 100755
--- a/afl-cmin
+++ b/afl-cmin
@@ -1,4 +1,475 @@
 #!/usr/bin/env sh
 THISPATH=`dirname ${0}`
 export PATH=${THISPATH}:$PATH
-awk -f ${0}.awk -- ${@+"$@"}
+awk -f - -- ${@+"$@"} <<'EOF'
+#!/usr/bin/awk -f
+
+# awk script to minimize a test corpus of input files
+#
+# based on afl-cmin bash script written by Michal Zalewski
+# rewritten by Heiko Eißfeldt (hexcoder-)
+#
+# uses getopt.awk package from Arnold Robbins
+#
+# external tools used by this script:
+# test
+# grep
+# rm
+# mkdir
+# ln
+# cp
+# pwd
+# which
+# cd
+# find
+# stat
+# sort
+# cut
+# and afl-showmap from this project :-)
+
+# getopt.awk --- Do C library getopt(3) function in awk
+
+# External variables:
+#    Optind -- index in ARGV of first nonoption argument
+#    Optarg -- string value of argument to current option
+#    Opterr -- if nonzero, print our own diagnostic
+#    Optopt -- current option letter
+
+# Returns:
+#    -1     at end of options
+#    "?"    for unrecognized option
+#    <c>    a character representing the current option
+
+# Private Data:
+#    _opti  -- index in multiflag option, e.g., -abc
+
+function getopt(argc, argv, options,    thisopt, i)
+{
+    if (length(options) == 0)    # no options given
+        return -1
+
+    if (argv[Optind] == "--") {  # all done
+        Optind++
+        _opti = 0
+        return -1
+    } else if (argv[Optind] !~ /^-[^:[:space:]]/) {
+        _opti = 0
+        return -1
+    }
+    if (_opti == 0)
+        _opti = 2
+    thisopt = substr(argv[Optind], _opti, 1)
+    Optopt = thisopt
+    i = index(options, thisopt)
+    if (i == 0) {
+        if (Opterr)
+            printf("%c -- invalid option\n", thisopt) > "/dev/stderr"
+        if (_opti >= length(argv[Optind])) {
+            Optind++
+            _opti = 0
+        } else
+            _opti++
+        return "?"
+    }
+    if (substr(options, i + 1, 1) == ":") {
+        # get option argument
+        if (length(substr(argv[Optind], _opti + 1)) > 0)
+            Optarg = substr(argv[Optind], _opti + 1)
+        else
+            Optarg = argv[++Optind]
+        _opti = 0
+    } else
+        Optarg = ""
+    if (_opti == 0 || _opti >= length(argv[Optind])) {
+        Optind++
+        _opti = 0
+    } else
+        _opti++
+    return thisopt
+}
+
+BEGIN {
+    Opterr = 1    # default is to diagnose
+    Optind = 1    # skip ARGV[0]
+
+    # test program
+    if (_getopt_test) {
+        while ((_go_c = getopt(ARGC, ARGV, "ab:cd")) != -1)
+            printf("c = <%c>, Optarg = <%s>\n",
+                                       _go_c, Optarg)
+        printf("non-option arguments:\n")
+        for (; Optind < ARGC; Optind++)
+            printf("\tARGV[%d] = <%s>\n",
+                                    Optind, ARGV[Optind])
+    }
+}
+
+function usage() {
+   print \
+"Usage: afl-cmin [ options ] -- /path/to/target_app [ ... ]\n" \
+"\n" \
+"Required parameters:\n" \
+"\n" \
+"  -i dir        - input directory with starting corpus\n" \
+"  -o dir        - output directory for minimized files\n" \
+"\n" \
+"Execution control settings:\n" \
+"\n" \
+"  -f file       - location read by the fuzzed program (stdin)\n" \
+"  -m megs       - memory limit for child process ("mem_limit" MB)\n" \
+"  -t msec       - run time limit for child process (none)\n" \
+"  -Q            - use binary-only instrumentation (QEMU mode)\n" \
+"  -U            - use unicorn-based instrumentation (unicorn mode)\n" \
+"\n" \
+"Minimization settings:\n" \
+"  -C            - keep crashing inputs, reject everything else\n" \
+"  -e            - solve for edge coverage only, ignore hit counts\n" \
+"\n" \
+"For additional tips, please consult docs/README.md\n" \
+"\n" \
+      > "/dev/stderr"
+   exit 1
+}
+
+function exists_and_is_executable(binarypath) {
+  return 0 == system("test -f "binarypath" -a -x "binarypath)
+}
+
+BEGIN {
+  print "corpus minimization tool for afl++ (awk version)\n"
+
+  # defaults
+  extra_par = ""
+  # process options
+  Opterr = 1    # default is to diagnose
+  Optind = 1    # skip ARGV[0]
+  while ((_go_c = getopt(ARGC, ARGV, "hi:o:f:m:t:eCQU?")) != -1) {
+    if (_go_c == "i") {
+      if (!Optarg) usage()
+      if (in_dir) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
+      in_dir = Optarg
+      continue
+    } else 
+    if (_go_c == "o") {
+      if (!Optarg) usage()
+      if (out_dir) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
+      out_dir = Optarg
+      continue
+    } else 
+    if (_go_c == "f") {
+      if (!Optarg) usage()
+      if (stdin_file) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
+      stdin_file = Optarg
+      continue
+    } else 
+    if (_go_c == "m") {
+      if (!Optarg) usage()
+      if (mem_limit) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
+      mem_limit = Optarg
+      mem_limit_given = 1
+      continue
+    } else 
+    if (_go_c == "t") {
+      if (!Optarg) usage()
+      if (timeout) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
+      timeout = Optarg
+      continue
+    } else 
+    if (_go_c == "C") {
+      ENVIRON["AFL_CMIN_CRASHES_ONLY"] = 1
+      continue
+    } else 
+    if (_go_c == "e") {
+      extra_par = extra_par " -e"
+      continue
+    } else 
+    if (_go_c == "Q") {
+      if (qemu_mode) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
+      extra_par = extra_par " -Q"
+      if ( !mem_limit_given ) mem_limit = "250"
+      qemu_mode = 1
+      continue
+    } else 
+    if (_go_c == "U") {
+      if (unicorn_mode) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
+      extra_par = extra_par " -U"
+      if ( !mem_limit_given ) mem_limit = "250"
+      unicorn_mode = 1
+      continue
+    } else 
+    if (_go_c == "?") {
+      exit 1
+    } else 
+      usage()
+  } # while options
+
+  if (!mem_limit) mem_limit = 200
+  if (!timeout) timeout = "none"
+
+  # get program args
+  i = 0
+  prog_args_string = ""
+  for (; Optind < ARGC; Optind++) {
+    prog_args[i++] = ARGV[Optind]
+    if (i > 1)
+      prog_args_string = prog_args_string" "ARGV[Optind]
+  }
+
+  # sanity checks
+  if (!prog_args[0] || !in_dir || !out_dir) usage()
+
+  target_bin = prog_args[0] 
+
+  # Do a sanity check to discourage the use of /tmp, since we can't really
+  # handle this safely from an awk script.
+
+  if (!ENVIRON["AFL_ALLOW_TMP"]) {
+    dirlist[0] = in_dir
+    dirlist[1] = target_bin
+    dirlist[2] = out_dir
+    dirlist[3] = stdin_file
+    "pwd" | getline dirlist[4] # current directory
+    for (dirind in dirlist) {
+      dir = dirlist[dirind]
+
+      if (dir ~ /^(\/var)?\/tmp/) {
+        print "[-] Error: do not use this script in /tmp or /var/tmp." > "/dev/stderr"
+        exit 1
+      }
+    }
+    delete dirlist
+  }
+
+  # If @@ is specified, but there's no -f, let's come up with a temporary input
+  # file name.
+
+  trace_dir = out_dir "/.traces"
+
+  if (!stdin_file) {
+    found_atat = 0
+    for (prog_args_ind in prog_args) {
+      if ("@@" == prog_args[prog_args_ind]) {
+        found_atat = 1
+        break
+      }
+    }
+    if (found_atat) {
+      stdin_file = trace_dir "/.cur_input"
+    }
+  }
+
+  # Check for obvious errors.
+
+  if (mem_limit && mem_limit != "none" && mem_limit < 5) {
+    print "[-] Error: dangerously low memory limit." > "/dev/stderr"
+    exit 1
+  }
+
+  if (timeout && timeout != "none" && timeout < 10) {
+    print "[-] Error: dangerously low timeout." > "/dev/stderr"
+    exit 1
+  }
+
+  if (target_bin && !exists_and_is_executable(target_bin)) {
+
+    "which "target_bin" 2>/dev/null" | getline tnew
+    if (!tnew || !exists_and_is_executable(tnew)) {
+      print "[-] Error: binary '"target_bin"' not found or not executable." > "/dev/stderr"
+      exit 1
+    }
+    target_bin = tnew
+  }
+
+  if (!ENVIRON["AFL_SKIP_BIN_CHECK"] && !qemu_mode && !unicorn_mode) {
+    if (0 != system( "grep -q __AFL_SHM_ID "target_bin )) {
+      print "[-] Error: binary '"target_bin"' doesn't appear to be instrumented." > "/dev/stderr"
+      exit 1
+    }
+  }
+
+  if (0 != system( "test -d "in_dir )) {
+    print "[-] Error: directory '"in_dir"' not found." > "/dev/stderr"
+    exit 1
+  }
+
+  if (0 == system( "test -d "in_dir"/queue" )) {
+    in_dir = in_dir "/queue"
+  }
+
+  system("rm -rf "trace_dir" 2>/dev/null");
+  system("rm "out_dir"/id[:_]* 2>/dev/null")
+
+  if (0 == system( "test -d "out_dir" -a -e "out_dir"/*" )) {
+    print "[-] Error: directory '"out_dir"' exists and is not empty - delete it first." > "/dev/stderr"
+    exit 1
+  }
+
+  if (stdin_file) {
+    # truncate input file
+    printf "" > stdin_file
+    close( stdin_file )
+  }
+
+  if (!ENVIRON["AFL_PATH"]) {
+    if (0 == system("test -f afl-cmin.awk")) {
+      showmap = "./afl-showmap"
+    } else {
+      "which afl-showmap 2>/dev/null" | getline showmap
+    }
+  } else {
+    showmap = ENVIRON["AFL_PATH"] "/afl-showmap"
+  }
+
+  if (!showmap || 0 != system("test -x "showmap )) {
+    print "[-] Error: can't find 'afl-showmap' - please set AFL_PATH." > "/dev/stderr"
+    exit 1
+  }
+  
+  # get list of input filenames sorted by size
+  i = 0
+  # yuck, gnu stat is option incompatible to bsd stat
+  # we use a heuristic to differentiate between
+  # GNU stat and other stats
+  "stat --version 2>/dev/null" | getline statversion
+  if (statversion ~ /GNU coreutils/) {
+    stat_format = "-c '%s %n'" # GNU
+  } else {
+    stat_format = "-f '%z %N'" # *BSD, MacOS
+  }
+  while ("cd "in_dir" && find . -type f -exec stat "stat_format" \\{\\} \\; | sort -n | cut -d' ' -f2-" | getline) {
+    infilesSmallToBig[i++] = $0
+  }
+  in_count = i
+
+  first_file = infilesSmallToBig[0]
+  
+  # Make sure that we're not dealing with a directory.
+
+  if (0 == system("test -d "in_dir"/"first_file)) {
+    print "[-] Error: The input directory contains subdirectories - please fix." > "/dev/stderr"
+    exit 1
+  }
+
+  # Check for the more efficient way to copy files...
+  if (0 != system("mkdir -p -m 0700 "trace_dir)) {
+    print "[-] Error: Cannot create directory "trace_dir > "/dev/stderr"
+    exit 1
+  }
+
+  if (0 == system("ln "in_dir"/"first_file" "trace_dir"/.link_test")) {
+    cp_tool = "ln"
+  } else {
+    cp_tool = "cp"
+  }
+
+  # Make sure that we can actually get anything out of afl-showmap before we
+  # waste too much time.
+
+  print "[*] Testing the target binary..."
+
+  if (!stdin_file) {
+    system( "AFL_CMIN_ALLOW_ANY=1 \""showmap"\" -m "mem_limit" -t "timeout" -o \""trace_dir"/.run_test\" -Z "extra_par" -- \""target_bin"\" "prog_args_string" <\""in_dir"/"first_file"\"")
+  } else {
+    system("cp "in_dir"/"first_file" "stdin_file)
+    system( "AFL_CMIN_ALLOW_ANY=1 \""showmap"\" -m "mem_limit" -t "timeout" -o \""trace_dir"/.run_test\" -Z "extra_par" -A \""stdin_file"\" -- \""target_bin"\" "prog_args_string" <dev/null")
+  }
+
+  first_count = 0
+
+  runtest = trace_dir"/.run_test"
+  while ((getline < runtest) > 0) {
+    ++first_count
+  }
+
+  if (first_count) {
+    print "[+] OK, "first_count" tuples recorded."
+  } else {
+    print "[-] Error: no instrumentation output detected (perhaps crash or timeout)." > "/dev/stderr"
+    if (!ENVIRON["AFL_KEEP_TRACES"]) {
+      system("rm -rf "trace_dir" 2>/dev/null")
+    }
+    exit 1
+  }
+
+  # Let's roll!
+
+  #############################
+  # STEP 1: Collecting traces #
+  #############################
+
+  print "[*] Obtaining traces for "in_count" input files in '"in_dir"'."
+
+  cur = 0;
+  if (!stdin_file) {
+    while (cur < in_count) {
+      fn = infilesSmallToBig[cur]
+      ++cur;
+      printf "\r    Processing file "cur"/"in_count
+      system( "AFL_CMIN_ALLOW_ANY=1 \""showmap"\" -m "mem_limit" -t "timeout" -o \""trace_dir"/"fn"\" -Z "extra_par" -- \""target_bin"\" "prog_args_string" <\""in_dir"/"fn"\"")
+    }
+  } else {
+    while (cur < in_count) {
+      fn = infilesSmallToBig[cur]
+      ++cur
+      printf "\r    Processing file "cur"/"in_count
+      system("cp "in_dir"/"fn" "stdin_file)
+      system( "AFL_CMIN_ALLOW_ANY=1 \""showmap"\" -m "mem_limit" -t "timeout" -o \""trace_dir"/"fn"\" -Z "extra_par" -A \""stdin_file"\" -- \""target_bin"\" "prog_args_string" <dev/null")
+    }
+  }
+
+  print ""
+
+
+  #######################################################
+  # STEP 2: register smallest input file for each tuple #
+  # STEP 3: copy that file (at most once)               #
+  #######################################################
+
+  print "[*] Processing traces for input files in '"in_dir"'."
+
+  cur = 0
+  out_count = 0
+  tuple_count = 0
+
+  while (cur < in_count) {
+    fn = infilesSmallToBig[cur]
+    ++cur
+    printf "\r    Processing file "cur"/"in_count
+    # create path for the trace file from afl-showmap
+    tracefile_path = trace_dir"/"fn
+    # gather all keys, and count them
+    while ((getline line < tracefile_path) > 0) {
+        key = line
+        if (!(key in key_count)) {
+          ++tuple_count
+        }
+        ++key_count[key]
+        if (! (key in best_file)) {
+            # this is the best file for this key
+            best_file[key] = fn
+            # copy file unless already done
+            if (! (fn in file_already_copied)) {
+                system(cp_tool" "in_dir"/"fn" "out_dir"/"fn)
+                file_already_copied[fn] = ""
+                ++out_count
+            }
+        }
+    }
+    close(tracefile_path)
+  }
+
+  print ""
+  print "[+] Found "tuple_count" unique tuples across "in_count" files."
+
+  if (out_count == 1) {
+    print "[!] WARNING: All test cases had the same traces, check syntax!"
+  }
+  print "[+] Narrowed down to "out_count" files, saved in '"out_dir"'."
+
+  if (!ENVIRON["AFL_KEEP_TRACES"]) {
+    system("rm -rf "trace_dir" 2>/dev/null")
+  }
+
+  exit 0
+}
+EOF
diff --git a/afl-cmin.awk b/afl-cmin.awk
deleted file mode 100755
index 967c4e87..00000000
--- a/afl-cmin.awk
+++ /dev/null
@@ -1,470 +0,0 @@
-#!/usr/bin/awk -f
-
-# awk script to minimize a test corpus of input files
-#
-# based on afl-cmin bash script written by Michal Zalewski
-# rewritten by Heiko Eißfeldt (hexcoder-)
-#
-# uses getopt.awk package from Arnold Robbins
-#
-# external tools used by this script:
-# test
-# grep
-# rm
-# mkdir
-# ln
-# cp
-# pwd
-# which
-# cd
-# find
-# stat
-# sort
-# cut
-# and afl-showmap from this project :-)
-
-# getopt.awk --- Do C library getopt(3) function in awk
-
-# External variables:
-#    Optind -- index in ARGV of first nonoption argument
-#    Optarg -- string value of argument to current option
-#    Opterr -- if nonzero, print our own diagnostic
-#    Optopt -- current option letter
-
-# Returns:
-#    -1     at end of options
-#    "?"    for unrecognized option
-#    <c>    a character representing the current option
-
-# Private Data:
-#    _opti  -- index in multiflag option, e.g., -abc
-
-function getopt(argc, argv, options,    thisopt, i)
-{
-    if (length(options) == 0)    # no options given
-        return -1
-
-    if (argv[Optind] == "--") {  # all done
-        Optind++
-        _opti = 0
-        return -1
-    } else if (argv[Optind] !~ /^-[^:[:space:]]/) {
-        _opti = 0
-        return -1
-    }
-    if (_opti == 0)
-        _opti = 2
-    thisopt = substr(argv[Optind], _opti, 1)
-    Optopt = thisopt
-    i = index(options, thisopt)
-    if (i == 0) {
-        if (Opterr)
-            printf("%c -- invalid option\n", thisopt) > "/dev/stderr"
-        if (_opti >= length(argv[Optind])) {
-            Optind++
-            _opti = 0
-        } else
-            _opti++
-        return "?"
-    }
-    if (substr(options, i + 1, 1) == ":") {
-        # get option argument
-        if (length(substr(argv[Optind], _opti + 1)) > 0)
-            Optarg = substr(argv[Optind], _opti + 1)
-        else
-            Optarg = argv[++Optind]
-        _opti = 0
-    } else
-        Optarg = ""
-    if (_opti == 0 || _opti >= length(argv[Optind])) {
-        Optind++
-        _opti = 0
-    } else
-        _opti++
-    return thisopt
-}
-
-BEGIN {
-    Opterr = 1    # default is to diagnose
-    Optind = 1    # skip ARGV[0]
-
-    # test program
-    if (_getopt_test) {
-        while ((_go_c = getopt(ARGC, ARGV, "ab:cd")) != -1)
-            printf("c = <%c>, Optarg = <%s>\n",
-                                       _go_c, Optarg)
-        printf("non-option arguments:\n")
-        for (; Optind < ARGC; Optind++)
-            printf("\tARGV[%d] = <%s>\n",
-                                    Optind, ARGV[Optind])
-    }
-}
-
-function usage() {
-   print \
-"Usage: afl-cmin [ options ] -- /path/to/target_app [ ... ]\n" \
-"\n" \
-"Required parameters:\n" \
-"\n" \
-"  -i dir        - input directory with starting corpus\n" \
-"  -o dir        - output directory for minimized files\n" \
-"\n" \
-"Execution control settings:\n" \
-"\n" \
-"  -f file       - location read by the fuzzed program (stdin)\n" \
-"  -m megs       - memory limit for child process ("mem_limit" MB)\n" \
-"  -t msec       - run time limit for child process (none)\n" \
-"  -Q            - use binary-only instrumentation (QEMU mode)\n" \
-"  -U            - use unicorn-based instrumentation (unicorn mode)\n" \
-"\n" \
-"Minimization settings:\n" \
-"  -C            - keep crashing inputs, reject everything else\n" \
-"  -e            - solve for edge coverage only, ignore hit counts\n" \
-"\n" \
-"For additional tips, please consult docs/README.md\n" \
-"\n" \
-      > "/dev/stderr"
-   exit 1
-}
-
-function exists_and_is_executable(binarypath) {
-  return 0 == system("test -f "binarypath" -a -x "binarypath)
-}
-
-BEGIN {
-  print "corpus minimization tool for afl++ (awk version)\n"
-
-  # defaults
-  extra_par = ""
-  # process options
-  Opterr = 1    # default is to diagnose
-  Optind = 1    # skip ARGV[0]
-  while ((_go_c = getopt(ARGC, ARGV, "hi:o:f:m:t:eCQU?")) != -1) {
-    if (_go_c == "i") {
-      if (!Optarg) usage()
-      if (in_dir) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
-      in_dir = Optarg
-      continue
-    } else 
-    if (_go_c == "o") {
-      if (!Optarg) usage()
-      if (out_dir) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
-      out_dir = Optarg
-      continue
-    } else 
-    if (_go_c == "f") {
-      if (!Optarg) usage()
-      if (stdin_file) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
-      stdin_file = Optarg
-      continue
-    } else 
-    if (_go_c == "m") {
-      if (!Optarg) usage()
-      if (mem_limit) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
-      mem_limit = Optarg
-      mem_limit_given = 1
-      continue
-    } else 
-    if (_go_c == "t") {
-      if (!Optarg) usage()
-      if (timeout) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
-      timeout = Optarg
-      continue
-    } else 
-    if (_go_c == "C") {
-      ENVIRON["AFL_CMIN_CRASHES_ONLY"] = 1
-      continue
-    } else 
-    if (_go_c == "e") {
-      extra_par = extra_par " -e"
-      continue
-    } else 
-    if (_go_c == "Q") {
-      if (qemu_mode) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
-      extra_par = extra_par " -Q"
-      if ( !mem_limit_given ) mem_limit = "250"
-      qemu_mode = 1
-      continue
-    } else 
-    if (_go_c == "U") {
-      if (unicorn_mode) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
-      extra_par = extra_par " -U"
-      if ( !mem_limit_given ) mem_limit = "250"
-      unicorn_mode = 1
-      continue
-    } else 
-    if (_go_c == "?") {
-      exit 1
-    } else 
-      usage()
-  } # while options
-
-  if (!mem_limit) mem_limit = 200
-  if (!timeout) timeout = "none"
-
-  # get program args
-  i = 0
-  prog_args_string = ""
-  for (; Optind < ARGC; Optind++) {
-    prog_args[i++] = ARGV[Optind]
-    if (i > 1)
-      prog_args_string = prog_args_string" "ARGV[Optind]
-  }
-
-  # sanity checks
-  if (!prog_args[0] || !in_dir || !out_dir) usage()
-
-  target_bin = prog_args[0] 
-
-  # Do a sanity check to discourage the use of /tmp, since we can't really
-  # handle this safely from an awk script.
-
-  if (!ENVIRON["AFL_ALLOW_TMP"]) {
-    dirlist[0] = in_dir
-    dirlist[1] = target_bin
-    dirlist[2] = out_dir
-    dirlist[3] = stdin_file
-    "pwd" | getline dirlist[4] # current directory
-    for (dirind in dirlist) {
-      dir = dirlist[dirind]
-
-      if (dir ~ /^(\/var)?\/tmp/) {
-        print "[-] Error: do not use this script in /tmp or /var/tmp." > "/dev/stderr"
-        exit 1
-      }
-    }
-    delete dirlist
-  }
-
-  # If @@ is specified, but there's no -f, let's come up with a temporary input
-  # file name.
-
-  trace_dir = out_dir "/.traces"
-
-  if (!stdin_file) {
-    found_atat = 0
-    for (prog_args_ind in prog_args) {
-      if ("@@" == prog_args[prog_args_ind]) {
-        found_atat = 1
-        break
-      }
-    }
-    if (found_atat) {
-      stdin_file = trace_dir "/.cur_input"
-    }
-  }
-
-  # Check for obvious errors.
-
-  if (mem_limit && mem_limit != "none" && mem_limit < 5) {
-    print "[-] Error: dangerously low memory limit." > "/dev/stderr"
-    exit 1
-  }
-
-  if (timeout && timeout != "none" && timeout < 10) {
-    print "[-] Error: dangerously low timeout." > "/dev/stderr"
-    exit 1
-  }
-
-  if (target_bin && !exists_and_is_executable(target_bin)) {
-
-    "which "target_bin" 2>/dev/null" | getline tnew
-    if (!tnew || !exists_and_is_executable(tnew)) {
-      print "[-] Error: binary '"target_bin"' not found or not executable." > "/dev/stderr"
-      exit 1
-    }
-    target_bin = tnew
-  }
-
-  if (!ENVIRON["AFL_SKIP_BIN_CHECK"] && !qemu_mode && !unicorn_mode) {
-    if (0 != system( "grep -q __AFL_SHM_ID "target_bin )) {
-      print "[-] Error: binary '"target_bin"' doesn't appear to be instrumented." > "/dev/stderr"
-      exit 1
-    }
-  }
-
-  if (0 != system( "test -d "in_dir )) {
-    print "[-] Error: directory '"in_dir"' not found." > "/dev/stderr"
-    exit 1
-  }
-
-  if (0 == system( "test -d "in_dir"/queue" )) {
-    in_dir = in_dir "/queue"
-  }
-
-  system("rm -rf "trace_dir" 2>/dev/null");
-  system("rm "out_dir"/id[:_]* 2>/dev/null")
-
-  if (0 == system( "test -d "out_dir" -a -e "out_dir"/*" )) {
-    print "[-] Error: directory '"out_dir"' exists and is not empty - delete it first." > "/dev/stderr"
-    exit 1
-  }
-
-  if (stdin_file) {
-    # truncate input file
-    printf "" > stdin_file
-    close( stdin_file )
-  }
-
-  if (!ENVIRON["AFL_PATH"]) {
-    if (0 == system("test -f afl-cmin.awk")) {
-      showmap = "./afl-showmap"
-    } else {
-      "which afl-showmap 2>/dev/null" | getline showmap
-    }
-  } else {
-    showmap = ENVIRON["AFL_PATH"] "/afl-showmap"
-  }
-
-  if (!showmap || 0 != system("test -x "showmap )) {
-    print "[-] Error: can't find 'afl-showmap' - please set AFL_PATH." > "/dev/stderr"
-    exit 1
-  }
-  
-  # get list of input filenames sorted by size
-  i = 0
-  # yuck, gnu stat is option incompatible to bsd stat
-  # we use a heuristic to differentiate between
-  # GNU stat and other stats
-  "stat --version 2>/dev/null" | getline statversion
-  if (statversion ~ /GNU coreutils/) {
-    stat_format = "-c '%s %n'" # GNU
-  } else {
-    stat_format = "-f '%z %N'" # *BSD, MacOS
-  }
-  while ("cd "in_dir" && find . -type f -exec stat "stat_format" \\{\\} \\; | sort -n | cut -d' ' -f2-" | getline) {
-    infilesSmallToBig[i++] = $0
-  }
-  in_count = i
-
-  first_file = infilesSmallToBig[0]
-  
-  # Make sure that we're not dealing with a directory.
-
-  if (0 == system("test -d "in_dir"/"first_file)) {
-    print "[-] Error: The input directory contains subdirectories - please fix." > "/dev/stderr"
-    exit 1
-  }
-
-  # Check for the more efficient way to copy files...
-  if (0 != system("mkdir -p -m 0700 "trace_dir)) {
-    print "[-] Error: Cannot create directory "trace_dir > "/dev/stderr"
-    exit 1
-  }
-
-  if (0 == system("ln "in_dir"/"first_file" "trace_dir"/.link_test")) {
-    cp_tool = "ln"
-  } else {
-    cp_tool = "cp"
-  }
-
-  # Make sure that we can actually get anything out of afl-showmap before we
-  # waste too much time.
-
-  print "[*] Testing the target binary..."
-
-  if (!stdin_file) {
-    system( "AFL_CMIN_ALLOW_ANY=1 \""showmap"\" -m "mem_limit" -t "timeout" -o \""trace_dir"/.run_test\" -Z "extra_par" -- \""target_bin"\" "prog_args_string" <\""in_dir"/"first_file"\"")
-  } else {
-    system("cp "in_dir"/"first_file" "stdin_file)
-    system( "AFL_CMIN_ALLOW_ANY=1 \""showmap"\" -m "mem_limit" -t "timeout" -o \""trace_dir"/.run_test\" -Z "extra_par" -A \""stdin_file"\" -- \""target_bin"\" "prog_args_string" <dev/null")
-  }
-
-  first_count = 0
-
-  runtest = trace_dir"/.run_test"
-  while ((getline < runtest) > 0) {
-    ++first_count
-  }
-
-  if (first_count) {
-    print "[+] OK, "first_count" tuples recorded."
-  } else {
-    print "[-] Error: no instrumentation output detected (perhaps crash or timeout)." > "/dev/stderr"
-    if (!ENVIRON["AFL_KEEP_TRACES"]) {
-      system("rm -rf "trace_dir" 2>/dev/null")
-    }
-    exit 1
-  }
-
-  # Let's roll!
-
-  #############################
-  # STEP 1: Collecting traces #
-  #############################
-
-  print "[*] Obtaining traces for "in_count" input files in '"in_dir"'."
-
-  cur = 0;
-  if (!stdin_file) {
-    while (cur < in_count) {
-      fn = infilesSmallToBig[cur]
-      ++cur;
-      printf "\r    Processing file "cur"/"in_count
-      system( "AFL_CMIN_ALLOW_ANY=1 \""showmap"\" -m "mem_limit" -t "timeout" -o \""trace_dir"/"fn"\" -Z "extra_par" -- \""target_bin"\" "prog_args_string" <\""in_dir"/"fn"\"")
-    }
-  } else {
-    while (cur < in_count) {
-      fn = infilesSmallToBig[cur]
-      ++cur
-      printf "\r    Processing file "cur"/"in_count
-      system("cp "in_dir"/"fn" "stdin_file)
-      system( "AFL_CMIN_ALLOW_ANY=1 \""showmap"\" -m "mem_limit" -t "timeout" -o \""trace_dir"/"fn"\" -Z "extra_par" -A \""stdin_file"\" -- \""target_bin"\" "prog_args_string" <dev/null")
-    }
-  }
-
-  print ""
-
-
-  #######################################################
-  # STEP 2: register smallest input file for each tuple #
-  # STEP 3: copy that file (at most once)               #
-  #######################################################
-
-  print "[*] Processing traces for input files in '"in_dir"'."
-
-  cur = 0
-  out_count = 0
-  tuple_count = 0
-
-  while (cur < in_count) {
-    fn = infilesSmallToBig[cur]
-    ++cur
-    printf "\r    Processing file "cur"/"in_count
-    # create path for the trace file from afl-showmap
-    tracefile_path = trace_dir"/"fn
-    # gather all keys, and count them
-    while ((getline line < tracefile_path) > 0) {
-        key = line
-        if (!(key in key_count)) {
-          ++tuple_count
-        }
-        ++key_count[key]
-        if (! (key in best_file)) {
-            # this is the best file for this key
-            best_file[key] = fn
-            # copy file unless already done
-            if (! (fn in file_already_copied)) {
-                system(cp_tool" "in_dir"/"fn" "out_dir"/"fn)
-                file_already_copied[fn] = ""
-                ++out_count
-            }
-        }
-    }
-    close(tracefile_path)
-  }
-
-  print ""
-  print "[+] Found "tuple_count" unique tuples across "in_count" files."
-
-  if (out_count == 1) {
-    print "[!] WARNING: All test cases had the same traces, check syntax!"
-  }
-  print "[+] Narrowed down to "out_count" files, saved in '"out_dir"'."
-
-  if (!ENVIRON["AFL_KEEP_TRACES"]) {
-    system("rm -rf "trace_dir" 2>/dev/null")
-  }
-
-  exit 0
-}
diff --git a/docs/ChangeLog b/docs/ChangeLog
index bb3537dd..33c6f618 100644
--- a/docs/ChangeLog
+++ b/docs/ChangeLog
@@ -25,6 +25,8 @@ Version ++2.60d (develop):
      - show in the help output for which llvm version it was compiled for
      - now does not need to be recompiled between trace-pc and pass
        instrumentation. compile normally and set AFL_LLVM_USE_TRACE_PC :)
+  - afl-cmin is now a sh script (invoking awk) instead of bash for portability
+    the original script is still present as afl-cmin.bash
   - added blacklisted function check in all modules of llvm_mode
   - added fix from Debian project to compile libdislocator and libtokencap
 
diff --git a/test/test.sh b/test/test.sh
index 3473155f..0ae6fd09 100755
--- a/test/test.sh
+++ b/test/test.sh
@@ -150,7 +150,7 @@ test "$SYS" = "i686" -o "$SYS" = "x86_64" -o "$SYS" = "amd64" && {
     }
     echo 000000000000000000000000 > in/in2
     mkdir -p in2
-    ../afl-cmin -i in -o in2 -- ./test-instr.plain > /dev/null
+    ../afl-cmin -i in -o in2 -- ./test-instr.plain > /dev/null 2>&1
     CNT=`ls in2/ | wc -l`
     case "$CNT" in
 1| *1) $ECHO "$GREEN[+] afl-cmin correctly minimized testcase numbers" ;;