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.

% 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

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.

_images/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.

_images/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:

import argparse

def build_argument_parser():
    return

The first step in creating our parser is creating an ArgumentParser object:

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:

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:

/Users/rebecca/.virtualenvs/bernauer/bin/python /Users/rebecca/tmp/bernauer/bernauer.py

Process finished with exit code 0

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:

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:

# 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:

# 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.

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:

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:

% 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().