Scapy Sniffing with Custom Actions
Part 2
In the previous article I demonstrated how to add a custom function to change the formatting of the packet output in the console or do some sort of custom action with each packet. That example passed the function (a Callable) without any additional args. When the prn passed function is called (with each packet), it receives a single argument of the packet that was just sniffed.
Using nested functions to harness the power of closure, you can bind any number of arguments to the function that is executed on each packet by Scapy. In order to bind additional arguments to the prn function, we have to use nested functions (similar to a decorator). Check out this example, created to upload the scapy packet info to an API via the Python Requests module:
#! /usr/bin/env python3
import json
import requests
from collections import Counter
from scapy.all import sniff
# define API options
url = "http://hosted.app/api/packets"
token = "supersecretusertoken"
# create parent function with passed in arguments
def custom_action(url: str, token: str):
# uploadPacket function has access to the url & token parameters
# because they are 'closed' in the nested function
def upload_packet(packet):
# upload packet, using passed arguments
headers = {'content-type': 'application/json'}
data = {
'packet': packet.summary(),
'token': token,
}
r = requests.post(url, data=data, headers=headers)
return upload_packet
sniff(prn=custom_action(url, token))
This may seem a little strange, but here's an order-of-events explanation for what's happening:
- We define our
url
&token
variables, just like in the first example. - We define the
custom_action
function. This will be run when the scapysniff
function first runs to get the value info for theprn
argument. Note the two parameters that we pass intocustom_action
. - Inside
custom_action
, we create another function that takes the scapy implicitly passed packet as a parameter. This is the function that will upload the packet info to our API. - The
upload_packet
function is nested incustom_action
so it has access to theurl
&token
variables because it is inside the parent function's scope. - The return value of custom_action is the
upload_packet
function, so this function will be run along with every sniffed packet based on theprn
argument. Even though thecustom_action
function is not executed to take in theurl
&token
parameters, they are locked into the nestedupload_packet
function due to Python's capacity for 'closure'. - After we define the
custom_action
and nestedupload_packet
functions, we run the Scapysniff
function with the returned value ofcustom_action
passed via theprn
argument.
Using closures to 'lock-in' any number of arguments to the custom_action
function gives us much more flexibility. I was able to modularize my custom_action.py
file in order to clean up my Scapy sniffing script.
Here's another example that achieves the same affect using functools.partial
to close in the url & token arguments:
#! /usr/bin/env python3
import json
import requests
from collections import Counter
from functools import partial
from scapy.all import sniff
# define API options
url = "http://hosted.app/api/packets"
token = "supersecretusertoken"
def upload_packet(api_endpoint: str, api_token: str, packet):
# upload packet, using passed arguments
headers = {'content-type': 'application/json'}
data = {
'packet': packet.summary(),
'token': api_token,
}
r = requests.post(api_endpoint, data=data, headers=headers)
sniff(prn=partial(upload_packet, url, token))
See how I was able to move a big chunk of verbose packet protocol checking into a separate module by looking at my two python files in this project: Scapy-to-API