© Chet Hosmer 2018
Chet HosmerDefending IoT Infrastructures with the Raspberry Pihttps://doi.org/10.1007/978-1-4842-3700-7_4

4. Raspberry Pi as a Sensor

Chet Hosmer1 
(1)
Longs, South Carolina, USA
 

Moving from a packet recorder to a packet sensor requires us to examine the differences between the activity that was observed during the recording period versus the active monitoring for aberrant behavior.

Turning the Packet Recorder into a Sensor

As we advance the PacketRecorder into a complete sensor platform that can monitor a live network and report anomalies, several major enhancements need to be made. These enhancements will make it easier to
  1. 1.

    Operate the recorder and sensor using the same interface.

     
  2. 2.
    Generate HTML reports that cover the following:
    1. a.

      Overall master report

       
    2. b.

      Observed MAC addresses/manufacturers

       
    3. c.

      Observed country connections

       
    4. d.

      Observed port usage

       
    5. e.

      Observed possible IoT connections

       
    6. f.

      Observed possible industrial control system (ICS) connections

       
    7. g.

      Alerts generated during sensor mode

       
     
  3. 3.

    Provide basic status information directly on the Raspberry Pi.

     
  4. 4.

    Finally, produce the recorder/sensor as a single executable file.

     

Raspberry Pi Sensor/Recorder Design

As you can see in Figure 4-1, the major operational elements of the design include an event-driven GUI (Graphical User Interface), completely developed in native Python, using the TKinter standard library. This adheres to our goal of keeping the code base small and portable. This ensures compatibility with new Raspberry Pi devices as they progress.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig1_HTML.jpg
Figure 4-1

Snapshot of the Raspberry Pi sensor/recorder GUI

In addition, a real-time ePaper display was added (as an optional element) to the Raspberry Pi itself (Figure 4-2). This provides feedback directly from the Raspberry Pi in both recording and monitoring modes. More information regarding the PaPirus ePaper display is available from the manufacturer at www.pi-supply.com/product/papirus-epaper-eink-screen-hat-for-raspberry-pi/ .
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig2_HTML.jpg
Figure 4-2

Raspberry Pi configured with a PaPirus real-time display

Design Overview

Figure 4-3 depicts the overall operational design of the Pi sensor/recorder.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig3_HTML.jpg
Figure 4-3

Raspberry Pi sensor/recorder

The Pi sensor/recorder is set up to execute within an event-driven application loop supported by Python and TKinter.

The main code section is established as follows:

# Script Constants
# M1
NAME    = "Raspberry Pi - IoT/ICS Packet Sensor / Recorder"
VERSION = " Version .99-4 Experimental "
TITLE   = NAME+' '+VERSION
# Initialize the root TK Window
# M2from Tkinter import *
root = Tk()    
def main():
    # Set the Title for the Main Window
    # M3
    root.title(TITLE)   
    # Instantiate the GUI Application Object
    app = Application(root)
    # Start App MainLoop  
    app.mainloop()
# Main Script Starts Here
# M3
if __name__ == '__main__':
    main()

A quick overview of the initialization is defined here:

M1: Creates the TITLE constant to be displayed in the application window title bar.
../images/448940_1_En_4_Chapter/448940_1_En_4_Figa_HTML.jpg

M2: Imports the TKinter Python library and instantiates a new TK object. This will be used as the main window object and event handler in the application.

M3: The main takeaway here is the highlighted call that initializes the application by passing in the root object instantiated from TK.

Next, we’ll look at this application handler and the list of methods that have been created to control the distinct aspects of the application.

class Application(Frame):
    ''' APPLICATION CLASS GUI FRAME USING Tkinter '''    
    def __init__(self, master=None):
        # Define the instance variables to be
        # collected from the GUI
        # A1
        self.folderSelection   = ''
        self.baselineSelection = ''
        self.baselineGood      = False
        self.reportFolderGood  = False
        self.abortFlag         = False
        self.baselineCC  = {}
        self.baselineMAC = {}
        # Create the basic frame
        # A2
        Frame.__init__(self, master)
        self.parent = master
        # Initialize the GUI
        self.initUI()
        # Initialize PaPirus if available
        # A3
        if PA_ON:
            self.paObj = PA()

Examining the main sections of the application class, we find three main sections:

A1: Establishes object attributes that will be associated with each instantiation of the application class. For example, variables that hold state information regarding the baseline and report selections are initialized here, along with dictionaries that will be used by the sensor during monitoring activities. For example, the baselineCC dictionary will hold countries that were observed during the recording phase. Then any new country observations that are observed during the sensor stage can be reported as anomalous.

A2: Creates the parent window frame that will be used by the application. Most importantly, the initUI() method is called; this establishes all the GUI widgets on the window, such as labels, buttons, text boxes, drop-down lists, progress bars, status displays, and menu options.

In the next section we will take a look at the list of methods that have been created and examine a couple of those in detail.

A3: Finally, if a PaPirus display was attached and detected, an object is instantiated to handle the interface with the display. We will see how this is done later in this chapter.

Now let’s take a 30,000-foot view of the methods that have been created to handle the user interface and perform the defined functions. At this point we are just looking at the methods that have been defined as shown in Figure 4-4 and Table 4-1.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig4_HTML.jpg
Figure 4-4

Application object methods

Brief descriptions of the application object methods are given in Table 4-1.
Table 4-1

GUI method definitions

Method

Description

initUI

Creates and initializes all the display widgets found on the application frame. Once all the widgets are created, the lookup tables used by the application are loaded and the status bar is updated.

btnSelectFolder

Handles the button click to the right of the Report Folder selection and provides a folder browser for the user. The user must select an existing folder, or create a new folder to store the results of the record baseline or activate sensor selections.

btnSelectBaseline

Handles the selection of an existing baseline that will be used in sensor mode to detect anomalies from the recorded baseline, for example, new connections, devices, port usage and countries contacted.

btnPerformCapture

Begins the record baseline process. Based on the selected duration, this method will run to completion unless interrupted by the stop button. Note: this button will not be active until a report folder has been selected.

btnActivateSensor

Utilizes the selected baseline and begins the process of monitoring network activity and comparing those results to the baseline. Like the btnPerformCapture method, it will run for the selected duration unless interrupted by the stop button. Note: this button will not be active until a report folder has been selected along with a valid baseline.

btnStopCapture

Activated upon pressing the stop button during a baseline recording or sensor execution. It will interrupt the recording or sensor, but will store the intermediate results. Note: this button will only be activated during baseline recording or sensor monitoring activity.

btnViewSelectedReport

Displays the report currently selected by the user in the Select Report drop-down menu. Possible reports include the following:

Master report

Manufacturer report (OUI device name)

Country report

Port usage report

IoT report

ICS report

btnViewAlerts

Activated by the user pressing the view alerts button. This button is only active after a sensor execution has been completed. The method will display the current alert report generated by the last sensor operation.

The next set of methods perform operations upon completing the recording of a baseline. The method uses the unique dictionary created during the baseline recording process.

SaveOB

Saves the baseline as a serialized Python pickle object. The baseline is saved in the baseline directory that is created under the selected report folder.

GenCSV

Generates a comma-separated value (CSV) file in the reports folder. The CSV contains all the unique observations during the record baseline process.

GenHTML

Generates the master HTML report

GenCountry

Generates the country HTML report

GenICS

Generates the possible ICS observed activity HTML report

GenIoT

Generates the possible IoT observed activity HTML report

GenMFG

Generates the observed manufacturers HTML report

GenPortUsage

Generates the PortUsage HTML report

translateAlertCodes

Converts alert codes generated by the Pi sensor into meaningful messages

GenAlerts

Generates the alerts HTML report generated by the sensor operation

btnStopCapture

Allows the user to stop the recording or sensor, but still generate the reports

MenuAbout

Displays the application about box

MenuToolsExit

Handles the exiting of the MenuTools

Method Details

Next, we will take a deeper look at some of the key methods defined here.

Note

A complete listing of the completed Python solutions is available in the Appedix A, provides instructions on accessing the source code.

Initializing the GUI (initUI)
def initUI(self):
    # Create Menu Bar
    # U1
    menuBar = Menu(self.parent)  # menu begin
    toolsMenu = Menu(menuBar, tearoff=0)
    toolsMenu.add_command(label='About', accelerator='Ctrl+A',
        command=self.menuAbout, underline=0)
    toolsMenu.add_separator()
    toolsMenu.add_command(label='Exit', accelerator='Ctrl+X',
         command=self.menuToolsExit)
    menuBar.add_cascade(label='Help', menu=toolsMenu, underline=0)   
    self.parent.config(menu=menuBar)  # menu ends
    self.bind_all("<Control-x>", self.menuToolsExit)
    self.bind_all("<Control-a>", self.menuAbout)
   # Folder Selection
   # U2
    self.lblReport = Label(self.parent, anchor="w", text="Report Folder")
    self.lblReport.grid(row=1, column=0, padx=5, pady=10, sticky="w")
    self.ReportFolder = Label(self.parent, anchor="w", bd=3, bg = 'white', fg="black",width=80, relief=SUNKEN)   
    self.ReportFolder.grid(row=1, column=1, padx=5, pady=0, sticky="w")
self.buttonReportFolder = Button(self.parent, text=' ... ',
    command=self.btnSelectFolder, width=5, bg ='gray', fg="black",
    activebackground='black', activeforeground="green")
    self.buttonReportFolder.grid(row=1, column=1, padx=585, pady=0, sticky="w")
    self.lblBaseline = Label(self.parent, anchor="w", text="Select Baseline")
    self.lblBaseline.grid(row=2, column=0, padx=5, pady=10, sticky="w")   
    self.fileBaseline = Label(self.parent, anchor="w", bd=3, bg = 'white', fg="black",width=80, relief=SUNKEN)
    self.fileBaseline.grid(row=2, column=1, padx=5, pady=0, sticky="w")
    self.buttonSelectBaseline = Button(self.parent, text=' ... ',
    command=self.btnSelectBaseLine, width=5, bg ='gray', fg="black",
    activebackground='black', activeforeground="green")
   self.buttonSelectBaseline.grid(row=2, column=1, padx=585, pady=0,
            sticky='w')      
    # Specify the Duration of the Scan
    # U3
    self.lblDuration = Label(self.parent, anchor="w", text="Select Duration")
    self.lblDuration.grid(row=3, column=0, padx=5, pady=10, sticky="w")
    self.durationValue = StringVar()
    self.duration = ttk.Combobox(self.parent,
            textvariable=self.durationValue)
    self.duration['values'] = ('1-Min', '10-Min', '30-Min', '1-Hr', '4-Hr', '8-Hr', '12-Hr', '18-Hr', '1-Day', '2-Day', '4-Day', '7-Day','2-Week', '4-Week')
    self.duration.current(0)
    self.duration.grid(row=3, column=1, padx=5, pady=10, sticky="w")
    # Capture Packet Button
    # U4 Action Buttons
    self.ActivateSensor = Button(self.parent, text='Activate Sensor',
            command=self.btnActivateSensor, bg ='gray', fg="black",
            activebackground='black', activeforeground="green")
    self.ActivateSensor.grid(row=8, column=1, padx=5, pady=5, sticky=W)
    self.ActivateSensor['state']=DISABLED
    self.CapturePackets = Button(self.parent, text='Record Baseline',
            command=self.btnPerformCapture, bg ='gray', fg="black",
            activebackground='black', activeforeground="green")
    self.CapturePackets.grid(row=8, column=1, padx=120, pady=5, sticky=W)
    self.CapturePackets['state']=DISABLED
    self.StopCapture = Button(self.parent, text="STOP",
            command=self.btnSTOPCapture, bg ='gray', fg="black",
        activebackground='black', activeforeground="green")
    self.StopCapture.grid(row=8, column=1, padx=240, pady=5, sticky=W)
    self.StopCapture['state']=DISABLED        
    self.ViewAlerts = Button(self.parent, text='View Alerts',
            command=self.btnViewAlerts, bg ='gray', fg="black",
            activebackground='black', activeforeground="green")
    self.ViewAlerts.grid(row=8, column=1, padx=320, pady=5, sticky=W)
    self.ViewAlerts['state']=DISABLED          
    # SETUP a Progress Bar
    # U5 Progress Bar Setup
    self.progressLabel = Label(self.parent, anchor="w", text="Progress")
    self.progressLabel.grid(row=9, column=0, padx=0, pady=10, sticky="w")
    self.progressBar = ttk.Progressbar(self.parent, orient="horizontal",
            mode='determinate')
    self.progressBar.grid(row=9, column=1, padx=5, pady=10, sticky="w")
    # Special Code to align the width of the progress bar
    colWidth = self.ReportFolder.winfo_width()
    self.progressBar['length'] = colWidth
    self.update()        
    # Report Setup
    # U6 Reporting
    self.lblReport = Label(self.parent, anchor="w", text=" Select Report")
    self.lblReport.grid(row=3, column=1, padx=175, pady=10, sticky="w")        
    self.ReportSelection = StringVar()
    self.report = ttk.Combobox(self.parent, textvariable=self.ReportSelection)
    self.report['values'] = ('Master Report', 'MFG Report', 'Country Report', 'Port Usage Report', 'ICS Report', 'IoT Report')
    self.report.current(0)
    self.report.grid(row=3, column=1, padx=275, pady=10, sticky="w")
    # View Report
    self.viewReport = Button(self.parent, text='View Selected Report', command=self.btnViewSelectedReport, bg ='gray', fg="black", activebackground="black", activeforeground="green")
    self.viewReport.grid(row=3, column=1, padx=425, pady=5, sticky=W)
    self.viewReport['state']=DISABLED
   # Status Message
   # U7 Status Bar
    self.statusText = Label(self.parent, anchor="w", width=80, bd=3, bg ='white', fg="black", relief=SUNKEN)     
    self.statusText.grid(row=10, column=0, columnspan=2, padx=5, pady=5, sticky="w")
    self.update()

Defining a GUI in Python can be accomplished with many different third-party libraries. However, here we have chosen to utilize the built-in Python TKinter Library, TK for short. Tk/Tcl is an integral component of standard Python. It provides a robust and platform independent windowing toolkit that is readily available to Python programmers using the TKinter module, and its extensions. The extensions include the Tix and ttk modules. Additional details regarding TKinter can be found in the Python Standard Library at https://docs.python.org/2/library/tk.html .

The TKinter module is a thin object-oriented layer on top of Tcl/Tk, which provides a set of wrappers that implement the Tk widgets as Python classes. In addition, the internal module _TKinter provides a threadsafe mechanism which allows Python and Tcl to interact.

Using TKinter requires us to make specific declarations and configurations for each of the onscreen widgets along with any event handlers (for example button clicks) for each widget.

Configuring TK can be done using one of two geometry-based methods, commonly referred to as Grid and Pack. We have chosen to use the Grid method. Using the Grid method organizes widgets in a table-like structure, where each widget (buttons, labels, combo boxes, progress bars, etc.) are then placed at a specific row and column location.

In addition to visual widgets, other objects such as menu-based objects like those declared in the U1 highlighted section are placed on the frames menu bar.

In order to better explain how this is done, we will walk through each code section U1 through U7.

U1-Menu Bar
This section declares the simple menu item “Help” that contains just three items:
  1. 1.

    About

     
  2. 2.

    A horizontal separator line

     
  3. 3.

    Exit

     

In addition, the keyboard shortcut bindings for Ctrl-X and Ctrl-A are defined to allow keystroke menu selections.

Finally, specific command executions are associated with the About and Exit menu options. For example:

command=self.menuAbout
command=self.menuToolsExit

If you examine Table 4-1 you will see the declarations for these two methods as part of the application object. We will examine those methods later in this chapter.

This produces the menu as shown in Figure 4-5.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig5_HTML.jpg
Figure 4-5

Menu bar illustration

U2-Folder and File Selection
The folder selection code section defines two selections, and each selection contains three widgets:
  1. 1.

    A label widget that indicates the name of the field

     
  2. 2.

    A sunken label that will hold the resulting user selection

     
  3. 3.

    A button to launch the requisite folder and file selection dialogs

     

Taking a close look at the first folder selection, we first define the label widget with the text Report Folder and place that label at row 1, column 0 on the parent frame and we anchor the frame to the westmost position in the column.

self.lblReport = Label(self.parent, anchor="w", text="Report Folder")
self.lblReport.grid(row=1, column=0, padx=5, pady=10, sticky="w")

Next, we specify another label widget at row 1, column 1 and specify the label to be sunken to represent data that is specified.

self.ReportFolder = Label(self.parent, anchor="w", bd=3, bg = 'white',
        fg='black',width=80, relief=SUNKEN)   
self.ReportFolder.grid(row=1, column=1, padx=5, pady=0, sticky="w")

Finally, we add a button widget at row 1, column 2 that will launch a dialog box for the user to select the folder where reports, baseline, and alerts will be stored. Notice this widget has a command associated with self.btnSelectFolder. This method is also defined in Table 4-1, and again, we will examine the details of this method. The method source code is shown here.

self.buttonReportFolder = Button(self.parent, text=' ... ',
        command=self.btnSelectFolder, width=5, bg ='gray', fg="black",
        activebackground='black', activeforeground="green")
self.buttonReportFolder.grid(row=1, column=1, padx=585, pady=0,
        sticky='w')
U3-Duration Selection
Duration selection specifies two widgets. The first is a label to display “Select Duration”, and the second is a combo box to list the possible duration options available. The label is placed at row 3, column 0, while the combo box is placed at row 3, column 1. When the user clicks the combo box, the list of possible options is displayed as shown in Figure 4-6. The current selection is maintained by the widget and we can retrieve that selection at any time. Of course, the string value will have to have been converted into a useable time value.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig6_HTML.jpg
Figure 4-6

Duration selection

U4-Action Buttons

The action buttons, activate sensor, record baseline, stop and view Alerts are defined here. They are all defined to be placed in row 8, column 1. However, each contains a different padx value (padding from the westmost position of the row) allowing the buttons to be separated. Without the padding, they would be displayed on top of each other.

In addition, each button has a defined command associated with it that will be executed when pressed.

Also, notice that each of the buttons is set to DISABLED. The rationale is that the buttons cannot activate the specific operations until the report folder and/or the baseline have been correctly selected.

In addition, the stop button will be enabled once either the activate sensor or record baseline operations are underway, allowing the user to interrupt the operations. Once the selections have been made the buttons become activated, as shown in Figure 4-7.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig7_HTML.jpg
Figure 4-7

Report and baseline selections enable activate sensor and record baseline buttons

The source code for each button selection are covered in the GUI Source Code Selection.

U5-Progress Bar

When the activate sensor or record baseline process is underway, a progress bar will be displayed to depict the time remaining in the scan. For this widget we are using a label and a ttk progress bar widget.

U6-Report Selection
As with the duration selection widgets, we are using a combo box to provide a list of possible reports that can be selected, a label to display the text “Select Report”, and a button to display the selected report. Note that the view selected report button is also disabled during initialization and only enabled when reports are available for display, as shown in Figure 4-8.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig8_HTML.jpg
Figure 4-8

Select report section

U7-Status Bar
The last section defines the status bar at the bottom of the frame. This is used to report status as the application executes. Once again, we use a simple sunken label widget for the status bar (Figure 4-9). The widget is placed at row 10, column 0.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig9_HTML.jpg
Figure 4-9

Application status bar

Exploring Other Application Methods

Now that we have initialized the application interface, let’s look at the underlying functions that perform operations based on the user interactions described in Figure 4-3. We will start with selection of the report folder and baseline. The application will write newly generated reports to the selected report folder. In addition, the subfolders Baselines and Alerts will be created to hold any recorded baselines and alerts generated during active sensor operation.

Selecting the Report Folder (btnSelectFolder)

Start with the btnSelectFolder method (depicted in code segment F1), which is activated upon the button click action defined in “U2 Folder and File Selection.” This section is straightforward; we are using the built-in tkFileDialog.askdirectory function, which displays a directory selection dialog as shown in Figure 4-10. As you can see, the baselines and alerts folders have also been created.

If the selection result is a valid directory (we use the os.path.isdir() method to verify this) then we can enable the record baseline button. In addition, if the baseline has previously been established, then the activate sensor button could also be enabled.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig10_HTML.jpg
Figure 4-10

Selection of the report folder

Source Code Methods for GUI Elements
    # F1 Report Folder Selection
    def btnSelectFolder(self):
        try:
            self.folderSelection = tkFileDialog.askdirectory(initialdir="./",
                        title='Select Report Folder')  
            self.ReportFolder['text'] = self.folderSelection
            if os.path.isdir(self.folderSelection) and
                        os.access(self.folderSelection, os.W_OK):
                self.reportFolderGood = True
                self.statusText['text'] = "Report Folder Selected"
                self.update()                
                ''' Ok to enable Record Baseline Button '''
                self.CapturePackets['state']=NORMAL
                if self.baselineGood:
                    self.ActivateSensor['state']=NORMAL
            else:
                self.reportFolderGood = False
                self.statusText['text'] = "Invalid Folder Selection ... Folder
                        must exist and be writable"
                self.update()                
        except Exception as err:
            self.reportFolderGood = False
        self.update()
    # Baseline Selection
    def btnSelectBaseLine(self):
        self.fileSelection = tkFileDialog.askopenfilename(initialdir="./",
                        self.fileSelection =
                         tkFileDialog.askopenfilename(initialdir="./",
                         filetypes=[("Sensor Baseline Files","*.baseline")],
                         title='Select Baseline File')  title='Select Baseline
                         File')  
        self.fileBaseline['text'] = self.fileSelection
        if self.fileBaseline:
            try:
                with open(self.fileSelection, 'rb') as base:
                    try:
                        ''' Make sure we loaded a dictionary '''
                        self.baselineDictionary = pickle.load(base)    
                        ''' Make sure the elements match our structure'''
                        if type(self.baselineDictionary) is dict:
                            value = self.baselineDictionary.values()[0]
                            if value[POV] == 'S' or value[POV] == 'D':
                                self.baselineGood = True
                            else:
                                self.baselineGood = False
                                self.statusText['text'] = "Baseline Load Failed"
                            if self.baselineGood:
                                ''' Create Quick Lookups for Country, MFG'''
                                self.statusText['text'] = "Loading Baseline
                                                Contents"
                                self.update()
                                for key, value in
                                                self.baselineDictionary.iteritems():
                                    try:
                                       srcCC = value[SRCCC]
                                       dstCC = value[DSTCC]
                                       srcMAC = value[SRCMAC]
                                       dstMAC = value[DSTMAC]
                                        if srcCC != '' and srcCC.lower() !=
                                                    'unknown':
                                            self.baselineCC[srcCC] = 1
                                        if dstCC != '' and dstCC.lower() !=
                                                    'unknown':
                                            self.baselineCC[dstCC] = 1
                                        if srcMAC != '' and srcMAC.lower() !=
                                                    'unknown':
                                            self.baselineMAC[srcMAC] = 1
                                        if dstMAC != '' and dstMAC.lower() !=
                                                    'unknown':
                                            self.baselineMAC[dstMAC] = 1       
                                    except:
                                        ''' ignore errors in baseline
                                                    loading'''
                                        continue
                                self.statusText['text'] = "Loading Baseline
                                                Completed"
                                ''' Ok to enable Activate Sensor Button '''
                                if self.reportFolderGood:
                                    self.ActivateSensor['state']=NORMAL    
                    except Exception as err:
                        self.statusText['text'] = "Baseline Load Failed"
            except Exception as err:
                self.statusText['text'] = "Baseline Load Failed: "+str(err)
        self.update()        

When we examine the btnSelectBaseLine method depicted in F2, we see that this function is a bit more complicated. First, this method uses the built-in tkFileDialog.askopenfilename to select the baseline. Since the user can select any file with the .baseline extension, we need to verify that this is a valid baseline generated by the record baseline method. Once this is verified, we create a set of local dictionaries to hold extracted values from the baseline, including previously observed countries and MAC addresses; these will be used during the monitoring process to generate alerts from unknown countries and new observed MAC addresses.

Once a verified baseline and report folder have been selected, both the activate sensor and record baseline selections are available, as shown in Figure 4-11.
../images/448940_1_En_4_Chapter/448940_1_En_4_Fig11_HTML.jpg
Figure 4-11

Properly selected and verified report folder and baseline

Record Baseline Method (btnPerformCapture)
Now we move to one of the critical methods of the application, the record baseline or btnPerformCapture method. This method utilizes two selections by the user:
  • Duration (determine how long to run the recording)

  • Report folder (where to record the results)

The method first performs some setup tasks (section R1) to disable the other action buttons and to enable the Stop button, allowing the user to interrupt the recording. In addition, a packet processor object is instantiated, which in turn loads the necessary lookups for manufacturer OUI identification, port and protocol translations, and country lookups. If the PaPirus display is detected and available, it will be initialized to display details of the ongoing recording.

Finally, the network adapter is set to promiscuous mode to collect all traffic presented at Eth0.

Moving to section R2, the main loop is established and processes each packet observed over the network. The loop continues until either the duration time expires or the user presses the stop button. Every 2 seconds, the packet count is updated in the status bar and the progress bar is updated marking the progress toward the time expiration.

Once R2 completes (either by the user interrupting the process or through time expiration), a new baseline is created and stored in the baseline directory, and all the HTML and CSV reports are generated and stored in the selected report folder. The code in the R3 section calls each report generation function. Let’s take a deeper look at one of the report generation functions to examine how the resulting HTML reports are generated in the next section.

# R1 Perform Capture
def btnPerformCapture(self):
    self.CapturePackets['state']=DISABLED
    saveActivateSensor = self.ActivateSensor['state']
    self.ActivateSensor['state']=DISABLED
    self.StopCapture['state']=NORMAL
    self.update()
    # create a packet processing object
    self.statusText['text'] = "Loading Lookups ..."
    self.update()
    self.packetObj = PacketProcessor(self.lookupList)
    if PA_ON:
        self.statusText['text'] = "Resetting PaPirus Display ... Please
            Wait"
        self.update()            
        self.paObj.ResetDisplay()            
        self.paObj.UpdateMode("Record ")
        self.paObj.UpdateStatus("Operation Started  ")       
    self.statusText['text'] = "Capturing Packets ..."
    self.update()        
    durationValue = self.duration.get()
    durSec = CONVERT[durationValue]
    startTime = time.time()
    curProgress = 0
    self.progressBar['value'] = curProgress
    # Python Packet Capture
    # configure the eth0 in promiscuous mode
    try:
        if platform.system() == "Linux":
            self.PLATFORM = "LINUX"
            ret =  os.system("ifconfig eth0 promisc")
            if ret == 0:
                LogEvent(LOG_INFO, 'Promiscuous Mode Enabled for eth0')             
                # create a new socket using the python socket module
                # PF_PACKET     : Specifies Protocol Family Packet Level
                # SOCK_RAW      : Specifies A raw network packet layer
                # htons(0x0003) : Specifies all headers and packets
                #               : Ethernet and IP, including TCP/UDP etc
                # attempt to open the socket for capturing raw packets
                rawSocket=socket.socket(socket.PF_PACKET,
                   socket.SOCK_RAW,
                   socket.htons(0x0003))     
            else:
                self.statusText['text'] = "Capture Failed ... Cannot Open
                            Socket"
                self.progressBar['value'] = 0                 
                self.update()   
                self.CapturePackets['state']=NORMAL
                self.StopCapture['state']=DISABLED
                self.update()                
                return                    
    except Exception as err:
        self.statusText['text'] = "Network Connection Failed: "+ str(err)  
        self.update()    
        return
    pkCnt = 0
    upTime = time.time()
    paTime = time.time()
# R2 Main Loop    
while True:
        curTime = time.time()
        elapsedTime = curTime - startTime
        if elapsedTime > durSec:
            break
        if self.abortFlag:
            ''' User Aborted '''
            ''' Reset the Flag for next use '''
            self.abortFlag = False
            break
        ''' Update the Progress Bar on Change vs Total Time'''
        newProgress = int(round((elapsedTime/durSec * 100)))
        if newProgress > curProgress:
            self.progressBar['value'] = newProgress
            curProgress = newProgress
            self.update()
        ''' Update the Status Window every two seconds'''
        newTime = time.time()
        if (newTime - upTime) >= 2:
            upTime = newTime
            cntStr = '{:,}'.format(pkCnt)
            self.statusText['text'] = "Connections Processed: " + cntStr                
            self.update()
        ''' Update the PA Display if available '''
        if PA_ON:
            newPATime = time.time()
            if (newPATime - paTime) >= 20:
                paTime = newPATime
                cntStr = '{:,}'.format(pkCnt)
                self.paObj.UpdatePacketCnt(cntStr)                
        # attempt to receive (this call is synchronous, thus it will wait)
        try:
            recvPacket=rawSocket.recv(65535)
            self.packetObj.PacketExtractor(recvPacket)
            pkCnt += 1
        except Exception as err:
            LogEvent(LOG_INFO,'Recv Packet Failed: '+str(err))
            continue
    self.statusText['text'] = "Generating Capture Reports and Saving
            Baseline ..."
    self.update()            
    # Generate Reports and Save the Baseline
    # R3 Report Generation
    self.SaveOb(self.packetObj.d)
    self.GenCSV(self.packetObj.d)
    self.GenHTML(self.packetObj.d)
    self.GenCOUNTRY(self.packetObj.d)
    self.GenMFG(self.packetObj.d)
    self.GenICS(self.packetObj.d)
    self.GenIOT(self.packetObj.d)
    self.GenPortUsage(self.packetObj.d)
    ''' Enable Report Button '''    
    self.viewReport['state']=NORMAL
    ''' Reset Progress Bar and Post Completed status'''
    self.progressBar['value'] = 0
    cntStr = '{:,}'.format(pkCnt)
unique = '{:,}'.format(len(self.packetObj.d))
    self.statusText['text'] = "Done:  Total Connections Processed:
            "+cntStr+"  Unique Observations Recorded: "+unique
    self.CapturePackets['state']=NORMAL
    # reset the ActivateSensor State
    self.ActivateSensor['state']=saveActivateSensor
    self.StopCapture['state']=DISABLED        
    self.update()          
    if PA_ON:
        self.paObj.UpdatePacketCnt(unique)              
        self.paObj.UpdateStatus("Operation Completed")        
        self.paObj.UpdateMode("            ")
Master Report Generation (GenHTML)

The report generators all work basically the same, but they filter and sort data based on the specific reports being created. The method is a unique method of autogenerating an HTML file. One could use XML (eXtensible Markup Language) and style sheets as an alternative.

Examining M1, we start by updating the status bar of our progress. The current date and time are obtained in order to generate a unique file name for the desired report. Each report name is prepended with the date-time in order to provide easy sorting of the report results. For this example, the report name would be in the following format:

2017-11-14-08-22-master.html
yyyy-mm-dd-hr-mm-master.html

Next, the html file is built from a template stored in the rpt.py file. Each report has a separate template that is used. Basically, the template contains an HTML_START section, HTML_HEADER section, (multiple) HTML_BODY sections, and HTML_END section.

Examining the code in section M2, the Python dictionary object d contains all the unique observations collected during this recording. A loop is created to iterate over each unique observation, and the values extracted from the key/value pairs of the dictionary are stored in local variable prefaced with fld (for example, fldAlert, fldSrcIP, etc.). Once they are collected we use the format method available for strings, as shown here, to replace the placeholders defined in the template HTML.

htmlSection = htmlSection.format(**locals())

The template HTML placeholders highlighted here are then replaced by the corresponding local variables to generate the final HTML code.

<td style="width: 250px;"> {fldAlert} </td>                       
<td style="width: 250px;"> {fldAlertCnt} </td>

Once all the HTML code has been generated, the code in section M3 writes out the complete htmlContents to the report filename created in section M1.

def GenHTML(self, d):
# M1 Update Report Date / Time
    ''' Produce the Master Report using the master dictionary provided '''
    path = self.ReportFolder['text']
    utc=datetime.datetime.utcnow()
    yr = str(utc.year)
    mt = '{:02d}'.format(utc.month)
    dy = '{:02d}'.format(utc.day)
    hr = '{:02d}'.format(utc.hour)
    mn = '{:02d}'.format(utc.minute)
    ''' Produce Master HTML Report'''
    self.statusText['text'] = "Generating Master HTML Report ..."+yr+'-
        '+mt+'-'+dy+'-'+hr+'-'+mn+"
        -Master.html"
    self.update()            
    filename = yr+'-'+mt+'-'+dy+'-'+hr+'-'+mn+"-Master.hmtl"
    self.MasterHTML = os.path.join(path, filename)
    htmlContents = ''
    htmlHeader = rpt.HTML_START
    fldDate = yr+'-'+mt+'-'+dy+'@'+hr+':'+mn+" UTC"
    htmlHeader = htmlHeader.format(**locals())   
    htmlContents = htmlContents + htmlHeader
    for eachKey in d:
    # M2 Adding Observation Data to the Reports
        htmlSection = rpt.HTML_BODY
        value = d[eachKey]
        fldAlert      = value[ALERT]
        fldSrcIP      = eachKey[SRCIP]
        fldDstIP      = eachKey[DSTIP]
        fldFrame      = eachKey[FRAMETYPE]
        fldProtocol   = eachKey[PROTOCOL]
        fldSrcPort    = value[SRCPORT]    
        fldSrcPortName= value[SRCPORTNAME]
        fldDstPort    = value[DSTPORT]    
        fldDstPortName= value[DSTPORTNAME]  
        fldSrcMAC     = value[SRCMAC]
        fldDstMAC     = value[DSTMAC]            
        fldSrcMFG     = value[SRCMFG]
        fldDstMFG     = value[DSTMFG]
        fldSrcCC      = value[SRCCC]
        fldDstCC      = value[DSTCC]
        fldPktSize    = value[AVGPCKSIZE]
        fldTwilight   = value[AM12]   
        fldMorning    = value[AM6]
        fldAfternoon  = value[PM12]
        fldEvening    = value[PM6]
        fldWeekend    = value[WKEND]
        fldTotal      = value[AM12]+value[AM6]+
                            value[PM12]+
                            value[PM6]+value[WKEND]
        htmlSection = htmlSection.format(**locals())
        htmlContents = htmlContents + htmlSection
    htmlContents = htmlContents + rpt.HTML_END
    ''' Write the Report to the output file'''
    # M3 Write HTML Report to File
    output = open(self.MasterHTML,"w")
    output.write(htmlContents)
    output.close()
Saving the Baseline (SaveOb)

In addition to generating the various reports associated with the record baseline process, the actual baseline is also created. This is a straightforward process in Python, as we are serializing the Python dictionary object d using the pickle standard library module.

Note

What is pickling? Pickling “serializes” Python objects prior to writing them to a file. Pickling converts Python objects (list, dict, etc.) into a character stream. The idea is that this character stream contains all the information necessary to reconstruct the object in another Python script. This is done by using the pickle.load(filename) method. This method was utilized in section F-2 when the baseline file was selected by the user.

In Section S1, the SaveOb method uses the same naming convention used when creating report files, but adds the file extension “.baseline” to the file. Then with only two lines of code, the baseline file is created.

with open(outFile, 'wb') as fp:
    pickle.dump(d, fp)  
def SaveOb(self, d):
# S1 Serializing and Saving the Object Baseline
    ''' Save the current observation dictionary to a the specified path '''
    try:
        path = self.ReportFolder['text']
        baseDir = os.path.join(path,'baselines')
        if not os.path.isdir(baseDir):
            os.mkdir(baseDir)
        self.statusText['text'] = "Generating Serialized Baseline ..."
        self.update()           
        utc=datetime.datetime.utcnow()
        yr = str(utc.year)
        mt = '{:02d}'.format(utc.month)
        dy = '{:02d}'.format(utc.day)
        hr = '{:02d}'.format(utc.hour)
        mn = '{:02d}'.format(utc.minute)
        filename = yr+'-'+mt+'-'+dy+'--'+hr+'-'+mn+".baseline"
        outFile = os.path.join(baseDir, filename)
        with open(outFile, 'wb') as fp:
            pickle.dump(d, fp)    
    except Exception as err:
        LogEvent(LOG_ERR, "Failed to Create Baseline Output"+str(err))
Activate Sensor (btnActivateSensor, PacketMonitor)

The final method to examine in this chapter is the btnActivateSensor method. The front end of this method mimics that of the packer recorder. The difference is in the processing of each received packet. The PacketMonitor method examines the received packet and determines if “the same packet construction” exists in the current baseline. If not, then an alert report item is generated to indicate a “New Observation”. In addition, the key elements of the packet, such as IP country location, MAC address, packet size, and time of the observation, are compared to known or expected values. If anomalies are discovered, additional report items are recorded. The following code snippet includes the btnActivateSensor, PacketMonitor, and supporting methods.

def btnActivateSensor(self):
     # Handle Active Sensor Button Click
    self.ActivateSensor['state']=DISABLED
    saveCaptureState = self.CapturePackets['state']
    self.CapturePackets['state']=DISABLED
    self.StopCapture['state']=NORMAL
    self.update()        
    # create a packet processing object
    self.statusText['text'] = "Loading Lookups ..."
    self.update()
    self.packetObj = PacketProcessor(self.lookupList,
            self.baselineDictionary)
   self.statusText['text'] = "Monitoring Packets ..."
    if PA_ON:
        self.statusText['text'] = "Resetting PaPirus Display ... Please
            Wait"
        self.update()            
        self.paObj.ResetDisplay()              
        self.paObj.UpdateMode("Monitor")
        self.paObj.UpdateStatus("Operation Started  ")
    self.statusText['text'] = "Monitoring Packets ..."
    self.update()
    durationValue = self.duration.get()
    durSec = CONVERT[durationValue]
    startTime = time.time()
    curProgress = 0
    self.progressBar['value'] = curProgress
    self.alertDict = {}
    # Python Packet Capture
    # configure the eth0 in promiscuous mode
    try:
        ret =  os.system("ifconfig eth0 promisc")
        if ret == 0:
            LogEvent(LOG_INFO, 'Promiscuous Mode Enabled for eth0')             
            # create a new socket using the python socket module
            # PF_PACKET     : Specifies Protocol Family Packet Level
            # SOCK_RAW      : Specifies A raw protocol at the network layer
            # htons(0x0003) : Specifies all headers and packets
            #               : Ethernet and IP, including TCP/UDP etc
            # attempt to open the socket for capturing raw packets
            rawSocket=socket.socket(socket.PF_PACKET,socket.SOCK_RAW,
                 socket.htons(0x0003))     
        else:
            self.statusText['text'] = "Monitoring Failed ...
                Cannot Open Socket"
            self.progressBar['value'] = 0                
            self.update()   
            self.CapturePackets['state']=NORMAL
            self.StopCapture['state']=DISABLED
            self.update()                
            return
    except Exception as err:
        self.statusText['text'] = "Socket Exception ... "+str(err)
        self.progressBar['value'] = 0
        self.CapturePackets['state']=NORMAL
        self.StopCapture['state']=DISABLED            
        self.update()  
        return        
    pkCnt = 0
    upTime = time.time()
    paTime = time.time()
    while True:
        curTime = time.time()
        elapsedTime = curTime - startTime
        if elapsedTime > durSec:
            break
        if self.abortFlag:
            ''' User Aborted '''
            ''' Reset the Flag for next use '''
            self.abortFlag = False
            break
        ''' Update the Progress Bar on Change vs Total Time'''
        newProgress = int(round((elapsedTime/durSec * 100)))
        if newProgress > curProgress:
            self.progressBar['value'] = newProgress
            curProgress = newProgress
            self.update()
        ''' Update the Status Window every two seconds'''
        newTime = time.time()
        if (newTime - upTime) >= 2:
            upTime = newTime
            cntStr = '{:,}'.format(pkCnt)
            self.statusText['text'] = "Pck Cnt: " + cntStr    
            self.update()
        ''' Update the PA Display if available '''
        if PA_ON:
            newPATime = time.time()
            if (newPATime - paTime) >= 20:
                paTime = newPATime
                cntStr = '{:,}'.format(pkCnt)
                self.paObj.UpdatePacketCnt(cntStr)      
        # attempt to receive (this call is synchronous, thus it will wait)
        try:
            recvPacket=rawSocket.recv(65535)
            self.packetObj.PacketMonitor(recvPacket,
                 self.alertDict, self.baselineCC,
                 self.baselineMAC)
            pkCnt += 1
        except Exception as err:
            LogEvent(LOG_INFO,'Recv Packet Failed: '+str(err))
            continue
    # Generate Sensor Reports
    self.GenAlerts(self.alertDict)
    ''' Enable Report Button '''    
    self.ViewAlerts['state']=NORMAL
    ''' Reset Progress Bar and Post Completed status'''
    self.progressBar['value'] = 0
    cntStr = '{:,}'.format(pkCnt)
    alertsGenerated = '{:,}'.format(len(self.alertDict))
    self.statusText['text'] = "Done:  Total Connections Processed
        :  "+cntStr+"  Alerts: "+alertsGenerated  
    self.CapturePackets['state'] = saveCaptureState
    self.ActivateSensor['state']=NORMAL
    self.StopCapture['state']=DISABLED
    self.update()
    if PA_ON:
        self.paObj.UpdateAlertCnt(alertsGenerated)
        self.paObj.UpdatePacketCnt(cntStr)                          
        self.paObj.UpdateStatus("Operation Completed")
        self.paObj.UpdateMode("            ")

def PacketMonitor (self, packet, alertDict, baseCC, baseMAC):

''' Extract Packet Data input: string packet, dictionary d
    result is to update dictionary d
'''
ETH_LEN  = 14      # ETHERNET HDR LENGTH
IP_LEN   = 20      # IP HEADER    LENGTH
IPv6_LEN = 40      # IPv6 HEADER  LENGTH
ARP_HDR  = 8       # ARP HEADER
UDP_LEN  = 8       # UPD HEADER   LENGTH
TCP_LEN  = 20      # TCP HEADER   LENGTH
''' Elements of the key '''
self.srcMac = ''
self.dstMac = ''
self.frType = ''
self.srcIP  = ''
self.dstIP  = ''
self.proto  = ''
self.opcode = ''
self.port   = ''
self.srcPort = ''
self.dstPort = ''
self.srcPortName = ''
self.dstPortName = ''
self.packetSize = 0
self.srcMFG = ''
self.dstMFG = ''
self.dstMacOui =''
self.srcMacOui = ''
self.srcCC = ''
self.dstCC = ''
self.alert = ''
self.lastObservationTime = time.ctime(time.time())
ethernetHeader=packet[0:ETH_LEN]
ethFields =struct.unpack("!6s6sH",ethernetHeader)
self.dstMac = hexlify(ethFields[0]).upper()
self.dstMacOui = self.dstMac[0:6]
self.dstMFG = self.ouiOBJ.lookup(self.dstMacOui)
self.alert = 'Normal'
self.srcMac    = hexlify(ethFields[1]).upper()
self.srcMacOui = self.srcMac[0:6]
self.srcMFG    = self.ouiOBJ.lookup(self.srcMacOui)
self.fType  = ethFields[2]
frameType = self.ethOBJ.lookup(self.fType)
self.frType = frameType
if frameType == "IPV4":
    # Process as IPv4 Packet
    ipHeader = packet[ETH_LEN:ETH_LEN+IP_LEN]
    # unpack the ip header fields
    ipHeaderTuple = struct.unpack('!BBHHHBBH4s4s' , ipHeader)
    # extract the key ip header fields of interest
                                           # Field Contents
    verLen       = ipHeaderTuple[0]        # Field 0: Version & Length
    TOS          = ipHeaderTuple[1]        # Field 1: Type of Service                                                
    packetLength = ipHeaderTuple[2]        # Field 2: Packet Length            
    protocol     = ipHeaderTuple[6]        # Field 6: Protocol Number
    sourceIP     = ipHeaderTuple[8]        # Field 8: Source IP
    destIP       = ipHeaderTuple[9]        # Field 9: Destination IP    
    timeToLive   = ipHeaderTuple[5]        # Field 5: Time to Live
    # Calculate / Convert extracted values
    version      = verLen >> 4     # Upper Nibble is the version Number
    length       = verLen & 0x0F   # Lower Nibble represents the size
    ipHdrLength  = length * 4      # Calculate the header size in bytes
    # covert the srcIP/dstIP to typical dotted notation strings
    self.packetSize = packetLength
    self.srcIP = socket.inet_ntoa(sourceIP);
    self.dstIP = socket.inet_ntoa(destIP);
    self.srcCC = self.cc.lookup(self.srcIP, 'IPv4')
    self.dstCC = self.cc.lookup(self.dstIP, 'IPv4')
    translate = self.traOBJ.lookup(str(protocol))
    transProtocol = translate[0]
    if transProtocol == 'TCP':
        self.proto = "TCP"
        stripTCPHeader = packet[ETH_LEN+ipHdrLength:ipHdrLength+
                             ETH_LEN+TCP_LEN]
        # unpack the TCP Header to obtain the
        # source and destination port
        tcpHeaderBuffer = struct.unpack('!HHLLBBHHH' , stripTCPHeader)
        self.srcPort = tcpHeaderBuffer[0]
        self.dstPort = tcpHeaderBuffer[1]
        self.srcPortName = self.portOBJ.lookup(self.srcPort, 'TCP')
        self.dstPortName = self.portOBJ.lookup(self.dstPort, 'TCP')
    elif transProtocol == 'UDP':
        self.proto = "UDP"
        stripUDPHeader = packet[ETH_LEN+ipHdrLength:ETH_LEN+
                            ipHdrLength+UDP_LEN]
        # unpack the UDP packet and obtain the
        # source and destination port
        udpHeaderBuffer = struct.unpack('!HHHH' , stripUDPHeader)
        self.srcPort = udpHeaderBuffer[0]
        self.dstPort = udpHeaderBuffer[1]
        self.srcPortName = self.portOBJ.lookup(self.srcPort, 'UDP')
        self.dstPortName = self.portOBJ.lookup(self.dstPort, 'UDP')
    elif transProtocol == 'ICMP':
        self.proto = "ICMP"
    elif transProtocol == 'IGMP':
        self.proto = "IGMP"
    else:
        self.proto = transProtocol
elif frameType == 'ARP':
    # Process as IPv4 Packet
    arpHeader = packet[ETH_LEN:ETH_LEN+ARP_HDR]
    # unpack the ARP header fields
    arpHeaderTuple = struct.unpack('!HHBBH' , arpHeader)
    ht = arpHeaderTuple[0]
    pt = arpHeaderTuple[1]
    hal = arpHeaderTuple[2]
    pal = arpHeaderTuple[3]
    op  = arpHeaderTuple[4]
    # set packetSize for ARP to zero
    self.packetSize = 0
    base = ETH_LEN+ARP_HDR
    shAddr = hexlify(packet[base:base+hal])
    base = base+hal
    spAddr = hexlify(packet[base:base+pal])
    base = base+pal
    thAddr = hexlify(packet[base:base+hal])
    base = base+hal
    tpAddr = hexlify(packet[base:base+pal])
    self.srcIP = shAddr
    self.dstIP = thAddr
    self.proto = str(op)  
elif frameType == "IPV6":
    # Process as IPv6 Packet
    ipHeader = packet[ETH_LEN:ETH_LEN+IPv6_LEN]
    # unpack the ip header fields
    ipv6HeaderTuple = struct.unpack('!IHBBQQQQ' , ipHeader)
    flush = ipv6HeaderTuple[0]
    pLength = ipv6HeaderTuple[1]
    nextHdr = ipv6HeaderTuple[2]
    hopLmt  = ipv6HeaderTuple[3]
    srcIP   = (ipv6HeaderTuple[4] << 64) | ipv6HeaderTuple[5]
    dstIP   = (ipv6HeaderTuple[6] << 64) | ipv6HeaderTuple[7]               
    self.packetSize = pLength
    self.srcIP = str(netaddr.IPAddress(srcIP))
    self.dstIP = str(netaddr.IPAddress(dstIP))    
    self.srcCC = self.cc.lookup(self.srcIP, 'IPv6')
    self.dstCC = self.cc.lookup(self.dstIP, 'IPv6')       
    translate = self.traOBJ.lookup(str(nextHdr))
    transProtocol = translate[0]
    if transProtocol == 'TCP':
        self.proto = "TCP"
        stripTCPHeader = packet[ETH_LEN+IPv6_LEN:ETH_LEN+
                             IPv6_LEN+TCP_LEN]
        # unpack the TCP Header to obtain the
        # source and destination port
        tcpHeaderBuffer = struct.unpack('!HHLLBBHHH' , stripTCPHeader)
        self.srcPort = tcpHeaderBuffer[0]
        self.dstPort = tcpHeaderBuffer[1]
        self.srcPortName = self.portOBJ.lookup(self.srcPort, 'TCP')
        self.dstPortName = self.portOBJ.lookup(self.dstPort, 'TCP')                
    elif transProtocol == 'UDP':
        self.proto = "UDP"
        stripUDPHeader = packet[ETH_LEN+IPv6_LEN:ETH_LEN+
                             IPv6_LEN+UDP_LEN]
        # unpack the UDP packet and obtain the
        # source and destination port
        udpHeaderBuffer = struct.unpack('!HHHH' , stripUDPHeader)
        self.srcPort = udpHeaderBuffer[0]
        self.dstPort = udpHeaderBuffer[1]
        self.srcPortName = self.portOBJ.lookup(self.srcPort, 'UDP')
        self.dstPortName = self.portOBJ.lookup(self.dstPort, 'UDP')                
    elif transProtocol == 'ICMP':
        self.proto = "ICMP"
    elif transProtocol == 'IGMP':
        self.proto = "IGMP"               
    else:
        self.proto = transProtocol
else:
    self.proto = frameType
valueNdx = getOccurrenceValue()     
if self.srcIP == '127.0.0.1' and self.dstIP == '127.0.0.1':
    ''' Ignore this packet '''
    return
if self.srcPort <= CORE_PORTS:
    ''' if srcPort is definately a service port'''
    key = (self.srcIP, self.dstIP, self.srcPort,
               self.frType, self.proto)
elif self.dstPort <= CORE_PORTS:
    ''' if dstPort is definately a service port'''
    key = (self.srcIP, self.dstIP, self.dstPort,
           self.frType, self.proto)
elif self.srcPort < self.dstPort:
    ''' Guess that srcPort is server '''
    key = (self.srcIP, self.dstIP, self.srcPort,
          self.frType, self.proto)
else:
    ''' guess destination port is server'''
    key = (self.srcIP, self.dstIP, self.dstPort,
           self.frType, self.proto)
''' Check Baseline for previously observed key '''
try:
    ''' if match found, snag the time entries and avg packet size'''
    value = self.b[key]
    avgPckSize = value[AVGPCKSIZE]
    timeList = [value[AM12], value[AM6], value[PM12],
                value[PM6], value[WKEND]]          
    newEntry = False
except:
    ''' Then this is a new observation'''
    self.CreateAlertEntry(key, alertDict, "New Observation")
    newEntry = True
chk, value = self.ouiOBJ.chkHotlist(self.dstMacOui)
if chk:
    self.CreateAlertEntry(key, alertDict, "HotList: "+value)
if self.isNewMAC(self.srcMac, baseMAC):
    self.CreateAlertEntry(key, alertDict, "New MAC Address")
if self.isNewMAC(self.dstMac, baseMAC):
    self.CreateAlertEntry(key, alertDict, "New MAC Address")
if self.isNewCC(self.srcCC, baseCC):
    self.CreateAlertEntry(key, alertDict, "New Country Code")
if self.isNewCC(self.dstCC, baseCC):
    self.CreateAlertEntry(key, alertDict, "New Country Code")
''' If this is not a new entry the safe to check pckSize and Times'''
if not newEntry:
    if self.isUnusualPckSize(self.packetSize, avgPckSize):
        self.CreateAlertEntry(key, alertDict, "Unusual Packet Size")       
    if self.isUnusualTime(timeList):
        self.CreateAlertEntry(key, alertDict, "Unusual Packet Time")    
Packet Monitor Supporting Methods
def isUnusualPckSize(self, pSize, avgSize):
        if float(pSize) < float(avgSize*.70):
            return True
        if float(pSize) < float(avgSize*1.30):
            return True
        return False
    def isNewMAC(self, mac, b):
        if mac == 'Unknown' or mac == '':
            return False
        if not mac in b:
            return True
        else:
            return False
    def isNewCC(self,cc, b):
        if cc == 'Unknown' or cc == '':
            return False
        if not cc in b:
            return True
        else:
            return False
    def isUnusualTime(self, occList):
        occ = getOccurrenceValue()
        if occList[occ] == 0:
            return True
        else:
            return False
    def CreateAlertEntry(self, key, alertDict, alertType):
        try:
            ''' See if the alert already exists '''
            value = alertDict[key]
            ''' if yes, then bump the occurrence count'''
            cnt = value[1] + 1
            alertDict[key] = [alertType, cnt,
                              self.lastObservationTime,
                              self.packetSize,
                              self.srcCC, self.dstCC, self.srcMac,
                              self.dstMac, self.srcMFG, self.dstMFG,
                              self.srcPort, self.dstPort, self.srcPortName,
                              self.dstPortName ]
        except:
            ''' Othewise create a new alert entry'''
            alertDict[key] = [alertType, 1, self.lastObservationTime,
                              self.packetSize, self.srcCC, self.dstCC,
                              self.srcMac, self.dstMac, self.srcMFG,
                              self.dstMFG,self.srcPort, self.dstPort,
                              self.srcPortName, self.dstPortName ]

Summary

This chapter provided both an overview of the Raspberry Pi sensor/recorder along with a detailed examination of many of the code elements that support the design. This included the following:
  • Design overview

  • Examination of the GUI approach

  • Integration of the PaPirus ePaper display

  • Details of the baseline recording method

  • Details of the sensor methods

  • Details of the report generation methods

  • Finally, the use of the Python pickle method to store and load the resulting recorded baselines

In Chapter 5, the focus will be on applying the Pi recorder/sensor to create baselines that are used to train and then activate the sensor. Finally, both the recorder-generated reports and the reports generated by the sensor will be examined to expose IoT-based operations within our test network.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.18.106.236