""" ip-threespace.py "An OpenGL packet capture visualizer" Copyright (C) 2004 Eli Fulkerson http://www.elifulkerson.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ---------------------------------------------------------------------- Other license terms may be negotiable. Contact the author if you would like a copy that is licensed differently. Contact information (as well as this script) lives at http://www.elifulkerson.com ========================================================================== This script requires the following dependencies to be correctly installed: ========================================================================== pyopengl - http://pyopengl.sourceforge.net/ winpcap - http://www.winpcap.org/install/default.htm impacket - http://oss.coresecurity.com/projects/impacket.html pcapy - http://oss.coresecurity.com/projects/pcapy.html Much of this is windows specific, this script is unlikely to function on other operating systems. ========================================================================== """ import sys from OpenGL.GL import * from OpenGL.GLE import * from OpenGL.GLUT import * from OpenGL.GLU import * import string from exceptions import Exception from threading import Thread import threading from time import clock from math import sin import pcapy import impacket from impacket.ImpactDecoder import EthDecoder, LinuxSLLDecoder from urllib import * from string import split, join import re import sgmllib from Tkinter import * import os class Page(Frame): """Page is the Frame that will be added/removed at will""" def __init__(self, root, id): Frame.__init__(self, root) Label(self, text="Frame %d" % id).pack() class Stripper(sgmllib.SGMLParser): def __init__(self): sgmllib.SGMLParser.__init__(self) def strip(self, some_html): self.theString = "" self.feed(some_html) self.close() return self.theString def handle_data(self, data): self.theString += data class ThreadedNetblockResolver(threading.Thread): def __init__(self, IP): #print "resolver init" self.done = 0 self.IP = IP self.values = {} threading.Thread.__init__(self) def run(self): #print "resolver running for ", self.IP global IP_NETBLOCK_LIST try: temp = IP_NETBLOCK_LIST[self.IP] #print temp except KeyError: #value["cheat_to_prevent_rechecking"] = 1 #IP_NETBLOCK_LIST[self.IP]["cheat_to_prevent_rechecking"] = 1 #print "open the url" file = urlopen('http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + self.IP) text = file.read() #close(file) output = re.compile('
(.*?)
', re.DOTALL | re.IGNORECASE).findall(text) for line in split(join(output, "\n"), "\n"): words = split(line, " ") if len(words) > 1 and words[:1] != "#": key = join(words[:1],"") #print "Key is: ", key value = join( words[1:], " ") stripper = Stripper() value = stripper.strip(value) #print "Value is: ", value #some fields I may be interested in later (dns servers) are dupes #if len(self.values[key]) > 0: # self.values[key + "2"] = value #else: self.values[key] = value # try and pre-calculate the subnet geometry try: netrange = join(self.values["NetRange:"],"") netrange = string.replace(netrange, " ", "") netrange = string.replace(netrange, "-", " ") netrange = split(netrange, " ") start = netrange[0] end = netrange[1] start = split(start, ".") end = split(end, ".") self.values["calc_start_1"] = int(start[0]) self.values["calc_start_2"] = int(start[1]) self.values["calc_start_3"] = int(start[2]) self.values["calc_start_4"] = int(start[3]) self.values["calc_end_1"] = int(end[0]) self.values["calc_end_2"] = int(end[1]) self.values["calc_end_3"] = int(end[2]) self.values["calc_end_4"] = int(end[3]) except: pass IP_NETBLOCK_LIST[self.IP] = self.values self.done = 1 def choose_sniffing_interface(): devices = pcapy.findalldevs() # # For whatever reason, the return values from pcapy.findalldevs is garbled funkily. This ungarbles it. # table = "`1234567890-=~!@#$%^&*()_+qwertyuiop[]\\asdfghjkl;zxcvbnm,./QWERTYUIOP{}|ASDFGHJKL:ZXCVBNM<>? '\"" cleandevices = [] queue = "" for dev in devices: dev = dev.encode('utf-16') for num in range(0,len(dev)): #print num, dev[num] if string.find(table, dev[num]) == -1: #print "INVALID ", dev[num] if len(queue) > 0: cleandevices.append(queue) queue = "" else: queue = queue + dev[num] done = 0 while not done: print "" print "Network devices found:" print "----------------------" num = 0 for dev in cleandevices: num = num + 1 print num,dev print "" choice = raw_input("Please choose a network device: ") #print "your choice was: ", choice try: choice = int(choice) - 1 p = pcapy.open_live( cleandevices[choice], 4096, True, 10000) p = "" done = 1 except: print "That device cannot be opened." print "" print "Beginning Sniff... right-click on cube to change filter." print "" return cleandevices[choice] def color_blue(): glColor3f(0.0, 0.0, 1.0) def color_red(): glColor3f(1.0, 0.0, 0.0) def color_green(): glColor3f(0.0, 1.0, 0.0) def color_magenta(): glColor3f(1.0, 0.0, 1.0) def color_cyan(): glColor3f(0.0, 1.0, 1.0) def color_yellow(): glColor3f(1.0, 1.0, 0.0) def color_white(): glColor3f(1.0, 1.0, 1.0) def color_black(): glColor3f(0.0, 0.0, 0.0) def set_viewpoint(): glLoadIdentity() glTranslatef(0.0, 0.0, -80.0) def drawCube(sideA,sideB,sideC): if sideA == 0: sideA = 1/25.6 if sideB == 0: sideB = 1/25.6 if sideC == 0: sideC = 1/25.6 side = 1.0 #@@TODO: This needs to seperate color from shape glShadeModel(GL_SMOOTH) # draw six faces of a cube glBegin(GL_QUADS); glNormal3f( 0.0, 0.0, 1.0) color_blue() glTexCoord2f(0.0, 0.0); glVertex3f(-sideA, -sideB, sideC); glTexCoord2f(side, 0.0); glVertex3f( sideA, -sideB, sideC); glTexCoord2f(side, side); glVertex3f( sideA, sideB, sideC); color_white() glTexCoord2f(0.0, side); glVertex3f(-sideA, sideB, sideC); glNormal3f( 0.0, 0.0,-1.0); color_blue() glTexCoord2f(side, 0.0); glVertex3f(-sideA, -sideB, -sideC); glTexCoord2f(side, side); glVertex3f(-sideA, sideB, -sideC); glTexCoord2f(0.0, side); glVertex3f( sideA, sideB, -sideC); color_black() glTexCoord2f(0.0, 0.0); glVertex3f( sideA, -sideB, -sideC); glNormal3f( 0.0, 1.0, 0.0) color_blue() glTexCoord2f(0.0, side); glVertex3f(-sideA, sideB, -sideC); color_white() glTexCoord2f(0.0, 0.0); glVertex3f(-sideA, sideB, sideC); color_blue() glTexCoord2f(side, 0.0); glVertex3f( sideA, sideB, sideC); color_blue() glTexCoord2f(side, side); glVertex3f( sideA, sideB, -sideC); glNormal3f( 0.0,-1.0, 0.0) color_blue() glTexCoord2f(side, side); glVertex3f(-sideA, -sideB, -sideC); color_black() glTexCoord2f(0.0, side); glVertex3f( sideA, -sideB, -sideC); color_blue() glTexCoord2f(0.0, 0.0); glVertex3f( sideA, -sideB, sideC); color_blue() glTexCoord2f(side, 0.0); glVertex3f(-sideA, -sideB, sideC); glNormal3f( 1.0 , 0.0, 0.0) color_black() glTexCoord2f(side, 0.0); glVertex3f( sideA, -sideB, -sideC); color_blue() glTexCoord2f(side, side); glVertex3f( sideA, sideB, -sideC); color_blue() glTexCoord2f(0.0, side); glVertex3f( sideA, sideB, sideC); color_blue() glTexCoord2f(0.0, 0.0); glVertex3f( sideA, -sideB, sideC); glNormal3f(-1.0, 0.0, 0.0) color_blue() glTexCoord2f(0.0, 0.0); glVertex3f(-sideA, -sideB, -sideC); color_blue() glTexCoord2f(side, 0.0); glVertex3f(-sideA, -sideB, sideC); color_white() glTexCoord2f(side, side); glVertex3f(-sideA, sideB, sideC); color_blue() glTexCoord2f(0.0, side); glVertex3f(-sideA, sideB, -sideC); glEnd() def print_text(text,size): glScalef(size,size,size); for char in text: glutStrokeCharacter(GLUT_STROKE_ROMAN, ord(char)) class Packet: def __init__(self, source_ip, source_port, dest_ip, dest_port, size): self.sip = source_ip self.sp = source_port self.dip = dest_ip self.dp = dest_port resolver = ThreadedNetblockResolver(self.dip) resolver.run() self.size = size self.when = int(clock()) # original x,y,z self.ox = 0 self.oy = 0 self.oz = 0 # current x,y,z self.cx = 0 self.cy = 0 self.cz = 0 # goal x,y,z self.gx = 0 self.gy = 0 self.gz = 0 # movement rate x,y,z self.mx = 0 self.my = 0 self.mz = 0 self.firstdraw = 1 self.done = 0 self.movements = 0 #here to end ip = self.sip dip = self.dip octets = ip.split(".") #print octets o1 = int(octets[0]) o2 = int(octets[1]) o3 = int(octets[2]) self.cx = self.ox = ( o1 - 128 ) / 12.8 self.cy = self.oy = ( o2 - 128 ) / 12.8 self.cz = self.oz = ( o3 - 128 ) / 12.8 doctets = dip.split(".") do1 = int(doctets[0]) do2 = int(doctets[1]) do3 = int(doctets[2]) self.gx = ( do1 - 128 ) / 12.8 self.gy = ( do2 - 128 ) / 12.8 self.gz = ( do3 - 128 ) / 12.8 self.mx = (self.gx - self.cx) / 30 self.my = (self.gy - self.cy) / 30 self.mz = (self.gz - self.cz) / 30 def __del__(self): hashkey = string.join([repr(self.sip), repr(self.sp), repr(self.dip), repr(self.dp)], "-") global PacketCounter num_packets = PacketCounter[hashkey] num_packets = num_packets - 1 PacketCounter[hashkey] = num_packets def draw(self, lastx, lasty): if self.firstdraw: self.firstdraw = 0 if 0: ip = self.sip dip = self.dip octets = ip.split(".") #print octets o1 = int(octets[0]) o2 = int(octets[1]) o3 = int(octets[2]) self.cx = self.ox = ( o1 - 128 ) / 12.8 self.cy = self.oy = ( o2 - 128 ) / 12.8 self.cz = self.oz = ( o3 - 128 ) / 12.8 doctets = dip.split(".") do1 = int(doctets[0]) do2 = int(doctets[1]) do3 = int(doctets[2]) self.gx = ( do1 - 128 ) / 12.8 self.gy = ( do2 - 128 ) / 12.8 self.gz = ( do3 - 128 ) / 12.8 self.mx = (self.gx - self.cx) / 30 self.my = (self.gy - self.cy) / 30 self.mz = (self.gz - self.cz) / 30 #print "movement:", self.mx, self.my, self.mz #set_viewpoint() #glRotatef(lastx, 0.0, 1.0, 0.0) #glRotatef(lasty, 1.0, 0.0, 0.0) #glTranslatef(self.cx, self.cy, self.cz) #glColor3f(0,0,1) #glutSolidSphere(0.2, 8, 8) else: global FreezeScreen if FreezeScreen == 0: self.movements = self.movements + 1 self.cx = self.cx + self.mx self.cy = self.cy + self.my self.cz = self.cz + self.mz if (self.movements == 90): self.done = 1 if self.movements <= 30: set_viewpoint() glRotatef(lastx, 0.0, 1.0, 0.0) glRotatef(lasty, 1.0, 0.0, 0.0) glTranslatef(self.cx, self.cy, self.cz ) #glTranslatef(self.cx + sin(self.cx), self.cy + sin(self.cy), self.cz + sin(self.cz) ) #if self.sp == 80 or self.dp == 80: # glColor(0,1,1) # text = "http" #else: glColor(1,1,1) text = str(self.sp) + "->" + str(self.dp) #text = "?" #glutSolidSphere(0.2, 8, 8) glutSolidCube(0.1) #glRotatef(lasty, -1.0, 0.0, 0.0) #glRotatef(lastx, 0.0, -1.0, 0.0) #print_text(text,0.0025) # and draw the origin and goal spots set_viewpoint() glRotatef(lastx, 0.0, 1.0, 0.0) glRotatef(lasty, 1.0, 0.0, 0.0) glTranslatef(self.ox, self.oy, self.oz) glColor3f(1,0,0) glutSolidCube(0.35) glRotatef(lasty, -1.0, 0.0, 0.0) glRotatef(lastx, 0.0, -1.0, 0.0) color_white() print_text(self.sip, 0.0025) class Decoder(threading.Thread): def __init__(self, packetstream): self.done = 0 threading.Thread.__init__(self) datalink = packetstream.datalink() if pcapy.DLT_EN10MB == datalink: self.decoder = EthDecoder() elif pcapy.DLT_LINUX_SLL == datalink: self.decoder = LinuxSLLDecoder() else: raise Exception("Datalink type not supported: " % datalink) self.pcap = packetstream self.connections = {} def run(self): self.pcap.loop(0, self.packetHandler) def packetHandler(self, hdr, data): """Handles an incoming pcap packet. This method only knows how to recognize TCP/IP connections. Be sure that only TCP packets are passed onto this handler (or fix the code to ignore the others). Setting r"ip proto \tcp" as part of the pcap filter expression suffices, and there shouldn't be any problem combining that with other expressions. """ global FreezeScreen if FreezeScreen == 0: # Use the ImpactDecoder to turn the rawpacket into a hierarchy # of ImpactPacket instances. p = self.decoder.decode(data) ip = p.child() tcp = ip.child() # Build a distinctive key for this pair of peers. sip = ip.get_ip_src() sport = tcp.get_th_sport() dip = ip.get_ip_dst() dport = tcp.get_th_dport() #print "handling packets" hashkey = string.join([repr(sip), repr(sport), repr(dip), repr(dport)], "-") #print hashkey global PacketCounter try: num_packets = PacketCounter[hashkey] if num_packets > 15: #arbitrary constant # don't even bother drawing this one self.done = 1 return except: num_packets = 0 num_packets = num_packets + 1 PacketCounter[hashkey] = num_packets src = (sip,sport ) dst = (dip,dport ) size = ip.get_size() global RecentPackets global PACKET_FILTER record_packet = 0 # if PACKET_FILTER == 0: record_packet = 1 else: for each in PACKET_FILTER: if sport == each or dport == each: record_packet = 1 #record_packet = 1 if record_packet == 1: newpacket = Packet(sip,sport,dip,dport,size) RecentPackets.append(newpacket) self.done = 1 class ThreadedSniffer: def __init__(self): self.numThreads = 0 self.threadList = [] device = choose_sniffing_interface() p = pcapy.open_live( device, 4096, True, 10000) # At the moment the callback only accepts TCP/IP packets. p.setfilter(r'ip proto \tcp') self.packetstream = p #def checkInclude(self, URL): # return string.find(URL, self.include) == 0 def run(self): while self.threadList: self.checkThreads() else: self.runDecoder() def checkThreads(self): for ret in self.threadList[:] : if ret.done: self.threadList.remove(ret) self.numThreads = self.numThreads - 1 def runDecoder(self): ret = Decoder(self.packetstream) ret.start() self.threadList.append(ret) self.numThreads = self.numThreads + 1 class IP_THREESPACE: def __init__(self): # initial mouse position self.lastx = 121 self.lasty = 121 self.last_button_down = 0 # set the display mode and create the window glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH) glutCreateWindow("ip-threespace.py - http://www.elifulkerson.com") # setup the callbacks glutDisplayFunc(self.on_display) glutMotionFunc(self.on_motion) glutReshapeFunc(self.on_reshape) #glutMouseFunc(self.on_mouseclick) glutIdleFunc(self.on_idle) glutKeyboardFunc(self.on_keypress) #glutTimerFunc(100, self.decode_packets,1) # glClearDepth(1.0) glEnable(GL_DEPTH_TEST) glClearColor(0.0, 0.0, 0.0, 0.0) glShadeModel(GL_SMOOTH) glMatrixMode(GL_MODELVIEW) # initialize lighting */ glLightfv(GL_LIGHT0, GL_POSITION, (40.0, 40, 100.0, 0.0)) glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.99, 0.99, 0.99, 1.0)) glEnable(GL_LIGHT0) glLightfv(GL_LIGHT1, GL_POSITION, (-40.0, 40, 100.0, 0.0)) glLightfv(GL_LIGHT1, GL_DIFFUSE, (0.99, 0.99, 0.99, 1.0)) glEnable(GL_LIGHT1) glEnable(GL_LIGHTING) glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE) glEnable(GL_COLOR_MATERIAL) def on_motion(self, x, y): self.lastx = x self.lasty = y # redisplay glutPostRedisplay() def on_idle(self): glutPostRedisplay() def on_keypress(self, key, x, y): global FreezeScreen if FreezeScreen == 1: FreezeScreen = 0 else: FreezeScreen = 1 def on_reshape(self, width, height): # setup the viewport glViewport(0, 0, width, height) # setup the projection matrix glMatrixMode(GL_PROJECTION) glLoadIdentity() # calculate left/right and top/bottom clipping planes based the smallest square viewport a = 9.0/min(width, height) clipping_planes = (a*width, a*height) # setup the projection glFrustum(-clipping_planes[0], clipping_planes[0], -clipping_planes[1], clipping_planes[1], 50.0, 150.0) def draw_area(self,s1,e1,s2,e2,s3,e3,label): # don't need step step = 0 # find the middle of the thing sideA = (e1 - s1) / 25.6 sideB = (e2 - s2) / 25.6 sideC = (e3 - s3) / 25.6 set_viewpoint() at = ( s1 - 128 ) / 12.8 bt = ( s2 - 128 ) / 12.8 ct = ( s3 - 128 ) / 12.8 glRotatef(self.lastx, 0.0, 1.0, 0.0) glRotatef(self.lasty, 1.0, 0.0, 0.0) glTranslatef(at + (sideA),bt + (sideB), ct + (sideC)) drawCube(sideA, sideB, sideC) glRotatef(self.lasty, -1.0, 0.0, 0.0) glRotatef(self.lastx, 0.0, -1.0, 0.0) color_white() print_text(label, 0.0025) def set_filter(self, value): global PACKET_FILTER if value == 1: PACKET_FILTER = 0 if value == 2: PACKET_FILTER = [25, 110] if value == 3: PACKET_FILTER = [80, 443] if value == 4: PACKET_FILTER = [22] if value == 1: print "Filter changed: now sniffing all packets." else: print "Filter changed, now sniffing for: ", PACKET_FILTER def case_menu(self, value): print value def on_display(self): # clear the buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Set up the model view matrix glMatrixMode(GL_MODELVIEW) set_viewpoint() glRotatef(self.lastx, 0.0, 1.0, 0.0) glRotatef(self.lasty, 1.0, 0.0, 0.0) glColor3f(0.8, 0.5, 0.1) # set the join styles for GLE gleSetJoinStyle(TUBE_NORM_EDGE | TUBE_JN_ANGLE | TUBE_JN_CAP) glColor3f(1, 1, 1) glutWireCube(20) set_viewpoint() glRotatef(self.lastx, 0.0, 1.0, 0.0) glRotatef(self.lasty, 1.0, 0.0, 0.0) global IP_NETBLOCK_LIST global RecentPackets global PACKET_FILTER NewRecentPackets = [] set_viewpoint() glTranslate(-15,10,10) #os.system("cls") #print(str(len(RecentPackets)) + " packets drawn", 0.005) for packet in RecentPackets: packet.draw(self.lastx, self.lasty) #else: # for each in PACKET_FILTER: # if packet.sp == each or packet.dp == each: # packet.draw(self.lastx, self.lasty) #netrange = "" #d_orgname = "" #try: # tmp = IP_NETBLOCK_LIST[packet.dip] # d_orgname = tmp["OrgName:"] # netrange = join(tmp["NetRange:"],"") # self.draw_area(tmp["calc_start_1"], tmp["calc_end_1"], tmp["calc_start_2"], tmp["calc_end_2"], tmp["calc_start_3"], tmp["calc_end_3"], d_orgname ) #except: #no lookup yet # pass if packet.done == 0: NewRecentPackets.append(packet) RecentPackets = NewRecentPackets #print "There are ", len(IP_NETBLOCK_LIST), " looked up IPs" # swap the buffer glutSwapBuffers() if __name__ == '__main__': global RecentPackets RecentPackets = [] global PacketSniffer PacketSniffer = ThreadedSniffer() PacketSniffer.run() global PacketCounter PacketCounter = {} #global NetBlockResolver #NetBlockResolver = ThreadedNetblockResolver() #NetBlockResolver.run() global IP_NETBLOCK_LIST IP_NETBLOCK_LIST = {} global FreezeScreen FreezeScreen = 0 global PACKET_FILTER PACKET_FILTER = 0 # initialize GLUT glutInit(sys.argv) # create the demo window map = IP_THREESPACE() map.set_filter(1) submenu2 = glutCreateMenu( map.set_filter) glutAddMenuEntry("All TCP", 1) glutAddMenuEntry("Email Traffic", 2) glutAddMenuEntry("Web Traffic", 3) glutAddMenuEntry("Ssh Traffic", 4) glutCreateMenu(map.case_menu) glutAddSubMenu("Filters", submenu2) glutAttachMenu(GLUT_RIGHT_BUTTON) # enter the event loop glutMainLoop ()