Portscanner mit pcap

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
elactic
User
Beiträge: 18
Registriert: Mittwoch 16. Dezember 2009, 14:49

Hallo alle zusammen,
ich habe ne Weile nicht in Python geschrieben, verzeiht mir also bitte etwas seltsame Codezeilen.

Es geht um Folgendes:
Ich soll (Uni) einen Portscanner bauen.
Der soll Connect Scanning und SYN Scanning beherrschen.
Connect Scanning ist kein Problem, habe ich mit dem Standard socket Modul gelöst.
Was leider nur bedingt klappt ist der SYN Scan.
Wir wurde gebeten nur die Libraries pylibpcap und pylibnet zu nutzen.

Ich möchte also mit libnet ein Paket mit gesetztem TCP SYN Flag bauen und dann mit pcap nach Paketen mit SYN-ACK oder ACK-RST Flags lauschen(da kann ich mit dem promiscuous Modus natürlich gleich filtern nach Paketen, die an mich gerichtet sind).

So das Bauen der Pakete ist kein Problem.
Das Capturen schon eher.
Eigentlich läuft es, es wird nur leider irgendwann nichts mehr aufgenommen.
Wenn ich bspw. die Ports 1 - 1000 scanne, dann geben der Connect Scan und der SYN Scan das gleiche Ergebnis, wenn ich aber 1 - 10.000 scanne gehen einige verloren (habe Differenz aus abgeschickten und erhaltenen gebildet).
Ich füge mal den gesammten Code an, interessant für euch dürfte nur die Klasse PortScanner_SYN sein.

Ganz unten gibt es zwei Aufrufe, einmal als Connect- und einmal als SYN Scan (jeweils des Localhosts)

Code: Alles auswählen

import threading
from random import shuffle
from time import sleep
import Queue
from sys import argv
import socket
import libnet
from libnet.constants import * 
import pcap

class PortScanner_Connect:
    def __init__(self, host, port_start, port_end, maxThreads):
        
        print('Performing Connect Scan\nHost: {0}\nPorts: {1} - {2}\nScanning...'.format(host, port_start, port_end))
        
        self.openPorts=[]
        self.tasks=Queue.Queue()
        
        portList = range(port_start, port_end+1)
        shuffle(portList)
        for port in portList:
            self.tasks.put((host, port))

        for i in range(port_start, port_end+1):
            while threading.activeCount() > maxThreads: pass
            thread = threading.Thread(target=self.scan)
            thread.daemon = True
            thread.start()

        self.tasks.join()
        self.openPorts.sort()
        print("The following ports were open at {0}: {1}".format(host, self.openPorts))
        
    def scan(self):
        host, port = self.tasks.get()
        
        try:
            sock=socket.socket()
            sock.connect((host, port))
            sock.close()
            self.openPorts.append(port)
        except socket.error:
            pass            
        
        self.tasks.task_done()
    

class PortScanner_SYN:
    
    def __init__(self, host, port_start, port_end, maxThreads, device='wlan0'):

        print('Performing SYN Scan\nHost: {0}\nPorts: {1} - {2}\nScanning...'.format(host, port_start, port_end))
        self.tasks=Queue.Queue()
        ports = set(range(port_start, port_end+1))
        
        for port in ports:
            self.tasks.put((host, port))
        
        capture = self.buildCapture(device, host)
        
        for i in ports:
            while threading.activeCount() > maxThreads: pass
            thread = threading.Thread(target=self.sendPacket(device))
            thread.daemon = True
            thread.start()
                    
        self.tasks.join()        
        result = self.capturePacket(ports, capture)
        print("The following ports were open at {0}: {1}\n{2} ports were filtered".format(host, result['open'], len(result['filtered' ])))
      
    
    def buildCapture(self, device, host):
        capture = pcap.pcapObject()
        capture.open_live(device, 48, 0, 100)
        capture.setfilter('(tcp[13] == 0x14 || tcp[13] == 0x12) && src host {0}'.format(host), 0, 0)        
        return capture
    
    def sendPacket(self, device):
        host, port = self.tasks.get()
        packet = libnet.context(RAW4,device)                
        packet.build_tcp(dp=port,control=TH_SYN)        
        packet.autobuild_ipv4(len=IPV4_H + TCP_H, prot=IPPROTO_TCP, dst=packet.name2addr4(host))       
        packet.write()  
        self.tasks.task_done()


    def capturePacket(self, ports, capture):
        
        results = {'open': [], 'closed': [], 'filtered': []}

        for i in ports:
            packet = capture.next()
            if packet is None:    
                break
            data = packet[1]
            port = ord(data[34]) * 256 + ord(data[35])

            if ord(data[47]) == 0x12:
                results['open'].append(port)

            elif ord(data[47]) == 0x14:
                results['closed'].append(port)

        responded = results['open'] + results['closed']
        results['filtered'] = list(ports.difference(responded))
        return results
 
maxThreads=100
PortScanner_Connect('127.0.0.1', 1, 10000, maxThreads)
PortScanner_SYN('127.0.0.1', 1, 10000, maxThreads, 'lo')

Ich vermute, dass das pcapObject einfach einen Puffer hat, der voll ist. Könnte es daran liegen?
Oder sind die Antworten auf einige Pakete vllt einfach noch nicht da?
In dem Fall hatte ich daran gedacht, jeden Thread ein paar sekunden warten zu lassen, bevor er meldet, dass er fertig ist.

Code: Alles auswählen

packet.write()
time.sleep(2)  
self.tasks.task_done()
Dummerweise scheinen bei diesem Design, was ich dreist bei doc.Python abgeschrieben habe, alle Threads unterbrochen zu werden.
Ich hatte noch mehr Ideen, eine dümmer als die andere ;)

Also ich muss gestehe ich habe keine Ahnung, habe aber bisher auch kaum mit Threads und noch nie mit pcap oder libnet gearbeitet.
Wo wir gerade dabei sind: Habt ihr eine Idee, wo es eine Dokumentation zu pcap gibt? Bei dem was ich mir da aus fremden Codes zusammengeschrieben habe, drehen sich einem die Fußnägel hoch... =(

Habt ihr Vorschläge, oder könnt ihr mir sagen, wo der Fehler liegt?

LG
elactic
Zuletzt geändert von Anonymous am Dienstag 22. November 2011, 19:08, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@elactic: Die ``while threading.activeCount() > maxThreads: pass``-Schleife ist ziemlich unschön. Das könnte man mit einer `threading.Semaphore` ohne „busy waiting“ formulieren. Und ich verstehe auch den Sinn von `self.tasks` nicht, wenn Du in der Schleife das Argument auch dem `Thread()` mitgeben könntest, statt den Umweg über eine `Queue` zu gehen. Eine `Queue` verwendet man eher um mehrere Threads die mehr als einen Auftrag abarbeiten mit Aufträgen zu versorgen, so dass jeder laufende Thread sich sobald er fertig ist, Daten für den nächsten Job aus der Schlange holen kann. Wenn man aber sowieso pro Job einen Thread startet, macht das wenig Sinn.
elactic
User
Beiträge: 18
Registriert: Mittwoch 16. Dezember 2009, 14:49

Ok das mit den Threads könnte echt schöner sein, wollte nur sichergehen, dass alles fertig ist, bevor ich weitermache. Ich überarbeite das.
Aber hat jemand eine Idee zum PCAP?
Warum werden keine Pakete mehr angenommen und wie kann ich das beheben?
LG
elactic
fsck
User
Beiträge: 5
Registriert: Dienstag 4. Januar 2011, 22:13

Eigentlich benutzt du im SYN-Scan keine Threads.

Code: Alles auswählen

        for i in ports:
            while threading.activeCount() > maxThreads: pass
            thread = threading.Thread(target=self.sendPacket(device))
            thread.daemon = True
            thread.start()
Du übergibst als target den Rückgabewert von sendPacket.
elactic hat geschrieben: Ich vermute, dass das pcapObject einfach einen Puffer hat, der voll ist. Könnte es daran liegen?
Oder sind die Antworten auf einige Pakete vllt einfach noch nicht da?
Ich habe pylibpcap hier auf einem Linux mal kurz getestet, es gibt einen Puffer mit einer bestimten Größe, d.h. du bekommst nur die letzten x Pakete mit.
Warum willst du das überhaupt mit Threads machen? Man könnte doch ein Paket senden, auf Antwort warten (mit Timeout) und dann weitermachen mit dem nächsten Port.
elactic
User
Beiträge: 18
Registriert: Mittwoch 16. Dezember 2009, 14:49

Also ich habe das mit den Threads jetzt umgebaut.
Habe in der Uni eigentlich gerade das Thema Critical Sections und co. aber war irgendwie zu faul es umzusetzen, tut mir Leid ;)
Es werden jetzt nurnoch so viele Threads erzeugt, wie ich mit maxThreads erlaube. Die greifen dann einfach so lange auf die Queue mit (host, port) Paaren zu, bis die Queue leer ist.
Der Zugriff auf die Queue und die Liste der offenen Ports wird jetzt mit einer Semaphore abgesichert. Brauche ich das bei der Queue eigentlich? Naja schaden kanns nicht =)
Sieht jetzt so aus:

Code: Alles auswählen

import threading
from random import shuffle
from time import sleep
import Queue
from sys import argv
import socket
import libnet
from libnet.constants import * 
import pcap

  


class PortScanner_Connect:
    """
    This class represents a portscanner performing a connect scan.
    Arguments are the host, the first and the last port to scan.
    """
    def __init__(self, host, port_start, port_end, maxThreads):
        """
        Initializes the scan. Prints a list of open ports found on the host.
        """
        
        print('Performing Connect Scan\nHost: {0}\nPorts: {1} - {2}\nScanning...'.format(host, port_start, port_end))
        
        self.openPorts=[]
        self.tasks=Queue.Queue()
        self.semaphore = threading.Semaphore()
        portList = range(port_start, port_end+1)
        shuffle(portList) #shuffeled so that it becomes a little harder to see a pattern
        
        for port in portList:
            self.tasks.put((host, port)) # tuples of host and port are out into a queue

        for i in range(maxThreads):
            thread = threading.Thread(target=self.scan) # the scan is done in another thread
            thread.daemon = True
            thread.start()
        self.tasks.join() # waits until all tasks are done
        self.openPorts.sort()
        print("The following ports were open at {0}: {1}".format(host, self.openPorts))
        
    def scan(self):
        
        try:
            while True:
                self.semaphore.acquire()
                host, port = self.tasks.get(False) # retrieve host and port from queue
                self.semaphore.release()
                try:
                    sock=socket.socket()
                    sock.connect((host, port))
                    sock.close()
                    self.semaphore.acquire()
                    self.openPorts.append(port)
                    self.semaphore.release()
                except socket.error:
                    pass      
                self.tasks.task_done()
   
        except Queue.Empty:   
            self.semaphore.release()

    

class PortScanner_SYN:
    
    def __init__(self, host, port_start, port_end, maxThreads, device='wlan0'):

        print('Performing SYN Scan\nHost: {0}\nPorts: {1} - {2}\nScanning...'.format(host, port_start, port_end))
        self.tasks=Queue.Queue()
        self.semaphore = threading.Semaphore()
        self.device=device
        ports = list(range(port_start, port_end+1))
        shuffle(ports)
        
        for port in ports:
            self.tasks.put((host, port))
        
        capture = self.buildCapture(device, host)
        
        for i in range(maxThreads):
            thread = threading.Thread(target=self.sendPacket)
            thread.daemon = True
            thread.start()
                    
        self.tasks.join()
        
        sleep(2)
        
        result = self.capturePacket(ports, capture)
        
        print("The following ports were open at {0}: {1}\n{2} ports were filtered".format(host, sorted(result['open']), len(result['filtered'])))
      
    
    def buildCapture(self, device, host):
        capture = pcap.pcapObject()
        capture.open_live(device, 48, 0, 100)
        capture.setfilter('(tcp[13] == 0x14 || tcp[13] == 0x12) && src host {0}'.format(host), 0, 0)        
        return capture
    
    def sendPacket(self):
        
        try:
            while True:
                self.semaphore.acquire()
                host, port = self.tasks.get(False)
                self.semaphore.release()
                packet = libnet.context(RAW4,self.device)                
                packet.build_tcp(dp=port,control=TH_SYN)        
                packet.autobuild_ipv4(len=IPV4_H + TCP_H, prot=IPPROTO_TCP, dst=packet.name2addr4(host))
                  
                packet.write()  
                self.tasks.task_done()

        except Queue.Empty:   
            self.semaphore.release()

    def capturePacket(self, ports, capture):
        
        results = {'open': [], 'closed': [], 'filtered': []}

        for i in ports:
            packet = capture.next()

            if packet is None:    
                break
            data = packet[1]

            port = ord(data[34]) * 256 + ord(data[35])

            if ord(data[47]) == 0x12:
                results['open'].append(port)

            elif ord(data[47]) == 0x14:
                results['closed'].append(port)

        responded = results['open'] + results['closed']
        results['filtered'] = list(set(ports).difference(responded))
        return results
 
maxThreads=50
PortScanner_Connect('127.0.0.1', 1, 65535, maxThreads)
PortScanner_SYN('127.0.0.1', 1, 1000, maxThreads, 'lo')  
fsck hat geschrieben:Eigentlich benutzt du im SYN-Scan keine Threads.

Code: Alles auswählen

        for i in ports:
            while threading.activeCount() > maxThreads: pass
            thread = threading.Thread(target=self.sendPacket(device))
            thread.daemon = True
            thread.start()
Du übergibst als target den Rückgabewert von sendPacket.
Mh also gemäß der Python Documentation kann ich als target ein Aufrufbares Objekt übergeben
"target is the callable object to be invoked by the run() method. Defaults to None, meaning nothing is called."
Habe es jetzt so umgeschrieben, dass es offensichtlich ist, dass ich die Funktion da noch nicht aufrufe, aber anhand der Geschwindigkeit bin ich ziemlich sicher, dass es parallel ablief.
fsck hat geschrieben: Ich habe pylibpcap hier auf einem Linux mal kurz getestet, es gibt einen Puffer mit einer bestimten Größe, d.h. du bekommst nur die letzten x Pakete mit.
Mh ich bekomme interessanterweise nur die ERSTEN x Pakete mit.
Also wenn ich von Port 1 bis 1000 scanne, finde ich als letzten offenen Port die 902.
Wenn ich bis 20.000 scanne finde ich als letzten Port auch die 902, ich weiß aber dass es noch einen im Bereich 17.000 gibt.
Die Zahl der verlorenen Pakete geht aber hoch (verloren = gesendet -(offen+geschlossen)).
Und die Zahl der verlorenen Pakete ist bei gleicher anzahl gescannter Ports immer gleich (1-10000 --> 1808 verlorene Pakete), deshalb denke ich nicht, dass es was mit der Zeit zu tun hat, sont würde die Zahl schwanken, oder?
fsck hat geschrieben: Warum willst du das überhaupt mit Threads machen? Man könnte doch ein Paket senden, auf Antwort warten (mit Timeout) und dann weitermachen mit dem nächsten Port.
Ja das wäre einfacher, aber es ist wirklich extrem langsam.
Ich habe das mal iterativ geschrieben und er hat für 10 Ports so lange gebraucht wie ich mit Threads 8000 Ports scanne. Es gibt 65535 verfügbare Ports^^

Komme echt nicht weiter =(
BlackJack

@elactic: Die `Queue` musst Du nicht absichern, die sollte „thread safe“ sein. Bei einer `Semaphore` ohne Argument, könntest Du eigentlich auch ein `Lock` nehmen. Das macht den Zweck vielleicht deutlicher. Im dem Zusammenhang solltest Du Dir auch einmal die ``with``-Anweisung anschauen.

Du zitierst richtig was in der Dokumentation zum `target`-Argument von `Thread` steht. Du hast es nicht umgeschrieben das es offensichtlich ist, dass die Funktion nicht vorher aufgerufen wird, denn vorher *wurde* sie dort aufgerufen und es lief *nicht* parallel. Falls es Dir so vorkam, als wenn das parallel lief, dann liegt das eher daran, dass es nicht viel bringt das parallel auszuführen. Python-Bytecode wird in CPython nicht parallel ausgeführt, also könnte das Programm an der Stelle nur von blockierenden E/A-Operationen profitieren. Die gibt es aber nicht. Es wird ja nicht auf eine Antwort gewartet wo in der Wartezeit andere Threads arbeiten könnten.

Die Schleife in `capturePacket()` kann man übrigens vereinfachen und ohne `i` und `range()` schreiben:

Code: Alles auswählen

        for i in ports:
            packet = capture.next()

            if packet is None:    
                break
        # 
        # ->
        # 
        for packet in iter(capture.next, None):
Du hast ein paar Methoden in der zweiten Klasse, die eigentlich gar keine Methoden sind. Das würde ich zumindest mit einem Dekorator mit `staticmethod()` kennzeichnen, dass das so gewollt ist.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

btw. simple portscanner with multithreading: http://code.activestate.com/recipes/286240/ (siehe auch http://www.python-forum.de/viewtopic.php?t=3262 )

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
elactic
User
Beiträge: 18
Registriert: Mittwoch 16. Dezember 2009, 14:49

jens hat geschrieben:btw. simple portscanner with multithreading: http://code.activestate.com/recipes/286240/ (siehe auch http://www.python-forum.de/viewtopic.php?t=3262 )
Heya danke, cooles Script. Leider geht es dabei ja um den Connect Scan, bei dem das OS versucht mir eine Verbindung aufzubauen.
Ich will ja nun einen SYN Scan durchführen, bei dem ich manuel ein SYN Paket sende und auch SYN-ACK oder RST-ACK lausche.
http://de.wikipedia.org/wiki/Portscanne ... 28.29_Scan

Das mit dem Connect Scan habe ich ja schon gelöst, steht im oberen Bereich des Posts.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Wäre es nicht vielleicht einfacher per subprocess nmap zu starten?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

jens hat geschrieben:Wäre es nicht vielleicht einfacher per subprocess nmap zu starten?
Da es sich um Übungsaufgaben aus der Uni handelt, ist das wahrscheinlich wenig zielführend ;-)
Das Leben ist wie ein Tennisball.
elactic
User
Beiträge: 18
Registriert: Mittwoch 16. Dezember 2009, 14:49

jens hat geschrieben:Wäre es nicht vielleicht einfacher per subprocess nmap zu starten?
Ja aber ich schreibe das für die Uni und habe gewisse Vorgaben, welche libs ich nutzen darf. Und das ist hier nur libnet und pcap
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ah, sorry. Hab ich nicht gelesen :oops:

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
fsck
User
Beiträge: 5
Registriert: Dienstag 4. Januar 2011, 22:13

elactic hat geschrieben:Ja das wäre einfacher, aber es ist wirklich extrem langsam.
Ich habe das mal iterativ geschrieben und er hat für 10 Ports so lange gebraucht wie ich mit Threads 8000 Ports scanne. Es gibt 65535 verfügbare Ports^^
Das musste ich jetzt mal ausprobieren :-)

Ich hab dein Skript auf iterativ umgeschrieben, der Code liegt hier: http://www.python-forum.de/pastebin.php?mode=view&s=246.

Die iterative Lösung ist etwas schneller, da hier nicht für jedes Paket eine extra DNS Anfrage gestellt wird. Außerdem fällt der Thread-Verwaltungskram weg. Trotzdem ist das noch grottenlahm, die meiste Zeit geht wohl in den libnet Funktionen drauf.

edit: Meine Lösung ist so eigentlich nicht korrekt.. Man müsste, bis eine Antwort kommt, eine gewisse Zeit warten, und das am besten asynchron zum senden.
Zuletzt geändert von fsck am Donnerstag 24. November 2011, 18:45, insgesamt 1-mal geändert.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hm. Der andere Server mag das vielleicht auch nicht, wenn plötzlich hunderte/tausende Ports getestet werden. Evtl. blockt da auch eine Firewall?!?

IMHO dauert es nun mal lange alle 64K Ports zu testen, egal wie man es implementiert, oder liege ich da falsch?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
elactic
User
Beiträge: 18
Registriert: Mittwoch 16. Dezember 2009, 14:49

Huhu,
will nichts aufwärmen, aber ich habe meine Probleme inzwischen gelöst, sollte IRGENDJEMAND das hier mal lesen...
Das Problem war, dass der Thread, der die Pakete gecaptured hat, den Netzwerkadapter belegt hat.
Deshalb habe ich den Capture-Thread immer, wenn gerade nichts im Puffer liegt, ganz kurz schlafen gelegt, damit die Sender-Threads die Chance haben, zu senden.
Habe den Code einmal komplett angehängt.
LG
elactic

Code: Alles auswählen

import threading
from random import shuffle
from time import sleep
import Queue
import sys
import socket
import libnet
from libnet.constants import * 
import pcap

  


class PortScanner_Connect:
    """
    This class represents a portscanner performing a connect scan.
    Arguments are the host, the first and the last port to scan.
    """
    def __init__(self, host, port_start, port_end, maxThreads):
        """
        Initializes the scan. Prints a all open ports found on the host in the range provided.
        """
 
        port_start = max([port_start, 0]) # ensures that the specified ports do not exceed the maximum range
        port_end = min([port_end, 65535])
        
        print('Performing Connect Scan\nHost: {0}\nPorts: {1} - {2}'.format(host, port_start, port_end))

        try:
            socket.gethostbyname(host) # this raises an exception if the host cannot be resolved
        except(socket.gaierror):
            print("Host does not exist!\n")
            return # stops the program if host cannot be resolved
        
        print('This might take some minutes\nFollowing ports are open:')
        
        lock = threading.Semaphore() # to ensure a 'clean' output
        tasks=Queue.Queue() # thread-safe container

        portList = range(port_start, port_end+1)
        shuffle(portList) #shuffled so that it becomes a little harder to see a pattern
        
        for port in portList:
            tasks.put((port)) # ports are stored in the queue

        for i in range(maxThreads): # only <maxThreads> threads work at once
            thread = threading.Thread(target=self.scan, args=(tasks, lock, host)) # the scan is done in another thread
            thread.start()
        tasks.join() # waits until all tasks are done (for each get() call in the thread there must be one task_done() call)
        
    def scan(self, tasks, lock, host):
        
        try:
            while True:
                port = tasks.get(False) # retrieve port from queue and do not block if the queue is empty
                try:
                    sock=socket.socket()
                    sock.settimeout(2) # elseway blocking sockets would make the program run forever
                    sock.connect((host, port)) # tries to connect, if this fails it throws a socket.error
                    sock.close() # closes port if connect was successful
                    lock.acquire() # locks so that only one thread can write on the terminal at the same time
                    print(port)
                    lock.release()
                except socket.error: # if the connect was not successful we skip the port
                    pass      
                tasks.task_done() # marks the task as processed
   
        except Queue.Empty: # when the queue is empty the thread stops working
            pass              
        

        

    
    
    
class PortScanner_SYN:
    """
    This class represents a portscanner performing a connect scan.
    Arguments are the host, the first and the last port to scan.
    Furthermore it needs the device to work on.
    """    
    def __init__(self, host, port_start, port_end, maxThreads, device):
        """
        Initializes the scan. Prints a all open ports found on the host in the range provided.
        """        

        port_start = max([port_start, 0]) # ensures that the specified ports do not exceed the maximum range
        port_end = min([port_end, 65535])

        print('Performing SYN Scan\nHost: {0}\nPorts: {1} - {2}'.format(host, port_start, port_end))

        try:
            socket.gethostbyname(host) # this raises an exception if the host cannot be resolved
        except(socket.gaierror):
            print("Host does not exist!\n")
            return # stops the program if host cannot be resolved
        
        print('This might take some minutes\nFollowing ports are open:')

        tasks=Queue.Queue() # thread-safe container
        ports = range(port_start, port_end+1)#shuffled so that it becomes a little harder to see a pattern
        shuffle(ports) # shuffled so that it becomes a little harder to see a pattern



        for port in ports:
            tasks.put(port) # ports are stored in the queue
        
        capture = threading.Thread(target=self.listener, args=(device, host,)) # sets up a capture object
        capture.setDaemon(True) # the program does not wait until the capturing thread is finished 
        capture.start() # starts capturing in another thread
        

        
        for i in range(maxThreads):
            thread = threading.Thread(target=self.sendPacket, args=(tasks, host,)) # only <maxThreads> threads work at once
            thread.start()
                    
        tasks.join() # waits until all tasks are done (for each get() call in the thread there must be one task_done() call)
        
        sleep(1) # wait another second to capture packets before killing the capturing thread
      
    
    def listener(self, device, host):
        capture = pcap.pcapObject() # initializes pcap object
        capture.open_live(device, 48, 0, 10) # the tcp flags are within the first 48 bytes of the received packet
        capture.setfilter('tcp[13] == 0x12 && src host {0}'.format(host), 0, 0) # only packets with set SYN-ACK flag received from the scanned host are taken into consideration

        
        while True: # this process ends when the parent thread (Portscanner_SYN) dies
            packet = capture.next()

            if packet is None: # if there is no packet in the buffer, the program waits a millisecond to give the sender threads a chance to write their packets 
                sleep(0.001)
                continue
            
            data = packet[1]
            port = ord(data[34]) * 256 + ord(data[35]) # port number
            print(port)


    def sendPacket(self, tasks, host):    
        packet = libnet.context(RAW4,device) # creates libnet context for the specified device in raw mode
        hostIP = packet.name2addr4(host) # performs a DNS lookup
        # creating the context and finding the host's IPv4 address is only performed once to save resources 
        
        try:
            while True:
                port = tasks.get(False) # retrieves the port from the queue
                packet.clear_packet() # clears the context from any tcp/ip data
                packet.build_tcp(dp=port,control=TH_SYN) # builds a tcp header with the specified port number and set SYN flag
                packet.autobuild_ipv4(len=IPV4_H + TCP_H, prot=IPPROTO_TCP, dst=hostIP) # builds IPv4 header with specified host IP
                  
                packet.write() # writes the the packet on the specified device
                tasks.task_done() # marks the task as processed
        except(Queue.Empty):
            pass    
       

def __usage(): # private method that explains the usage
    print("\nportscanner - Usage:")
    print("Call:\npython portscanner.py <method> <port_start> <port_end> [<device>]\n")
    print("\tmethod: 'connect' for Connect Scan, 'syn' for SYN Scan")
    print("\tport_start, port_end: Range of ports to be scanned")
    print("\tdevice: Only for SYN Scan. Specifies the network device\n")
    print("This program has to be called with root privileges!\n")


if __name__ == '__main__':
    
    if len(sys.argv)<5 or len(sys.argv)>6: __usage()
    
    else:
        
        maxThreads=50
        
        try:
            method = sys.argv[1]
            host = sys.argv[2]
            port_start = int(sys.argv[3])
            port_end = int(sys.argv[4])
            
            if method == 'connect':
                PortScanner_Connect(host, port_start, port_end, maxThreads)
                
            elif method == 'syn':
                device = sys.argv[5]
                PortScanner_SYN(host, port_start, port_end, maxThreads, device)
            
            else: 
                raise ValueError()
        except(ValueError):
            __usage()
Zuletzt geändert von Anonymous am Dienstag 6. Dezember 2011, 10:42, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@elactic: Noch mal allgemeine Anmerkungen zum Quelltext von mir:

Es gibt eine Menge Zeilen die länger als 80 Zeichen sind. Ich denke sehr häufig sind es lange Kommentare die ans Ende einer Zeile gesetzt wurden. Das ist je nach Umbruchverhalten der Anzeige schwer bis sehr schwer zu lesen. Und dabei sollte man nicht nur an den eigenen Editor denken, sondern auch an die Editoren anderer Leute, das Konsolenfenster, Textfelder in GUIs für Versionskontrollsoftware, E-Mail-Clients, Webseiten, und so weiter. Eben alles wo Quelltexte/Patches noch so angezeigt werden und gelesen werden müssen.

Einige von den Kommentaren sind auch überflüssig. Ein Kommentar sollte einen Mehrwert zum Code bieten. Einige bei Dir sagen nochmal das Offensichtliche was schon im Code steht. Was Code macht sollte man nur kommentieren wenn der Code zu komplex ist und man ihn nicht einfacher bekommt. Kommentare sind eher dazu da zu beschreiben warum der Code das tut was er tut.

Die `min()`/`max()`-Funktionen kann man auch mit mehreren Argumenten aufrufen. Also statt ``min([a, b])`` geht auch ``min(a, b)``. IMHO ist das Beschränken der Ports da nicht an der richtigen Stelle beziehungsweise generell nicht gut. In der `__init__()` würde ich eher auf die Grenzen prüfen und auch ob `port_start` kleiner oder gleich `port_end` ist, und falls nicht eine Ausnahme auslösen. Das wäre schliesslich eine fehlerhafte Eingabe vom Benutzer.

Den Code im ``if __name__ == '__main__':``-Zweig solltest Du noch in eine Funktion stecken. Sonst hast Du sowohl in Funktionen als auch auf Modulebene einige identische Namen definiert. So etwas kann zu subtilen Fehlern führen, wenn man dann doch aus versehen mal auf einen Namen auf Modulebene zugreift, den man in einer Methode für einen Lokalen hält.

``except`` und ``print`` sind keine Funktionen. ``print`` ist das erst in Python 3, da Du aber `random.shuffle()` auf das Ergebnis von `range()` anwendest, muss das Python 2-Quelltext sein:

Code: Alles auswählen

>>> import random
>>> random.shuffle(range(42))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.1/random.py", line 270, in shuffle
    x[i], x[j] = x[j], x[i]
TypeError: 'range' object does not support item assignment
Also weg mit den überflüssigen Klammern. Oder Du importierst `print()` als Funktion aus dem `__future__`-Modul.

Beide Portscanner sind überflüssigerweise Klassen. Du nutzt nirgends den Umstand dass das eine Klasse ist auch tatsächlich aus. Klassen sind kein Selbstszweck und in Python auch nicht das Mittel um einen Namensraum für einfache Funktionen zu schaffen. Dafür gibt es Module.

Statt einer `Semaphore` hätte man auch ein `Lock` nehmen können. Und die kann man mit der ``with``-Anweisung kombinieren. Das ist sicherer.

Beide Scanner enthalten ähnlichen Quelltext, den man heraus ziehen könnte.

Es gibt in Python keine ``private``-Methoden. Auf Modulebene schon gar nicht. Implementierungsdetails werden mit *einem* führenden Unterstrich gekennzeichnet.

Die Argumente könnte man mit `argparse` verarbeiten.
elactic
User
Beiträge: 18
Registriert: Mittwoch 16. Dezember 2009, 14:49

BlackJack hat geschrieben: Es gibt eine Menge Zeilen die länger als 80 Zeichen sind. Ich denke sehr häufig sind es lange Kommentare die ans Ende einer Zeile gesetzt wurden. Das ist je nach Umbruchverhalten der Anzeige schwer bis sehr schwer zu lesen. Und dabei sollte man nicht nur an den eigenen Editor denken, sondern auch an die Editoren anderer Leute, das Konsolenfenster, Textfelder in GUIs für Versionskontrollsoftware, E-Mail-Clients, Webseiten, und so weiter. Eben alles wo Quelltexte/Patches noch so angezeigt werden und gelesen werden müssen.
Wohl wahr.
Kannst du mir sagen, wie ich Anweisungen über mehrere Zeilen ersterecke?
Ich sehe hin und wieder mal sachen wie

Code: Alles auswählen

befehl( arg1= x,
		arg2= y, 
		arg3= z)
Aber wenn ich sowas selbst versuche...
BlackJack hat geschrieben: Einige von den Kommentaren sind auch überflüssig. Ein Kommentar sollte einen Mehrwert zum Code bieten. Einige bei Dir sagen nochmal das Offensichtliche was schon im Code steht. Was Code macht sollte man nur kommentieren wenn der Code zu komplex ist und man ihn nicht einfacher bekommt. Kommentare sind eher dazu da zu beschreiben warum der Code das tut was er tut.
Ja das war eine Arbeit für die Uni mit der klaren Anweisung, ALLES zu kommentieren.
Da schreib ich dann einen (unsinnigen) Kommentar mehr als einen zu wenig (hätte es für euch natürlich rausnehmen können).
BlackJack hat geschrieben: Die `min()`/`max()`-Funktionen kann man auch mit mehreren Argumenten aufrufen. Also statt ``min([a, b])`` geht auch ``min(a, b)``.
Ok da habe ich das Falsche aus der API abgeschrieben ;)
BlackJack hat geschrieben: IMHO ist das Beschränken der Ports da nicht an der richtigen Stelle beziehungsweise generell nicht gut. In der `__init__()` würde ich eher auf die Grenzen prüfen und auch ob `port_start` kleiner oder gleich `port_end` ist, und falls nicht eine Ausnahme auslösen. Das wäre schliesslich eine fehlerhafte Eingabe vom Benutzer.
Ganz idiotensicher ist es nicht, stimmt. Aber ich wollte alle möglichen Fehlerfälle (also zB auch die Frage, ob der gesuchte Port überhaupt existiert) so früh wie irgendwie möglich abfangen.
Aber ob ich es in Klassen hätte packen sollen...
BlackJack hat geschrieben: Den Code im ``if __name__ == '__main__':``-Zweig solltest Du noch in eine Funktion stecken. Sonst hast Du sowohl in Funktionen als auch auf Modulebene einige identische Namen definiert. So etwas kann zu subtilen Fehlern führen, wenn man dann doch aus versehen mal auf einen Namen auf Modulebene zugreift, den man in einer Methode für einen Lokalen hält.
Warte mal. Variablen auf Modulebene sind in Funktionen sichtbar?
BlackJack hat geschrieben: ``except`` und ``print`` sind keine Funktionen. ``print`` ist das erst in Python 3, da Du aber `random.shuffle()` auf das Ergebnis von `range()` anwendest, muss das Python 2-Quelltext sein:

Code: Alles auswählen

>>> import random
>>> random.shuffle(range(42))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.1/random.py", line 270, in shuffle
    x[i], x[j] = x[j], x[i]
TypeError: 'range' object does not support item assignment
Also weg mit den überflüssigen Klammern. Oder Du importierst `print()` als Funktion aus dem `__future__`-Modul.
Ahhh naja ich schreibe eigentlich lieber Python 3. Ich finde es 'schöner' print als Funktion aufzurufen. Bei dieser Übung bin ich leider auf das bekannte Problem von Python 3 gestoßen, nämlich dass viele Bibliotheken nicht existieren.
Bin deshalb auf Python 2.7 umgeschwänkt, habe mir mein print() aber beibehalten...
BlackJack hat geschrieben: Statt einer `Semaphore` hätte man auch ein `Lock` nehmen können. Und die kann man mit der ``with``-Anweisung kombinieren. Das ist sicherer.
Oh naja ich hatte irgendwo vorher einen Anwendungsfall, bei dem ich mehrere gleichzeitige Ausführungen zulassen wollte (weiß auch nicht mehr was das war).
Un da habe ich einfach weiterhin die Semaphore genommen. Semaphore(1) und Lock() sind doch eigentlich das gleiche oder? (natürlich nicht in der Implementierung, aber vom äußeren Verhalten)
Was meinst du mit with?
BlackJack hat geschrieben: Es gibt in Python keine ``private``-Methoden. Auf Modulebene schon gar nicht. Implementierungsdetails werden mit *einem* führenden Unterstrich gekennzeichnet. Druckansicht
Ok auf Modulebene war das tatsächlich dämlich.
Aber auf Klassenebene?
Ich dachte das ginge (auch wenn sich "The Guido" tierisch drüber aufregt).
Ich hatte immer im Kopf, dass ein Unterstrich dem entwickler anzeigt, dass er es nicht nutzen SOLL, und dass der doppelte Unterstrick die Methode bzw Variable tatsächlich private macht....
BlackJack hat geschrieben: Die Argumente könnte man mit `argparse` verarbeiten.
Sieht cool aus, danke =)
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

elactic hat geschrieben:Ich hatte immer im Kopf, dass ein Unterstrich dem entwickler anzeigt, dass er es nicht nutzen SOLL, und dass der doppelte Unterstrick die Methode bzw Variable tatsächlich private macht....
Nein, ein führender doppelter Unterstrich verwurstet den Namen des Bezeichners nur (name mangling). In PEP-8 und im Tutorial zum Thema Klassen steht etwas dazu. Es verhindert nicht, dass trotzdem ein Zugriff auf die Daten möglich ist.
BlackJack

@elactic: „Logische“ Zeilen enden erst wenn alle Klammern geschlossen sind. Dabei zählen alle Arten von Klammern, also '()', '[]', und '{}'. Solange also noch offene Klammern existieren, kann man Zeilenumbrüche einfügen. Dann gibt es noch '\' am Zeilenende um eine logische Zeile in der nächsten „physikalischen“ Zeile fort zu setzen. Das versuche ich zu vermeiden und setze da lieber einen Ausdruck in ansonsten überflüssige Klammern. Das ist aber eine Geschmacksfrage.

Also wenn das eine Vorgabe der Uni war, dann sind zu wenig Kommentare enthalten. ;-) Mal ernsthaft: Ich glaube auch dann hätte ich etwas ausführlicher über einem grösseren Konstrukt kommentiert. Für Uni-Hausaufgaben hatte ich öfter auch mal „literate programming“ betrieben. Da gibt es mit `pylit` ein ganz nettes Tool für Python und reStructuredText. Ein Beispielprojekt von Python-Quelltext mit unverschämt viel Kommentaren findet sich hier:

Python-Datei: http://bj.spline.de/simpledb/simpledb.py
umgewandelt in HTML: http://bj.spline.de/simpledb/simpledb.html
umgewandelt in PDF: http://bj.spline.de/simpledb/simpledb.pdf

Das HTML ist ohne Syntaxhervorhebung für den Python-Quelltext — ich weiss nicht ob das 2007 noch nicht ging, aber heute würde das natürlich mit `pygmentize` eingefärbt. Das PDF hat einfache Syntaxhervorhebung über Schriftstile. Da hatte ich glaube ich ein kleines Skript gehackt, dass die Python-Quelltexte im LaTeX-Quelltext in ``lstlisting``-Umgebungen gesteckt hat.

Natürlich sind Namen auf Modulebene in Funktionen sichtbar. Wie sollte man sonst Module, Klassen, und Funktionen in Funktionen verwenden!? Alles was man an einen Namen binden kann, ist ein Objekt. Und dem Namen ist der Typ des Objekts egal. Also können auch für alle Namen nur die gleichen Sichtbarkeitsregeln gelten, egal ob da nun per ``import`` ein Modul, per ``class`` eine Klasse, per ``def`` eine Funktion, oder per ``=`` irgend ein Objekt, dran gebunden wurde.

Du kannst ab Python 2.6 vor allen anderen Importen oben um Modul ``from __future__ import print_funktion`` schreiben, dann ist `print()` eine Funktion wie in Python 3.x.

Vom Effekt ist die Semaphore in diesem Fall das gleiche wie ein Lock, ja. Mit ``with`` meine ich die ``with``-Anweisung. `Semaphore` und `Lock` implementieren die dazu notwendigen „magischen“ Methoden:

Code: Alles auswählen

lock.aquire()
# do something
lock.release()

# Besser:

with lock:
    # do something

# Entspricht:

lock.aquire()
try:
    # do something
finally:
    lock.release()
Es wird also sichergestellt, dass in jedem Fall die Sperre auch wieder aufgehoben wird. Und es ist sogar kürzer als die unsicherere, erste Variante.
Antworten