Sunday 20 September 2020

Turning the Ping Scanner into a Network Scanner

by Vaggelis Atlasis

Previously I added some lines of code so that the ping scanner could utilise a DNS server and reverse look-up the hostnames of the responsive IP address. Up until that point, the scanner only utilised the ICMP protocol to send packets. As a new addition to the program, a TCP packet was sent to the selected addresses in the subnet along with the ICMP one, initiating a communication process between the two computers known as handshaking.

TCP 3-Way Handshaking

Handshaking is the process that occurs before two computers that use the TCP protocol start communicating. It involves Computer A, sending an SYN or S flag (TCP uses flag for the same purposes ICMP uses types) to Computer B. This lets B know that A wants to communicate with B. In the case that B also wants to communicate with A, he sends a packet with two flags; an ACK or A flag - used to let A know that B has acknowledged the message - as well as an SYN flag, communicating to computer A, returning the favour (this is an ACK/SYN packet) Finally, computer A sends an ACK packet back to B and communication between the two computers begins. 

Note that handshaking only takes place in TCP and not in UDP as UDP prioritises fast delivery over making sure all packets arrive.

A simple diagram showing how handshaking works in TCP



Finding the Most Common Scanned Ports 

We begin by importing TCP  from the scapy. all module. In order to make the process a little more specific, I looked for the most popular ports when it came to network scanning. I found, what I believed was, an appropriate list containing 20 ports (for a list of all TCP and UDP ports take a look at this Wikipedia page) and created a dictionary with the port number as the key and the protocol it corresponded to as the value. 

A dictionary named "tcp_common_ports" containing the 20 TCP ports



Sending the TCP Packet

Firstly, we define the function send_tcp with address as its parameter where n IP address will be inputted. Next, a list is initialised using the dictionary keys (which are the port numbers). 

For every port, p, in the list, a packet is sent to the designated IP address fro ma random source port to the destination port p. As we want to initiate the handshaking process, the flag in the packet is "S". We use sr1 to send the packet made above and wait for a response. The waiting time per packet is 2 seconds as indicated by the timeout in the variable. We name the whole process response. If we get an answer from the host, the response is true. 

If it is true, the program looks at whether the response contains a TCP layer. If that is the case, the program then looks for a TCP layer with the "SA"  - SYN and ACK - flag, indicating that computer B has acknowledged the packet we sent and has accepted the handshake. We then output a message that this certain IP address is listening on port p (number), which corresponds to a certain protocol in the tcp_common_ports dictionary. 

In the case, that there is no packet with the "SA" flag, we look for an "R" flag instead which would indicate the host is rejecting the connection. Just like above, we display am message wit the address of the host and the port the packet was sent to (the number and the corresponding protocol); only this time we let the user know that he host is rejecting connection on that port. 

Moreover, we often found that the host responded with an ICMP layer of type 3 and code 13 (destination unreachable, communication prohibited). Therefore, if the response did not contain a TCP layer, we checked for a response containing the layer mentioned above. If that was the case, a message stating that the host of that IP address prohibited communication on port p

Finally, if none of the cases mentioned above was true, the program prints a summary of the response so that it can be added to the possible list of replies. 
The function for sending TCP packets

The function is then implemented in the send_ping function that already existed under the line where an ICMP type 0 response (Echo-reply) is received. Therefore, the function is only called if the host replies to an ICMP packet Echo-request packet with an Echo-reply one.

Current Version of the Program 

As the program has now become too big to fit into one picture, I will leave a copy-pasted version of it. 

from scapy.all import IP, ICMP, TCP, sr1, UDP, DNS, RandShort, DNSQR
from ipaddress import IPv4Network, ip_address
from multiprocessing import Pool
from argparse import ArgumentParser

dns_server=None

tcp_common_ports = {
    21: "ftp",
    22: "ssh",
    23: "telnet",
    25: "smtp",
    53: "domain name system",
    80: "http",
    110: "pop3",
    111: "rpcbind",
    135: "msrpc",
    139: "netbios-ssn",
    143: "imap",
    443: "https",
    445: "microsoft-ds",
    993: "imaps",
    995: "pop3s",
    1723: "pptp",
    3306: "mysql",
    3389: "ms-wbt-server",
    5900: "vnc",
    8080: "http-proxy" } 

#the following function will to resolve an IPv4 address to its host name
def resolve_IP(ip_address_to_resolve):
    global dns_server 
    reverse_ip_address = ip_address(ip_address_to_resolve).reverse_pointer
    #print("The reverse pointer of" , ip_address_to_resolve , "is" , reverse_ip_address)
    packet = IP(dst = dns_server)/UDP(dport = 53, sport = RandShort())/DNS(rd = 1, qd = DNSQR(qname = reverse_ip_address, qtype = "PTR"))
    response = sr1(packet, verbose = 0, timeout = 2)
    host_name = "not resolved"
    if response:
        if int(response.getlayer(UDP).sport) == 53:
            try:
                #print("The host name of" , ip_address_to_resolve , "is" , response.an.rdata.decode('utf-8'))
                host_name = response.an.rdata.decode('utf-8')
            except Exception as e:
                pass
    return host_name

        
def send_ping(address):
    packet = IP(dst = str(address))/ICMP(type = 8, code = 0)
    response = sr1(packet, verbose = 0, timeout = 2)
    if response:
        if int(response.getlayer(ICMP).type) == 0:
            host_name = resolve_IP(address)
            print("Host" , address , "is reachable. Its host name is" , host_name)
            send_tcp(address)
        elif int(response.getlayer(ICMP).type) == 3:
            print("Destination" , address , "is unreachable")

def send_tcp(address):
    ports = list(tcp_common_ports.keys()) ##returns a list of all keys (aka port numbers) of tcp_common_ports dictionary
    for p in ports:
        packet = IP(dst = str(address))/TCP(sport = RandShort(),dport = p, flags = "S")
        response = sr1(packet, verbose = 0, timeout = 2)
        if response:
            if response.haslayer(TCP):
                if str(response.getlayer(TCP).flags) == "SA":
                    print("Host ", address , "is listening in port",p,tcp_common_ports[p])
                elif str(response.getlayer(TCP.flags)) == "R":
                    print("Host ", address , "is rejecting conenction on port",p,tcp_common_ports[p])
            else:
                if int(response.getlayer(ICMP).type) == 3 and int(response.getlayer(ICMP).code) == 13:
                    print("Host " , address, ": communication prohibited on port",p,tcp_common_ports[p])
                else:
                    print(response.summary())
        

def main():
    parser = ArgumentParser()
    parser.add_argument("-p", "--processes" , help = "the number of processes to run in parallel (must be integer)"  , type = int, default = 10)
    parser.add_argument("-dns", "--dns_server" , help = "the DNS server to be used for our querries"  , type = str, default = "8.8.8.8")
    values = parser.parse_args()
   
    global dns_server 
    dns_server = values.dns_server

    network = input("Please enter the IPv4 network you'd like to scan (e.g. 192.168.1.0/24) or just an IPv4 address: ")
    valid_target = False
    while valid_target == False:
        try:
            targets = IPv4Network(network)
            valid_target = True
        except Exception as e:
            print(e)
            network = input("Please enter a valid IPv4 subnet: ")
            
    with Pool(values.processes) as process:
        process.map(send_ping, targets)


if __name__ == "__main__":
    main()




Wind turbine experiment


by Filippos and Vaggelis Atlasis 

We conducted an experiment to find out which wind turbine design generates the most energy. We used the EV3 energy pack to build the turbine and a fan to create "wind". Attached below you will find our full report created as a pdf as well as some pictures and videos of our experiment. The report contains everything from an introduction and method, to our analysis and a conclusion.