├── Anaximander.py ├── Anaximander_54.py ├── Anaximander_72.py ├── README.md └── dbFill.py /Anaximander.py: -------------------------------------------------------------------------------- 1 | # Anaximander v020160830 2 | # Python 2.7 3 | # Written by Matt Edmondson 4 | # Thanks to Ed Michael 5 | 6 | 7 | import xml.etree.ElementTree as ET 8 | import sqlite3 9 | import os 10 | import optparse 11 | import sys 12 | 13 | ####Forces input of xml file 14 | parser = optparse.OptionParser('correct usage is: Anaximander.py -t' + ' CellebriteTowers.xml') 15 | parser.add_option('-t', dest='targetpage', type='string', help='Specify the Cellebrite cellphone tower XML output file') 16 | (options, args) = parser.parse_args() 17 | tgtFile = options.targetpage 18 | if (tgtFile == None): 19 | print parser.usage 20 | sys.exit(0) 21 | 22 | z = open('cellTowers.kml', 'w') 23 | z.write("\n") 24 | z.write("n" ) 25 | z.write("\n") 26 | z.write(" Cell Towers \n") 27 | 28 | tree = ET.parse(tgtFile) 29 | root = tree.getroot() 30 | x = 0 31 | for child in root[6][0]: 32 | 33 | varTimestamp = root[6][0][x][1][0].text 34 | #print "Timestamp: %s" % varTimestamp 35 | varPackageType = root[6][0][x][2][0].text 36 | #print "Package Type: %s " % varPackageType 37 | varCellTowerType = root[6][0][x][3][0].text 38 | #print "Cell Tower Type: %s" % varCellTowerType 39 | varMcc = root[6][0][x][4][0].text 40 | #print "MCC: %s" % varMcc 41 | varMnc = root[6][0][x][5][0].text 42 | #print "MNC: %s " % varMnc 43 | varLac = root[6][0][x][6][0].text 44 | #print "LAC: %s" % varLac 45 | varCid = root[6][0][x][7][0].text 46 | #print "CID: %s" % varCid 47 | print "[+] Inserting the following record (MCC: %s, MNC: %s, LAC: %s, CID: %s)" % (varMcc, varMnc, varLac, varCid) 48 | print "[+] Cont: (Timestamp: %s, Pack Type: %s, Cell Tower Type: %s)" % (varTimestamp, varPackageType, varCellTowerType) 49 | 50 | ###DB for Tower Locations 51 | db = sqlite3.connect('cellTowers.sqlite') 52 | cursor = db.cursor() 53 | cursor.execute("select * from towers where mcc = '%s' and net = '%s' and area = '%s' and cell = '%s' limit 1" % (varMcc, varMnc, varLac, varCid) ) 54 | for row in cursor: 55 | try: 56 | varLon = row[6] 57 | #print "Lon: %s" % varLon 58 | varLat = row[7] 59 | #print "Lat: %s " % varLat 60 | print "[+] Cont: (Lon: %s, Lat: %s)" % (varLon, varLat) 61 | 62 | 63 | kml_contents = " \n "+ varTimestamp +" " + str(x)+ "\n

Cell ID: " + varCid + "

Date: " + varTimestamp + "

\n \n " + varLon + "," + varLat + ",0 \n \n
\n" 64 | z.write(kml_contents) 65 | except: 66 | print "[!] Error with the following record (MCC: %s, MNC: %s, LAC: %s, CID: %s)!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" % (varMcc, varMnc, varLac, varCid) 67 | 68 | print "############################" 69 | x =x + 1 70 | 71 | cursor.close 72 | print "\n%s Records Parsed" % x 73 | 74 | ######Finalizing and Closing KML############ 75 | z.write("
\n") 76 | z.write("
\n") 77 | z.close() 78 | print"[+] Generated KML file" -------------------------------------------------------------------------------- /Anaximander_54.py: -------------------------------------------------------------------------------- 1 | # Anaximander v020160830 2 | # Python 2.7 3 | # Written by Matt Edmondson 4 | # Thanks to Ed Michael & Douglas Kein 5 | 6 | 7 | import xml.etree.ElementTree as ET 8 | import sqlite3 9 | import os 10 | import optparse 11 | import sys 12 | 13 | ####Forces input of xml file 14 | parser = optparse.OptionParser('correct usage is: Anaximander.py -t' + ' CellebriteTowers.xml') 15 | parser.add_option('-t', dest='targetpage', type='string', help='Specify the Cellebrite cellphone tower XML output file') 16 | (options, args) = parser.parse_args() 17 | tgtFile = options.targetpage 18 | if (tgtFile == None): 19 | print parser.usage 20 | sys.exit(0) 21 | 22 | z = open('cellTowers.kml', 'w') 23 | z.write("\n") 24 | z.write("n" ) 25 | z.write("\n") 26 | z.write(" Cell Towers \n") 27 | 28 | tree = ET.parse(tgtFile) 29 | root = tree.getroot() 30 | x = 0 31 | for child in root[6][0]: 32 | 33 | varTimestamp = root[6][0][x][2][0].text 34 | #print "Timestamp: %s" % varTimestamp 35 | varPackageType = root[6][0][x][3][0].text 36 | #print "Package Type: %s " % varPackageType 37 | varCellTowerType = root[6][0][x][4][0].text 38 | #print "Cell Tower Type: %s" % varCellTowerType 39 | varMcc = root[6][0][x][5][0].text 40 | #print "MCC: %s" % varMcc 41 | varMnc = root[6][0][x][6][0].text 42 | #print "MNC: %s " % varMnc 43 | varLac = root[6][0][x][7][0].text 44 | #print "LAC: %s" % varLac 45 | varCid = root[6][0][x][8][0].text 46 | #print "CID: %s" % varCid 47 | print "[+] Inserting the following record (MCC: %s, MNC: %s, LAC: %s, CID: %s)" % (varMcc, varMnc, varLac, varCid) 48 | print "[+] Cont: (Timestamp: %s, Pack Type: %s, Cell Tower Type: %s)" % (varTimestamp, varPackageType, varCellTowerType) 49 | 50 | ###DB for Tower Locations 51 | db = sqlite3.connect('cellTowers.sqlite') 52 | cursor = db.cursor() 53 | cursor.execute("select * from towers where mcc = '%s' and net = '%s' and area = '%s' and cell = '%s' limit 1" % (varMcc, varMnc, varLac, varCid) ) 54 | for row in cursor: 55 | try: 56 | varLon = row[6] 57 | #print "Lon: %s" % varLon 58 | varLat = row[7] 59 | #print "Lat: %s " % varLat 60 | print "[+] Cont: (Lon: %s, Lat: %s)" % (varLon, varLat) 61 | 62 | 63 | kml_contents = " \n "+ varTimestamp +" " + str(x)+ "\n

Cell ID: " + varCid + "

Date: " + varTimestamp + "

\n \n " + varLon + "," + varLat + ",0 \n \n
\n" 64 | z.write(kml_contents) 65 | except: 66 | print "[!] Error with the following record (MCC: %s, MNC: %s, LAC: %s, CID: %s)!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" % (varMcc, varMnc, varLac, varCid) 67 | 68 | print "############################" 69 | x =x + 1 70 | 71 | cursor.close 72 | print "\n%s Records Parsed" % x 73 | 74 | ######Finalizing and Closing KML############ 75 | z.write("
\n") 76 | z.write("
\n") 77 | z.close() 78 | print"[+] Generated KML file" -------------------------------------------------------------------------------- /Anaximander_72.py: -------------------------------------------------------------------------------- 1 | # Anaximander v20190819 2 | # Python 3.7.x 3 | # Written by Matt Edmondson 4 | # Thanks to Ed Michael & Douglas Kein 5 | # Modified to Python 3.7x and other things by Micah "WebBreacher" Hoffman 6 | 7 | from xml.dom import minidom 8 | import sqlite3 9 | import os 10 | import optparse 11 | import signal 12 | import sys 13 | 14 | #### Functions 15 | def keyboardInterruptHandler(signal, frame): 16 | # Capture Ctrl-C and finalize the KML before we leave to capture whatever content we have in there 17 | print('KeyboardInterrupt (ID: {}) has been caught. Cleaning up...'.format(signal)) 18 | closeKml() 19 | exit(0) 20 | 21 | def closeKml(): 22 | #### Finalizing and Closing KML 23 | z.write('\n') 24 | z.write('\n') 25 | z.close() 26 | print('[+] Finished generating the KML file\n') 27 | 28 | #### Forces input of xml file 29 | parser = optparse.OptionParser('Correct usage is: Anaximander.py -t CellebriteTowers.xml') 30 | parser.add_option('-t', dest='targetpage', type='string', help='Specify the Cellebrite cellphone tower XML output file') 31 | (options, args) = parser.parse_args() 32 | tgtFile = options.targetpage 33 | if (tgtFile == None): 34 | print(parser.usage) 35 | sys.exit(1) 36 | 37 | #### Creates KML header 38 | z = open('cellTowers.kml', 'w') 39 | z.write('\n') 40 | z.write('\n') 41 | z.write('\n') 42 | z.write('Cell Towers from Anaximander\n') 43 | z.write('Automatically generated using Anaximander (https://github.com/azmatt/Anaximander)\n') 44 | # Use Pink Paddle icons for the points so they stand out in Google Earth 45 | z.write(' \n\n') 46 | 47 | #### Open XML document using minidom parser 48 | DOMTree = minidom.parse(tgtFile) 49 | models = DOMTree.getElementsByTagName('model') 50 | varNumModels = len(models) 51 | print('[+] There are {} records to extract.'.format(varNumModels)) 52 | varCounter = 1 53 | 54 | #### Open SQLite Database with Cell Tower Location Info 55 | db = sqlite3.connect('cellTowers.sqlite') 56 | cursor = db.cursor() 57 | 58 | #### Start signal to wait for CTRL-C and finish gracefully 59 | signal.signal(signal.SIGINT, keyboardInterruptHandler) 60 | 61 | #### Find each record and cycle through them 62 | for model in models: 63 | fields = model.getElementsByTagName('field') 64 | varModelId = model.attributes['id'].value 65 | for field in fields: 66 | #print(field.attributes['name'].value) #DEBUG 67 | if field.getAttribute("name") == "TimeStamp": 68 | varTimestampEle = field.getElementsByTagName('value')[0] 69 | varTimestamp = varTimestampEle.firstChild.data 70 | if field.getAttribute("name") == "Name": 71 | nameDataRaw = field.getElementsByTagName('value')[0] 72 | #print(nameDataRaw.firstChild.data) #DEBUG 73 | nameDatasplit = nameDataRaw.firstChild.data.splitlines() 74 | varCellTowerType = nameDatasplit[0] 75 | varMcc = nameDatasplit[1].split()[1] 76 | varMnc = nameDatasplit[2].split()[1] 77 | varLac = nameDatasplit[3].split()[1] 78 | varCid = nameDatasplit[4].split()[1] 79 | #print(varCellTowerType, varMcc, varMnc, varLac, varCid) #DEBUG 80 | print('[+] Extracted record (MCC: {}, MNC: {}, LAC: {}, CID: {}) - Rcd# {} / {}'.format(varMcc, varMnc, varLac, varCid, varCounter, varNumModels)) 81 | 82 | ### Search DB for Cell Tower Locations 83 | print('[+] Searching DB for this record.') 84 | cursor.execute("select * from towers where mcc = '{}' and net = '{}' and area = '{}' and cell = '{}' limit 1".format(varMcc, varMnc, varLac, varCid)) 85 | for row in cursor: 86 | try: 87 | varLon = row[6] 88 | varLat = row[7] 89 | print('[+] Found: (Lon: {}, Lat: {}), Writing to KML.'.format(varLon, varLat)) 90 | kml_contents = "\n"+ varTimestamp +"" + str(varTimestamp)+ "\n

Cell ID: " + varCid + "

Date: " + varTimestamp + "

\n\n" + varLon + "," + varLat + ",0\n\n#pinkPaddle
\n" 91 | z.write(kml_contents) 92 | except Exception as e: 93 | print('\n[!!!] Error ({}) with the following record (MCC: {}, MNC: {}, LAC: {}, CID: {})\n'.format(e, varMcc, varMnc, varLac, varCid)) 94 | varCounter += 1 95 | cursor.close 96 | 97 | closeKml() 98 | print('[+] Script completed.') -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Anaximander 2 | Python Code to Map Cell Towers From a Cellebrite Android Dump 3 | 4 | Instructions and example located at: 5 | http://digitalforensicstips.com/2016/08/python-script-to-map-cell-tower-locations-from-an-android-device-report-in-cellebrite/ 6 | 7 | 8/19/2019 Added Anaximander_72.py for a version compatible with Cellebrate 7.2 and Python 3.x 8 | 12/12/2016 Added Anaximander_54.py for a version compatible with Cellebrite 5.4 9 | 10 | This script uses the GPL v3 license. 11 | -------------------------------------------------------------------------------- /dbFill.py: -------------------------------------------------------------------------------- 1 | #Takes the CSV database download from http://opencellid.org/ (Free with API), creates a SQLite database called "cellTowers.sqlite" and inserts the downloaded data into the Database 2 | #The Schema for the database is as follows: radio, mcc, net, area, cell,unit, lon,lat, range, samples, changeable, created, updated, averageSignal 3 | #As of 8/2016 the size of the SQLITE database is approximately 2.6 GB 4 | 5 | 6 | import csv 7 | import sqlite3 8 | 9 | con = sqlite3.Connection('cellTowers.sqlite') 10 | cur = con.cursor() 11 | cur.execute('CREATE TABLE "towers" ("radio" varchar(12), "mcc" varchar(12), "net" varchar(12),"area" varchar(12),"cell" varchar(12),"unit" varchar(12),"lon" varchar(12),"lat" varchar(12),"range" varchar(12),"samples" varchar(12),"changeable" varchar(12),"created" varchar(12),"updated" varchar(12),"averageSignal" varchar(12));') 12 | 13 | f = open('cell_towers.csv') 14 | csv_reader = csv.reader(f, delimiter=',') 15 | 16 | cur.executemany('INSERT INTO towers VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', csv_reader) 17 | cur.close() 18 | con.commit() 19 | con.close() 20 | f.close() --------------------------------------------------------------------------------