Authors: TJ O'Connor
Geo-Location through the Windows Registry
Recycle Bin Investigation
Examining Metadata in PDFs and Microsoft Documents
Extracting GPS Coordinates from Exif Metadata
Investigating Skype Artifacts
Enumerating Browser Artifacts from Firefox Databases
Examining Mobile Device Artifacts
Ultimately, you must forget about technique. The further you progress, the fewer teachings there are. The Great Path is really NO PATH…
—Ueshiba Morihei, Kaiso, Founder, Aikido
In February 2005, Wichita police forensic investigator Mr. Randy Stone unraveled the final clues of a 30-year-old mystery. A couple of days earlier, KSAS Television station had handed the police a 3.5” floppy disk they had received from the infamous BTK (Bind, Torture, Kill) Killer. Responsible for at least 10 murders from 1974 to 1991, the BTK Killer eluded capture while repeatedly taunting the police and his victims. On February 16th, 2005, the BTK Killer sent the television station a 3.5” disk with communication instructions. Among these instructions, the disk contained a file named Test.A.rtf. (
Regan, 2006
). While the file contained instructions from the BTK Killer, it also contained something else: metadata. Embedded in the Microsoft proprietary
Rich Text Format (RTF), the file contained the first name of the BTK Killer and the physical location at which the user had last saved the file.
This narrowed the investigation to a man named Denis at the local Wichita Christ Lutheran Church. Mr. Stone verified that a man named Denis Rader served as a church officer at the Lutheran Church (
Regan, 2006
). With this information, police requested a warrant for a DNA sample from the medical records of Denis Rader’s daughter (
Shapiro, 2007
). The DNA sample confirmed what Mr. Stone already knew—Denis Rader was the BTK Killer. A 31-year investigation that had exhausted 100,000 man hours ended with Mr. Stone’s examination of metadata (
Regan, 2006
).
Computer forensic investigations prove only as good as the investigator and the tools in his or her arsenal. All too often an investigator may have a nagging question but does not have the tool to answer his question. Enter Python. As we have seen in previous chapters, solving complex problems with minimal code proves a strength of the Python programming language. As we will see in the following sections, we can answer questions some pretty complex questions with minimal lines of Python code. Let’s begin by using some unique Windows Registry keys to physically track a user.
The Windows registry contains a hierarchical database that stores the configuration settings of the operating system. With the advent of wireless networking, the Windows Registry stores information related to the wireless connection. Understanding the location and meaning of these registry keys can provide us with geo-location information about where a laptop has been. From Windows Vista on, the Registry stores each of the networks in subkey under
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged.
From the Windows command prompt, we can list each of the networks, showing the profile Guid, network description, network name, and gateway MAC address.
C:\Windows\system32>reg query “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\
CurrentVersion\NetworkList\Signatures\Unmanaged” /s
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Sign
atures\Unmanaged\010103000F0000F0080000000F0000F04BCC2360E4B8F7DC8BDAFAB8AE4DAD8
62E3960B979A7AD52FA5F70188E103148
ProfileGuid REG_SZ {3B24CE70-AA79-4C9A-B9CC-83F90C2C9C0D}
Description REG_SZ Hooters_San_Pedro
Source REG_DWORD 0x8
DnsSuffix REG_SZ
FirstNetwork REG_SZ Public_Library
DefaultGatewayMac REG_BINARY 00115024687F0000
The registry stores the gateway MAC address as a REG_BINARY type. In the previous example, the hex bytes \x00\x11\x50\x24\x68\x7F\x00\x00 refer to the actual address 00:11:50:24:68:7F. We will write a quick function to convert the REG_BINARY value to an actual MAC address. Knowing the MAC address of the wireless network can prove useful, as we will see later.
def val2addr(val):
addr = “”
for ch in val:
addr += (“%02x “% ord(ch))
addr = addr.strip(“ “).replace(“ “,”:”)[0:17]
return addr
Now, let’s write a function to extract the network name and MAC address for every listed network profile from the specific keys in the Windows registry. To do this, we will utilize the
_winreg
library, installed by default with the Windows default Python Installer. After connecting to the registry, we can open the key with the
OpenKey()
function and loop through the network profiles under this key. For each profile, it contains the following sub-keys: ProfileGuid, Description, Source, DnsSuffix, FirstNetwork, DefaultGatewayMac. The registry key indexes the network name and DefaultGatewayMAC as fourth and fifth values in the array. We can now enumerate each of these keys and print them to the screen.
from _winreg import ∗
def printNets():
net = “SOFTWARE\Microsoft\Windows NT\CurrentVersion”+\
“\NetworkList\Signatures\Unmanaged”
key = OpenKey(HKEY_LOCAL_MACHINE, net)
print ‘\n[∗] Networks You have Joined.’
for i in range(100):
try:
guid = EnumKey(key, i)
netKey = OpenKey(key, str(guid))
(n, addr, t) = EnumValue(netKey, 5)
(n, name, t) = EnumValue(netKey, 4)
macAddr = val2addr(addr)
netName = str(name)
print ‘[+] ’ + netName + ‘ ’ + macAddr
CloseKey(netKey)
except:
break
Putting everything together, we now have a script that will print out the previously connected wireless networks stored in the Windows Registry.
from _winreg import ∗
def val2addr(val):
addr = ’’
for ch in val:
addr += ‘%02x ’% ord(ch)
addr = addr.strip(‘ ’).replace(‘ ’, ‘:’)[0:17]
return addr
def printNets():
net = “SOFTWARE\Microsoft\Windows NT\CurrentVersion”+\
“\NetworkList\Signatures\Unmanaged”
key = OpenKey(HKEY_LOCAL_MACHINE, net)
print ‘\n[∗] Networks You have Joined.’
for i in range(100):
try:
guid = EnumKey(key, i)
netKey = OpenKey(key, str(guid))
(n, addr, t) = EnumValue(netKey, 5)
(n, name, t) = EnumValue(netKey, 4)
macAddr = val2addr(addr)
netName = str(name)
print ‘[+] ’ + netName + ‘ ’ + macAddr
CloseKey(netKey)
except:
break
def main():
printNets()
if __name__ == “__main__”:
main()
Running our script against a target laptop, we see the previously connected wireless networks along with their MAC addresses. When testing the script, ensure you are running from inside an Administrator console or you will be unable to read the keys.
C:\Users\investigator\Desktop\python discoverNetworks.py
[∗] Networks You have Joined.
[+] Hooters_San_Pedro, 00:11:50:24:68:7F
[+] LAX Airport, 00:30:65:03:e8:c6
[+] Senate_public_wifi, 00:0b:85:23:23:3e
However, the script does not end here. With the MAC address of a wireless access point, we can now also print out the physical location of the access point as well. Quite a few databases, both open-source and proprietary, contain enormous listings of wireless access points correlated to their physical locations. Proprietary products such as cell phones use these databases to geo-locate without the use of GPS.
The SkyHook database, available at
http://www.skyhookwireless.com/
, provides a software developer kit to geo-locate based off of Wi-Fi positioning. An open-source project developed by Ian McCracken provided access to this database for several years at
http://code.google.com/p/maclocate/
. However, just recently SkyHook changed the SDK to use an API key to interact with the database. Google also maintained a similarly large database for the purpose of correlating access-point MAC addresses to physical locations. However, shortly after Gorjan Petrovski developed an NMAP NSE script to interact with it, Google deprecated open source interaction with the database (
Google, 2012
;
Petrovski, 2011
). Microsoft locked down a similar Wi-Fi geo-location database shortly afterwards, citing privacy concerns (
Bright, 2011
).
A remaining database and open-source project, wigle.net, continues to allow users to search for physical locations from an access point address. After registering for an account, a user can interact with wigle.net with a little creative Python scripting. Let us quickly examine how to build a script to interact with wigle.net.
Using wigle.net, a user will quickly realize that he or she must interact with three separate pages in order to return a Wigle result. First, he must open the wigle.net initial page at
http://wigle.net
; next the user must log in to Wigle at
http://wigle.net//gps/gps/main/login
. Finally, the user can query a specific wireless SSID MAC address at the page
http://wigle.net/gps/gps/main/confirmquery/
. Capturing the MAC address query, we see that the netid parameter
contains the MAC address in the HTTP Post that requests the GPS location of the wireless access point.
POST /gps/gps/main/confirmquery/ HTTP/1.1
Accept-Encoding: identity
Content-Length: 33
Host: wigle.net
User-Agent: AppleWebKit/531.21.10
Connection: close
Content-Type: application/x-www-form-urlencoded
netid=0A%3A2C%3AEF%3A3D%3A25%3A1B
<..SNIPPED..>
Furthermore, we see the response from the page includes the GPS coordinates. The string maplat=47.25264359&maplon=-87.25624084 contains the latitude and longitude of the access point.
With this information, we now have enough to build a simple function that will return the latitude and longitude of a wireless access point as recorded in the Wigle database. Notice the use of the mechanize library. Available from The script may appear complex, but let’s quickly walk through it together. First, we create an instance of a mechanize browser. Next, we open the initial wigle.net page. We then encode our username and password as parameters and request a login at the Wigle login page. Once we have successfully logged in, we create an HTTP post using the parameter netid as the MAC address to search the database. We then search the result of our HTTP post for the terms maplat= and maplon= for our latitude and longitude coordinates. Once found, we return these coordinates as a tuple. import mechanize, urllib, re, urlparse def wiglePrint(username, password, netid): browser = mechanize.Browser() browser.open(‘http://wigle.net’) reqData = urllib.urlencode({‘credential_0’: username, ‘credential_1’: password}) browser.open(‘https://wigle.net/gps/gps/main/login’, reqData) params = {} params[‘netid’] = netid reqParams = urllib.urlencode(params) respURL = ‘http://wigle.net/gps/gps/main/confirmquery/’ resp = browser.open(respURL, reqParams).read() mapLat = ‘N/A’ mapLon = ‘N/A’ rLat = re.findall(r‘maplat=.∗\&’, resp) if rLat: mapLat = rLat[0].split(‘&’)[0].split(‘=’)[1] rLon = re.findall(r‘maplon=.∗\&’, resp) if rLon: mapLon = rLon[0].split print ‘[-] Lat: ’ + mapLat + ‘, Lon: ’ + mapLon Adding the Wigle MAC address functionality to our original script, we now have the ability to examine a registry for previously connected wireless access points and then look up their physical locations. import os import optparse import mechanize import urllib import re import urlparse from _winreg import ∗ def val2addr(val): addr = ’’ for ch in val: addr += ‘%02x ’% ord(ch) addr = addr.strip(‘ ’).replace(‘ ’, ‘:’)[0:17] return addr def wiglePrint(username, password, netid): browser = mechanize.Browser() browser.open(‘http://wigle.net’) reqData = urllib.urlencode({‘credential_0’: username, ‘credential_1’: password}) browser.open(‘https://wigle.net//gps/gps/main/login’, reqData) params = {} params[‘netid’] = netid reqParams = urllib.urlencode(params) respURL = ‘http://wigle.net/gps/gps/main/confirmquery/’ resp = browser.open(respURL, reqParams).read() mapLat = ‘N/A’ mapLon = ‘N/A’ rLat = re.findall(r‘maplat=.∗\&’, resp) if rLat: mapLat = rLat[0].split(‘&’)[0].split(‘=’)[1] rLon = re.findall(r‘maplon=.∗\&’, resp) if rLon: mapLon = rLon[0].split print ‘[-] Lat: ’ + mapLat + ‘, Lon: ’ + mapLon def printNets(username, password): net = \ “SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged” key = OpenKey(HKEY_LOCAL_MACHINE, net) print ‘\n[∗] Networks You have Joined.’ for i in range(100): try: guid = EnumKey(key, i) netKey = OpenKey(key, str(guid)) (n, addr, t) = EnumValue(netKey, 5) (n, name, t) = EnumValue(netKey, 4) macAddr = val2addr(addr) netName = str(name) print ‘[+] ’ + netName + ‘ ’ + macAddr wiglePrint(username, password, macAddr) CloseKey(netKey) except: break def main(): parser = \ optparse.OptionParser(“usage%prog “+ “-u ) parser.add_option(‘-u’, dest=‘username’, type=‘string’, help=‘specify wigle password’) parser.add_option(‘-p’, dest=‘password’, type=‘string’, help=‘specify wigle username’) (options, args) = parser.parse_args() username = options.username password = options.password if username == None or password == None: print parser.usage exit(0) else: printNets(username, password) if __name__ == ‘__main__’: main() Running our script with the new functionality, we now see the previously connected wireless networks and their physical locations. With the knowledge of where a computer has been, let’s now use the next section to examine the trash. C:\Users\investigator\Desktop\python discoverNetworks.py [∗] Networks You have Joined. [+] Hooters_San_Pedro, 00:11:50:24:68:7F [-] Lat: 29.55995369, Lon: -98.48358154 [+] LAX Airport, 00:30:65:03:e8:c6 [-] Lat: 28.04605293, Lon: -82.60256195 [+] Senate_public_wifi, 00:0b:85:23:23:3e [-] Lat: 44.95574570, Lon: -93.102775570A:2C:EF:3D:25:1B McDonald’s FREE Wifi <
http://wwwsearch.sourceforge.net/mechanize/
, mechanize allows stateful web programming in Python. This means that once we correctly log on to the Wigle service, it will store and reuse the authentication cookie for us.Other books