diff --git a/RawTCP.py b/RawTCP.py new file mode 100644 index 0000000..b7aadd7 --- /dev/null +++ b/RawTCP.py @@ -0,0 +1,98 @@ +from struct import pack, unpack +import socket + + +class TCPHeader(): + # TCP header class. Thanks to Silver Moon for the flags calculation and packing order + # This was designed to be re-used. You might want to randomize the seq number + # get_struct performs packing based on if you have a valid checksum or not + def __init__(self, src_port=47123, dst_port=80, seqnum=1000, acknum=0, data_offset=80, fin=0, syn=1, rst=0, psh=0, ack=0, urg=0, window=5840, check=0, urg_ptr=0): + # !=network(big-endian), H=short(2), L=long(4),B=char(1) + self.order = "!HHLLBBHHH" + self.src_port = src_port + self.dst_port = dst_port + self.seqnum = seqnum + self.acknum = acknum + # size of tcp header; size is specified by 4-byte words; This is 80 + # decimal, which is 0x50, which is 20bytes (5words*4bytes). + self.data_offset = data_offset + self.fin = fin + self.syn = syn + self.rst = rst + self.psh = psh + self.ack = ack + self.urg = urg + self.window = socket.htons(window) + self.check = check + self.urg_ptr = urg_ptr + + def flags(self): + return self.fin + (self.syn << 1) + (self.rst << 2) + (self.psh << 3) + (self.ack << 4) + (self.urg << 5) + + def get_struct(self, check=False, checksummed=False): + if check is not False: + self.check = check + if checksummed: + return pack('!HHLLBBH', self.src_port, self.dst_port, self.seqnum, self.acknum, self.data_offset, self.flags(), self.window) + pack('H', self.check) + pack('!H', self.urg_ptr) + else: + return pack(self.order, self.src_port, self.dst_port, self.seqnum, self.acknum, self.data_offset, self.flags(), self.window, self.check, self.urg_ptr) + + +def checksum(msg): + # Shoutout to Silver Moon @ binarytides for this checksum algo. + sum = 0 + for i in range(0, len(msg), 2): + w = msg[i] + (msg[i + 1] << 8) + sum = sum + w + + sum = (sum >> 16) + (sum & 0xffff) + sum = sum + (sum >> 16) + sum = ~sum & 0xffff + return sum + + +def tcp_checksum(source_ip, dest_ip, tcp_header, user_data=b''): + # Calculates the correct checksum for the tcp header + tcp_length = len(tcp_header) + len(user_data) + # This is an IP header w/ TCP as protocol. + ip_header = pack('!4s4sBBH', socket.inet_aton(source_ip), socket.inet_aton( + dest_ip), 0, socket.IPPROTO_TCP, tcp_length) + # Assemble the packet (IP Header + TCP Header + data, and then send it to + # checksum function) + packet = ip_header + tcp_header + user_data + return checksum(packet) + + +def handle_packet(raw_packet): + # Now we need to unpack the packet. It will be an IP/TCP packet + # We are looking for SYN-ACKs from our SYN scan + # Fields to check: IP - src addr; TCP - src port, flags + # We want to pull out and compare only these three + # Heres the math for unpacking: B=1, H=2, L=4, 4s=4 (those are bytes) + packet = raw_packet[0] + # This is the IP header, not including any self.options OR THE DST ADDR. + # Normal length is 20!! Im parsing as little as possible + ip_header = unpack('!BBHHHBBH4s', packet[0:16]) + # If there are any self.options, the length of the IP header will be >20. We + # dont care about self.options + ip_header_length = (ip_header[0] & 0xf) * 4 + # This is the source address (position 8, or the first "4s" in our + # unpack) + src_addr = socket.inet_ntoa(ip_header[8]) + + # We had to get the proper IP Header length to find the TCP header + # offset. + tcp_header_raw = packet[ip_header_length:ip_header_length + 14] + # TCP header structure is pretty straight-forward. We want PORTS and + # FLAGS, so we partial unpack it + tcp_header = unpack('!HHLLBB', tcp_header_raw) + + src_port = tcp_header[0] # self-explanatory + dst_port = tcp_header[1] # self-explanatory FIXME: notused + # We only care about syn-ack, which will be 18 (0x12) + flag = tcp_header[5] + + if flag == 18: + return (src_addr, src_port) + else: + return None diff --git a/TCPScan.py b/TCPScan.py new file mode 100644 index 0000000..dd77e13 --- /dev/null +++ b/TCPScan.py @@ -0,0 +1,89 @@ +from RawTCP import TCPHeader, handle_packet, tcp_checksum +import socket +import sys + + +class TCPScanner: + def __init__(self): + self.source_ips = {} + # TODO: options, threading, input/output queues + self.options = None + return + + def in_scope(self): + """Check that IP is in scanning scope""" + # TODO + pass + + def tcp_listener(self): + # Raw socket listener for when send_raw_syn() is used. This will catch + # return SYN-ACKs + listen = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) + while True: + # packet = ('E \x00(\x1f\xaa@\x00w\x06\x99w2\xe0\xc8\xa2\xa2\xf3\xac\x18\xdf\xb3\x00\x16\xb6\x80\xc1\xa0/\xa6=$P\x10\xce\xab\xd1\xe4\x00\x00', ('50.XXX.200.162', 0)) + raw_packet = listen.recvfrom(65565) + ret = handle_packet(raw_packet) + if ret is None: + continue + src_addr, src_port = ret + if self.in_scope(src_addr) and src_port in self.ports: + self.output_queue.put((src_addr, src_port)) + + def send_raw_syn(self, dest_ip, dst_port): + # Use raw sockets to send a SYN packet. + # If you want, you could use the IP header assembled in the tcp_checksum + # function to have a fully custom TCP/IP stack + try: + # Using IPPROTO_TCP so the kernel will deal with the IP packet for us. + # Change to IPPROTO_IP if you want control of IP header as well + s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) + except Exception: + sys.stderr.write("Error creating socket in send_raw_syn\n") + return + if self.options.source == "auto": + # This gets the correct source IP. Just in case of multiple interfaces, + # it will pick the right one + src_addr = self.get_source_ip(dest_ip) + else: + src_addr = self.options.source + src_port = 54321 + make_tcpheader = TCPHeader(src_port, dst_port) + tcp_header = make_tcpheader.get_struct() + packet = make_tcpheader.get_struct(check=tcp_checksum( + src_addr, dest_ip, tcp_header), checksummed=True) + try: + s.sendto(packet, (dest_ip, 0)) + except Exception as e: + sys.stderr.write("Error utilizing raw socket in send_raw_syn: {}\n".format(e)) + + def get_source_ip(self, dst_addr): + # Credit: 131264/alexander from stackoverflow. This gets the correct IP for sending. Useful if you have multiple interfaces + # NOTE: This will send an additional packet for every single IP to confirm + # the route. (but just one packet) + try: + if dst_addr in self.source_ips: + return self.source_ips[dst_addr] + else: + self.source_ips[dst_addr] = [(s.connect((dst_addr, 53)), s.getsockname()[0], s.close( + )) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1] + return self.source_ips[dst_addr] + except Exception: + sys.stderr.write( + "Something went wrong in get_source_ip, results might be wrong\n") + + +def send_full_connect_syn(ip, port, timeout): + # Normal scan using socket to connect. Does 3-way handshack, then graceful + # teardown using FIN + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + except Exception as e: + sys.stderr.write("Error creating socket in send_full_connect_syn: {}\n".format(e)) + return False + try: + s.connect((ip, port)) + return True + s.close() + except Exception: + return False