Python Scapy RTP header manipulation - how can I decode RTP in scapy? - python

I need to edit 2 RTP header fields in a PACP file.
I want to edit the RTP timestamp field and the SSRC field. This is so that I can manipulate some capture for replay using tcpreplay for testing end device for a couple of reasons.
Using the Python REPL i am able to load the file:
from scapy.all import rdpcap
pkts_list = rdpcap("foo.pcap")
the I get the length of the file
len(pkts_list)
then I can inspect a packet
pkts_list[xxx].show()
How / Can I decode / see RTP header information? Sorry scapy docs are a bit think for me on this one.
Thanks in advance
Neil

RTP(pkts_list[xxx]['UDP'].payload).timestamp did the job with a bit of trial and error
my other question is more python related I will ask that somewhere else.
Update:
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)

you can use from this command for write a rtp header packet. but whet you trying to write this packet in a pcap. you will see this error:
TypeError: clone_with() got multiple values for keyword argument
'payload'
therefor you before write packet must change scapy/packet.py lib. this link explain better:
https://github.com/CJTozer/scapy/commit/d0367f2efa86e65fa2b0a82a864b7fe89222f153

Related

How to replace the IP addresses within a PCAP file using Scapy

I want to replace the IP addresses in a given PCAP file with Scapy.
The PCAP file (e.g. eth0.pcap) contains captures by Wireshark.
My message is not code, it shows the data flow between 2 IP addresses. I want to replace the original 2 addresses with another 2 addresses.
Example
Given before:
message1: 192.168.10.10-->192.168.20.20
message2: 192.168.20.20-->192.168.10.10
I want to replace for all packages within the file:
192.168.10.10 (source of first package) with 8.8.8.8
and 192.168.20.20 (destination of first package) with 1.1.1.1
So that afterwards there are:
message1: 172.10.10.10-->172.10.20.20
message2: 172.10.20.20-->172.10.10.10
How can I do this with?
hc_dev's answer guided me towards the solution.
I tried to understand it and make it work in my issue.
Below is my code:
from scapy.all import *
from scapy.utils import PcapWriter
packets = rdpcap('ftp.pcap')
new_cap = PcapWriter("ftp_new.pcap")
srcIp=packets[0][IP].src
dstIP=packets[0][IP].dst
#define new ip address I want use
ip1='8.8.8.8'
ip2='1.1.1.1'
#replace orinigal address
for p in packets:
if(p[IP].src == srcIp):
p[IP].src = ip1
elif(p[IP].src == dstIP):
p[IP].src = ip2
if(p[IP].dst == srcIp):
p[IP].dst = ip1
elif(p[IP].dst == dstIP):
p[IP].dst = ip2
new_cap.write(p)
wrpcap("ftp_new.pcap", packets)
So you want to modify packet capture output, like in PCAP file format.
This format is used by libpcap library to record captured packets to a file.
Scapy can read and write PCAP files, see the Scapy docs for PCAP.
Read, write, print, replace
To replace the IP addresses within this file, you have to read its packets into an object-model using the rdpcap() function.
Then you can print the IP addresses for each packet (with desired replacement).
Or you can also replace the IP within the object-model in memory.
Then write the complete model back using the wrpcap() function.
Example to print IP addresses
I used the example PCAP file dhcp.pcap from PCAP to Mermaid parser on GitHub:
from scapy.all import *
# load the PCAP file using rdpcap
packets = rdpcap('dhcp.pcap')
# Let's iterate through every packet
for packet in packets:
source_ip = packet.getlayer(IP).src
destination_ip = packet.getlayer(IP).dst
print(f"{source_ip} --> {destination_ip}")
# TODO: replace in model and write to PCAP file using wrpcap
See also
IncognitJoe's tutorial on Reading Pcap files with Scapy.
Getting source IP of packet using Scapy for getting the IP fields of a packet.
Update:
How to replace in your case
Refined talentldk's solution with some simplification and debug-prints:
no separate import needed (all imports all)
no writer needed, just use wrpcap to write the read model
use a replacement-dict with string's replace function where dict entry (a tuple) is passed to function using the unpack-operator * as prefix resulting in 2 separate arguments (key is replaced by value)
use iter over the dict's items to process all replacements (here 2 entries) where the next item can be drawn by next function
from scapy.all import *
# rdpcap loads in our pcap file
packets = rdpcap('dhcp.pcap')
# define search
first_src_ip = packets[0][IP].src
first_dst_ip = packets[0][IP].dst
# define new ip address to use as replacement
ip_replacement = {f"{first_src_ip}" : '8.8.8.8', f"{first_dst_ip}" : '1.1.1.1'}
print(f"replacement: {ip_replacement}")
# Let's iterate through every packet
for i, packet in enumerate(packets):
source_ip = packet.getlayer(IP).src
destination_ip = packet.getlayer(IP).dst
print(f"[{i:3}] original: {source_ip} --> {destination_ip}")
# replace in model
replacement = iter(ip_replacement.items())
source_ip = source_ip.replace(*next(replacement))
destination_ip = destination_ip.replace(*next(replacement))
print(f"[{i:3}] replaced: {source_ip} --> {destination_ip}")
wrpcap("dhcp_replaced.pcap", packets)
Prints:
replacement: {'0.0.0.0': '8.8.8.8', '255.255.255.255': '1.1.1.1'}
[ 0] original: 0.0.0.0 --> 255.255.255.255
[ 0] replaced: 8.8.8.8 --> 1.1.1.1
[ 1] original: 192.168.0.1 --> 192.168.0.10
[ 1] replaced: 192.168.0.1 --> 192.168.0.10
[ 2] original: 0.0.0.0 --> 255.255.255.255
[ 2] replaced: 8.8.8.8 --> 1.1.1.1
[ 3] original: 192.168.0.1 --> 192.168.0.10
[ 3] replaced: 192.168.0.1 --> 192.168.0.10

NFQueue/Scapy Man in the Middle

I'm trying to construct a man in the middle attack on a webpage (i.e. HTTP traffic). I'm doing this by using a Linux machine attached to Ethernet and a client attached to the Linux box via its WiFi hotspot.
What I've done so far is use NFQueue from within the IPTables Linux firewall to route all TCP packets on the FORWARD chain to the NFQueue queue, which a Python script is picking up and then processing those rules. I'm able to read the data off of the HTTP response packets, but whenever I try to modify them and pass them back (accept the packets), I'm getting an error regarding the strings:
Exception AttributeError: "'str' object has no attribute 'build_padding'" in 'netfilterqueue.global_callback' ignored
My code is here, which includes things that I've tried that didn't work. Notably, I'm using a third-party extension for scapy called scapy_http that may be interfering with things, and I'm using a webpage that is not being compressed by gzip because that was messing with things as well. The test webpage that I'm using is here.
#scapy
from scapy.all import *
#nfqueue import
from netfilterqueue import NetfilterQueue
#scapy http extension, not really needed
import scapy_http.http
#failed gzip decoding, also tried some other stuff
#import gzip
def print_and_accept(packet):
#convert nfqueue datatype to scapy-compatible
pkt = IP(packet.get_payload())
#is this an HTTP response?
if pkt[TCP].sport == 80:
#legacy trial that doesn't work
#data = packet.get_data()
print('HTTP Packet Found')
#check what's in the payload
stringLoad = str(pkt[TCP].payload)
#deleted because printing stuff out clogs output
#print(stringLoad)
#we only want to modify a specific packet:
if "<title>Acids and Bases: Use of the pKa Table</title>" in stringLoad:
print('Target Found')
#strings kind of don't work, I think this is a me problem
#stringLoad.replace('>Acids and Bases: Use of the pK<sub>a</sub>', 'This page has been modified: a random ')
#pkt[TCP].payload = stringLoad
#https://stackoverflow.com/questions/27293924/change-tcp-payload-with-nfqueue-scapy
payload_before = len(pkt[TCP].payload)
# I suspect this line is a problem: the string assigns,
# but maybe under the hood scapy doesn't like that very much
pkt[TCP].payload = str(pkt[TCP].payload).replace("Discussion", "This page has been modified")
#recalculate length
payload_after = len(pkt[TCP].payload)
payload_dif = payload_after - payload_before
pkt[IP].len = pkt[IP].len + payload_dif
#recalculate checksum
del pkt[TCP].chksum
del pkt[IP].chksum
del pkt.chksum
print('Packet Modified')
#redudant
#print(stringLoad)
#this throws an error (I think)
print(str(pkt[TCP].payload))
#no clue if this works or not yet
#goal here is to reassign modified packet to original parameter
packet.set_payload(str(pkt))
#this was also throwing the error, so tried to move away from it
#print(pkt.show2())
#bunch of legacy code that didn't work
#print(GET_print(pkt))
#print(pkt.show())
#decompressed_data = zlib.decompress(str(pkt[TCP].payload), 16 + zlib.MAX_WBITS)
#print(decompressed_data)
#print(str(gzip.decompress(pkt[TCP].payload)))
# print(pkt.getlayer(Raw).load)
#print('HTTP Contents Shown')
packet.accept()
def GET_print(packet1):
ret = "***************************************GET PACKET****************************************************\n"
ret += "\n".join(packet1.sprintf("{Raw:%Raw.load%}\n").split(r"\r\n"))
ret += "*****************************************************************************************************\n"
return ret
print('Test: Modify a very specific target')
print('Program Starting')
nfqueue = NetfilterQueue()
nfqueue.bind(1, print_and_accept)
try:
print('Packet Interface Starting')
nfqueue.run()
except KeyboardInterrupt:
print('\nProgram Ending')
nfqueue.unbind()
Apologies in advance if this is hard to read or badly formatted code; Python isn't a language that I write in often. Any help is greatly appreciated!

scapy PcapReader cut short

I'm trying to rewrite a pcap file with different IP and IPv6 addresses. But after I extract a packt by PcapReader and change its IP adresses, the packets in the output pcap file is cut short (that is to say the payload of the packet is lost).
Here's the example code:
from scapy.all import PcapReader
from scapy.all import PcapWriter
def test():
f = "input.pcap"
writers = PcapWriter("output.pcap")
with PcapReader(f) as pcap_reader:
for pkt in pcap_reader:
# somehow change the IP address
writers.write(pkt=pkt)
test()
When I open the .pcap file with WireShark, it shows "The capture file appears to have been cut short in the middle of a packet".
Is there any solution in scapy to maintain the payload or is there any other python packets to recommand?
here I did not change anything and the results are like this:
input file:
enter image description here
output file:
enter image description here
I think the problem must be in the code you use to modify the packet (and which you did not show) or that your source file already had short packets (i.e. snaplen less than packet len). The following code works for me without problems:
from scapy.all import PcapReader,PcapWriter,IP
writer = PcapWriter('output.pcap')
for pkt in PcapReader('input.pcap'):
# somehow change the IP address
pkt[IP].dst = '1.2.3.4'
pkt[IP].src = '5.6.7.8'
writer.write(pkt=pkt)

Filter protocols in python

I'm trying to filter certain packets with protocols using user input from a given pcap file and than move the packets to a new pcap file.
That the code I made so far:
# ===================================================
# Imports
# ===================================================
from scapy.all import *
from scapy.utils import PcapWriter
"""
your going to need to install the modules below
"""
from Tkinter import Tk
from tkFileDialog import askopenfilename
# ===================================================
# Constants
# ===================================================
#OS commands:
#~~~~~~~~~~~~~
if "linux2" in sys.platform:
"""
linux based system clear command
"""
CLEAR_COMMAND = "clear"
elif "win32" in sys.platform:
"""
windows based system clear command
"""
CLEAR_COMMAND = "cls"
elif "cygwin" in sys.platform:
"""
crygwin based clear command
"""
CLEAR_COMMAND = "printf \"\\033c\""
elif "darwin" in sys.platform:
"""
mac OS X based clear command
"""
CLEAR_COMMAND = "printf \'\\33c\\e[3J\'"
#Usage string:
#~~~~~~~~~~~~~~
FILE_STRING = "please choose a pcap file to use"
BROWSE_STRING = "press any key to browser files\n"
BAD_PATH_STRING = "bad file please try agien\n"
BAD_INPUT_STRING = "bad input please try agien\n"
PROTOCOL_STRING = "please enter the protocol you wish to filter\n"
NAME_STRING = "please enter the new pcap file name\n"
# ===================================================
# Code
# ===================================================
def filter_pcap():
"""
filtering from the given pcap file a protocol the user chooce (from any layer in the OSI model)
and than asks for a new pcap file name, than filters the given protocol to a new pcap file
:param none
:return nothing:
"""
path = file_browse()
i = 0
filtertype = raw_input(PROTOCOL_STRING)
name = raw_input(NAME_STRING)
packs = rdpcap(path)
for i in range(len(packs)):
if filtertype in packs[i]:
wrpcap(name +".pcap", packs[i])
def file_browse():
"""
Purpose: It will allow the user to browse files on his computer
than it will check if the path is ok and will return it
:returns - the path to the chosen pcap file
"""
path = "test"
while ".pcap" not in path:
print FILE_STRING
raw_input(BROWSE_STRING)
os.system(CLEAR_COMMAND)
Tk().withdraw()
path = askopenfilename()
if ".pcap" not in path:
print BAD_PATH_STRING
return path
filter_pcap()
Now the problem is that I'm failing to filter the packets correctly.
The code need to filter protocols from any layer and any kind.
I have checked that thread: How can I filter a pcap file by specific protocol using python?
But as you can see it was not answered and the user added the problems I had in the edit, if any one could help me it would be great
Example for how it should work:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lets say i use the file "sniff" as my first pcap file and it has 489 packets when 200 from those packets are http packets.
now there is that print:
please enter the protocol you wish to filter
'http'
and than there is the print:
please enter the new pcap file name
'new'
the user input was 'http' now the program will search for every packet that run on http protocol and will create a new pcap file called 'new.pcap'.
the file 'new.pcap' will contain 200 http packets.
now that thing should work with any protocol on the OSI model including protocols like IP, TCP, Ethernet and so on (all the protocols in the ascii model).
I have found out that wireshark command line has the option -R and tshark has .protocols, but it dont really work... Can any one check that?
edit: i found pyshark but i dont know how to write with it
I don't believe that scapy has any functions or methods to support application layer protocols in the way that you are after. However, using sport and dport as a filter will do the trick (provided you are going to see/are expecting default ports).
Try something like this:
def filter_pcap(filtertype = None):
..... snip .....
# Static Dict of port to protocol values. Like so:
protocols = {'http': 80, 'ssh': 22, 'telnet': 21}
# Check to see if it is in the list
while filtertype not in protocols:
filtertype = raw_input(PROTOCOL_STRING)
# Name for output file
name = raw_input(NAME_STRING)
# Read Input File
packs = rdpcap(path)
# Filters to only TCP packets
for packet in packs[TCP]:
# Filter only the proto (aka Port) that we want
if protocols[filtertype] in packet.sport or protocols[filtertype] in packet.dport :
# Write to file
wrpcap(name +".pcap", packet)

Show protocols of packets captured and saved in a .pcap with scapy on python

I am capturing live air WiFi traffic and saving only the headers of the packets captures in a .pcap file.
Is it possible to find out what protocols have been used on the whole capture? If yes, how can I keep track of the number of packets under every protocol found?
I've found a lot of info on injecting packets with Scapy but not on analyzing.
So far I've tried:
from scapy.all import * # import scapy package
from scapy.utils import rdpcap # import module for loading pcaps
pkts = rdpcap("./traffic/capture20131120-001.pcap") # load pcap
pkts.summary(lambda(r): r.sprintf("%Dot11.proto%")) # protocol?
print -(256-ord(pkts[24].notdecoded[-4:-3])) # signal strength of packet 24
Seems like pkts.summary(lambda(r): r.sprintf("%Dot11.proto%")) returns 0L and I don't understand that.
Currently, Scapy does not support very many protocols, so it's great for some tasks, but not others. Using pyshark instead (a Python wrapper for Wireshark), there are many more supported protocols.
Using Scapy:
from scapy.all import *
def process_with_scapy(fileName):
protocol_count = {}
pcap_data = rdpcap(fileName)
sessions = pcap_data.sessions()
for session in sessions:
for packet in sessions[session]:
for i in range(len(packet.layers())):
layer = packet.getlayer(i)
protocol = layer.name
# Count the number of occurences for each protocol type
if protocol not in protocol_count: protocol_count[protocol] = 1
else: protocol_count[protocol] += 1
# Sort the dictionary in descending order
protocol_count = dict(sorted(protocol_count.items(), key=lambda item: item[1], reverse=True))
# Print the output
for protocol in protocol_count:
print(f'{protocol_count[protocol]} packets have layer "{protocol}"')
process_with_scapy('./traffic/capture20131120-001.pcap')
Documentation:
https://readthedocs.org/projects/scapy/downloads/pdf/latest
Using PyShark (slower but more supported):
import pyshark
def process_with_pyshark(fileName):
protocol_count = {}
pcap_data = pyshark.FileCapture(fileName)
for packet in pcap_data:
for layer in packet:
protocol = layer.layer_name
# Count the number of occurences for each protocol type
if protocol not in protocol_count: protocol_count[protocol] = 1
else: protocol_count[protocol] += 1
# Sort the dictionary in descending order
protocol_count = dict(sorted(protocol_count.items(), key=lambda item: item[1], reverse=True))
# Print the output
for protocol in protocol_count:
print(f'{protocol_count[protocol]} packets have layer "{protocol}"')
process_with_pyshark('./traffic/capture20131120-001.pcap')
For information on a specific protocol:
https://www.wireshark.org/docs/dfref/
The source code for a specific protocol dissector can also sometimes be useful:
https://github.com/wireshark/wireshark/tree/master/epan/dissectors

Categories