Unable to send ICMP packets from a raw socket in Python - python

I have a raw Python socket initialized like so:
mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
mySocket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
My problem is that I can't send any packet from the socket where the protocol field in my IP header is specified as '1' (ICMP). If this field is anything else it'll work just fine, including '17' (UDP) for example. I don't get any errors, the packet just doesn't display in Wireshark, and I can't receive it on my server.
I'm generating the IP header like so:
def getIPHeader(src, dst, proto):
version = 4
IHL = 5
DSCP = 0
ECN = 0
totalLength = 40
identification = 333
flags = 0
fragmentOffset = 0
timeToLive = 128
protocol = proto
headerChecksum = 0
sourceIP = socket.inet_aton(src)
destIP = socket.inet_aton(dst)
options = 0
version_IHL = (version << 4) | IHL
DSCP_ECN = (DSCP << 2) | ECN
flags_fragmentOffset = (flags << 13) | fragmentOffset
# The '!' ensures all arguments are converted to network byte order
header = struct.pack("!BBHHHBBH4s4s", version_IHL, DSCP_ECN, totalLength, identification, flags_fragmentOffset, timeToLive, protocol, headerChecksum, sourceIP, destIP)
return header
And sending like so:
icmpHeader = struct.pack("!bbHHh", 8, 0, 0, 666, 0)
packet = getIPHeader("192.168.2.15", "8.8.8.8", 1) + icmpHeader
mySocket.sendto(packet, ("8.8.8.8", 0))
Sending with UDP protocol set works (obviously malformed):
icmpHeader = struct.pack("!bbHHh", 8, 0, 0, 666, 0)
packet = getIPHeader("192.168.2.15", "8.8.8.8", 17) + icmpHeader
mySocket.sendto(packet, ("8.8.8.8", 0))
The worst part is if I send a ping from the command line, then copy the exact hex from Wireshark of the IP and ICMP data into my code, it STILL doesn't work, like so:
packet = b'\x45\x00\x00\x3c\x24\xad\x00\x00\x80\x01\x43\x4d\xc0\xa8\x02\x0f\x08\x08\x08\x08\x08\x00\x4d\x44\x00\x01\x00\x17\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x61\x62\x63\x64\x65\x66\x67\x68\x69'
mySocket.sendto(packet, ("8.8.8.8", 0))
However, as before, if I change the 10th byte of the hard coded data to '\x11' (17 decimal, UDP), I can then see it in Wireshark (obviously with a malformed UDP section as the rest of the packet is ICMP).
I'm running Windows 10, Python 3.4.1, and my firewall is off. Any ideas?
UPDATE: I've tried this on two different computers, a Windows 8 and a Windows 10 machine, both worked, so it's something to do with my computer, the plot thickens...

Related

Python network scanner (host discovery tool) is telling me that my source and destination are the same when sending UDP datagrams

I'm following Black Hat Python (2ed.), in which I'm writing a network scanning tool. The tool is in theory supposed to send UDP packets out to a given subnet, and if a host is up on that subnet, the response packet is decoded, found to contain the message in the original datagram, and used to indicate the host is up. This seems to generally be working well to capture packets; I can go to a website, or ping another host, and the tool reliably provides the correct source and destination addresses for those cases.
Here is the meat of the code (I have not included the class creation, or the passing of the host argument for brevity, but the host is 192.168.10.85).
class IP:
"""layer 3 (IP) packet header decoder"""
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]
# make IP addrs human readable
self.src_address = ipaddress.ip_address(self.src)
self.dst_address = ipaddress.ip_address(self.dst)
# the protocol_num is actually a code for the protocol name
self.protocol_name = {1: 'ICMP', 6: 'TCP', 17: 'UDP'}
# try to provide the human version of the protocol, otherwise just give the code
try:
self.protocol = self.protocol_name[self.protocol_num]
except KeyError as error:
self.protocol = self.protocol_num
print(f'Protocol is unrecognized, try googling "IP protocol {self.protocol_num}"')
class ICMP:
"""layer 4 (ICMP) packet header decoder"""
def __init__(self, buff):
header = struct.unpack('<BBHHH', buff)
self.type = header[0]
self.code = header[1]
self.checksum = header[2]
self.ident = header[3]
self.seq_num = header[4]
def udp_sender():
# blasts udp packets into the network to solicit responses
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sender:
for ip in ipaddress.ip_network(SUBNET).hosts():
# time.sleep(1)
print(f'sending a test message to {ip}')
# send our test message out to port 65212 on the destination
sender.sendto(bytes(MESSAGE, 'utf8'), (str(ip), 65212))
class Scanner:
def __init__(self, host):
self.host = host
# create raw socket, bind to public interface
# if windows:
if os.name == 'nt':
socket_protocol = socket.IPPROTO_IP
# if linux/mac:
else:
socket_protocol = socket.IPPROTO_ICMP
self.socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
self.socket.bind((host, 0))
# socket options, include header
self.socket.setsockopt(socket_protocol, socket.IP_HDRINCL, 1)
# enable promiscuous mode for windows
if os.name == 'nt':
self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
def sniff(self):
# set of all hosts that are up (respond to our ICMP message)
hosts_up = {f'{str(self.host)} *'}
try:
while True:
# read a packet, and parse the IP header
raw_buffer = self.socket.recvfrom(65535)[0]
# create IP header from the first 20 bytes
ip_header = IP(raw_buffer[0:20])
# if the protocol is ICMP, do some additional things
# print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if ip_header.protocol == 'ICMP':
# calculate where the ICMP packet starts
offset = ip_header.ihl * 4
buf = raw_buffer[offset:offset + 8]
# create ICMP structure
icmp_header = ICMP(buf)
print(f'type: {icmp_header.type}, code: {icmp_header.code}')
print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if icmp_header.type == 3 and icmp_header.code == 3:
print(f'type: {icmp_header.type}, code: {icmp_header.code}')
print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if ipaddress.ip_address(ip_header.src_address) in ipaddress.IPv4Network(SUBNET):
# make sure the packet has our test message
if raw_buffer[len(raw_buffer) - len(MESSAGE):] == bytes(MESSAGE, 'utf8'):
tgt = str(ip_header.src_address)
if tgt != self.host and tgt not in hosts_up:
hosts_up.add(str(ip_header.src_address))
print(f'Host Up: {tgt}')
However, when receiving the ICMP responses as a result of my datagram, the tool reports that the source and destination addresses are the same (my host, 192.168.10.85). Furthermore, while I should be receiving responses with Type 3 and Code 3 (destination unreachable, and port unreachable), but I am receiving (in my program) Type 3 and Code 1.
Here is an example of the output when I issue a ping command while the scanner is running, which seems correct:
src=192.168.10.85, dst=192.168.10.200, prot_name=ICMP type: 0, code: 0 src=192.168.10.200, dst=192.168.10.85, prot_name=ICMP type: 8, code: 0
Here is an example of the output to what I am assuming is the UDP packet response, which seems incorrect):
src=192.168.10.85, dst=192.168.10.85, prot_name=ICMP type: 3, code: 1
If I open wireshark while I'm running my code, I can correctly see the ICMP Type 3/Code 3 responses, so I know they are going through, here is a screen grab of one host on the target subnet as an example:
Why is my scanner not seeing these responses that are in wireshark?
I've tried running wireshark alongside my program, to see if the packets are being correctly decoded, and that the message in the UDP packet is properly in place. All signs indicate that the packets are going out to the hosts I'm trying to detect, and the correct responses are coming back, but my scanner refuses to find them.

Sending UDP RAW Data on windows with python

I want to sending simple data with UDP protocol, I have tested on ubuntu and it worked, here's my code
import time
import socket
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
s.bind(("eno2", 0))
ethernet = b'\x7c\x10\xc9\x51\xb7\x7b' # MAC Address Destination
ethernet += b'\x7c\x10\xc9\x51\xb3\xd6' # MAC Address Source
ethernet += b'\x08\x00' # Protocol-Type: IPv4
ip_header = b'\x45\x00\x00\x2a' # Version, IHL, Type of Service | Total Length
ip_header += b'\xa0\x6a\x00\x00' # Identification | Flags, Fragment Offset
ip_header += b'\x80\x11\x0e\xe5' # TTL, Protocol | Header Checksum
ip_header += b'\xc0\xa8\x05\x0f' # Source Address
ip_header += b'\xc0\xa8\x05\x14' # Destination Address
udp_header = b'\xce\x55\xc3\x55' # Source Port | Destination Port
udp_header += b'\x00\x16\x1f\x83' # Sequence Number
data = b'\x68\x65\x6c\x6c'
data += b'\x6f\x20\x77\x6f'
data += b'\x72\x6c\x64\x20'
data += b'\x31\x31'
padding = b'\x00\x00\x00\x00'
packet = ethernet + ip_header + udp_header + data
while True:
s.send(packet)
time.sleep(0.5)
but when I running on windows AF_PACKET has no attribute on Socket module, so what similar AF_PACKET on windows? someone can help me?
I just want to send UDP RAW Data on windows

"OSError: [WinError 10022] An invalid argument was supplied" when trying to send TCP SYN packet (python)

Currently trying to make handshake process on python using raw sockets but for some reason I can't send any packet with TCP protocol receiving OSError: [WinError 10022] An invalid argument was supplied. Here is my code:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
s.sendto(packet, ('8.8.8.8', 80))
As packet I have tried to use scapy's TCP packet, TCP bytes from wireshark after successful sendings with other libraries and also hand-made byte strings:
def chksum(packet: bytes) -> int:
if len(packet) % 2 != 0:
packet += b'\0'
res = sum(array.array("H", packet))
res = (res >> 16) + (res & 0xffff)
res += res >> 16
return (~res) & 0xffff
class TCPPacket:
def __init__(self,
src_host: str,
src_port: int,
dst_host: str,
dst_port: int,
flags: int = 0):
self.src_host = src_host
self.src_port = src_port
self.dst_host = dst_host
self.dst_port = dst_port
self.flags = flags
def build(self) -> bytes:
packet = struct.pack(
'!HHIIBBHHH',
self.src_port, # Source Port
self.dst_port, # Destination Port
0, # Sequence Number
0, # Acknoledgement Number
5 << 4, # Data Offset
self.flags, # Flags
8192, # Window
0, # Checksum (initial value)
0 # Urgent pointer
)
pseudo_hdr = struct.pack(
'!4s4sHH',
socket.inet_aton(self.src_host), # Source Address
socket.inet_aton(self.dst_host), # Destination Address
socket.IPPROTO_TCP, # PTCL
len(packet) # TCP Length
)
checksum = chksum(pseudo_hdr + packet)
packet = packet[:16] + struct.pack('H', checksum) + packet[18:]
return packet
So literally no idea why my socket doesn't like any packet
I found out what is wrong. Windows doesn't allow to send TCP packets with raw sockets so this code will never work. Probably it is possible to write the same with scapy or using other libraries but that's not what I need so the only way to make it work is to run on linux. Still not sure if the packet creation is correct but TCP protocol with raw sockets sure works fine on linux.

Unpacking TCP-fragment give incorrect result

I have a problem with my packet sniffer. The destination port and source port seems to be wrong in my sniffer. In wireshark the ports is totally different from my sniffers. No result contains port 443 expected from TLS. (The whole tcp-fragment might be wrong.)
Does it have to do something to do with the router?
I also know that there is some problems doing sniffing in windows. Or is my unpacking code just wrong? Am i missing some offset between ip-header and tcp-fragment ?
Socket code: https://pastebin.com/tMuHgz0R
Unpacking code: https://pastebin.com/9ZVfYNEE (full code)
# Unpack tcp fragment
def tcp_fragment(raw_data):
tcp_header = struct.unpack('!HHLLBBHHH', raw_data[:20])
source_port = tcp_header[0]
destionation_port = tcp_header[1]
sequence_number = tcp_header[2]
acknowledgement_number = tcp_header[3]
offset = tcp_header[4] >> 4
reserved = tcp_header[4] & 0xF
flags = get_tcp_flags(tcp_header[5])
window = tcp_header[6]
checksum = tcp_header[7]
pointer = tcp_header[8]
return {
TCP_SOURCE_PORT: source_port,
TCP_DESTINATION_PORT: destionation_port,
TCP_SEQUENCE_NUMBER: sequence_number,
TCP_ACKNOWLEDGEMENT_NUMBER: acknowledgement_number,
TCP_OFFSET: offset,
TCP_RESERVED: reserved,
TCP_FLAGS: flags,
TCP_WINDOW: window,
TCP_CHECKSUM: checksum,
TCP_POINTER: pointer,
TCP_PAYLOAD_DATA: raw_data[20:]
}
TCP header result: https://pastebin.com/7xhaEGer
Wireshark result for same packets:
Thanks in advance for any help you can provide.
Okay so i managed to solve it. It was a quite stupid error. I forgot to account for the ip-header bits when unpacking tcp.
Fixed code would look something like this:
# Unpack tcp & ip
def ip_tcp(raw_data):
iph = ip_header(raw_data)
iph_length = iph[IP_IHL] * 4
tcp = tcp_fragment(raw_data, iph_length)
return (iph, tcp)
# Unpack tcp fragment
def tcp_fragment(raw_data, iph_length):
tcp_header = struct.unpack('!HHLLBBHHH', raw_data[iph_length:iph_length + 20])
source_port = tcp_header[0]
destionation_port = tcp_header[1]
sequence_number = tcp_header[2]
acknowledgement_number = tcp_header[3]
offset = tcp_header[4] >> 4
reserved = tcp_header[4] & 0xF
flags = get_tcp_flags(tcp_header[5])
window = tcp_header[6]
checksum = tcp_header[7]
pointer = tcp_header[8]
return {
TCP_SOURCE_PORT: source_port,
TCP_DESTINATION_PORT: destionation_port,
TCP_SEQUENCE_NUMBER: sequence_number,
TCP_ACKNOWLEDGEMENT_NUMBER: acknowledgement_number,
TCP_OFFSET: offset,
TCP_RESERVED: reserved,
TCP_FLAGS: flags,
TCP_WINDOW: window,
TCP_CHECKSUM: checksum,
TCP_POINTER: pointer,
TCP_PAYLOAD_DATA: raw_data[iph_length + 20:]
}

Why can't set the source ip address to 0.0.0.0 in python?

I am trying to implement DHCP client in python.I used a raw socket.
s=socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
def ip_packet(source_ip,dest_ip):
ip_ihl = 5
ip_ver = 4
ip_tos = 0
ip_tot_len = 0
ip_id = 54321 #Id of this packet
ip_frag_off =0
ip_ttl = 255
ip_proto = socket.IPPROTO_UDP
ip_check = 0
ip_saddr = socket.inet_aton ( source_ip )
print ip_saddr.encode('hex')
ip_daddr = socket.inet_aton ( dest_ip )
ip_ihl_ver = (ip_ver << 4) + ip_ihl
ip_header = struct.pack('!BBHHHBBH4s4s' , ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr)
return ip_header
s.sendto(packet,('255.255.255.255',67))
I sat the source ip address to 0.0.0.0 ,but when I sniffed the generated packet in wireshark ,I was surprised to see that the source ip address is sat to my real ip address.
But when I used Scapy everything worked as expacted
sendp(Ether(dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0",dst="255.255.255.255")/UDP(sport=68,dport=67)/BOOTP(chaddr=hw)/DHCP(options=[("message-type","request")]),count=3)
So, how dose Scapy manage to set the source ip address to 0.0.0.0?? Is it possible to do that using raw socket ??

Categories

Resources