Nmap Development mailing list archives

Zenmap idea for Summer of Code 2008


From: Vladimir Mitrovic <snipe714 () gmail com>
Date: Tue, 11 Mar 2008 12:27:20 +0100

Hello everyone,

I have an idea for this year's Summer of Code, so I figured I should share it early with you guys. I'm hoping for a chance to work on Zenmap this summer.

I was doing some thinking/research about graphical network topology mapping (or a lack thereof) and how cool it would be if Zenmap had that functionality, so I came up with a simple algorithm and implemented it in Python. I've attached the source code (disclaimer: it's a quick hack), so I'll continue with a brief explanation of the basic ideas behind it.

First, a user supplies an IP address (or an IP range) which is our "destination", and the prefix length (PLEN), which is used for standard CIDR-style subnet scanning (performed later). All IP addresses are added to the "to be processed" (TBP) list.

For every address ADDR in the TBP list, do the following:
* Run a traceroute to ADDR, and add any newly-discovered intermediate hosts to the TBP list. Update the topology graph using traceroute's data to add new nodes and construct edges between nodes. * Run a nmap scan on ADDR/PLEN (standard CIDR-style designation) and add any discovered hosts to the TBP list.

When there are no addresses left in the TBP list, the construction of the topology graph is "finished". I say "finished" here because, of course, a user can only see the portion of the net she scanned. What I thought would be cool (but didn't implement it) is that a user should be able to manually expand the constructed topology once the algorithm finishes its run (that's the TODO on stinkfist.py:85). For example, she knows that there's a live host (or hosts) on the local LAN that wasn't detected because PLEN was too big.

There are also some other ideas that can be implemented, for example if a node has more than two edges connected to it, then it's probably a router. Also I had some thoughts about the starting node - maybe it doesn't have to be the localhost, so we can simulate being on some other machine and starting the scan from there. The possibilities are endless... ;)

As for the code, I used (py)graphviz to represent the topology graph and plot it to an SVG image. You can use "stinkfist.py dst TARGET PLEN" (note the "dst" there) or "stinkfist.py dst TARGET" (PLEN = 29, default) to generate the topology file for graphviz (in the form TARGET_PLEN.dot). You can then use "draw.py FILENAME" to convert the .dot file into an .svg image (Firefox can open it). Also attached is a sample scan of my ISP's subnet with PLEN = 29, converted to a GIF image. I've blurred out the first two octets of IP addresses for privacy reasons.

So guys, any input is welcome, and please let me know if I'm reinventing the wheel here. I'm looking forward to discussing this with you.

Cheers,
Vladimir

p.s. I apologize if the name "stinkfist" sounds bad, it's just a working title.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os
import re
import pygraphviz

default_plen = 29       # default
topology = pygraphviz.AGraph()
startaddr = "localhost" # TODO get a real local IP (ifconfig, whatever)

def traceroute(dest):
        addrlist = []
        tr_out = os.popen("traceroute -I -n " + dest + " 2> /dev/null", "r")
        
        for line in tr_out:
                # find the first IP address in the current line
                addr = re.findall(r'[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+', line)
                if addr != []: addrlist.append(addr[0])
        
        return addrlist

def scan_subnet(subnet):
        addrlist = []
        nmap_out = os.popen("nmap -sP " + subnet + " 2> /dev/null", "r")
        
        for line in nmap_out:
                addr = re.findall(r'[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+', line)
                if addr != []: addrlist.append(addr[0])
        
        return addrlist

def destination_based(dest, plen):
        ip_list = []            # list of not-yet-processed nodes
        result_list = []        # list of processed nodes
        
        ### 1. traceroute to the destination address
        print "initial traceroute run:\n  " + startaddr,
        for addr in traceroute(dest):
                print "-> " + addr,
                ip_list.append(addr)
        print
        
        # add these nodes to the topology graph
        if len(ip_list) > 0: topology.add_edge(startaddr, ip_list[0])   # cheat
        for i in range(0, len(ip_list)-1):
                topology.add_edge(ip_list[i], ip_list[i+1])
        
        ### 2.
        for addr in ip_list:
                result_list.append(addr)
                
                ### 2.1 traceroute to the current node, add any new nodes to ip_list
                print "processing " + addr
                lastaddr = startaddr
                for newaddr in traceroute(addr):
                        if newaddr not in result_list and newaddr not in ip_list:
                                ip_list.append(newaddr)
                        
                        # add the new node to the topology graph
                        topology.add_edge(lastaddr, newaddr)
                        lastaddr = newaddr
                
                ### 2.2 scan this host's subnet, using a given host width
                for newaddr in scan_subnet(addr + "/" + str(plen)):
                        if newaddr not in result_list and newaddr not in ip_list:
                                ip_list.append(newaddr)
                
                ### 2.3
                
                ### 2.4
                #ip_list.remove(addr)
        
        # save the topology into a .dot file
        tfile = dest + "_" + str(plen) + ".dot"
        topology.write(tfile)
        print "written topology into " + tfile

def incremental():
        # TODO implement
        pass

def single_node():
        # TODO implement
        pass


if __name__ == "__main__":
        if sys.argv[1] == "dst":
                if len(sys.argv) == 4:
                        destination_based(sys.argv[2], sys.argv[3])
                else:
                        destination_based(sys.argv[2], default_plen)
        elif sys.argv[1] == "inc":
                incremental()
        elif sys.argv[1] == "scn":
                scan_subnet(sys.argv[2])
        elif sys.argv[1] == "sgl":
                single_node()
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pygraphviz import *
import re
import sys

format = "svg"
layout = "dot"
#layout = "circo"

A = AGraph()
A.read(sys.argv[1])
A.layout(layout)
A.graph_attr["label"]=sys.argv[1]
A.node_attr["shape"] = "circle"

# put the format extension instead of the original extension and draw
A.draw(re.sub(r'\.\w+$', '', sys.argv[1]) + "." + format)


_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://SecLists.Org

Current thread: