├── .gitattributes ├── .gitignore ├── COPYING ├── README ├── TODO ├── arguments ├── __init__.py ├── simplearguments.py └── valuedarguments.py ├── legacymanfuzzer └── __init__.py ├── manfuzzer ├── manparser └── __init__.py ├── setup.py └── values ├── __init__.py ├── datagen.py ├── filegen.py └── textgen.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Translations 157 | *.mo 158 | 159 | #Mr Developer 160 | .mr.developer.cfg 161 | 162 | # Mac crap 163 | .DS_Store 164 | 165 | ############# 166 | ## Python 167 | ############# 168 | 169 | .idea -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 1. 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | 39 | 2. 40 | You must cause any modified files to carry prominent notices stating that You changed the files; and 41 | 42 | 3. 43 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 44 | 45 | 4. 46 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 47 | 48 | 49 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 50 | 51 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 52 | 53 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 54 | 55 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 56 | 57 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 58 | 59 | END OF TERMS AND CONDITIONS 60 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | # The ManFuzzer 2 | 3 | The ManFuzzer produces test cases for an executable 4 | based on a grammar generated from `-h`, `-H`, `--help` and 5 | the manual page, with the option to execute those test cases. 6 | 7 | The ManFuzzer was created by Peter Chapman in 2012. 8 | Recently the ManFuzzer was modified and used for a [large-scale 9 | experiment on vulnerability prediction](http://www.vdiscover.org/OS-fuzzing.html). 10 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Nothing right now! -------------------------------------------------------------------------------- /arguments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroundPound/ManFuzzer/9507682406e3985fd177c87fcc617d54561420c6/arguments/__init__.py -------------------------------------------------------------------------------- /arguments/simplearguments.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Dec 28, 2012 3 | 4 | @author: Peter 5 | 6 | Parses and generates simple arguments. 7 | ''' 8 | 9 | import re 10 | import random 11 | 12 | class SimpleArguments(object): 13 | 14 | _SIMPLE_ARGUMENTS = '[\\s]\\-?\\-[\\w]+[\\w\\-]*' 15 | 16 | def __init__(self,parsetext=""): 17 | self.arguments = set() 18 | self.parse(parsetext) 19 | 20 | 21 | def parse(self,parsetext): 22 | matches = re.findall(self._SIMPLE_ARGUMENTS,parsetext) 23 | self.arguments |= {match.strip() for match in matches} 24 | 25 | def size(self): 26 | return len(self.arguments) 27 | 28 | def __str__(self, *args, **kwargs): 29 | return str(self.arguments) 30 | 31 | def genarg(self,value=None): 32 | ''' 33 | value is unused for simple arguments. 34 | ''' 35 | return random.sample(self.arguments, 1)[0] 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /arguments/valuedarguments.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Dec 28, 2012 3 | 4 | @author: Peter 5 | ''' 6 | 7 | import re 8 | import random 9 | import logging 10 | from arguments.simplearguments import SimpleArguments 11 | 12 | class ValuedArguments(object): 13 | ''' 14 | This argument class is for arguments whose values must be inserted with an = sign. 15 | 16 | Excerpt from ENV: 17 | 18 | 19 | -u NAME 20 | --unset=NAME 21 | Remove variable NAME from the environment, if it was in the 22 | environment. 23 | 24 | Here, --unset is considered a valued argument. 25 | ''' 26 | 27 | _VALUED_ARGUMENTS = '[\\s]\\-\\-?[\\w]+[\\w\\-]*=' 28 | logger = logging.getLogger('valued-arguments') 29 | 30 | 31 | def __init__(self,parsetext=""): 32 | self.valuedarguments = set() 33 | self.simplearguments = SimpleArguments(parsetext) 34 | self.parse(parsetext) 35 | 36 | def parse(self,parsetext): 37 | self.logger.debug("Parsing: " + parsetext) 38 | matches = re.findall(self._VALUED_ARGUMENTS,parsetext) 39 | self.valuedarguments |= {match.strip() for match in matches} 40 | self.simplearguments.parse(parsetext) 41 | 42 | def size(self): 43 | return len(self.valuedarguments) + self.simplearguments.size() 44 | 45 | def __str__(self, *args, **kwargs): 46 | return str(self.valuedarguments) + str(self.simplearguments) 47 | 48 | def genarg(self,value=None): 49 | ''' 50 | If a string value is provided, then it will be added to an argument. 51 | ''' 52 | if value is None: 53 | return self.simplearguments.genarg() 54 | else: 55 | if random.randint(0,self.size()) < len(self.valuedarguments): 56 | return random.sample(self.valuedarguments, 1)[0] + value 57 | else: 58 | return self.simplearguments.genarg() + " " + value 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /legacymanfuzzer/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import random 3 | import subprocess 4 | import re 5 | 6 | 7 | MAGIC_REGEX = '[\\s]\\-?\\-[\\w\\-]+' 8 | DEFAULT_TEST_CASES = 1000 9 | AVERAGE_PARAMETERS = 1 10 | STDDEV_PARAMETERS = 2 11 | 12 | 13 | def legacy(executable,combinations,mean,stddev): 14 | # Mine the flags 15 | hFlags = mine_h_flags(executable) 16 | HFlags = mine_H_flags(executable) 17 | helpFlags = mine_Help_flags(executable) 18 | manFlags = mine_Man_flags(executable) 19 | 20 | 21 | flags = hFlags | HFlags | helpFlags | manFlags # union everything together 22 | 23 | 24 | genderated_test_cases = set() # a set of sets for each test case (to remove repeated trials) 25 | 26 | 27 | for _ in range(combinations): 28 | num_flags_used = int(random.gauss(mean,stddev)) 29 | num_flags_used = max(0,min(num_flags_used,len(flags))) 30 | test_case = frozenset(random.sample(flags,num_flags_used)) # created a test case! 31 | if test_case not in genderated_test_cases: 32 | genderated_test_cases.add(test_case) 33 | yield ' '.join(test_case) 34 | 35 | 36 | 37 | 38 | def extract_arguments(command): 39 | try: 40 | child = subprocess.Popen(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True) 41 | child_output = child.communicate() 42 | logging.debug(child_output) 43 | matches = re.findall(MAGIC_REGEX,repr(child_output)) 44 | return {match.strip() for match in matches} 45 | except: 46 | logging.error("Command failed: %s" % str(command)) 47 | return set() 48 | 49 | 50 | def mine_h_flags(executable): 51 | logging.info("Running -h") 52 | return extract_arguments(str(executable) + " -h") 53 | 54 | 55 | def mine_H_flags(executable): 56 | logging.info("Running -H") 57 | return extract_arguments(str(executable) + " -H") 58 | 59 | 60 | def mine_Help_flags(executable): 61 | logging.info("Running --help") 62 | return extract_arguments(str(executable) + " --help") 63 | 64 | 65 | def mine_Man_flags(executable): 66 | logging.info("Finding man page") 67 | return extract_arguments("man " + str(executable)) 68 | -------------------------------------------------------------------------------- /manfuzzer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | """ Copyright 2015 Peter Chapman, GGrieco 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | The ManFuzzer: Given a binary path this file produces test cases to that binary 18 | based on the flags found in -h,-H,--help and the man page. There is also the 19 | option to execute those test cases.""" 20 | 21 | import argparse 22 | import logging 23 | import sys 24 | import manparser 25 | import random 26 | import time 27 | import subprocess 28 | from values.textgen import TextValueGenerator 29 | from values.filegen import FileValueGenerator 30 | from values.datagen import Int32ValueGenerator 31 | 32 | import legacymanfuzzer 33 | import os 34 | import signal 35 | 36 | 37 | DEFAULT_TEST_CASES = 1000 38 | DEFAULT_PARAMS_MEAN = 1 39 | DEFAULT_PARAMS_STDDEV = 2 40 | DEFAULT_VALUES_PROB = 0.05 41 | DEFAULT_TEXT_PROB = 1 42 | DEFAULT_TEXT_MEAN = 20 43 | DEFAULT_TEXT_STDDEV = 100 44 | DEFAULT_FILE_PROB = 1 45 | DEFAULT_FILE_MEAN = 20 46 | DEFAULT_FILE_STDDEV = 100 47 | DEFAULT_INT32_PROB = 1 48 | DEFAULT_PROGRAM_INPUT_PROB = 0.05 49 | DEFAULT_STDIN_PROB = 0.05 50 | DEFAULT_TIMEOUT = 3 51 | DEFAULT_EXECTIME = 60 * 10 # in seconds 52 | 53 | 54 | def main(): 55 | # Parse arguments 56 | argparser = argparse.ArgumentParser(description='''Produces and executes 57 | command-line inputs to an executable for fuzzing based on the flags found by 58 | running -h, -H, --help, and the man page for software testing.''') 59 | argparser.add_argument('-d','--debug',help="Enables debugging output.",action="store_true") 60 | argparser.add_argument('-v','--verbose',help="Enables verbose output. Debugging output includes verbose output.",action="store_true") 61 | argparser.add_argument('-n','--testcases',help="The maximum number of test cases to generate. The default value is %d." % DEFAULT_TEST_CASES,type=int,default=DEFAULT_TEST_CASES) 62 | argparser.add_argument('-e','--execute',help="Execute the generated test cases.",action="store_true") 63 | argparser.add_argument('-t','--timeout',help='The timeout in seconds for running an fuzz input.',default=DEFAULT_TIMEOUT,type=float) 64 | argparser.add_argument('--exectime',help='The amount of time in seconds to spend on a single program.',default=DEFAULT_EXECTIME,type=float) 65 | argparser.add_argument('--paramsmean',help="The mean number of parameters to use in generated test cases. The default number is %d." % DEFAULT_PARAMS_MEAN, type=float, default=DEFAULT_PARAMS_MEAN) 66 | argparser.add_argument('--paramsstddev', help="The standard deviation in the number of parameters to use in generated test cases. The default number is %d." % DEFAULT_PARAMS_STDDEV, type=float, default=DEFAULT_PARAMS_STDDEV) 67 | argparser.add_argument('--valuesprob',help="The probability parameters will be given values. The default probability is %d." % DEFAULT_VALUES_PROB, type=float, default=DEFAULT_VALUES_PROB) 68 | argparser.add_argument('--textprob',help="The probability text values will be used. The default probability is uniform between the other value types.", type=float, default=DEFAULT_TEXT_PROB) 69 | argparser.add_argument('--textmean',help="The mean length of generated text values. The default mean length is %d." % DEFAULT_TEXT_MEAN, type=float, default=DEFAULT_TEXT_MEAN) 70 | argparser.add_argument('--textstddev', help="The standard deviation in the length of generated text values. The default is %d." % DEFAULT_TEXT_STDDEV, type=float, default=DEFAULT_TEXT_STDDEV) 71 | argparser.add_argument('--fileprob',help="The probability file values will be used. The default probability is uniform between the other value types.", type=float, default=DEFAULT_FILE_PROB) 72 | argparser.add_argument('--filemean',help="The mean length of generated text values. The default mean length is %d." % DEFAULT_FILE_MEAN, type=float, default=DEFAULT_FILE_MEAN) 73 | argparser.add_argument('--filestddev', help="The standard deviation in the length of generated file values in bytes. The default is %d." % DEFAULT_FILE_STDDEV, type=float, default=DEFAULT_FILE_STDDEV) 74 | argparser.add_argument('--int32prob',help="The probability 32-bit integer values will be used. The default probability is uniform between the other value types.", type=float, default=DEFAULT_INT32_PROB) 75 | argparser.add_argument('--programinputprob', help="The probability of giving the entire program an input. The default is %d." % DEFAULT_PROGRAM_INPUT_PROB, type=float, default=DEFAULT_PROGRAM_INPUT_PROB) 76 | argparser.add_argument('--stdinprob', help="The probability of feeding a file in through standard input. The default is %d." % DEFAULT_STDIN_PROB, type=float, default=DEFAULT_STDIN_PROB) 77 | argparser.add_argument('--legacy', help="Runs manfuzzer in the legacy mode that performs better magically for certain inputs.", action="store_true") 78 | argparser.add_argument('executable', help='The relative or absolute path to the executable to be fuzzed.') 79 | 80 | 81 | args = argparser.parse_args() 82 | debuglogging = args.debug 83 | verboselogging = args.verbose 84 | # Create Logger 85 | loglevel = logging.WARNING 86 | 87 | if debuglogging == True: 88 | loglevel = logging.DEBUG 89 | elif verboselogging == True: 90 | loglevel = logging.INFO 91 | 92 | logging.basicConfig(level=loglevel) 93 | logging.StreamHandler(sys.stderr) 94 | 95 | logger = logging.getLogger('man-fuzzer') 96 | 97 | logger.info("Logger setup complete.") 98 | 99 | # Extract command-line arguments 100 | executable = args.executable 101 | 102 | testcases = args.testcases 103 | timeout = args.timeout 104 | exectime = args.exectime 105 | execute = args.execute 106 | 107 | paramsmean = args.paramsmean 108 | paramsstddev = args.paramsstddev 109 | programinputprob = args.programinputprob 110 | stdinprob = args.stdinprob 111 | valuesprob = args.valuesprob 112 | 113 | 114 | logger.debug("%d test cases requested." % testcases) 115 | logger.debug("Test cases will be generated with a mean number of parameters %f and standard deviation %f." % (paramsmean,paramsstddev)) 116 | logger.info("Executable path: %s" % str(executable)) 117 | argumentgenerator = manparser.mineflags(executable) 118 | logger.debug("The mined flags: %s" % str(argumentgenerator)) 119 | 120 | textmean = args.textmean 121 | textstddev = args.textstddev 122 | textprob = args.textprob 123 | textgen = TextValueGenerator(textmean,textstddev) 124 | 125 | 126 | filemean = args.filemean 127 | filestddev = args.filestddev 128 | fileprob = args.fileprob 129 | filegen = FileValueGenerator(filemean,filestddev) 130 | 131 | int32prob = args.int32prob 132 | int32gen = Int32ValueGenerator() 133 | 134 | sumprobs = sum([textprob, fileprob, int32prob]) 135 | valuegens = {(textprob/sumprobs, textgen), (fileprob/sumprobs, filegen), (int32prob/sumprobs, int32gen)} 136 | 137 | legacy = args.legacy 138 | generator = lambda : generate_testcases(executable, argumentgenerator, valuegens, testcases = testcases, paramsmean = paramsmean, paramsstddev = paramsstddev, valuesprob = valuesprob,programinputprob = programinputprob, stdinprob = stdinprob) 139 | if legacy: 140 | generator = lambda : legacymanfuzzer.legacy(executable,testcases,paramsmean,paramsstddev) 141 | 142 | # Generate and possibly execute test cases 143 | start_time = time.time() 144 | for test_case in generator(): 145 | print(executable + ' ' + test_case) 146 | if execute: 147 | if time.time() - start_time > exectime: 148 | logger.info("Time has expired for fuzzing %s" % executable) 149 | break 150 | command = executable + " " + test_case 151 | logger.info("Executing command '%s'" % (command)) 152 | try: 153 | run_command_check(command,timeout) 154 | except subprocess.CalledProcessError as e: 155 | returncode = e.returncode 156 | if os.WIFSIGNALED(returncode) and os.WTERMSIG(returncode) == signal.SIGSEGV: 157 | logger.warning("Crash (crash) input: %s" % str(command)) 158 | try: 159 | run_command("cp `ls -t /tmp/tmp* | head -1` tempfiles/.",1) # Save crash input 160 | except Exception as e: 161 | logger.error("Could not copy temp file") 162 | except subprocess.TimeoutExpired: 163 | logger.info("Command timeout: %s" % str(command)) 164 | except Exception as e: 165 | logger.error("Command failed: %s" % str(command)) 166 | logger.exception(e) 167 | 168 | 169 | # Close 170 | def run_command(command,timeout): 171 | return subprocess.call(command,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,shell=True,timeout = timeout) 172 | 173 | def run_command_check(command,timeout): 174 | return subprocess.check_call(command,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,shell=True,timeout = timeout) 175 | 176 | 177 | 178 | def generate_testcases(executable, argumentgenerator, valuegens, testcases = DEFAULT_TEST_CASES, paramsmean = DEFAULT_PARAMS_MEAN, paramsstddev = DEFAULT_PARAMS_STDDEV, valuesprob = DEFAULT_VALUES_PROB, programinputprob = DEFAULT_PROGRAM_INPUT_PROB, stdinprob = DEFAULT_STDIN_PROB): 179 | 180 | generatedtestcases = set() 181 | for _ in range(testcases): 182 | num_flags_used = int(random.gauss(paramsmean, paramsstddev)) 183 | num_flags_used = max(0, min(num_flags_used, argumentgenerator.size())) 184 | test_case = set() 185 | for _ in range(num_flags_used): 186 | test_case.add(argumentgenerator.genarg(value=(None if random.random() >= valuesprob else pickgen(valuegens).generate()))) 187 | test_case = ' '.join(test_case) 188 | 189 | if random.random() <= programinputprob: 190 | test_case += ' ' + pickgen(valuegens).generate() 191 | if random.random() <= stdinprob: 192 | test_case += ' < ' + pickgen(valuegens).generate() 193 | 194 | if test_case not in generatedtestcases: 195 | generatedtestcases.add(test_case) 196 | yield test_case 197 | 198 | 199 | 200 | 201 | def pickgen(valuegens): 202 | randomness = random.random() 203 | probsum = 0 204 | for prob,gen in valuegens: 205 | probsum += prob 206 | if randomness <= probsum: 207 | return gen 208 | 209 | 210 | if __name__ == "__main__": 211 | main() 212 | 213 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /manparser/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Dec 23, 2012 3 | 4 | @author: Peter 5 | 6 | This module contains a few functions for extracting the parameters out of a man page. 7 | ''' 8 | import subprocess 9 | import logging 10 | from arguments.valuedarguments import ValuedArguments 11 | 12 | TIMEOUT = 3 13 | 14 | logger = logging.getLogger('man-fuzzer') 15 | 16 | 17 | 18 | def mineflags(executable): 19 | '''Returns a set of progargs that can be used to generate arguments in a test case.''' 20 | # Mine the flags 21 | valuedarguments = ValuedArguments() 22 | 23 | valuedarguments.parse(_mine_h_flags(executable,TIMEOUT)) 24 | valuedarguments.parse(_mine_H_flags(executable,TIMEOUT)) 25 | valuedarguments.parse(_mine_Help_flags(executable,TIMEOUT)) 26 | valuedarguments.parse(_mine_Man_flags(executable,TIMEOUT)) 27 | 28 | return valuedarguments 29 | 30 | def _extract_arguments(command,timeout): 31 | try: 32 | child = subprocess.Popen(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True) 33 | child_output = child.communicate(timeout = timeout) 34 | return repr(child_output) 35 | except Exception as e: 36 | logger.exception(e) 37 | return "" 38 | 39 | def _mine_h_flags(executable,timeout): 40 | return _extract_arguments(str(executable) + " -h",timeout) 41 | 42 | def _mine_H_flags(executable,timeout): 43 | return _extract_arguments(str(executable) + " -H",timeout) 44 | 45 | def _mine_Help_flags(executable,timeout): 46 | return _extract_arguments(str(executable) + " --help",timeout) 47 | 48 | def _mine_Man_flags(executable,timeout): 49 | return _extract_arguments("man " + str(executable),timeout) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from os import path 3 | from setuptools import setup 4 | 5 | 6 | # https://github.com/pypa/sampleproject/blob/master/setup.py 7 | here = path.abspath(path.dirname(__file__)) 8 | 9 | # Get the long description from the relevant file 10 | with open(path.join(here, 'README'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | setup( 14 | name='ManFuzzer', 15 | version='1.1', 16 | packages=['manparser', 'arguments', 'values', 'legacymanfuzzer'], 17 | include_package_data=True, 18 | license='Apache2.0', 19 | description='The ManFuzzer is a tool to create fuzzing inputs for command line programs using help options ' 20 | 'and man pages.', 21 | long_description=long_description, 22 | url='https://github.com/GroundPound/ManFuzzer', 23 | author='Peter Chapman & G.Grieco', 24 | author_email='peter@cmu.edu, gg@cifasis-conicet.gov.ar', 25 | scripts=['manfuzzer'], 26 | install_requires=[ 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /values/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroundPound/ManFuzzer/9507682406e3985fd177c87fcc617d54561420c6/values/__init__.py -------------------------------------------------------------------------------- /values/datagen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on Dec 30, 2012 3 | 4 | @author: Peter, GGrieco 5 | """ 6 | import random 7 | import string 8 | 9 | 10 | class Int32ValueGenerator(object): 11 | """ 12 | Generates 32-bit integer values. 13 | """ 14 | 15 | def generate(self): 16 | return str(random.randint(-2**31, 2**31 - 1)) 17 | 18 | class DataValueGenerator(object): 19 | ''' 20 | Generates random binary values. 21 | ''' 22 | 23 | 24 | def __init__(self,meanlen,stdlen): 25 | self.meanlen = meanlen 26 | self.stdlen = stdlen 27 | 28 | 29 | 30 | def generate(self): 31 | length = max(0,int(random.gauss(self.meanlen, self.stdlen))) 32 | if length % 2 == 1: 33 | length += 1 34 | return "".join([random.choice(string.hexdigits) for _ in range(length)]) 35 | 36 | -------------------------------------------------------------------------------- /values/filegen.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Dec 30, 2012 3 | 4 | @author: Peter 5 | ''' 6 | import tempfile 7 | import os 8 | import binascii 9 | import logging 10 | from values.datagen import DataValueGenerator 11 | 12 | 13 | class FileValueGenerator(object): 14 | ''' 15 | Generates random files. 16 | ''' 17 | 18 | logger = logging.getLogger('filegen') 19 | 20 | def __init__(self,meanlen,stdlen): 21 | self.meanlen = meanlen 22 | self.stdlen = stdlen 23 | 24 | 25 | 26 | def generate(self): 27 | dvg = DataValueGenerator(self.meanlen, self.stdlen) 28 | data = dvg.generate() 29 | fp = tempfile.NamedTemporaryFile(delete=False) 30 | fp.write(binascii.a2b_hex(data)) 31 | fp.close() 32 | self.logger.debug("Temp file name: %s " % fp.name) 33 | return '"' + os.path.abspath(fp.name) + '"' 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /values/textgen.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Dec 28, 2012 3 | 4 | @author: Peter 5 | ''' 6 | import random 7 | import string 8 | 9 | class TextValueGenerator(object): 10 | ''' 11 | Generates random text. 12 | ''' 13 | 14 | 15 | def __init__(self,meanlen,stdlen): 16 | self.meanlen = meanlen 17 | self.stdlen = stdlen 18 | 19 | 20 | 21 | def generate(self): 22 | length = max(0,int(random.gauss(self.meanlen, self.stdlen))) 23 | return '"' + "".join([random.choice(string.ascii_letters + string.digits) for _ in range(length)]) + '"' 24 | 25 | --------------------------------------------------------------------------------