Counting the number of SYN and ACK packets using Python and Scapy - python

I am analysing a pcap file using Python and Scapy.
Currently, I have it counting the number of packets
I would like to count the number of SYN and ACK packets, is there a way to do this?
My main piece of code thus far is
for (pkt_data, pkt_metadata,) in RawPcapReader(file_name):
count+=1

the code is the folowing:
import scapy.all as scapy
from scapy.layers.inet import TCP
pkt_count = 0
pkt_tcp_ack_count = 0
pkt_tcp_syn_count = 0
for pkt in scapy.PcapReader(file_name):
pkt_count += 1
if TCP in pkt:
if "A" in pkt[TCP].flags:
pkt_tcp_ack_count += 1
if "S" in pkt[TCP].flags:
pkt_tcp_syn_count += 1
print("pkt_count: %d" % pkt_count)
print("pkt_tcp_ack_count: %d" % pkt_tcp_ack_count)
print("pkt_tcp_syn_count: %d" % pkt_tcp_syn_count)
now, a bit of context.
Scapy is building all the layers, so you can simply query for their presence in the packet.
for a given packet you can run:
pkt.show()
which show you how the packet has been decoded by scapy

Related

Python update List value decoded with a function in scapy

I'm using a function in scapy
RTP(pl[0]['UDP'].payload).timestamp
this works and in this example returns 2983999523 as expected
I want to set this value to 0
RTP(pl[0]['UDP'].payload).timestamp = 0
This doesn't work... I assume its something to do with how i'm using the function to set the list value... ? Is this even possible?
https://fossies.org/linux/scapy/scapy/layers/rtp.py
any thoughts?
RTP() is a function which is probably returning a class which has a parameter timestamp. This should in theory work to change that parameter:
foo = RTP(pl[0]['UDP'].payload)
foo.timesamp
> 2983999523
foo.timestamp = 0
foo.timesamp
> 0
I don't know if that's useful in this context because I don't know what you're trying to do with the timestamp = 0, but maybe you can adapt this to your purposes.
Edit - To extend the function:
def RTP2(obj):
result = RPT(obj)
result.timestamp = 0
return result
I didn't really understand how the scapy python library worked with layers.
Posting my full code for manipulating RTP headers as it took a lot of trial and error and very small titbits of info to sort it ... its a hack but it works, might help someone else.
### Python 2.7 Script by Neil Bernard neil#themoog.org
## This script is for editing RTP header information in PCAP files to manipulate RTP timestamp and SSRC
## for testing hardware transport stream devices and testing SMPTE 2022-2 handling
## There is some error checking for scapy network layers but try to keep capture as clean as possible
## also try to keep captures under 260Mb for performance, can take a good 20mins on an intel i7 / 16GB ram
#### Basic Scapy Tutorial
# *https://www.youtube.com/watch?v=ADDYo6CgeQY
#### Scapy cheat sheet
# https://blogs.sans.org/pen-testing/files/2016/04/ScapyCheatSheet_v0.2.pdf
#### Scapy RTP Library https://fossies.org/linux/scapy/scapy/layers/rtp.py
# import scapy
from scapy.all import rdpcap
from scapy.all import wrpcap
from scapy.all import RTP
from scapy.all import *
infile = "test_rtp.pcap"
outfile = "modified_" + infile
dest_port = 2000 # usefull to make sure you only action packets that are RTP
# load packet capture
print "Loading Packet Capture Keep <200Mb - Might take a few mins....."
pl = rdpcap(infile)
print "Loading complete!"
# print number of packets
print(len(pl))
# # print rtp timestamp
# print(RTP(pl[0][UDP].payload).timestamp)
numberofpckts = len(pl)
print numberofpckts
for pkt in range(numberofpckts):
# You cant use the [RTP] layer on a list index so you have to put it in a
# variable first. Also need to make sure its a UDP packet with .haslayer(UDP):
# https://stackoverflow.com/questions/48763072/scapy-getting-trailer-field-in-the-dissector
if pl[pkt].haslayer(UDP):
packet = pl[pkt][UDP]
else:
print "Probably Not a UDP / RTP Packet# {0}".format(pkt)
# You need to do the line below to force RTP detection and manipulation
# https://stackoverflow.com/questions/44724186/decode-rtp-over-udp-with-scapy
if pl[pkt].haslayer(UDP):
if packet["UDP"].dport==2000: # Make sure its actually RTP
packet["UDP"].payload = RTP(packet["Raw"].load)
#### un-commment and change lines below to manipulate headers
# packet[RTP].version = 0
# packet[RTP].padding = 0
# packet[RTP].extension = 0
# packet[RTP].numsync = 0
# packet[RTP].marker = 0
# packet[RTP].payload_type = 0
# packet[RTP].sequence = 0
# packet[RTP].timestamp = 0
packet[RTP].sourcesync = 0
# packet[RTP].sync = 0
### Calculate UDP Checksum or they will now be wrong!
#https://scapy.readthedocs.io/en/latest/functions.html
checksum_scapy_original = packet[UDP].chksum
# set up and calculate some stuff
packet[UDP].chksum = None ## Need to set chksum to None before starting recalc
packetchk = IP(raw(packet)) # Build packet (automatically done when sending)
checksum_scapy = packet[UDP].chksum
packet_raw = raw(packetchk)
udp_raw = packet_raw[20:]
# in4_chksum is used to automatically build a pseudo-header
chksum = in4_chksum(socket.IPPROTO_UDP, packetchk[IP], udp_raw) # For more infos, call "help(in4_chksum)"
# Set the new checksum in the packet
packet[UDP].chksum = checksum_scapy # <<<< Make sure you use the variable in checksum_scapy
# needed below to test layers before printing newts/newsourcesync etc to console
if pl[pkt].haslayer(UDP):
newts = RTP(pl[pkt][UDP].payload).timestamp
newsourcesync = RTP(pl[pkt][UDP].payload).sourcesync
else:
newts = 999
newsourcesync = 999
print("Changing packet {0} of {3} to new timestamp {1} SSRC {2} Old UDP chksum {4} >> New UDP chksum ???").format(pkt+1,newts,newsourcesync,numberofpckts,hex(checksum_scapy_original))
else:
print "Probably Not a UDP / RTP Packet# {0}".format(pkt)
# Write out new capture file
wrpcap(outfile, pl)

Python sockets stealing each other's packets

I'm trying to write a program to test data transfer speeds for various-sized packets in parallel. I noticed something odd, though, that the size of the packet seemed to have no effect on transfer time according to my program, whereas the Unix ping binary would time out on some of the packet sizes I'm using. I was sending 4 packets containing the string 'testquest' and one that was just 2000 bytes set to 0. However, when I printed the results, they all contained 'testquest' (and were far shorter than 2000 bytes). The only thing I can conclude is that these sockets are somehow all receiving the same packet, which would explain how they all had the same rtt.
I made this MCVE to illustrate the issue (you can ignore the 'checksum' function, it's included for completeness but I know from experience that it works):
#!/usr/bin/env python3
import socket
import struct
import time
from multiprocessing.pool import ThreadPool as Pool
from sys import argv, byteorder
def calculate_checksum(pkt):
"""
Implementation of the "Internet Checksum" specified in RFC 1071 (https://tools.ieft.org/html/rfc1071)
Ideally this would act on the string as a series of 16-bit ints (host
packed), but this works.
Network data is big-endian, hosts are typically little-endian,
which makes this much more tedious than it needs to be.
"""
countTo = len(pkt) // 2 * 2
total, count = 0, 0
# Handle bytes in pairs (decoding as short ints)
loByte, hiByte = 0, 0
while count < countTo:
if (byteorder == "little"):
loByte = pkt[count]
hiByte = pkt[count + 1]
else:
loByte = pkt[count + 1]
hiByte = pkt[count]
total += hiByte * 256 + loByte
count += 2
# Handle last byte if applicable (odd-number of bytes)
# Endianness should be irrelevant in this case
if countTo < len(pkt): # Check for odd length
total += pkt[len(pkt) - 1]
total &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
# uses signed ints, but overflow is unlikely in ping)
total = (total >> 16) + (total & 0xffff) # Add high 16 bits to low 16 bits
total += (total >> 16) # Add carry from above (if any)
return socket.htons((~total) & 0xffff)
def ping(args):
sock, payload = args[0], args[1]
header = struct.pack("!BBH", 8, 0, 0)
checksum = calculate_checksum(header+payload)
header = struct.pack("!BBH", 8, 0, checksum)
timestamp = time.time()
sock.send(header+payload)
try:
response = sock.recv(20+len(payload))
except socket.timeout:
return 0
return (len(response), (time.time() - timestamp) * 1000)
host = argv[1] # A host that doesn't respond to ping packets > 1500B
# 1 is ICMP protocol number
sockets = [socket.socket(socket.AF_INET, socket.SOCK_RAW, proto=1) for i in range(12)]
for i, sock in enumerate(sockets):
sock.settimeout(0.1)
sock.bind(("0.0.0.0", i))
sock.connect((host, 1)) # Port number should never matter for ICMP
args = [(sockets[i], bytes(2**i)) for i in range(12)]
for arg in args:
print(ping(arg))
arg[0].close()
This actually shows me something more troubling - it seems that the rtt is actually decreasing with increasing packet size! Calling this program (as root, to get socket permissions) outputs:
0
0
(24, 15.784025192260742)
(28, 0.04601478576660156)
(28, 0.025033950805664062)
(28, 0.033855438232421875)
(28, 0.03528594970703125)
(28, 0.04887580871582031)
(28, 0.05316734313964844)
(28, 0.03790855407714844)
(28, 0.0209808349609375)
(28, 0.024080276489257812)
but now notice what happens when I try to send a packet of size 2048 using ping:
user#mycomputer ~/src/connvitals $ time ping -c1 -s2048 $box
PING <hostname redacted> (<IP address redacted>): 2048 data bytes
--- <hostname redacted> ping statistics ---
1 packets transmitted, 0 packets received, 100.0% packet loss
real 0m11.018s
user 0m0.005s
sys 0m0.008s
Not only is the packet dropped, but it takes 11 seconds to do so! So why - if my timeout is set to 100ms - is this packet getting a "successful" response from my python script in only ~0.04ms??
Thank you in advance for any help you can provide.
Update:
I just checked again, and it seems that it's multiple sockets that are the problem, and the threading seems to have nothing to do with it. I get the same issue when I ping with each socket - then immediately close it - sequentially.
All your sockets are identical, and all bound to the same host. There simply isn't any information in the packet for the kernel to know which socket to go to, and raw(7) seems to imply all sockets will receive them.
You're probably getting all the responses in all the threads, meaning you're getting 12 times as many responses per thread as you're expecting.

Scapy TCP Checksum Recalculation Odd behaviour

I'm trying to do a TCP ACK Spoofing. I sniff one ACK packet from a pcap file and send it in a loop incrementing its ACK number as well as another option field.
Sniffing Part: (Prespoofing)
from scapy.all import *
from struct import unpack, pack
pkt = sniff(offline="mptcpdemo.pcap", filter="tcp", count=15)
i=6
while True:
ack_pkt = pkt[i]
if ack_pkt.sprintf('%TCP.flags%') == 'A':
break
i+=1
del ack_pkt.chksum
del ack_pkt[TCP].chksum
print ack_pkt.chksum, ack_pkt[TCP].chksum
hex2pkt = ack_pkt.__class__
Spoofing Part: (Non Optimized)
count=1
while count<5:
ack_pkt[TCP].ack += 1
pkt_hex = str(ack_pkt)
rest = pkt_hex[:-4]
last_4_bit = unpack('!I',pkt_hex[-4:])[0]
new_hex_pkt = rest + pack('>I',(last_4_bit+1))
new_pkt=hex2pkt(new_hex_pkt)
#sendp(new_pkt, verbose=0)
print new_pkt.chksum, new_pkt[TCP].chksum
count+=1
The output comes like this: (Which is changing)
None None
27441 60323
27441 58895
27441 57467
27441 56039
After sending, The average time gap between two packets is around 15 ms. (For 1000 Packets)
When I check it with Wireshark, it shows "checksum is correct" for the 1st packet and "incorrect" for others.
Spoofing Part: (Little bit Optimized)
pkt_hex=str(ack_pkt)
rest1=pkt_hex[:42]
tcp_ack=unpack('!I',pkt_hex[42:46])[0]
rest2=pkt_hex[46:-4]
last_4_bit = unpack('!I',pkt_hex[-4:])[0]
count=1
while count<5:
new_hex_pkt = rest1 + pack('>I',(tcp_ack+1)) + rest2 + pack('>I',(last_4_bit+1))
new_pkt = hex2pkt(new_hex_pkt)
#sendp(new_pkt, verbose=0)
print new_pkt.chksum, new_pkt[TCP].chksum
count+=1
The output comes like this: (Which is not changing)
None None
27441 61751
27441 61751
27441 61751
27441 61751
After sending, The average time gap between two packets is around 10 ms. (For 1000 Packets)
The Checksum is not changing for the 2nd case. The process is quite same. Then what is the problem in the 2nd optimized case? And why the TCP checksum calculated in a loop are wrong for subsequent packets?
Note:
last_4_bit is not the checksum field.
I'm able to see the ack number of the packets being incremented in tcpdump.
After a extended testing, I saw that, del ack_pkt[TCP].checksum deletes the checksum. But while converting to hex string with str(ack_pkt), I guess, it recalculates the checksum. After trying:
ack_pkt = sniff(offline="mptcpdemo.pcap", filter="tcp", count=15)[14]
del ack_pkt[TCP].chksum
print ack_pkt[TCP].chksum
print str(ack_pkt)
It 1st prints the checksum as None. But while printing the hex string, I'm able to see that the checksum field is non zero and contains the actual recalculated checksum.
In the non-optimized code, inside the loop, the packet is converted to hex-string and hence it's re-calculating the checksum each time. But in the optimized version, conversion is outside the loop and hence it carries one value only.

Scapy how get ping time?

I'm trying to write a scapy script which can make an average on the ping time, so I need to get the time elapsed between ICMP echo/reply packet sent and reply packet received. For now, I have this:
#! /usr/bin/env python
from scapy.all import *
from time import *
def QoS_ping(host, count=3):
packet = Ether()/IP(dst=host)/ICMP()
t=0.0
for x in range(count):
t1=time()
ans=srp(packet,iface="eth0", verbose=0)
t2=time()
t+=t2-t1
return (t/count)*1000
The problem is that using time() function doesn't rise a good result. For example, I find 134 ms on one domain, and with the ping system function on the same domain, I have found 30 ms (average of course).
My question is: Is there a way to get the exactly time elpased beetween sent packet and received packet by scapy?
I don't want to use popen() function or other system call because I need scapy for futur packet management.
Is there a way to get the exactly time elpased beetween sent packet and received packet by scapy?
You can use pak.time and pak.sent_time
I modified your script to use them...
import statistics
import os
from scapy.all import Ether, IP, ICMP, srp
if os.geteuid() > 0:
raise OSError("This script must run as root")
ping_rtt_list = list()
def ping_addr(host, count=3):
packet = Ether()/IP(dst=host)/ICMP()
t=0.0
for x in range(count):
x += 1 # Start with x = 1 (not zero)
ans, unans = srp(packet, iface="eth0", filter='icmp', verbose=0)
rx = ans[0][1]
tx = ans[0][0]
delta = rx.time - tx.sent_time
print("ping #{0} rtt: {1} second".format(x, round(delta, 6)))
ping_rtt_list.append(round(delta, 6))
return ping_rtt_list
if __name__=="__main__":
ping_rtt_list = ping_addr('172.16.15.1')
rtt_avg = round(statistics.mean(ping_rtt_list), 6)
print("Avg ping rtt (seconds):", rtt_avg)
An example run:
$ sudo /opt/virtual_env/py37_test/bin/python ./ping_w_scapy.py
ping #1 rtt: 0.002019 second
ping #2 rtt: 0.002347 second
ping #3 rtt: 0.001807 second
Avg ping rtt (seconds): 0.002058
BTW, using unbuffered python (python -u to start it) increases the timing accuracy as python is not waiting for the buffers to decide to dump. Using your above script, it changed my results from being off by 0.4 ms to being off by 0.1-ish.
Mike, just a small fix in order to get the average time, change:
print "Average %0.3f" % float(match.group(1))
to:
print "Average %0.3f" % float(match.group(2))
since (match.group(1)) will get the min time and not the avg as mentioned.

Python and Scapy: plain text SMTP session

Is it possible to perform the the simplest SMTP session using Scapy?
I tried to read captured with tcpdump using Scapy and to send packets, but no luck...
This is what I have
#!/usr/bin/python
from scapy.all import *
from scapy.layers.inet import IP, TCP
source_ip = '1.2.3.4'
source_port = 5100
source_isn = 1000
dest_ip = '1.2.3.5'
dest_port = 25
ip=IP(src=source_ip, dst=dest_ip)
SYN=TCP(sport=source_port, dport=dest_port, flags="S", seq=source_isn)
SYNACK=sr1(ip/SYN)
source_isn = SYN.seq + 1
source_ack = SYNACK.seq + 1
CK=TCP(ack=source_ack, sport=source_port, dport=dest_port, flags="A",seq=source_isn)
handshakedone=sr1(ip/ACK)
DTA=TCP(ack=handshakedone.seq+len(handshakedone.load), seq=source_isn, sport=source_port,dport=dest_port,flags="PA")
sr(ip/DTA/Raw(load='mail from: test#gmail.com\r\n'))
send(ip/DTA/Raw(load='rcpto to: me#gmail.com\r\n'))
source_isn = ACK.seq + len(mfrom)
.....
RST=TCP(ack=SYNACK.seq + 1, seq=source_isn, sport=source_port, dport=dest_port, flags="RA")
send(ip/RST)
Handshake is successful but what ACK and SEQ values should be during the session? How can I calculate them?
TCP seq and ack numbers are described in rfc 793 (start at page 24). The whole spec is too long to post here, but basically, every byte of payload has a sequence number. In addition to the payload bytes, there are two control flags (SYN and FIN) that get their own sequence numbers. Initial sequence numbers should be randomized, but don't really matter if you're just playing around. The ack number in your packet is the next sequence number you expect to receive, and the seq field in the packet is the first sequence number in the segment.
So to ack all packets up to a given one, add the sequence number from the given packet to its length (including FIN or SYN flags, if set) and put that in your ack field.

Categories