# Sniffer ###### tags: `python` `hacking` `sniffer` `Networking` [TOC] # Capture the singular packet Access to the raw packet is different between Windows and Linux ## Under the Windows We have to add some flags to header, if we are running system on windows. if we want to know what OS we're using, we could use os.name. os.name == 'nt' (nt means that we're running window. and 'posix'MAC) ### Setting Protocol ```python def main (): if os == 'nt': socketProtocol = socket.IPPROTO_IP # Windows can listen all packets, it won't be constrained else: socketProtocol = socket.IPPROTO_ICMP #Linux only can listin ICMP packet ``` ### Create the socket ```python #Create the socket object sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socketProtocol) sniffer.bind((host,0)) ``` ### include ip header ```python #when we're capturing, include ip header sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) ``` ### Enable Promiscuous Model ``` if os.name =="nt": sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) ``` ### Print packet ```python print(sniffer.recvfrom(65565)) ``` ### Disable Promiscuous Model ```python if os.name == 'nt': sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) ``` ## Source code ```python import socket import os host = '10.0.0.182' def main(): if os == 'nt': socketProtocol = socket.IPPROTO_IP # Windows can listen all packets, it won't be constrained else: socketProtocol = socket.IPPROTO_ICMP #Linux only can listin ICMP packet #Create the socket object sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socketProtocol) sniffer.bind((host,0)) #when we're capturing, include ip header sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) if os.name =="nt": sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) print(sniffer.recvfrom(65565)) if os.name == 'nt': sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) if __name__ == "__main__": main() ``` ## Result ![](https://i.imgur.com/nsi31ay.png) --- # Parse IP Header and ICMP We need to trans binary into the data structure. And then, we have two model to defined data structure model 1: ctype modle 2: struct ## ctypes ### Library ```python from ctypes import * import socket import struct ``` before we parse the ip header, we have to create a class to put header information _fields_ structure is extend from the Structure class of Ctype. Before we create an any object, we're going to define a _fields structure. Just like this ```python class IP(Structure): _fields_ =[ ("version", c_ubyte,4), ("headerLength", c_ubyte,4), ("typeOfServer", c_ubyte,8), ("totolLength", c_ushort,16), ("identification", c_ushort,16), ("offset",c_short,16), ("timeToLive", c_byte,8), ("protocol", c_ubyte,8), ("headerCheckSum",c_ushort,16) ("sourceIP", c_uint32,32), ("destinationIP",c_uint32,32) ] ``` Each field description use three parameter - first: field name - Second: a type of a vale - three: bits length c_ubyte means unsigned char c_ushort means unsigned short ### New method ```python def __new__(cls, socket_buffer=None): return cls.from_buffer_copy(socket_buffer) ``` function: padding _field_ structure Generate an object ### Construct ```python def __init__(self,socket_buffer=None): self.srcAddress = socket.inet_ntoa(struct.pack("<L",self.sourceIP)) #inet_ntoa trans bytes into strings self.dstAddress = socket.inet_ntoa(struct.pack("<L",self.destinationIP)) ``` ## Struct ### library ```python import ipaddress import struct ``` ### IP class ```python class IP: def __init__(self, buff=None): header = struct.unpack('<BBHHHBBH4s4s', buff) #little-endian # Niabble (It's four-bits aggregation) self.version = header[0] >> 4 #it means right-shift 4 bits (Hight singnificate) self.headerLength =header[0] & 0xF # do and 00001111 (Low singnificate) self.typeOfservice=header[1] self.totalLength=header[2] self.identification=header[3] self.offset=header[4] self.timeToLive=header[5] self.protocol=header[6] self.checkSum=header[7] self.source=header[8] self.destination=header[9] self.srcAddressReadable = ipaddress.ip_address(self.source) self.desAddressReadable = ipaddress.ip_address(self.destination) ``` < means the Endian What is the Endian, it's very sample **Memory layout** ![](https://i.imgur.com/tiO3MOa.png) ![](https://i.imgur.com/vkqLEjc.png) ![](https://i.imgur.com/DvJRwSv.png) In little endian machine, LSB is going to put first, on the other hand , MSB is goint to put first. B 1byte (unsigned charter H 2bytes (unsigned shorted number 4s 4-bytes (strings ### Nibble (4-bit data unit) ```python self.version = header[0] >> 4 #it means right-shift 4 bits (Hight singnificate) self.headerLength =header[0] & 0xF # do and 00001111 (Low singnificate) ``` #### Get version We could use way is right shift 4 bit ![](https://i.imgur.com/l71BMmv.png) #### Get Header length ### Source code ```python import ipaddress import socket import struct import sys import os class IPdecode: def __init__(self, buff=None): header = struct.unpack('<BBHHHBBH4s4s', buff) #little-endian # Niabble (It's four-bits aggregation) self.version = header[0] >> 4 #it means right-shift 4 bits (Hight singnificate) self.headerLength =header[0] & 0xF # do and 00001111 (Low singnificate) self.typeOfservice=header[1] self.totalLength=header[2] self.identification=header[3] self.offset=header[4] self.timeToLive=header[5] self.protocolNumber=header[6] self.checkSum=header[7] self.source=header[8] self.destination=header[9] self.srcAddressReadable = ipaddress.ip_address(self.source) self.desAddressReadable = ipaddress.ip_address(self.destination) self.protocolMap ={ 1:"ICMP", 6:"TCP", 17:"UDP" } try: self.protocol = self.protocolMap[self.protocolNumber] except Exception as e: print(f"{e} NO protocol for {self.protocolNumber} ") self.protocol = str(self.protocolNumber) def sniff(targetHost): if os.name =="nt": socketProtocol = socket.IPPROTO_IP else : socketProtocol = socket.IPPROTO_ICMP snifferSocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socketProtocol) snifferSocket.bind((targetHost,0)) # Adding the ip header snifferSocket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) if os.name == "nt": snifferSocket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) try: while 1: #Read packets rawBuffer = snifferSocket.recvfrom(65536)[0] #create the ip header IPHeader = IPdecode(rawBuffer[0:20]) print(f"{IPHeader.protocol} {IPHeader.srcAddressReadable}->{IPHeader.desAddressReadable}") except KeyboardInterrupt : if os.name == 'nt': snifferSocket.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) sys.exit() if __name__ == "__main__": if len(sys.argv) == 2: targetHost = sys.argv[1] else: targetHost = "192.168.151.54" sniff(targetHost) ``` ![](https://i.imgur.com/kE76IDb.png) ![](https://i.imgur.com/2UcZ6Wf.png) ## Decode ICMP sniffer_with_icmp.py ![](https://i.imgur.com/mTiUcwC.png) Library ``` import ipaddress import os import socket import struct import sys ``` ICMP 要去decode 部分 - type - code - checksum 可以去看 type,code的Value e.g. code = Port Unreachable type = Destination Unreachable 下面URL 有更多 type,code info https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-types 定義 ICMP Structure ``` class ICMP: def __init__(self, buff): header = struct.unpack('<BBHHH', buff) self.type = header[0] self.code = header[1] self.sum = header[2] self.id = header[3] self.seq = header[4] ``` 去認收到 ICMP packet ``` if ip_header.protocol == "ICMP": print('Protocol: %s %s -> %s' % (ip_header.protocol, ip_header.src_address, ip_header.dst_address)) print(f'Version: {ip_header.ver} Header Length: {ip_header.ihl} TTL: {ip_header.ttl}') ``` Calculate ICMP offest 去取出 ICMP 的type 和 code Internet Header Length 會給出 有幾個 4byte 數量 e.g. 5 * 4 = 20 (NO options) 5 -> 32 bit 的數量 ip Header 以32 bit 0x00000000 ``` # calculate where our ICMP packet starts offset = ip_header.ihl * 4 buf = raw_buffer[offset:offset + 8] icmp_header = ICMP(buf) print('ICMP -> Type: %s Code: %s\n' % (icmp_header.type, icmp_header.code)) ``` Source Code ```python import ipaddress import os import socket import struct import sys class IP: def __init__(self, buff=None): header = struct.unpack('<BBHHHBBH4s4s', buff) self.ver = header[0] >> 4 self.ihl = header[0] & 0xF self.tos = header[1] self.len = header[2] self.id = header[3] self.offset = header[4] self.ttl = header[5] self.protocol_num = header[6] self.sum = header[7] self.src = header[8] self.dst = header[9] # human readable IP addresses self.src_address = ipaddress.ip_address(self.src) self.dst_address = ipaddress.ip_address(self.dst) # map protocol constants to their names self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"} try: self.protocol = self.protocol_map[self.protocol_num] except Exception as e: print('%s No protocol for %s' % (e, self.protocol_num)) self.protocol = str(self.protocol_num) class ICMP: def __init__(self, buff): header = struct.unpack('<BBHHH', buff) self.type = header[0] self.code = header[1] self.sum = header[2] self.id = header[3] self.seq = header[4] def sniff(host): if os.name == 'nt': socket_protocol = socket.IPPROTO_IP else: socket_protocol = socket.IPPROTO_ICMP sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) sniffer.bind((host, 0)) sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) if os.name == 'nt': sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) try: while True: raw_buffer = sniffer.recvfrom(65535)[0] ip_header = IP(raw_buffer[0:20]) if ip_header.protocol == "ICMP": print('Protocol: %s %s -> %s' % (ip_header.protocol, ip_header.src_address, ip_header.dst_address)) print(f'Version: {ip_header.ver} Header Length: {ip_header.ihl} TTL: {ip_header.ttl}') # calculate where our ICMP packet starts offset = ip_header.ihl * 4 buf = raw_buffer[offset:offset + 8] icmp_header = ICMP(buf) print('ICMP -> Type: %s Code: %s\n' % (icmp_header.type, icmp_header.code)) except KeyboardInterrupt: if os.name == 'nt': sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) sys.exit() if __name__ == '__main__': if len(sys.argv) == 2: host = sys.argv[1] else: host = '192.168.1.206' sniff(host) ```