========================================================= Build Your Command Centre ========================================================= The first step in building a tool (if you ask me) is to create a command centre, or a way for your user to interact with your tools -- you may have experience with many command line applications where you pile a bunch of different directives and options onto an application, hit enter and bam you have your answers! For those who may not have used command line applications before I've included an example using the free and open-source tool `nmap `_ which can be used for security scanning, port scanning, and network exploration. .. code-block:: console % nmap -p- 192.168.0.1 In our example above we are telling ``nmap`` to scan all ports ``-p-`` on the host ``192.168.0.1``. This is a relatively straight-forward example, but in some cases command line arguments can get complicated pretty quickly. But we aren't worrying about that just yet. --------------------------------------------------------- A Recipe for Calling Upon the User --------------------------------------------------------- .. topic:: Concepts Covered - Creating new files - Creating methods - Basic argument parsing - Returning and storing data - Name guard basics - UNIX error codes - Keyword/named parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Ingredients ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - One python file called ``main.py`` or ``yourprojectname.py`` - One dash of ``argparse``, a Python "built-in" meaning you don't need to install anything - `One cup of sweet ASCII font `_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First we will want to create a new Python file, this can be done by right clicking on your project directory (in the example below this is the directory **bernauer**), highlighting **New** a submenu will appear, from here select **Python File**. .. image:: _static/imgs/subdomain_enumeration_new_file.png In this step we are creating the file that users (or just you) will be using to use the application -- I suggest naming this file either the same thing as your project, or ``main``. .. image:: _static/imgs/subdomain_enumeration_name_file.png The first step is creating a command line argument parser that users can use to specify how the application should run. For now, it should only have a few things (we will add more as we go). You will want to include: - an option to provide a domain name - an option to provide a word list ``argparse`` ========================================================= Python has a built in parser for command-line options called ``argparse`` in previous versions this was ``optparse`` however, this library has been deprecated since Python 2.7 and will not be developed any further. The benefit of using ``argparse`` over rolling your own command-line parser is ``argparse`` will figure out how to parse ``sys.argv`` (user-input) allowing you to focus on building tools. ``argparse`` will also help automatically generate help and usage messages and issue errors when the user provides invalid arguments. To get started we will want to create a function to build and handle arguments passed by the user, a very simple example which does nothing: .. code-block:: python import argparse def build_argument_parser(): return The first step in creating our parser is creating an ``ArgumentParser`` object: .. code-block:: python import argparse def build_argument_parser(): parser = argparse.ArgumentParser() # add_arguments here parser.parse_args() Before we run this code we want to put another ✨ magical ✨ function at the very bottom of our file, this is the a ``__name__`` guard: .. code-block:: python import argparse def build_argument_parser(): parser = argparse.ArgumentParser() # add_arguments here parser.parse_args() if __name__ == '__main__': build_argument_parser() Run your application, if everything went the way we expected you probably got something like the following in the console at the bottom of the application: .. code-block:: console /Users/rebecca/.virtualenvs/bernauer/bin/python /Users/rebecca/tmp/bernauer/bernauer.py Process finished with exit code 0 .. admonition:: Question - What happens when you run your application with ``-h`` or ``--help``? - What happens when you run your application with random input? Here is what is happening: - Running the script without any options results in "nothing" displayed in ``stdout`` because we are using the Python debugger, we get debugging information such as the Python and script path, as well as the exit code. - Running the script with the ``-h`` or ``--help`` flag demonstrates how useful ``argparse`` can be. We have done very little to configure the parser, however it has generated a handler for you. Keep in mind this is the only option you get for free, the others you will need to specify help text for. - Specifying any other output will result in an error, but we do get a useful usage message, also for free! .. important:: On Portable Operating System Interface (POSIX) systems (like Linux and Mac OS X) the standard exit code is 0 for success and any number from 1 to 255 for anything else. Now we have a skeleton function where we can build out parser. In an ``argparse`` argument parser there are two **basic** types of arguments, which you will also encounter while programming in Python -- these are Positional Arguments and Optional Arguments. Positional Arguments is as it says on the tin, a parameter, or collection of parameters that much be provided in a certain order. A simple example of this is provided below: .. code-block:: python def divide(value1, value2): return value1 / value2 In the example above, the order the parameters are provided in is important because ``1/2`` will return very different results to ``2/1``. In the same way, Positional Arguments in an argument parser can vary the results given and therefore need to be provided in the correct order. Optional Arguments, will be covered in more depth in a programming context later, because they do not map one-to-one to the way we use them in argument parsers. But I digress. Again, as the name suggests Optional Arguments are... optional. This could be options like the verbosity of output, allowing the user more control over the volume of information they receive from your application. Filling an ``ArgumentParser`` with information about your application is done by using the ``add_argument()`` function. This information is stored and can be accessed when ``parse_args()`` is called. The basic syntax to add a command line option is: .. code-block:: python # Positional Argument parser.add_argument("name") # Optional Argument parser.add_argument("-g") Of course you can customise each of these arguments providing what the argument parser should do if a particular argument is given, where the value should be saved (if needed) the default value and a brief sentence about what the parameter is for, or how the argument works. The syntax for using these more advanced arguments is: .. code-block:: python # Positional Argument parser.add_argument("name", action="", dest="", default="", help="") # Optional Argument parser.add_argument("-a", "--advanced", action="", dest="", default="", help="", required=False) .. important:: In Python when you see a function with parameters like ``action="something"`` it means these are Optional and do not need to be included, these are often referred to as Keyword, Named, or Optional Arguments. ``ArgumentParser`` parses arguments through the ``parse_args()`` function. This determines what options have and have not been selected and convert each argument to the appropriate type. In most cases this means a ``Namespace`` object will be built and returned to you (the programmer). To have the arguments parsed and returned, we want to modify the last line in our function so instead of just calling ``parser.parse_args()`` we ``return`` the results of this function. .. code-block:: python return parser.parse_args() .. note:: ``argeparse.Namespace`` is a simple class that is used by default by the ``parse_args()`` function to create an object that can hold attributes and return it. The last thing we need to do is create a variable for the ``parser.parse_args()`` information to be stored in. Like when we created the ``argparse.ArgumentParser()`` we will want to tell the program where the call to the ``build_argument_parser()`` function should store the ``return`` value. If you're the decorating and/or customisation type you can also add a sweet description to your argument parser so it comes out something like the example below: .. code-block:: console usage: bernauer.py [-h] -d DOMAIN [-w PATH_TO_WORDLIST] _ | |_ ___ ___ ___ ___ _ _ ___ ___ | . | -_| _| | .'| | | -_| _| |___|___|_| |_|_|__,|___|___|_| Version: 0.0.0 optional arguments: -h, --help show this help message and exit -d DOMAIN, --domain DOMAIN the domain which you wish to bruteforce subdomains e.g. google.com -w PATH_TO_WORDLIST, --wordlist PATH_TO_WORDLIST the word list you wish to use to find subdomains, if no word list is specified the in-build one will be used. To get started doing this you will want to add the named parameter ``description="your text here"`` to your call to ``argparse.ArgumentParser()``. .. hint:: ``argparse.RawTextHelpFormatter`` is a special ``formatter_class`` used with ``ArgumentParser`` to give you more control over how text descriptions are displayed. Normally ``ArgumentParser`` objects will line-wrap the description. Using the information above, you should now be able to build an Argument Parser that allows a user to provide: - a domain name - a word list Notes ========================================================= When the Python interpreter reads a source file it configures a number of special variables, most of which we don't need to worry about. However, in this case we care about the ``__name__`` variable. When your "module" is the main program, that is you are running your code like: .. code-block:: console % python3 program.py % ./program.py The interpreter will add the hard-coded string ``"__main__"`` to the ``__name__`` variable. When your "module" is imported by another "module" or application the interpreter will look at the filename of your module, ``program.py``, strip off the ``.py``, and assign that string to your module's ``__name__`` variable. When your code is eventually getting executed it will see that ``__name__`` is set to ``"__main__"`` it will call any function within that ``if``-statement, in our case ``build_argument_parser()``. Further Reading ========================================================= - `ArgumentParser `_. - `ArgumentParser - action `_. - `ArgumentParser - dest `_. - `ArgumentParser - default `_. - `ArgumentParser - help `_. - `ArgumentParser - required `_. - `ArgumentParser - formatter-class `_. - `Argparse Tutorial `_. - `UNIX error codes `_. - `Comprehensive UNIX error codes `_. - `__main__ — Top-level script environment `_.