├── 1 - IP Geolocation with Websites.ipynb ├── 2 - IP Geolocation by database lookup.ipynb ├── 3 - Geolocating a Single BSSID.ipynb ├── 4 - Geolocating Multiple Access Points.ipynb ├── Forensics Challenge Solutions.ipynb ├── Forensics Challenge.ipynb ├── README.md ├── dnslog.txt ├── docs ├── DONOTOPEN.docm ├── DONOTOPEN.hta ├── DONOTOPEN.xlsm └── GeoLocationWorkshop-Slides.pdf ├── images ├── Event6100.jpg └── aps.png └── results.html /1 - IP Geolocation with Websites.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Welcome!\n", 8 | "\n", 9 | "Welcome to this special workshop on using Python for Geolocation of forensic artifacts! In this session we will explore a couple of online APIs that can be used to determine the physical location of a device or connection.\n", 10 | "\n", 11 | "One easy way to determine a location is based upon the IP Address being used. IP Addresses are often found inside of logs on victim machines and forensics images. On victim machines these IP Addresses can be less than reliable because attackers will use various VPN services to hide their actual IP Address. We will discuss how to get around that problem later. For now, let's assume we are analyzing a forensics image of the attacker machine or we have captured an early connection before the attacker began obfuscating their location. To determine their location, we can use an online website such as this one.\n", 12 | "\n", 13 | "https://iplocation.com/\n", 14 | "\n", 15 | "The main page of this website is not an API (Application Programming Interface). It is an interface that was intended for a human being to interact with. However, we can still determine the inputs and outputs of the program and have our script perform the same steps that a human would take. This process is known as \"Web Scraping\". \n", 16 | "By viewing the source code on this website, we can determine how to interact with the website and use it automatically.\n", 17 | "\n", 18 | "Let's walk through that process together.\n", 19 | "\n", 20 | "Once we understand the website form, we can use Python to automatically retrieve information from the website. Websites have different terms of use so make sure you are limiting your interactions with the website to those terms.\n" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "import requests\n", 30 | "browser = requests.session()\n", 31 | "response = browser.post(\"https://iplocation.com/\", {\"ip\":\"8.8.8.8\"})\n", 32 | "response.json()\n" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "\n", 40 | "Web scraping is not the most efficient way for our script to interact with hosts. As a matter of fact, the response we just got from the https://iplocation.net website is much cleaner than we would usually expect to get back when web scraping. Usually, you have to clean up the response and filter out all of the data intended for human consumption. Ideally, we would interact with computers that designed to make it easy for programs rather than humans. Instead of scraping the website we could go through an API (Application Programming Interface). There are many different websites that offer APIs for IP location. A quick internet search will reveal many more than those listed here.\n", 41 | "\n", 42 | "https://ipgeolocation.io/ \n", 43 | "\n", 44 | "http://iplocation.net \n", 45 | "\n", 46 | "https://ipstack.com/ \n", 47 | "\n", 48 | "https://ipinfodb.com/api \n", 49 | "\n", 50 | "https://ip-api.com/ \n", 51 | "\n", 52 | "https://www.ip2location.com/ \n", 53 | "\n", 54 | "Most require you to register for a free API key, but some do not. For example, the following website will permit you to make 15000 queries per hour without any registration. Copy and Paste the following link to an API into a new tab on this browser. You will notice that the response is not very human friendly, but it is exactly what our programs would like to see.\n", 55 | "\n", 56 | "https://freegeoip.app/json\n", 57 | "\n", 58 | "Our script can query this API and get back results in a few simple lines of code.\n", 59 | "\n" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "ip_address = \"8.8.8.8\"\n", 69 | "result = browser.get(f\"https://freegeoip.app/json/{ip_address}\").json()\n", 70 | "result" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "\n", 78 | "The variable 'result' contains a json object. Because it is in a computer friendly response we can access individual values in the response by using the .get() method.\n" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "print(f\"The IP Address is {result.get('ip')}\")\n", 88 | "print(f\"The country is {result.get('country_name')}\")" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "\n", 96 | "With a little bit of Python skill, you can extract large numbers of IP addresses from logs and other forensics artifacts then use these services to lookup their location. How to extract those IP addresses is beyond the scope of this workshop but is covered extensively in SEC573 Automating Information Security with Python. Once you have those IP addresses a simple loop can resolve those locations for you. Here is a bit of code to build a list of IP Addresses from a DNS Servers query log.\n" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "import pathlib\n", 106 | "import re\n", 107 | "log_data = pathlib.Path(\"./dnslog.txt\").read_text()\n", 108 | "list_of_addresses = re.findall(r\"client (\\S+?)#\", log_data)[:100]\n", 109 | "list_of_addresses" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "\n", 117 | "Now, the variable list_of_addresses contains the first 100 IP Addresses inside of this DNS servers log file. We limited it to only 100 IP Addresses with Python's slicing syntax [:100]. The reasons we limited it here are two-fold. First, 100 addresses are more than enough to get a sense of how it works. Second, we don't want our workshop to consume a lot of their free service unnecessarily. Now that we have a list of IP Addresses, we can look them all up with a small loop.\n" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "for each_ip in list_of_addresses:\n", 127 | " url = f\"https://freegeoip.app/json/{each_ip}\"\n", 128 | " result = browser.get(url).json()\n", 129 | " print(f\"The country for IP Address {result.get('ip')} is {result.get('country_name')}\")" 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "\n", 137 | "Notice the speed is less than ideal. We can see each request as it goes to the server and we wait for the response. However, the data is useful. Let's add three lines of code to help categorize this data. These three lines use another trick we cover in SEC573 and create a \"default dictionary\" to group together our IP Addresses by the country of origin.\n", 138 | "\n", 139 | "When you run the next cell, it will take the same amount of time or less than it did above. Printing things is a slow process for computers so by only printing at the end we can speed up the process. With 100 IP Addresses you may not notice, but with 1000s of addresses you will be glad you knew this. You will notice that response contains a group of IP Addresses that do not have any associated country. These are IP addresses for which the http://freegeoip.app website has no known location information.\n" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "from collections import defaultdict\n", 149 | "by_country = defaultdict(lambda :[])\n", 150 | "print(\"Processing. Please wait...\")\n", 151 | "for each_ip in list_of_addresses:\n", 152 | " url = f\"https://freegeoip.app/json/{each_ip}\"\n", 153 | " result = browser.get(url).json()\n", 154 | " by_country[result.get('country_name')].append(result.get('ip'))\n", 155 | "by_country " 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "metadata": {}, 161 | "source": [ 162 | "\n", 163 | "You can see that with very little code you can look up the location of IP addresses with online resources. This is useful for small sets of data but if you have a large amount of data there are faster ways. We will address this problem in our next section.\n", 164 | "\n" 165 | ] 166 | } 167 | ], 168 | "metadata": { 169 | "kernelspec": { 170 | "display_name": "Python 3", 171 | "language": "python", 172 | "name": "python3" 173 | }, 174 | "language_info": { 175 | "codemirror_mode": { 176 | "name": "ipython", 177 | "version": 3 178 | }, 179 | "file_extension": ".py", 180 | "mimetype": "text/x-python", 181 | "name": "python", 182 | "nbconvert_exporter": "python", 183 | "pygments_lexer": "ipython3", 184 | "version": "3.6.9" 185 | } 186 | }, 187 | "nbformat": 4, 188 | "nbformat_minor": 2 189 | } 190 | -------------------------------------------------------------------------------- /2 - IP Geolocation by database lookup.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Finding IP Locations in Mass Quantities\n", 8 | "\n", 9 | "** NOTE: This notebook will only work in the workshop VM. It does not work via the binder.org network interface. **\n", 10 | "\n", 11 | "By default Jupyter will only show you the output of the last command in a cell. I want you to see what each of these commands does, so run the following cell to change that behavior for this notebook.\n" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from IPython.core.interactiveshell import InteractiveShell\n", 21 | "InteractiveShell.ast_node_interactivity = \"all\"" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "\n", 29 | "Now let's take a look at how Python can enable you to lookup IP address locations based on a free database that you can download from maxmind.com.\n" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "import geoip2.database\n", 39 | "reader = geoip2.database.Reader(\"/home/student/Desktop/GeoLite2-City_20200310/GeoLite2-City.mmdb\")\n", 40 | "record = reader.city(\"145.60.3.100\")\n", 41 | "record\n" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "\n", 49 | "With these commands, we opened the database and created a \"reader\" that we can use to query records. Then we can pass any IP Address to \"reader.city\" and it will retrieve the location if it has it in its database. That information was stored in the variable \"record\" which we printed above. You can also access individual components in the record using some additional Python syntax. In this next cell we will access the individual records in the results and product a Google Maps link that you can click on to see where the IP address originated.\n" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "record.continent.names.get(\"en\")\n", 59 | "record.country.name\n", 60 | "record.subdivisions.most_specific.name\n", 61 | "record.location.latitude\n", 62 | "record.location.longitude\n", 63 | "maps_url = f\"http://maps.google.com/maps?q={record.location.latitude:0>3.9f},{record.location.longitude:0>3.9f}&z=15\"\n", 64 | "print(maps_url)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "\n", 72 | "But we have a problem. If you ask for an IP address that isn't in the database it will cause the database to \"Raise an Exception\". This is a crash condition for our programs. If this occurs in a python program rather than our notebook the program stops executing and dies.\n" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "record = reader.city(\"127.0.1.1\")" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "\n", 89 | "To prevent these errors from crashing our programs, we typically do this inside of an \"Exception Handler\". The exception handler will detect when a crash condition has occurred and gracefully handle the error. In this next section I will create a python function that takes two inputs. The first is the \"reader\" that we can use to query the database. The second is the IP address we want to retrieve. If IP Address does not exist in the database it will ignore the address. After creating this new function called **get_geoip2_record()** we will call it once with a bogus IP address and once with a legitimate one.\n" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "def get_geoip2_record(database, ip_address):\n", 99 | " try:\n", 100 | " record = database.city(ip_address)\n", 101 | " except geoip2.errors.AddressNotFoundError:\n", 102 | " record = None\n", 103 | " return record\n", 104 | "\n", 105 | "get_geoip2_record(reader, \"127.0.1.1\")\n", 106 | "get_geoip2_record(reader, \"2.2.2.2\")\n" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "\n", 114 | "Because the information is local in a database and not across the internet, we can lookup IP addresses very fast! To get a sense for how fast, let's process the same DNS logs we used last time, but this time we won't limit ourselves to only the first 100 entries. We will do all 16,773 of the IP addresses.\n" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "import pathlib\n", 124 | "import re\n", 125 | "from collections import defaultdict\n", 126 | "\n", 127 | "log_data = pathlib.Path(\"./dnslog.txt\").read_text()\n", 128 | "list_of_addresses = re.findall(r\"client (\\S+?)#\", log_data)\n", 129 | "by_state = defaultdict(lambda :[])\n", 130 | "print(\"Processing. Please wait...\")\n", 131 | "for each_ip in list_of_addresses:\n", 132 | " record = get_geoip2_record(reader, each_ip)\n", 133 | " if record:\n", 134 | " location = f\"{record.subdivisions.most_specific.name},{record.country.name}\"\n", 135 | " by_state[location].append(each_ip)\n", 136 | "print(\"Done!\")" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "\n", 144 | "Did you blink? You might have missed it. You just processed 168 times the volume of data we did in the previous page where we had to wait 20 seconds or so for 100 records. We didn't have to watch the results slowly scroll in like last time. Now look at the results broken down by state and country.\n" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "by_state" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "\n", 161 | "As you can see with just a little bit of Python code, we can give some context to all of those anonymous IP addresses we find in logs. Now you can focus your attention on those IP addresses that are coming from the same point of origin. If you are interested in one specific State and Country combination you don't have to scroll through all the data. Instead, you can use the .get() method to retrieve the results for a specific State and Country.\n" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "by_state.get(\"Maine,United States\")" 171 | ] 172 | } 173 | ], 174 | "metadata": { 175 | "kernelspec": { 176 | "display_name": "Python 3", 177 | "language": "python", 178 | "name": "python3" 179 | }, 180 | "language_info": { 181 | "codemirror_mode": { 182 | "name": "ipython", 183 | "version": 3 184 | }, 185 | "file_extension": ".py", 186 | "mimetype": "text/x-python", 187 | "name": "python", 188 | "nbconvert_exporter": "python", 189 | "pygments_lexer": "ipython3", 190 | "version": "3.6.9" 191 | } 192 | }, 193 | "nbformat": 4, 194 | "nbformat_minor": 2 195 | } -------------------------------------------------------------------------------- /3 - Geolocating a Single BSSID.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# When you have a single Access Point (BSSID)\n", 8 | "\n", 9 | "Finding wireless networks can be done in a variety of ways. Which way you use depends upon what you are starting out with. If all that you have is the \"BSSID\" (AKA the MAC Address) of the wireless access point, then your free choices are limited. Here is an example of a BSSID:\n", 10 | "\n", 11 | "`DE-AD-BE-EF-01-02` or more commonly when dealing with APIs they are colon delimited `ED:15:BA:DB:AD:ED` \n", 12 | "\n", 13 | "Mylnikov.org is a free website that will convert these MAC addresses to a location for you. It has 34.5 million plus records of wireless access points (APs). Most of its data is for APs in Europe . In addition to its API, they make their entire database downloadable as a CSV.\n", 14 | "\n", 15 | "Mylnikov.org doesn't require registration, so that means we you can immediately begin sending requests to it. \n" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import requests\n", 25 | "browser = requests.session()\n", 26 | "bssid = \"00:0C:42:1F:65:E9\"\n", 27 | "bssid = \"28:28:5d:d6:39:8a\"\n", 28 | "url = \"http://api.mylnikov.org/geolocation/wifi\"\n", 29 | "response = browser.get(url , params = {\"v\":\"1.1\", \"data\":\"open\", \"bssid\":bssid} )\n", 30 | "response.content" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "\n", 38 | "http://wigle.net is another website that has the ability to convert a single BSSID to a location. Of the two sites, wigle.net has much more data, but you must have a registered account to use the API. Additionally, in my experience, the folks at wigle are very aggressive about protecting their data (as we all should be). They will likely block our location's IP address if multiple people begin making queries at the same time. That said, they are very nice people and I have found they are willing to bump the limits on your accounts and ensure continued access if you simply let them know what you're doing after you get blocked.\n", 39 | "\n", 40 | "The wigle.net API is only slightly more complicated. In addition to the BSSID you are looking for you also have to pass an \"auth\" argument containing an API Username and an API Password.\n", 41 | "\n", 42 | "You can register for an account here: https://wigle.net/login?destination=/account\n", 43 | "\n", 44 | "But please don't do that today. As I mentioned they are a little aggressive with blocking IP Addresses and I would like to be able to demonstrate how to use the website for you.\n", 45 | "\n", 46 | "Once you have an API username and password, this is what your code would look like. \n" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "#This is the BSSID of Wireless AP we are looking for\n", 56 | "bssid = 'aa:bb:cc:dd:ee:ff'\n", 57 | "#THese are our credentials to wigle and the wigle url\n", 58 | "wigle_user = \"###USERNAMEHERE###\"\n", 59 | "wigle_pass = \"###PASSWORDHERE###\"\n", 60 | "#The URL is commented out in hope of preventing you from running this one. As I said. \n", 61 | "#Id rather not get blocked befrore demoes. You can unblock this line at home.\n", 62 | "#url = \"https://api.wigle.net/api/v2/network/search\"\n", 63 | "#Make the request\n", 64 | "webresp = requests.get(url, auth = (wigle_user,wigle_pass), params = {'netid' : bssid } )\n", 65 | "#Process the resposse\n", 66 | "webresp.json()" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "\n", 74 | "Many things can go wrong when making a request to a website so normally we would wrap this call in an exception handling routine to gracefully handle errors.\n" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [ 83 | "try:\n", 84 | " webresp = requests.get(url, auth = (wigle_user,wigle_pass), params = {'netid' : bssid } )\n", 85 | "except (requests.ConnectTimeout, requests.HTTPError, requests.ReadTimeout, requests.Timeout, requests.ConnectionError) as e:\n", 86 | " log.info(f\"Web communications error {str(e)}\")\n", 87 | "except Exception as e:\n", 88 | " log.info(f\"Error {str(e)}\")\n", 89 | "if webresp.status_code != 200:\n", 90 | " log.info(\"{} There was an error from Wigle. {}\".format(\"*\"*25, webresp.reason))\n", 91 | "wigle_data = webresp.json()\n", 92 | "wigle_data" 93 | ] 94 | } 95 | ], 96 | "metadata": { 97 | "kernelspec": { 98 | "display_name": "Python 3", 99 | "language": "python", 100 | "name": "python3" 101 | }, 102 | "language_info": { 103 | "codemirror_mode": { 104 | "name": "ipython", 105 | "version": 3 106 | }, 107 | "file_extension": ".py", 108 | "mimetype": "text/x-python", 109 | "name": "python", 110 | "nbconvert_exporter": "python", 111 | "pygments_lexer": "ipython3", 112 | "version": "3.6.9" 113 | } 114 | }, 115 | "nbformat": 4, 116 | "nbformat_minor": 2 117 | } 118 | -------------------------------------------------------------------------------- /4 - Geolocating Multiple Access Points.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# When you have Multiple Access Points\n", 8 | "\n", 9 | "If you have access to multiple access points and their associated signal strength then you can triangulate a position based upon that information with several online APIs. This is one of the ways our phones and web browsers will identify our current location. You may have seen a message from your MAPS app on your phone that says something like \"This works better if you turn on your wireless\". If you obtain multiple access points that the laptop has been near you can use that same technique to identify its position.\n", 10 | "\n", 11 | "### Unmasking VPNed and spoofed IPs with active collection\n", 12 | "First, let's discuss how to obtain artifacts that include multiple access points. One way would be to persuade visitors to a website that will actively query this information and give it to you. This is the technique used by the honeybadger project. When a visitor to the website opens an office document, java applet, .HTA file or downloads and runs a Powershell script it queries the wireless information in the background and submits it to the website. It gets the wireless information by running the command below. Give this a try! If you have wireless capabilities on your current Windows device run the following in a Windows Command prompt.\n", 13 | "```\n", 14 | "\"netsh wlan show networks mode=bssid | findstr \"SSID Signal Channel\"\n", 15 | "```\n", 16 | "\n", 17 | "You see it shows a list of all the access points that are near your computer and their signal strengths. Honeybadger collects this data and uses it to identify the individual's location. Because this is based on the physical location of Wireless Access Points and not an IP address that can be spoofed or hidden behind a VPN this is a reliable way to identify the location of a device that is actively trying to hide their location.\n", 18 | "The \"HoneyBadger\" project can be downloaded for free here: https://github.com/lanmaster53/honeybadger\n", 19 | "The Setup of the Honeybadger server can be a little overwhelming so Tim Tomes also offers HoneyBadger as a paid subscription service. https://hb.lanmaster53.com/\n", 20 | "This tool does, after asking for permission, execute the command above on the targets machine. You will want to **check with your legal counsel** before using this tool.\n", 21 | "Let me do a quick demo of the paid service.\n", 22 | "https://hb.lanmaster53.com/\n", 23 | "\n", 24 | "### Looking up APs found in forensics images\n", 25 | "You can also just collect these artifacts as part of a forensics investigation. We can easily pick up a few access points from the registry, SRUM and other Windows artifacts. This information also appears in the windows event logs when \"Diagnose my network\" buttons are clicked. When you run the diagnostic it generates several events with an ID of 6100. One of those 6100 events has a list of all the wireless access points and signal strengths at the time of the diagnostic.\n", 26 | "\n", 27 | "![alt text](images/Event6100.jpg \"Event 6100\")\n", 28 | "\n", 29 | "\n", 30 | "Take that Event 6100 information and triangulate it using Google's API. This next cell will put the text from a 6100 event into a variable named log_data. We will pretend that you have used your awesome SEC573 skills to retrieve it from an event log on the target computer.\n", 31 | "\n" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "log_data = \"\"\"\n", 41 | "List of visible access point(s): 17 item(s) total, 17 item(s) displayed\n", 42 | " BSSID\t\tBSS Type PHY\tSignal(dB)\tChnl/freq SSID\n", 43 | "-------------------------------------------------------------------------\n", 44 | "1E-8D-CB-84-FA-DF\tInfra\t \t-62\t\t5745000\t ARFF\n", 45 | "06-8D-CB-84-FA-DF\tInfra\t \t-63\t\t5745000\t ARAwifi1\n", 46 | "0A-8D-CB-84-FA-DF\tInfra\t \t-62\t\t5745000\t AirportPrivateWiFi\n", 47 | "0E-8D-CB-84-FA-DF\tInfra\t \t-63\t\t5745000\t Airport_Wifi\n", 48 | "02-8D-DB-84-FA-DF\tInfra\t \t-44\t\t11\t (Unnamed Network)\n", 49 | "1E-8D-DB-84-FA-DF\tInfra\t \t-44\t\t11\t ARFF\n", 50 | "06-8D-DB-84-FA-DF\tInfra\t \t-44\t\t11\t ARAwifi1\n", 51 | "0A-8D-DB-84-FA-DF\tInfra\t \t-44\t\t11\t AirportPrivateWiFi\n", 52 | "0C-8D-DB-84-FA-DF\tInfra\t \t-42\t\t11\t Airport_Wifi\n", 53 | "F4-39-09-61-0E-1F\tInfra\t \t-74\t\t6\t DIRECT-1E-HP OfficeJet Pro 8710\n", 54 | "B2-C1-9E-51-BE-D3\tInfra\t \t-51\t\t6\t ATT-WIFI-3191\n", 55 | "FA-E4-F0-BE-E3-01\tInfra\t \t-58\t\t6\t Esneepez\n", 56 | "54-75-D0-84-2E-F9\tInfra\t g\t-54\t\t4\t Tailwinds Wi-Fi\n", 57 | "54-75-D0-84-2E-F8\tInfra\t g\t-54\t\t4\t (Unnamed Network)\n", 58 | "5C-B0-66-D8-07-F6\tInfra\t \t-57\t\t1\t SBG6580-2-FD39B\n", 59 | "6A-E7-C2-C5-D2-BD\tInfra\t \t-60\t\t1\t Samsung Galaxy_8515\n", 60 | "0C-8C-24-6F-9E-28\tInfra\t \t-84\t\t10\t SNRS_6F9E28\n", 61 | "\"\"\"" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "\n", 69 | "First we will use some regular expressions which are covered extensively in SEC573 to extract all of the BSSID and signal strengths from the text in the Event Log. Execute the next cell to extract the data from the log_data variable set in the previous cell.\n" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "import re\n", 79 | "ap_data = re.findall(r\"([\\dA-F-]+)\\s+?Infra.*?(-\\d+)\", log_data, re.MULTILINE)\n", 80 | "ap_data" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "\n", 88 | "Next we build the JSON data structure that Google's API is expecting us to submit and we make the request. In this data structure we define several \"macAddress\" and \"signalStrength\" fields in a JSON record that matches the specification for Google's API.\n" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": { 95 | "scrolled": true 96 | }, 97 | "outputs": [], 98 | "source": [ 99 | "ap_list = []\n", 100 | "for mac,signal in ap_data:\n", 101 | " ap_list.append({\"macAddress\":mac ,\"signalStrength\":signal})\n", 102 | "google_data = {\"considerIP\": \"false\", \"wifiAccessPoints\": ap_list}\n", 103 | "google_data\n" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "\n", 111 | "This data structure can now be submitted to Google's API. But you have to have an API key. To get an API key you have to provide Google with a Credit Card. Rather than asking you to do this I'm going to give you my API key which is associated with my personal credit card. So please be kind and limit your request to those required for todays lab. Once we get back the record we extract the latitude and longitude and generate a Google Maps link. After executing the next cell you can open the Google maps link in another window to see where the laptop was when the event 6100 was generated.\n" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "import requests\n", 121 | "\n", 122 | "#First borrow Mark Baggetts personal API key (please be kind)\n", 123 | "#Change the IP to the one provided in class\n", 124 | "key = ''\n", 125 | "\n", 126 | "#Then generate the request\n", 127 | "url = f'https://www.googleapis.com/geolocation/v1/geolocate?key={key}' \n", 128 | "response = requests.post(url=url, json=google_data) \n", 129 | "\n", 130 | "#Then extract the data and print a google maps link\n", 131 | "loc_record = response.json()\n", 132 | "lat = loc_record.get(\"location\").get(\"lat\")\n", 133 | "lng = loc_record.get(\"location\").get(\"lng\")\n", 134 | "maps_url = f\"http://maps.google.com/maps?q={lat:0>3.9f},{lng:0>3.9f}&z=15\"\n", 135 | "print(loc_record.get(\"accuracy\"))\n", 136 | "print(maps_url)\n" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "\n", 144 | "That's amazing. It has an accuracy of approximately 20 meters. But what if you are not lucky enough to find an event 6100. They are in fact rather rare to find on a forensics investigation. What if rather than an event 6100 you are only able to find two BSSIDs in the registry? At a minimum, Google's API will allow you to submit two BSSIDs with no signal strength. Try it to see what happens.\n" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "ap_data = [('0C-8C-24-6F-9E-28'), ('0C-8D-DB-84-FA-DF')]\n", 154 | "ap_list = []\n", 155 | "for mac in ap_data:\n", 156 | " ap_list.append({\"macAddress\":mac})\n", 157 | " \n", 158 | "response = requests.post(url=url, json= {\"considerIP\": \"false\", \"wifiAccessPoints\": ap_list}) \n", 159 | "loc_record = response.json()\n", 160 | "lat = loc_record.get(\"location\").get(\"lat\")\n", 161 | "lng = loc_record.get(\"location\").get(\"lng\")\n", 162 | "maps_url = f\"http://maps.google.com/maps?q={lat:0>3.9f},{lng:0>3.9f}&z=15\"\n", 163 | "print(loc_record.get(\"accuracy\"))\n", 164 | "print(maps_url)" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "\n", 172 | "\n", 173 | "Our accuracy fell way down. As a matter a fact, without the Signal strength it appears that Google will always give you an accuracy of 150 meters.\n", 174 | "\n", 175 | "Perhaps 150 meters is good enough. If not we can submit a fake signal strength to force Google's algorithms to give us a better location. Most of the time the signal strength is somewhere between -40 and -90. Anecdotally I believe the average is round -60. By setting an arbitrary signal strength of -60 we force Google to choose the most accurate point that is equal distance from the access points.\n", 176 | "\n", 177 | "This time I will put this into a function that you can call. The function find_location() defined below requires that you pass it a list of BSSIDs and the Google API key. It returns back to you both the accuracy and a URL that you can use to see the location.\n", 178 | "\n", 179 | "In the next cell we create the find_location() function and we call it with two sets of wireless APs. The first set is pulled from the event 6100 above. The second set is made up of access points that shouldn't exist anywhere. Let's run this and look at the results.\n", 180 | "\n", 181 | "\n" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "def find_location(list_of_aps, key):\n", 191 | " url = f'https://www.googleapis.com/geolocation/v1/geolocate?key={key}' \n", 192 | " ap_list = []\n", 193 | " for mac in list_of_aps:\n", 194 | " ap_list.append({\"macAddress\":mac ,\"signalStrength\":\"-60\"})\n", 195 | " google_data = {\"considerIP\": \"false\", \"wifiAccessPoints\": ap_list}\n", 196 | " response = requests.post(url=url, json=google_data) \n", 197 | " loc_record = response.json()\n", 198 | " lat = loc_record.get(\"location\").get(\"lat\")\n", 199 | " lng = loc_record.get(\"location\").get(\"lng\")\n", 200 | " accuracy = loc_record.get('accuracy')\n", 201 | " return accuracy, f\"http://maps.google.com/maps?q={lat:0>3.9f},{lng:0>3.9f}&z=15\"\n", 202 | "\n", 203 | "print(\"Here is a good response from google.\")\n", 204 | "ap_data = [('0C-8C-24-6F-9E-28'), ('0C-8D-DB-84-FA-DF')]\n", 205 | "the_accuracy,the_url = find_location(ap_data, key)\n", 206 | "print(f\"The accuracy is {the_accuracy}\")\n", 207 | "print(the_url)\n", 208 | "\n", 209 | "print(\"\\n\\nHere is a response from google when we send it made up BSSIDs.\")\n", 210 | "ap_data = [('11-22-33-44-55-66'), ('AA-BB-CC-DD-EE-FF')]\n", 211 | "the_accuracy,the_url = find_location(ap_data, key)\n", 212 | "print(f\"The accuracy is {the_accuracy}\")\n", 213 | "print(the_url)\n", 214 | "\n" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "\n", 222 | "Our accuracy went back up for the two access points. It is almost as good as when we submitted a half dozen access points. But what about that second link? Was Google able to geolocation our imaginary made up access points? Where does that one lead? It points at you!\n", 223 | "\n", 224 | "When Google doesn't have any information about the Wireless access point pairs you provide it gives you a link to your current location based upon the IP address you are submitting it from. This is the \"I don't know\" answer from Google. You need to detect that your script. Don't try to do it based on the accuracy. The accuracy will change depending upon your location. Instead submit one request with made up BSSIDs and check to see if the location in all future responses match that. Using this technique we can discover access point locations when Google knows about any two AP's we manage to find on the system. You just have to pair up the Access Points.\n", 225 | "\n", 226 | "Consider this scenario. You find artifacts on the laptop that indicate it was in the vicinity of 5 access points. We will call them AP1, AP2, AP3, AP4 and AP5. Some of those APs may be in far off distant locations that Google cannot triangulate but others may be close enough to one another to establish a location. \n", 227 | "\n", 228 | "![alt text](images/aps.png \"AP Locations\")\n", 229 | "\n", 230 | "\n", 231 | "### We ask Google about locations.\n", 232 | "\n", 233 | "We ask: What's the location of AP1 and AP2?\n", 234 | "\n", 235 | "Google: Here is a link to your current location (ie I don't know)\n", 236 | "\n", 237 | "We ask: What's the location of AP1 and AP3?\n", 238 | "\n", 239 | "Google: Here is a link to your current location\n", 240 | "\n", 241 | "We ask: Whats the location of AP1 and AP4?\n", 242 | "\n", 243 | "Google: Google gives a location which by itself would be somewhere along the blue line.\n", 244 | "\n", 245 | "We ask: What's the location of AP1 and AP5?\n", 246 | "\n", 247 | "Google: Here is a link to your current location\n", 248 | "\n", 249 | "We ask: What's the location of AP2 and AP3?\n", 250 | "\n", 251 | "Google: Here is a link to your current location\n", 252 | "\n", 253 | "We ask: What's the locationa of AP2 and AP4?\n", 254 | "\n", 255 | "Google: Google gives you a location which by itself would be somewhere along the green line. \n", 256 | "\n", 257 | "Now, by looking at the intersection of these sets we can identify that AP4 has a relationship with both AP2 and AP1. Then ...\n", 258 | "\n", 259 | "We ask: What is the location of AP1, AP2, and AP4?\n", 260 | "\n", 261 | "Google: Google gives us a location somewhere close to the purple square in the middle.\n", 262 | "\n", 263 | "Repeating this process we can find locations that would otherwise be inaccessible through most APIs.\n", 264 | "\n", 265 | "### Building our sets of APs\n", 266 | "Now, with just a disconnected set of Wireless APs that we find in various artifacts on the computer we can build a list of AP pairs and find their locations. Python also makes building combinations of pretty simple by calling itertools.combinations. Try this next cell to see how you would do that.\n" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": null, 272 | "metadata": {}, 273 | "outputs": [], 274 | "source": [ 275 | "import itertools\n", 276 | "\n", 277 | "list_of_aps = ['AP1','AP2','AP3','AP4','AP5']\n", 278 | "list(itertools.combinations(list_of_aps,2))" 279 | ] 280 | } 281 | ], 282 | "metadata": { 283 | "kernelspec": { 284 | "display_name": "Python 3", 285 | "language": "python", 286 | "name": "python3" 287 | }, 288 | "language_info": { 289 | "codemirror_mode": { 290 | "name": "ipython", 291 | "version": 3 292 | }, 293 | "file_extension": ".py", 294 | "mimetype": "text/x-python", 295 | "name": "python", 296 | "nbconvert_exporter": "python", 297 | "pygments_lexer": "ipython3", 298 | "version": "3.6.9" 299 | } 300 | }, 301 | "nbformat": 4, 302 | "nbformat_minor": 2 303 | } -------------------------------------------------------------------------------- /Forensics Challenge Solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\n", 8 | "# Forensics Challenge 1 - The Outlook Web Access Attack\n", 9 | "\n", 10 | "The victim lost 1.2 million dollars in an Outlook Web Access Phishing attack. You have pulled all of the SUCCESSFUL LOGINS from the Outlook Administrator console. But there are THOUSANDS of IP addresses to look through. The original attack came from Iran. You decide to focus your attention on those requests that are from the same country of origin.\n", 11 | "\n", 12 | "```\n", 13 | "['144.12.97.175',\n", 14 | " '47.33.190.195',\n", 15 | " '54.213.136.220',\n", 16 | " '37.27.54.15',\n", 17 | " '91.107.252.94',\n", 18 | " '112.245.28.187',\n", 19 | " '77.198.208.109',\n", 20 | " '79.195.246.215',\n", 21 | " '109.219.196.231',\n", 22 | " '111.14.43.156',\n", 23 | " '102.111.174.6',\n", 24 | " '176.65.253.46',\n", 25 | " '102.155.114.82']\n", 26 | " ```\n", 27 | "\n", 28 | "Which IP Addresses originated in Iran?\n", 29 | "\n", 30 | "## Solutions\n", 31 | "\n", 32 | "One easy solution for this is to use the code we looked at in notebook title \"1 - IP Geolocation with Websites\".\n", 33 | "\n" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "import requests\n", 43 | "\n", 44 | "browser = requests.session()\n", 45 | "list_of_addresses = ['144.12.97.175', '47.33.190.195', '54.213.136.220', '37.27.54.15', '91.107.252.94', '112.245.28.187', '77.198.208.109', '79.195.246.215', '109.219.196.231', '111.14.43.156', '102.111.174.6', '176.65.253.46', '102.155.114.82']\n", 46 | "for each_ip in list_of_addresses:\n", 47 | " url = f\"https://freegeoip.app/json/{each_ip}\"\n", 48 | " result = browser.get(url).json()\n", 49 | " print(f\"The country for IP Address {result.get('ip')} is {result.get('country_name')}\")" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "# Forensics Challenge 2 - At the scene of the crime\n", 57 | "\n", 58 | "After obtaining the suspect's laptop you need to place him at the scene of the crime. After pulling their wireless history from the registry, SRUM and Windows Event logs you come up with the following list of Wireless BSSIDs. What locations can you place the suspects laptop at from the following Wireless BSSID addresses?\n", 59 | "\n", 60 | "```\n", 61 | "['70-0F-6A-32-2C-0F',\n", 62 | "'0C-F5-A4-96-CD-C7',\n", 63 | "'74-3E-2B-36-57-E8',\n", 64 | "'08-4F-A9-3C-F5-A0',\n", 65 | "'70-70-8B-29-12-81',\n", 66 | "'0C-F5-A4-96-CD-CB']\n", 67 | "```\n", 68 | "\n", 69 | "## Solution\n", 70 | "\n", 71 | "Here is one way you could solve this one. We combine various techniques used in notebook \"4 - Geolocating Multiple Access Points\"." 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "import requests\n", 81 | "import itertools\n", 82 | " \n", 83 | "key = 'copy and paste mark baggetts API key here'\n", 84 | "\n", 85 | "list_of_aps = ['70-0F-6A-32-2C-0F',\n", 86 | "'0C-F5-A4-96-CD-C7',\n", 87 | "'74-3E-2B-36-57-E8',\n", 88 | "'08-4F-A9-3C-F5-A0',\n", 89 | "'70-70-8B-29-12-81',\n", 90 | "'0C-F5-A4-96-CD-CB']\n", 91 | "\n", 92 | "def find_location(list_of_aps, key):\n", 93 | " url = f'https://www.googleapis.com/geolocation/v1/geolocate?key={key}' \n", 94 | " ap_list = []\n", 95 | " for mac in list_of_aps:\n", 96 | " ap_list.append({\"macAddress\":mac ,\"signalStrength\":\"-60\"})\n", 97 | " google_data = {\"considerIP\": \"false\", \"wifiAccessPoints\": ap_list}\n", 98 | " response = requests.post(url=url, json=google_data) \n", 99 | " loc_record = response.json()\n", 100 | " lat = loc_record.get(\"location\").get(\"lat\")\n", 101 | " lng = loc_record.get(\"location\").get(\"lng\")\n", 102 | " accuracy = loc_record.get('accuracy')\n", 103 | " return accuracy, f\"http://maps.google.com/maps?q={lat:0>3.9f},{lng:0>3.9f}&z=15\"\n", 104 | "\n", 105 | "my_location = find_location(['00-00-00-00-00-00','11-11-11-11-11-11'],key)\n", 106 | "for each_pair in itertools.combinations(list_of_aps,2):\n", 107 | " location = find_location(each_pair, key)\n", 108 | " if location != my_location:\n", 109 | " print(f\"WIFI {str(each_pair)} Accuracy {location[0]}. Link {location[1]}\")\n", 110 | " else:\n", 111 | " print(f\"WIFI {str(each_pair)} not found\")\n" 112 | ] 113 | } 114 | ], 115 | "metadata": { 116 | "kernelspec": { 117 | "display_name": "Python 3", 118 | "language": "python", 119 | "name": "python3" 120 | }, 121 | "language_info": { 122 | "codemirror_mode": { 123 | "name": "ipython", 124 | "version": 3 125 | }, 126 | "file_extension": ".py", 127 | "mimetype": "text/x-python", 128 | "name": "python", 129 | "nbconvert_exporter": "python", 130 | "pygments_lexer": "ipython3", 131 | "version": "3.6.9" 132 | } 133 | }, 134 | "nbformat": 4, 135 | "nbformat_minor": 2 136 | } 137 | -------------------------------------------------------------------------------- /Forensics Challenge.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\n", 8 | "# Forensics Challenge 1 - The Outlook Web Access Attack\n", 9 | "The victim lost 1.2 million dollars in an Outlook Web Access Phishing attack. You have pulled all of the SUCCESSFUL LOGINS from the Outlook Administrator console. But there are THOUSANDS of IP addresses to look through. The original attack came from Iran. You decide to focus your attention on those requests that are from the same country of origin. \n", 10 | "\n", 11 | "```\n", 12 | "['144.12.97.175',\n", 13 | " '47.33.190.195',\n", 14 | " '54.213.136.220',\n", 15 | " '37.27.54.15',\n", 16 | " '91.107.252.94',\n", 17 | " '112.245.28.187',\n", 18 | " '77.198.208.109',\n", 19 | " '79.195.246.215',\n", 20 | " '109.219.196.231',\n", 21 | " '111.14.43.156',\n", 22 | " '102.111.174.6',\n", 23 | " '176.65.253.46',\n", 24 | " '102.155.114.82']\n", 25 | " ```\n", 26 | "\n", 27 | "Which IP Addresses originated in Iran?" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "# Forensics Challenge 2 - At the scene of the crime\n", 42 | "\n", 43 | "\n", 44 | "After obtaining the suspect's laptop you need to place him at the scene of the crime. After pulling their wireless history from the registry, SRUM and Windows Event logs you come up with the following list of Wireless BSSIDs. What locations can you place the suspects laptop at from the following Wireless BSSID addresses?\n", 45 | "\n", 46 | "```\n", 47 | "['70-0F-6A-32-2C-0F',\n", 48 | "'0C-F5-A4-96-CD-C7',\n", 49 | "'74-3E-2B-36-57-E8',\n", 50 | "'08-4F-A9-3C-F5-A0',\n", 51 | "'70-70-8B-29-12-81',\n", 52 | "'0C-F5-A4-96-CD-CB']\n", 53 | "```\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [] 62 | } 63 | ], 64 | "metadata": { 65 | "kernelspec": { 66 | "display_name": "Python 3", 67 | "language": "python", 68 | "name": "python3" 69 | }, 70 | "language_info": { 71 | "codemirror_mode": { 72 | "name": "ipython", 73 | "version": 3 74 | }, 75 | "file_extension": ".py", 76 | "mimetype": "text/x-python", 77 | "name": "python", 78 | "nbconvert_exporter": "python", 79 | "pygments_lexer": "ipython3", 80 | "version": "3.6.9" 81 | } 82 | }, 83 | "nbformat": 4, 84 | "nbformat_minor": 2 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeoLocationWorkshop 2 | This is a set of scripts and resources for the Geolocation workship. 3 | 4 | Register to attend the Free Workshop: 5 | 6 | | DATE AND TIME | LINK | 7 | |---------------|------| 8 | |May 19th @ 1:00 PM EST | [EXPIRED](https://www.sans.org/webcasts/hacker-art-thou-hands-on-python-workshop-geolocating-attackers-115340)| 9 | |July 14th @ 1:00 PM EST | [EXPIRED](https://www.sans.org/webcasts/tech-tuesday-workshop-hacker-art-thou-hands-on-python-workshop-geolocating-attackers-back-popular-demand-116075)| 10 | |Dec 15th @ 9:30 AM EST | [HERE](https://www.sans.org/webcasts/tech-tuesday-workshop-hacker-art-thou-hands-on-python-workshop-geolocating-attackers-117940?utm_medium=Social&utm_source=Twitter&utm_content=CM+OO&utm_campaign=Tech+Tuesdays)| 11 | 12 | 13 | You will need this virtual machine to fully participate in the lab. You can download that [HERE](https://tinyurl.com/locatehacker)​ with the password "`whereRUhacker?​`" 14 | 15 | If you are unable to get the Virtual Machine downloaded and working for some reason, you can still participate with most of the workshop by using the binder link below. **NOTE: Approximatly 15% of the material covered will not work properly if you choose to use the website below instead of the virtual machine.** 16 | 17 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/MarkBaggett/GeoLocationNotebook/master) 18 | 19 | -------------------------------------------------------------------------------- /docs/DONOTOPEN.docm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBaggett/GeoLocationNotebook/55abfe3a905cd5d62164dc85200e9bdef19b0862/docs/DONOTOPEN.docm -------------------------------------------------------------------------------- /docs/DONOTOPEN.hta: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTA Execute 4 | 5 | 12 | 13 | 26 | 27 |

Error loading application.

28 | 29 | -------------------------------------------------------------------------------- /docs/DONOTOPEN.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBaggett/GeoLocationNotebook/55abfe3a905cd5d62164dc85200e9bdef19b0862/docs/DONOTOPEN.xlsm -------------------------------------------------------------------------------- /docs/GeoLocationWorkshop-Slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBaggett/GeoLocationNotebook/55abfe3a905cd5d62164dc85200e9bdef19b0862/docs/GeoLocationWorkshop-Slides.pdf -------------------------------------------------------------------------------- /images/Event6100.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBaggett/GeoLocationNotebook/55abfe3a905cd5d62164dc85200e9bdef19b0862/images/Event6100.jpg -------------------------------------------------------------------------------- /images/aps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBaggett/GeoLocationNotebook/55abfe3a905cd5d62164dc85200e9bdef19b0862/images/aps.png -------------------------------------------------------------------------------- /results.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Werejugo Results 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
Date/TimeSourceCoordinateMap Link
2020-03-06 09:29:05.221000Windows Diagnostice Event 6100 test,(Unnamed,grovy,kalki,FBI-VAN5,DESKTOP-NH41,DIRECT-T02-10T,RES-ANS-738,Verizon-MiFi8L-DCE,MyIphone,For33.4183727,-82.15896240000001Show on Google Maps
2019-03-03 10:38:46.923000.RSACONFERENCE 00:00:5E:00:01:01-First-Connect40.45789337,-111.93411255Show on Google Maps
2019-03-04 20:16:01.725000.RSACONFERENCE 00:00:5E:00:01:01-Last-Connect40.45789337,-111.93411255Show on Google Maps
2019-05-21 17:39:44.491000SEC573 7 C4:12:F5:5D:90:C7-First-Connect-35.29946136,149.12487793Show on Google Maps
2019-05-23 22:56:10.240000SEC573 7 C4:12:F5:5D:90:C7-Last-Connect-35.29946136,149.12487793Show on Google Maps
2020-03-06 09:29:05WLAN Event 8001 MyIphone33.4183727,-82.15896240000001Show on Google Maps
2020-03-06 08:10:24WLAN Event 8001 MyIphone33.4183727,-82.15896240000001Show on Google Maps
32 |
33 | 34 | 35 | 36 | 41 | 42 | 85 | 86 | 87 | --------------------------------------------------------------------------------