top of page

Python Basics for Hackers, Part 4: How to Find the Exact Location of any IP Address

Updated: Dec 28, 2022

Welcome back, my aspiring cyberwarriors!

As hackers, we instinctively want to know as much as possible. Among this desired information may be the Geo-Location of an IP address. Thankfully for us, our friends over at MaxMind have built a database that’ll do just that! Thing is, if we want to use it, we have to pay. Lucky for us that MaxMind offers a developer version of the database for free, and that someone developed a Python module to query this database. So by making a script with this module we can effectively develop an IP Geo-Location tool. So, let’s gets started.

Step 1: Import Modules

First things first, we need to import all the modules that we need to build our script. This will get a bit complicated, so let’s import our basic modules first:

You can see in the above screen shot that we’ve set the interpreter path using a shebang and we’ve imported many standard libraries. These libraries may seem irrelevant to querying a database, but we’re going to use them for automatic installation later in the script. Now that we have the standard libraries imported, we need to import pygeoip. This is the module that will allow us to query the database. Since it’s not a standard library, it does not come pre-installed. This means that the user must install it manually, or we could handle that for them! We’re going to try and automate the installation of pygeoip. Let’s see the code for this, and then we’ll break it down:

This may look like a lot, but it really isn’t. First, we try to import pygeoip, and if everything goes well nothing happens. But, if the import fails, we print that we failed to import pygeoip and then we prompt the user for auto-installation. For simplicity's sake, we only evaluate the first letter of the user’s response. If the first letter is “y,” then we assume they said yes, obviously “n” would mean no and any other letter is an invalid option.

If the user says yes to the install, we attempt to import the pip library (which is used for installing non-standard libraries). Since pip isn’t on every system, we need to place this within a try as well. We then call the main function out of pip and attempt to install pygeoip. You can see that we gave “-q” as an argument, this will hide all output from the pip installation. Once pip finishes running we attempt to import pygeoip again, if it fails again then our installation obviously failed. Now that we have our modules sorted out we can move on to building our geo-locator.

Step 2: Build the Locator Class

Since this is going to be a bit complicated, we’re going to put everything under one class named “Locator.” This will make it easier to organize and call our functions at the end of the script. Now that we know what we’re going with this class, let’s declare it and make our __init__ function:

Here we’ve declared our Locator class and we’ve made our __init__ function. We’ve accepted three arguments (besides the required self). We’ve set the default value for all arguments to False. This isn’t really necessary, but it will make more sense in the end of the script. Once we take our arguments, we assign them to their corresponding self attributes. Now that we’ve done this, we can access these from anywhere in the script.

Step 3: Build the Database Checking Function

Now that we have our __init__ function made we can start making the other functions of our class. Let’s start with a function that will check for the existence of the MaxMind database and will install it if it is not found. Let’s take a look at the first part of our function:

We start by defining our function and passing self to it as an argument. We then use the logical operator not on the current value of self.datfile. If we recall from earlier, we set the default datfile value to False. So, it stands to reason that if the result of calling not on self.datfile is True, no database file was given. In that case we assign a pre-determined file path to the directory GeoIP in /usr/share. Now that we for sure have a file path to a database we can proceed to validate its presence. We can call os.path.isfile to check for the existence of the database file. We end up using this twice, once if a custom database path is given, and again when testing the default database location.

If the default database detection executes and fails, we prompt the user a yes/no prompt for an automatic installation. This is where this snippet ends and next begins. Let’s show the second snippet of this function and break it down:

We started by using the os module to test to see if /usr/share/GeoIP exists. If it does not, we use the makedirs function to create it. This is where we will store the default database file. Now that we have a place to put the file, we can begin the download using the urlretrieve function out of urllib. This will download the file “GeoLiteCity.dat.gz” to the GeoIP directory.

You may have noticed that the file extension for the database is gz. This means that it is compressed in the GZ format in order to take up less space. This also means that we’re going to have to decompress the dat file before we can use it. This is where the gzip module from earlier comes into play. Let’s see the final snippet of this function:

First, we open the compress dat file with the open function out of gzip under the alias “compressed_dat” (note that we open it in binary read-only mode). Then we create a new file named “GeoLiteCity.dat.” We open this file in binary write-only mode under the alias of “new_dat.” We then call the write method on the new dat file and the read method on the compressed one. It will then proceed to decompress the entire GZ file and write it the new dat file. The result of this is the fully decompressed developer’s version of the MaxMind database!

Now that we have the function to get the database in line, we can finally make the function to query the database.

Step 3: Build the Query Function

Now that we have everything lined up, we can finally query the database for the location of our target IP address! This function is rather simple, so let’s take a look:

First, we make an if statement that tests if self.url has any value. Calling the not operator on anything other than a Boolean value will return False. So calling not twice on a string will return True. We can use this to determine if a variable has a value, which can be useful when juggling values like we are here.

If we find a value in self.url, we need to translate it to an IP address for querying. We can do this by using the gethostbyname function out of the socket module. Once we have the IP address, we append it to Earlier we set equal to a blank string, so this method will work quite efficiently. In the case that the user did not give a URL, they must’ve given an IP instead, so then we append the value of self.ip to the value of Once this process is complete, we will have a target address.

Next, we tell the user that we’re querying for their specified target. Then we create a pygeoip.GeoIP object that will query the file at the end of self.datfile for the target. We then enter a for loop that iterates through the keys and values found when calling the .items() method on the dictionary returned by the query. When iterating through these keys/values, we simply print the key, followed by a colon, then the value. This will present the information found in an easily readable format.

Step 4: Parse and Apply Arguments

It may seem like we’re done, but we’ve still got one more snippet to cover. Up until now, we’ve only made functions that will do the work, but we haven’t actually done it. Now it’s time to get input from the user and call our previous functions. Let’s take a look at the first of the final two snippets of our script:

(Note: I apologize for the small screenshot; this part of the script is more lengthy than it is tall.) First we test to see if the script is running as the main program and not being called by something else. This is so that others can import the class/functions we made earlier, but the user can still execute it as its own script.

Once we pass this if statement, we import the argparse module. This will make the process of taking command line argument ten times more effective. I’m not going to go through and explain every detail about every argument, so let’s just get an overview. First we make the parser object. Then we add three arguments to be parsed. The first two arguments will designate the target. --url will specify a URL target and -t/--target will specify an IP address. The final argument is completely optional. If the user already has the database installed, they can call --dat argument in order to specify a file path leading the database. Once we have all our arguments lined up, we use the parse_args() method in order to store the values of our arguments under the args variable. (Note: if any argument is left blank, the value will default to False.)

Now that we have our arguments, we need to do a bit of checking to make sure everything is in order, then we can execute our query. Let’s take a look at the final snippet of our IP Geo-Location tool:

We start by using a rather long if statement. To sum it up, this conditional statement will test to see if the user used both target specification arguments, or neither of them. If either are true, it will display a formatted error through our previously made parser object. It is due to this if statement here that we can afford to be so careless with our value checking inside of the functions. This conditional statement will weed out all the invalid input.

Once we make it through this conditional statement, we finally create can object using the Locator class that constructed earlier. We then pass all of the arguments to the Locator, no matter what their value. Since we built the Locator around the premise that the default value for each argument is False, this will be perfectly fine.

Now that we have our Locator object, we call the check_database() function as a method in order to run it. Once that function is complete, we call the query() function as a method in order to execute our query. That’s it! The script is complete, now we only have to test it!

Step 5: Test it Out

Now that we have our script (available here) we can use it to find the approximate location of many IP addresses! Let’s start by viewing the help page so that we can see the formatting of the argparse module:

Now in order to test the automatic installation code, I’ve uninstalled pygeoip from my Kali and have deleted the database. Let’s attempt to use our script to find where Hackers-Arise is being hosted:

We can see that the automatic installation is going through just fine (The database installation may take a minute, as it is quite a lot to download and decompress). Now we just have to wait for the query to complete:

We can see by the results of our query that it’s possible that Hackers-Arise is being hosted somewhere near Ashburn, Virginia. We did it!

That does it for this one! Since we’re going in sequentially based on the chronological order, the next script we make will be an ARP-based network enumeration tool. I’ll see you there!

Thank you for reading.

47,527 views3 comments
bottom of page