How to Use Raw Socket in Python

How Do I Use Raw Socket in Python?

You do it like this:

First you disable your network card's automatic checksumming:

sudo ethtool -K eth1 tx off

And then send your dodgy frame from python 2 (You'll have to convert to Python 3 yourself):

#!/usr/bin/env python
from socket import socket, AF_PACKET, SOCK_RAW
s = socket(AF_PACKET, SOCK_RAW)
s.bind(("eth1", 0))

# We're putting together an ethernet frame here,
# but you could have anything you want instead
# Have a look at the 'struct' module for more
# flexible packing/unpacking of binary data
# and 'binascii' for 32 bit CRC
src_addr = "\x01\x02\x03\x04\x05\x06"
dst_addr = "\x01\x02\x03\x04\x05\x06"
payload = ("["*30)+"PAYLOAD"+("]"*30)
checksum = "\x1a\x2b\x3c\x4d"
ethertype = "\x08\x01"

s.send(dst_addr+src_addr+ethertype+payload+checksum)

Done.

Using raw socket in Python

Reading from a raw socket and parsing out the IP_ID is trivial:

response, addr = s.recvfrom(65535)
response_id = struct.unpack('!H', response[4:6])
print response_id

The hard part is getting someone to send you a packet in the first place. I'm pretty sure you can't use the same socket in raw mode and stream mode at the same time, so you're going to need to replace that connect and send with a bunch of much more complicated code that constructs and sends the appropriate TCP packets to initiate the connection. There are libraries like scapy that will do all that hard stuff for you, but if you want to do it manually, you just need to read RFC 791 and RFC 793 carefully, do all the tedious stuff (making sure you get all the endianness right), and you're on your way.

On *BSD (including OS X), the kernel will fill in the IP length, TCP length, and, best of all, the TCP checksum. It gets much more painful if you have to handle those yourself. (If this doesn't work on your platform, you probably do… either that, or I screwed up something else that OS X fixes for me automagically and your platform doesn't.)

import socket
import struct

def make_ip(proto, srcip, dstip, ident=54321):
saddr = socket.inet_aton(srcip)
daddr = socket.inet_aton(dstip)
ihl_ver = (4 << 4) | 5
return struct.pack('!BBHHHBBH4s4s' ,
ihl_ver, 0, 0, ident, 0, 255, proto, 0, saddr, daddr)

def make_tcp(srcport, dstport, payload, seq=123, ackseq=0,
fin=False, syn=True, rst=False, psh=False, ack=False, urg=False,
window=5840):
offset_res = (5 << 4) | 0
flags = (fin | (syn << 1) | (rst << 2) |
(psh <<3) | (ack << 4) | (urg << 5))
return struct.pack('!HHLLBBHHH',
srcport, dstport, seq, ackseq, offset_res,
flags, window, 0, 0)

srcip = dstip = '127.0.0.1'
srcport, dstport = 11001, 11000
payload = '[TESTING]\n'

ip = make_ip(socket.IPPROTO_TCP, srcip, dstip)
tcp = make_tcp(srcport, dstport, payload)
packet = ip + tcp + payload

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
s.sendto(packet, (dstip, 0))
response, addr = s.recvfrom(65535)
response_id = struct.unpack('!H', response[4:6])
print response_id

Each time I run this, I get a nak (if no one's listening on port 11000) or ack packet with a randomized IP_ID, just as you'd expect.

RAW client-server socket python

That's because a raw socket doesn't utelize the Ethernet/TCP/IP library at all. It's a RAW socket, you're in charge of whatever data you send. You're also in charge of connecting to your peer by sending the right SYN/ACK order.

Traditional sockets is an "abstraction" layer for you to send your payload (data).
Meaning you connect your socket to a destination, you tell the socket what data to send, assuming you're using a TCP based socket your data will be prepended with a header corresponding to the TCP protocol and version and your data might get segmented based on how much data you're trying to push through.

All this happens automatically with a traditional socket.

This is what a TCP header looks like, roughly (taken out of context but it will give you an idea):

   0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Note that data is what you normally do, but when working with RAW sockets, you need to send all these information blocks on to your Ethernet cable.

I could post some code later tonight perhaps if someone doesn't beat me to it, but here's a good short usage example: How Do I Use Raw Socket in Python?



tl;dr:

You need to build a Ethernet header and a TCP header and add your data to it according to the RFC standard (this might be a good place to start: https://www.rfc-editor.org/rfc/rfc793). Then you need to "simply" send that out on to your "socket". There's no magic involved with RAW sockets, you build your header with a source+destination address, and you send your payload out onto the cable hoping you built the packet correctly.

Answer to comments:

socket.accept() - This function is used in traditional sockets to "store" session information (Source:Port -> Destination:Port). This function takes clients from a buffered queue of incoming connection attempts and "activates" them. This does not apply to raw sockets, the reason being is that the abstraction layer from normal sockets is again, not present. Your RAW socket will listen to any incoming data (not connections), meaning you're in charge of receiving first a SYN packet which you need to respond with a SYN-ACK in which you'll receive a final ACK. At this point, you're good to go for sending data between you with the correct information (source port etc).

Here's a good (ASCII) flow-chart of the abstraction layer provided in a normal socket:

                              +---------+ ---------\      active OPEN  
| CLOSED | \ -----------
+---------+<---------\ \ create TCB
| ^ \ \ snd SYN
passive OPEN | | CLOSE \ \
------------ | | ---------- \ \
create TCB | | delete TCB \ \
V | \ \
+---------+ CLOSE | \
| LISTEN | ---------- | |
+---------+ delete TCB | |
rcv SYN | | SEND | |
----------- | | ------- | V
+---------+ snd SYN,ACK / \ snd SYN +---------+
| |<----------------- ------------------>| |
| SYN | rcv SYN | SYN |
| RCVD |<-----------------------------------------------| SENT |
| | snd ACK | |
| |------------------ -------------------| |
+---------+ rcv ACK of SYN \ / rcv SYN,ACK +---------+
| -------------- | | -----------
| x | | snd ACK
| V V
| CLOSE +---------+
| ------- | ESTAB |
| snd FIN +---------+
| CLOSE | | rcv FIN
V ------- | | -------
+---------+ snd FIN / \ snd ACK +---------+
| FIN |<----------------- ------------------>| CLOSE |
| WAIT-1 |------------------ | WAIT |
+---------+ rcv FIN \ +---------+
| rcv ACK of FIN ------- | CLOSE |
| -------------- snd ACK | ------- |
V x V snd FIN V
+---------+ +---------+ +---------+
|FINWAIT-2| | CLOSING | | LAST-ACK|
+---------+ +---------+ +---------+
| rcv ACK of FIN | rcv ACK of FIN |
| rcv FIN -------------- | Timeout=2MSL -------------- |
| ------- x V ------------ x V
\ snd ACK +---------+delete TCB +---------+
------------------------>|TIME WAIT|------------------>| CLOSED |
+---------+ +---------+

Here's a server example:

#!/usr/bin/env python
from socket import socket, AF_PACKET, SOCK_RAW
s = socket(AF_PACKET, SOCK_RAW)
#s.bind(("eth1", 0))

# We're putting together an ethernet frame here,
# NOTE: Not a full TCP frame, this is important to remember!
src_addr = "\x01\x02\x03\x04\x05\x06"
dst_addr = "\x01\x02\x03\x04\x05\x06"
payload = ("["*30)+"PAYLOAD"+("]"*30)
checksum = "\x1a\x2b\x3c\x4d"
ethertype = "\x08\x01"

s.send(dst_addr+src_addr+ethertype+payload+checksum)

Found some old code that I barely got started on, might come in handy: https://github.com/Torxed/Scripts/tree/master/python/Laboratory

How to receive data from a raw socket in Python?

The book Black Hat Python has en example using the socket library to create a scanner, unfortunately not a port scanner. They check if a host is up, and they use a raw socket to receive data. The code is available here.

They are sending SYN-packets with one socket object in a new thread, and sniffing the replies using another socket object.

In the example they use socket.IPPROTO_IP or socket.IPPROTO_ICMP instead of socket.IPPROTO_RAW depending on if it is Windows or not.

For the sniffer they use the function setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) for sniffing, where IPPROTO_IP is a dummy-protocol for TCP, IP_HDRINCL is to include headers in the IP packets, and 1 is mapped to the ICMP-protocol in the code.

Good luck!

Python RAW socket interpret data bytes

Kevin's comment helped me in solving my problem:

This is the code that works for me. It does not feel like a clean solution but it works. If someone knows a proper way how to print the actual bytes without converting them, I would appreciate knowing that as well.

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
s.bind(("192.168.1.100", 0))

i = 5
while i > 0:
data = s.recvfrom(10000)
print("b'{}'".format(''.join(' {:02x}'.format(b) for b in data[0])))
i -= 1


Related Topics



Leave a reply



Submit