├── .gitignore ├── DOCs ├── 6921_IEC61850Network_MS_20190712_Web.pdf ├── A-Pratical-Guide-of-Troubleshooting-IEC-61850-GOOSE-communicaiton-Wei-Huang.pdf ├── B5_PS1_117_DE_Jenkins_2017.pdf ├── Digital-Substations_Thompson.pdf ├── IEC_61850_Engineering_Guide_756475_ENd.pdf ├── Paper_GOOSE_Utilisation_in_Protection.pdf ├── TR-61850.pdf ├── elsarticle-template.pdf ├── energies-12-02536.pdf ├── gegrid_iec61850.pdf └── sensors-21-01554-v2.pdf ├── LICENSE ├── PCAPs ├── 8d7c7db0-9804-012b-b2a6-0016cb8cea27.pcap ├── GOOSE.pcap ├── GOOSE_DEMO.pcap ├── GOOSE_wireshark.pcap ├── Sample_File_GOOSE.pcap └── Sample_File_MMS_and_GOOSE.pcap ├── README.md ├── goose ├── LICENSE ├── __init__.py ├── goose.py └── goose_pdu.py └── scripts ├── goose_dataset_checker.py ├── goose_device_cnt.py ├── goose_device_vlans.py ├── goose_packet_mod_test.py ├── goose_parser.py ├── goose_routable_checker.py ├── goose_security_checker.py ├── goose_send_mod_packet.py ├── goose_time_sync_checker.py └── goose_type_checker.py /.gitignore: -------------------------------------------------------------------------------- 1 | Pipfile.lock 2 | *.swp 3 | .DS_Store 4 | *.pyc 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /DOCs/6921_IEC61850Network_MS_20190712_Web.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/6921_IEC61850Network_MS_20190712_Web.pdf -------------------------------------------------------------------------------- /DOCs/A-Pratical-Guide-of-Troubleshooting-IEC-61850-GOOSE-communicaiton-Wei-Huang.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/A-Pratical-Guide-of-Troubleshooting-IEC-61850-GOOSE-communicaiton-Wei-Huang.pdf -------------------------------------------------------------------------------- /DOCs/B5_PS1_117_DE_Jenkins_2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/B5_PS1_117_DE_Jenkins_2017.pdf -------------------------------------------------------------------------------- /DOCs/Digital-Substations_Thompson.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/Digital-Substations_Thompson.pdf -------------------------------------------------------------------------------- /DOCs/IEC_61850_Engineering_Guide_756475_ENd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/IEC_61850_Engineering_Guide_756475_ENd.pdf -------------------------------------------------------------------------------- /DOCs/Paper_GOOSE_Utilisation_in_Protection.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/Paper_GOOSE_Utilisation_in_Protection.pdf -------------------------------------------------------------------------------- /DOCs/TR-61850.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/TR-61850.pdf -------------------------------------------------------------------------------- /DOCs/elsarticle-template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/elsarticle-template.pdf -------------------------------------------------------------------------------- /DOCs/energies-12-02536.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/energies-12-02536.pdf -------------------------------------------------------------------------------- /DOCs/gegrid_iec61850.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/gegrid_iec61850.pdf -------------------------------------------------------------------------------- /DOCs/sensors-21-01554-v2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/DOCs/sensors-21-01554-v2.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Keith Gray 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 | -------------------------------------------------------------------------------- /PCAPs/8d7c7db0-9804-012b-b2a6-0016cb8cea27.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/PCAPs/8d7c7db0-9804-012b-b2a6-0016cb8cea27.pcap -------------------------------------------------------------------------------- /PCAPs/GOOSE.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/PCAPs/GOOSE.pcap -------------------------------------------------------------------------------- /PCAPs/GOOSE_DEMO.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/PCAPs/GOOSE_DEMO.pcap -------------------------------------------------------------------------------- /PCAPs/GOOSE_wireshark.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/PCAPs/GOOSE_wireshark.pcap -------------------------------------------------------------------------------- /PCAPs/Sample_File_GOOSE.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/PCAPs/Sample_File_GOOSE.pcap -------------------------------------------------------------------------------- /PCAPs/Sample_File_MMS_and_GOOSE.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/PCAPs/Sample_File_MMS_and_GOOSE.pcap -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GooseStalker 2 | 3 | ## Purpose 4 | 5 | GooseStalker is a project to analyze and interact with Ethernet types associated with IEC 61850. Currently, the project is based on the Goose network packet parsing from the [Keith Gray Power Engineering Goose Repo](https://github.com/keith-gray-powereng/goose). These modules and scripts will parse network traffic to understand the IEC 61850 communications and to interact with devices communicating with these protocols. 6 | 7 | ## Modules and Scripts 8 | 9 | * Goose 10 | * goose.py - Scapy layers to analyze packets (see TODO) 11 | * goose_pdu.py - ASN1 layers to analyze Goose data 12 | * Scripts 13 | * goose_parser.py - script to display the Scapy layers and parsed Goose data. Outputs text version of Goose layers and data. 14 | * goose_dataset_checker.py - display dataset information for all devices. 15 | * goose_device_cnt.py - count all devices and display, in CSV format, the source hardware address, destination hardware address, and Goose ID for each device. 16 | * goose_device_vlans.py - display Virtual Local Area Network (VLAN) information for Goose packets. 17 | * goose_packet_mod_test.py - template script to test Goose message modifications and print to terminal, without sending. 18 | * goose_routable_checker.py - check if the Goose implementation is configured to use routable Goose messages. 19 | * goose_security_checker.py - check if the Goose implementation is configured to use the Goose security features. 20 | * goose_send_mod_packet.py - template script to modify and send Goose messages from a network capture. 21 | * goose_time_sync_checker.py - check the timestamps in the Goose messages. This should represent the device time setting and help understand if a time server is being used to syncronize time on the subnet. NOTE: Devices do not have to have the exact same time because Goose messages are managed by timing and not the device time. 22 | * goose_type_checker.py - displays the Goose message types. See IEC 61850 for descriptions of each type. 23 | * PCAPS 24 | * GOOSE_wireshark.pcap - Wireshark's PCAP file for testing. This does not contain messages with VLAN layers (see TODO list). 25 | * [ITI IEC61850 Goose PCAPS](https://github.com/ITI/ICS-Security-Tools/tree/master/pcaps/IEC61850) 26 | * DOCS 27 | * Research into IEC61850 that outlines usage and packet format (see DOCS section) 28 | * LICENSE - maintained the Keith's original MIT license for this work 29 | * Pipfile - required Python modules. Probably contains a few more than necessary to allow for additional development. See requirements below. 30 | 31 | ## Usage 32 | 33 | ### Scripts 34 | 35 | #### Count of devices producing Goose messages 36 | 37 | ```shell 38 | python3 ./goose_device_cnt.py 39 | ``` 40 | 41 | ```console 42 | CutSec 21-12-13 9:37:52 43 | > python3 ./goose_device_cnt.py ../PCAPs/GOOSE_wireshark.pcap 44 | ################################################## 45 | ### Goose Source Interface Address and Destination Addresses with Goose ID 46 | ################################################## 47 | Goose Device Count: 1 48 | 49 | Source Address,Destivation Address,goID 50 | 00:a0:f4:08:2f:77,01:a0:f4:08:2f:77,F650_GOOSE1 51 | ``` 52 | 53 | #### Type of Goose messages 54 | 55 | ```shell 56 | python3 ./goose_type_checker.py 57 | ``` 58 | 59 | ```console 60 | CutSec 21-12-13 9:40:30 61 | > python3 ./goose_type_checker.py ../PCAPs/GOOSE_wireshark.pcap 62 | Goose Packets: 8 63 | Type 1 : 8 64 | Type 1a : 0 65 | GSE Management: 0 66 | Sampled Values: 0 67 | ``` 68 | 69 | #### Audit Goose configurations by running multiple scripts on a single file 70 | 71 | ```shell 72 | for inf in goose_type_checker.py goose_routable_checker.py goose_security_checker.py \ 73 | goose_device_cnt.py goose_time_sync_checker.py goose_dataset_checker.py; do echo; \ 74 | echo ################; echo $inf; echo ################; python3 ./$inf ; \ 75 | echo; done 76 | ``` 77 | 78 | ```console 79 | CutSec 21-12-13 9:37:45 80 | > for s in goose_type_checker.py goose_routable_checker.py goose_security_checker.py \ 81 | goose_device_cnt.py goose_time_sync_checker.py goose_dataset_checker.py; do echo; \ 82 | echo ################; echo $s; echo ################; python3 ./$s ../PCAPs/GOOSE_wireshark.pcap; \ 83 | echo; done 84 | 85 | ################ 86 | goose_type_checker.py 87 | ################ 88 | Goose Packets: 8 89 | Type 1 : 8 90 | Type 1a : 0 91 | GSE Management: 0 92 | Sampled Values: 0 93 | 94 | 95 | ################ 96 | goose_routable_checker.py 97 | ################ 98 | Routable Goose and Sampled Values 99 | No routable Goose or Sampled Values detected. 100 | 101 | 102 | ################ 103 | goose_security_checker.py 104 | ################ 105 | Goose Packets: 8 106 | Security: 0 107 | No Security: 8 108 | 109 | 110 | ################ 111 | goose_device_cnt.py 112 | ################ 113 | ################################################## 114 | ### Goose Source Interface Address and Destination Addresses with Goose ID 115 | ################################################## 116 | Goose Device Count: 1 117 | 118 | Source Address,Destination Address,goID 119 | 00:a0:f4:08:2f:77,01:a0:f4:08:2f:77,F650_GOOSE1 120 | 121 | 122 | ################ 123 | goose_time_sync_checker.py 124 | ################ 125 | ################################################## 126 | ### Goose Timestamps and TTL by Goose ID and stNum 127 | ### NOTE: Devices times can be different and not 128 | ### have a negative impact on operations. 129 | ### Goose devices are more interested in 130 | ### message timing than device time. 131 | ################################################## 132 | Source Device: F650_GOOSE1 133 | 1 : 2000-01-02 02:46:11 : 40000 134 | 1 : 2000-01-02 02:47:29 : 1000 135 | 1 : 2000-01-02 02:47:29 : 2000 136 | 1 : 2000-01-02 02:47:29 : 40000 137 | 138 | 139 | ################ 140 | goose_dataset_checker.py 141 | ################ 142 | Goose Data by Device Hardware Address 143 | Source Device: 00:a0:f4:08:2f:77 144 | GEDeviceF650/LLN0$GO$gcb01 - GEDeviceF650/LLN0$GOOSE1 - F650_GOOSE1 - 8 145 | ``` 146 | 147 | ### IPython Usage 148 | 149 | * TODO 150 | 151 | ## DOCS - Research into IEC61850 that outlines usage and packet format 152 | 153 | * [6921_IEC61850Network_MS_20190712_Web.pdf](https://cms-cdn.selinc.com/assets/Literature/Publications/Technical%20Papers/6921_IEC61850Network_MS_20190712_Web.pdf?v=20190821-201111) 154 | * [B5_PS1_117_DE_Jenkins_2017.pdf](https://www.researchgate.net/publication/339303436_How_to_hack_an_IEC_61850_system_or_protect_one) 155 | * [TR-61850.pdf](https://www.fit.vut.cz/research/publication/11832/.en) 156 | * [elsarticle-template.pdf](https://www.researchgate.net/publication/312327440_Interpreting_and_implementing_IEC_61850-90-5_Routed-Sampled_Value_and_Routed-GOOSE_protocols_for_IEEE_C371182_compliant_wide-area_synchrophasor_data_transfer) 157 | * [energies-12-02536.pdf](https://www.mdpi.com/1996-1073/12/13/2536/pdf-vor) 158 | * [sensors-21-01554-v2.pdf](https://www.mdpi.com/1424-8220/21/4/1554/pdf) 159 | * [IEC61850 - The Digital Power System](https://www.cscos.com/wp-content/uploads/2019/01/Digital-Substations_Thompson.pdf) 160 | * [Utilization of IEC 61850 GOOSE messaging in protection applications in distribution network](https://library.e.abb.com/public/dc853877595c4086ae649ca29924c0ec/Paper_GOOSE%20Utilisation%20in%20Protection.pdf) 161 | * [A Practical Guide of Troubleshooting IEC 61850 GOOSE Communication](http://prorelay.tamu.edu/wp-content/uploads/sites/3/2017/04/A-Pratical-Guide-of-Troubleshooting-IEC-61850-GOOSE-communicaiton-Wei-Huang.pdf) 162 | * [Relion® Protection and Control IEC 61850 615 series Engineering Guide](https://library.e.abb.com/public/3d6fbd4567e4bda6c1257b130056a8aa/IEC_61850_Engineering_Guide_756475_ENd.pdf) 163 | * [GE Grid: IEC 61850 Communication Networks and Systems In Substations: An Overview for Users](https://www.gegridsolutions.com/multilin/journals/issues/spring09/iec61850.pdf) 164 | 165 | ## Requirements and Installation 166 | 167 | * [Pipenv](https://docs.pipenv.org/) - Pipfile should contain all required packages, to include a few nice-to-haves. 168 | * [Scapy](https://github.com/secdev/scapy) - comes with its own set of required packages 169 | * [PyASN1](https://pypi.org/project/pyasn1/) - Python ASN1 module 170 | * [iPython](https://ipython.org/) 171 | * cryptography - may or may not need this 172 | * [Wireshark](https://www.wireshark.org/) - you'll want a second source to analyze PCAPs 173 | * [Herb Falk’s Skunkwork Network Analyzer](http://www.otb-consultingservices.com/home/shop/skunkworks-network-analyzer/) - a bit dated, but helps to analyze Goose / MMS / IEC61850 packets. 174 | * [Tshark](https://www.wireshark.org/docs/man-pages/tshark.html) - because command line packet analysis is always more fun. 175 | * Admin Privileges - you'll need administrative privileges to capture and resend data on your system's network interface. 176 | 177 | ## TODO 178 | 179 | * Convert parser into module for other scripts 180 | * Script to provide packet statistics 181 | * Script to identify control packets 182 | * Replay script 183 | * Spoofing script 184 | -------------------------------------------------------------------------------- /goose/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Don C. Weber / Cutaway Security, LLC. 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 | -------------------------------------------------------------------------------- /goose/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutaway-security/goosestalker/c68185dc6a9e4c9e28b6e14b499b1c2c251c5e49/goose/__init__.py -------------------------------------------------------------------------------- /goose/goose.py: -------------------------------------------------------------------------------- 1 | from struct import pack 2 | 3 | from scapy.packet import Packet 4 | from scapy.fields import XShortField, XByteField, ConditionalField 5 | from scapy.all import bind_layers 6 | 7 | # TODO: It might be useful to move from PYASN1 to SCAPY ASN. Need to investigate. 8 | # Until then, GoosePDU parsing is handled in goose_pdu.py 9 | 10 | class GOOSE(Packet): 11 | name = "GOOSE" 12 | fields_desc = [ 13 | XShortField("appid", 0), 14 | XShortField("length", 8), 15 | XShortField("reserved1", 0), 16 | XShortField("reserved2", 0), 17 | ] 18 | 19 | def post_build(self, packet, payload): 20 | goose_pdu_length = len(packet) + len(payload) 21 | packet = packet[:2] + pack('!H', goose_pdu_length) + packet[4:] 22 | return packet + payload 23 | 24 | class GOOSEPDU(Packet): 25 | name = "GOOSEPDU" 26 | fields_desc = [ 27 | XByteField("ID",0x61), 28 | XByteField("DefLen",0x81), 29 | # NOTE: Length comes from this byte's Least Significant Nibble. Not sure what MSN is for. 30 | ConditionalField(XByteField("PDU1ByteLen",0x00),lambda pkt:pkt.DefLen^0x80 == 1), 31 | ConditionalField(XShortField("PDU2BytesLen",0x0000),lambda pkt:pkt.DefLen^0x80 == 2) 32 | ] 33 | 34 | bind_layers(GOOSE, GOOSEPDU) -------------------------------------------------------------------------------- /goose/goose_pdu.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from pyasn1.codec.ber import encoder 4 | from pyasn1.type import char, constraint, namedtype, tag, univ, useful 5 | 6 | class FloatingPoint(univ.OctetString): 7 | pass 8 | 9 | class Unsigned(univ.Integer): 10 | subtypeSpec = constraint.ValueRangeConstraint(0, float('inf')) 11 | 12 | class BCD(univ.Integer): 13 | subtypeSpec = constraint.ValueRangeConstraint(0, float('inf')) 14 | 15 | class TimeOfDay(univ.OctetString): 16 | subtypeSpec = constraint.ConstraintsUnion( 17 | constraint.ValueSizeConstraint(4, 4), 18 | constraint.ValueSizeConstraint(6, 6) 19 | ) 20 | 21 | class MMSString(char.UTF8String): 22 | pass 23 | 24 | class UtcTime(univ.OctetString): 25 | subtypeSpec = constraint.ValueSizeConstraint(8, 8) 26 | 27 | class Data(univ.Choice): 28 | pass 29 | 30 | Data.componentType = namedtype.NamedTypes( 31 | namedtype.NamedType('array', univ.SequenceOf(componentType=Data()).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), 32 | namedtype.NamedType('structure', univ.SequenceOf(componentType=Data()).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))), 33 | namedtype.NamedType('boolean', univ.Boolean().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), 34 | namedtype.NamedType('bit-string', univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))), 35 | namedtype.NamedType('integer', univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))), 36 | namedtype.NamedType('unsigned', univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))), 37 | namedtype.NamedType('floating-point', FloatingPoint().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))), 38 | namedtype.NamedType('real', univ.Real().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))), 39 | namedtype.NamedType('octet-string', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 9))), 40 | namedtype.NamedType('visible-string', char.VisibleString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10))), 41 | namedtype.NamedType('binary-time', TimeOfDay().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 12))), 42 | namedtype.NamedType('bcd', univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 13))), 43 | namedtype.NamedType('booleanArray', univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 14))), 44 | namedtype.NamedType('objId', univ.ObjectIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 15))), 45 | namedtype.NamedType('mMSString', MMSString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 16))), 46 | namedtype.NamedType('utc-time', UtcTime().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 17))) 47 | ) 48 | 49 | 50 | class AllData(univ.SequenceOf): 51 | componentType = Data() 52 | 53 | 54 | class IECGoosePDU(univ.Sequence): 55 | componentType = namedtype.NamedTypes( 56 | namedtype.NamedType( 57 | 'gocbRef', 58 | char.VisibleString().subtype( 59 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 0) 60 | ) 61 | ), 62 | namedtype.NamedType( 63 | 'timeAllowedtoLive', 64 | univ.Integer().subtype( 65 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 1) 66 | ) 67 | ), 68 | namedtype.NamedType( 69 | 'datSet', 70 | char.VisibleString().subtype( 71 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 2) 72 | ) 73 | ), 74 | namedtype.OptionalNamedType( 75 | 'goID', 76 | char.VisibleString().subtype( 77 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 3) 78 | ) 79 | ), 80 | namedtype.NamedType( 81 | 't', 82 | UtcTime().subtype( 83 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 4) 84 | ) 85 | ), 86 | namedtype.NamedType( 87 | 'stNum', 88 | univ.Integer().subtype( 89 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 5) 90 | ) 91 | ), 92 | namedtype.NamedType( 93 | 'sqNum', 94 | univ.Integer().subtype( 95 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 6) 96 | ) 97 | ), 98 | namedtype.NamedType( 99 | 'test', 100 | univ.Boolean(False).subtype( 101 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 7) 102 | ) 103 | ), 104 | namedtype.NamedType( 105 | 'confRev', 106 | univ.Integer().subtype( 107 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 8) 108 | ) 109 | ), 110 | namedtype.NamedType( 111 | 'ndsCom', 112 | univ.Boolean(False).subtype( 113 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 9) 114 | ) 115 | ), 116 | namedtype.NamedType( 117 | 'numDatSetEntries', 118 | univ.Integer().subtype( 119 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 10) 120 | ) 121 | ), 122 | namedtype.NamedType( 123 | 'allData', 124 | AllData().subtype( 125 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 11) 126 | ) 127 | ), 128 | namedtype.OptionalNamedType( 129 | 'security', 130 | univ.OctetString().subtype( 131 | implicitTag=tag.Tag( tag.tagClassContext, tag.tagFormatSimple, 12) 132 | ) 133 | ), 134 | ) 135 | -------------------------------------------------------------------------------- /scripts/goose_dataset_checker.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect 5 | 6 | ############################### 7 | # Import ASN1 modules 8 | ############################### 9 | from pyasn1.codec.ber import decoder 10 | from pyasn1.codec.ber import encoder 11 | from pyasn1.type import char 12 | from pyasn1.type import tag 13 | from pyasn1.type import univ 14 | 15 | ############################### 16 | # Import Scapy and Goose Modules 17 | ############################### 18 | # We have to tell script where to find the Goose module in parent directory 19 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.insert(0, parentdir) 22 | 23 | from scapy.layers.l2 import Ether 24 | from scapy.layers.l2 import Dot1Q 25 | from scapy.compat import raw 26 | from scapy.all import rdpcap 27 | from goose.goose import GOOSE 28 | from goose.goose import GOOSEPDU 29 | from goose.goose_pdu import AllData 30 | from goose.goose_pdu import Data 31 | from goose.goose_pdu import IECGoosePDU 32 | from goose.goose_pdu import UtcTime 33 | 34 | ############################### 35 | # Global Variables 36 | ############################### 37 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 38 | 39 | ############################### 40 | # Import packets into SCAPY 41 | ############################### 42 | inf = sys.argv[1] 43 | packets = rdpcap(inf) 44 | 45 | ############################### 46 | # Identify packets containing GOOSE messages. 47 | # Sometimes these will be hidden within VLAN packets, so account for these 48 | ############################### 49 | 50 | GOOSE_TYPE = 0x88b8 51 | def gooseTest(pkt): 52 | isGoose = False 53 | # Test for a Goose Ether Type 54 | if pkt.haslayer('Dot1Q'): 55 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 56 | if pkt.haslayer('Ether'): 57 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 58 | return isGoose 59 | 60 | 61 | ############################### 62 | # Process GOOSE PDU by decoding it with PYASN1 63 | ############################### 64 | def goose_pdu_decode(encoded_data): 65 | 66 | # Debugging on 67 | if DEBUG > 2: 68 | from pyasn1 import debug 69 | debug.setLogger(debug.Debug('all')) 70 | 71 | g = IECGoosePDU().subtype( 72 | implicitTag=tag.Tag( 73 | tag.tagClassApplication, 74 | tag.tagFormatConstructed, 75 | 1 76 | ) 77 | ) 78 | decoded_data, unprocessed_trail = decoder.decode( 79 | encoded_data, 80 | asn1Spec=g 81 | ) 82 | # This should work, but not sure. 83 | return decoded_data 84 | 85 | ############################### 86 | # Process packets and search for GOOSE 87 | ############################### 88 | # datasets = {src_mac:{'gocbref':GoCBRef, 'dataset':DataSet, 'goid':GoID}} 89 | datasets = {} 90 | for p in packets: 91 | # Only process Goose Packets 92 | if gooseTest(p): 93 | # Use SCAPY to parse the Goose header and the Goose PDU header 94 | d = GOOSE(p.load) 95 | 96 | # Grab the Goose PDU for processing 97 | gpdu = d[GOOSEPDU].original 98 | 99 | # Use PYASN1 to parse the Goose PDU 100 | gd = goose_pdu_decode(gpdu) 101 | 102 | src_mac = p['Ether'].src 103 | gocbref = str(gd['gocbRef']) 104 | dataset = str(gd['datSet']) 105 | goid = str(gd['goID']) 106 | numDatSetEntries = int(gd['numDatSetEntries']) 107 | godata = '%s - %s - %s - %s'%(gocbref, dataset, goid, numDatSetEntries) 108 | if src_mac in datasets.keys(): 109 | if godata not in datasets[src_mac]: 110 | datasets[src_mac].append(godata) 111 | else: 112 | datasets[src_mac] = [godata] 113 | 114 | ############################### 115 | # Print Statements and Functions 116 | ############################### 117 | ## Normal Print Statement 118 | print('Goose Data by Device Hardware Address') 119 | indent = ' ' 120 | for src in datasets.keys(): 121 | print('Source Device: %s'%(src)) 122 | for e in datasets[src]: 123 | print('%s%s'%(indent,e)) 124 | -------------------------------------------------------------------------------- /scripts/goose_device_cnt.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect 5 | 6 | ############################### 7 | # Import ASN1 modules 8 | ############################### 9 | from pyasn1.codec.ber import decoder 10 | from pyasn1.codec.ber import encoder 11 | from pyasn1.type import char 12 | from pyasn1.type import tag 13 | from pyasn1.type import univ 14 | 15 | ############################### 16 | # Import Scapy and Goose Modules 17 | ############################### 18 | # We have to tell script where to find the Goose module in parent directory 19 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.insert(0, parentdir) 22 | 23 | from scapy.layers.l2 import Ether 24 | from scapy.layers.l2 import Dot1Q 25 | from scapy.compat import raw 26 | from scapy.all import rdpcap 27 | from goose.goose import GOOSE 28 | from goose.goose import GOOSEPDU 29 | from goose.goose_pdu import AllData 30 | from goose.goose_pdu import Data 31 | from goose.goose_pdu import IECGoosePDU 32 | from goose.goose_pdu import UtcTime 33 | 34 | ############################### 35 | # Global Variables 36 | ############################### 37 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 38 | 39 | ############################### 40 | # Import packets into SCAPY 41 | ############################### 42 | inf = sys.argv[1] 43 | packets = rdpcap(inf) 44 | 45 | ############################### 46 | # Identify packets containing GOOSE messages. 47 | # Sometimes these will be hidden within VLAN packets, so account for these 48 | ############################### 49 | 50 | GOOSE_TYPE = 0x88b8 51 | def gooseTest(pkt): 52 | isGoose = False 53 | # Test for a Goose Ether Type 54 | if pkt.haslayer('Dot1Q'): 55 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 56 | if pkt.haslayer('Ether'): 57 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 58 | return isGoose 59 | 60 | 61 | ############################### 62 | # Process GOOSE PDU by decoding it with PYASN1 63 | ############################### 64 | def goose_pdu_decode(encoded_data): 65 | 66 | # Debugging on 67 | if DEBUG > 2: 68 | from pyasn1 import debug 69 | debug.setLogger(debug.Debug('all')) 70 | 71 | g = IECGoosePDU().subtype( 72 | implicitTag=tag.Tag( 73 | tag.tagClassApplication, 74 | tag.tagFormatConstructed, 75 | 1 76 | ) 77 | ) 78 | decoded_data, unprocessed_trail = decoder.decode( 79 | encoded_data, 80 | asn1Spec=g 81 | ) 82 | # This should work, but not sure. 83 | return decoded_data 84 | 85 | ############################### 86 | # Process packets and search for GOOSE 87 | ############################### 88 | # devsrc = {src_mac:(dst_mac:goid)} 89 | devsrc = {} 90 | for p in packets: 91 | # Only process Goose Packets 92 | if gooseTest(p): 93 | # Use SCAPY to parse the Goose header and the Goose PDU header 94 | d = GOOSE(p.load) 95 | 96 | # Grab the Goose PDU for processing 97 | gpdu = d[GOOSEPDU].original 98 | 99 | # Use PYASN1 to parse the Goose PDU 100 | gd = goose_pdu_decode(gpdu) 101 | 102 | # Grab Source address, destination address, and Goose ID 103 | src_mac = p['Ether'].src 104 | dst_mac = p['Ether'].dst 105 | goid = str(gd['goID']) 106 | 107 | # Combine stNum and t as they are related 108 | devgoose = (dst_mac, goid) 109 | if src_mac in devsrc.keys(): 110 | if devgoose not in devsrc[src_mac]: 111 | devsrc[src_mac].append(devgoose) 112 | else: 113 | devsrc[src_mac] = [devgoose] 114 | 115 | ############################### 116 | # Print Statements and Functions 117 | ############################### 118 | ## Normal Print Statement 119 | print('##################################################') 120 | print('### Goose Source Interface Address and Destination Addresses with Goose ID') 121 | print('##################################################') 122 | indent = ' ' 123 | print('Goose Device Count: %s\n'%(len(devsrc.keys()))) 124 | 125 | print('Source Address,Destination Address,goID') 126 | for src_mac in devsrc.keys(): 127 | #print('Source Device: %s'%(src_mac)) 128 | 129 | # Print all as CSV 130 | for e in devsrc[src_mac]: 131 | # Each device should have a destination mac and a goID 132 | #print('%s%s : %s'%(indent,e[0],e[1])) 133 | print('%s,%s,%s'%(src_mac,e[0],e[1])) 134 | -------------------------------------------------------------------------------- /scripts/goose_device_vlans.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect 5 | 6 | ############################### 7 | # Import ASN1 modules 8 | ############################### 9 | from pyasn1.codec.ber import decoder 10 | from pyasn1.codec.ber import encoder 11 | from pyasn1.type import char 12 | from pyasn1.type import tag 13 | from pyasn1.type import univ 14 | 15 | ############################### 16 | # Import Scapy and Goose Modules 17 | ############################### 18 | # We have to tell script where to find the Goose module in parent directory 19 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.insert(0, parentdir) 22 | 23 | from scapy.layers.l2 import Ether 24 | from scapy.layers.l2 import Dot1Q 25 | from scapy.compat import raw 26 | from scapy.all import rdpcap 27 | from goose.goose import GOOSE 28 | from goose.goose import GOOSEPDU 29 | from goose.goose_pdu import AllData 30 | from goose.goose_pdu import Data 31 | from goose.goose_pdu import IECGoosePDU 32 | from goose.goose_pdu import UtcTime 33 | 34 | ############################### 35 | # Global Variables 36 | ############################### 37 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 38 | 39 | ############################### 40 | # Import packets into SCAPY 41 | ############################### 42 | inf = sys.argv[1] 43 | packets = rdpcap(inf) 44 | 45 | ############################### 46 | # Identify packets containing GOOSE messages. 47 | # Sometimes these will be hidden within VLAN packets, so account for these 48 | ############################### 49 | 50 | GOOSE_TYPE = 0x88b8 51 | def gooseTest(pkt): 52 | isGoose = False 53 | # Test for a Goose Ether Type 54 | if pkt.haslayer('Dot1Q'): 55 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 56 | if pkt.haslayer('Ether'): 57 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 58 | return isGoose 59 | 60 | 61 | ############################### 62 | # Process GOOSE PDU by decoding it with PYASN1 63 | ############################### 64 | def goose_pdu_decode(encoded_data): 65 | 66 | # Debugging on 67 | if DEBUG > 2: 68 | from pyasn1 import debug 69 | debug.setLogger(debug.Debug('all')) 70 | 71 | g = IECGoosePDU().subtype( 72 | implicitTag=tag.Tag( 73 | tag.tagClassApplication, 74 | tag.tagFormatConstructed, 75 | 1 76 | ) 77 | ) 78 | decoded_data, unprocessed_trail = decoder.decode( 79 | encoded_data, 80 | asn1Spec=g 81 | ) 82 | # This should work, but not sure. 83 | return decoded_data 84 | 85 | ############################### 86 | # Process packets and search for GOOSE 87 | ############################### 88 | # vlans = {'src':[],'dst':[],'prio':[]} 89 | vlans = {} 90 | for p in packets: 91 | # Only process Goose Packets 92 | if gooseTest(p): 93 | # Use SCAPY to parse the Goose header and the Goose PDU header 94 | d = GOOSE(p.load) 95 | 96 | # Grab the Goose PDU for processing 97 | gpdu = d[GOOSEPDU].original 98 | 99 | # Use PYASN1 to parse the Goose PDU 100 | gd = goose_pdu_decode(gpdu) 101 | 102 | gocbRef = str(gd['gocbRef']) 103 | src_mac = p['Ether'].src 104 | dst_mac = p['Ether'].dst 105 | device = ('%s - %s'%(src_mac,gocbRef)) 106 | # Not all Goose networks have VLANs 107 | if p.haslayer(Dot1Q): 108 | pvlan = p.vlan 109 | prio = p.prio 110 | if pvlan in vlans.keys(): 111 | if device not in vlans[pvlan]['src']: 112 | vlans[pvlan]['src'].append(device) 113 | if dst_mac not in vlans[pvlan]['dst']: 114 | vlans[pvlan]['dst'].append(dst_mac) 115 | # Not sure if a VLAN's priority can change, so build a list, just in case 116 | if prio not in vlans[pvlan]['prio']: 117 | vlans[pvlan]['prio'].append(prio) 118 | else: 119 | vlans[pvlan] = {'src':[device],'dst':[dst_mac],'prio':[prio]} 120 | 121 | ############################### 122 | # Print Statements and Functions 123 | ############################### 124 | ## Normal Print Statement 125 | if not vlans: 126 | print('\nERROR: Packets in PCAP did not contain VLAN layers') 127 | exit() 128 | 129 | print('Goose VLANS by Device Hardware Address') 130 | indent = ' ' 131 | for vid in vlans.keys(): 132 | # Print VLAN ID and all priorities. Prio is a list of integers, so convert 133 | print('VLAN ID: %s has Priorities: %s'%(vid,', '.join(map(str,vlans[vid]['prio'])))) 134 | print('%sSource Devices:'%(indent)) 135 | for s in vlans[vid]['src']: 136 | print('%s%s'%(indent*2,s)) 137 | print('%sMulticast Addresses:'%(indent)) 138 | for d in vlans[vid]['dst']: 139 | print('%s%s'%(indent*2,d)) 140 | -------------------------------------------------------------------------------- /scripts/goose_packet_mod_test.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect, struct, time 5 | from copy import deepcopy 6 | 7 | ############################### 8 | # Import SCAPY and ASN1 modules 9 | ############################### 10 | from pyasn1.codec.ber import decoder 11 | from pyasn1.codec.ber import encoder 12 | from pyasn1.type import char 13 | from pyasn1.type import tag 14 | from pyasn1.type import univ 15 | from scapy.layers.l2 import Ether 16 | from scapy.layers.l2 import Dot1Q 17 | from scapy.compat import raw 18 | from scapy.all import rdpcap 19 | 20 | ############################### 21 | # Import Goose module 22 | ############################### 23 | # We have to tell script where to find the Goose module in parent directory 24 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 25 | parentdir = os.path.dirname(currentdir) 26 | sys.path.insert(0, parentdir) 27 | 28 | from goose.goose import GOOSE 29 | from goose.goose import GOOSEPDU 30 | from goose.goose_pdu import AllData 31 | from goose.goose_pdu import Data 32 | from goose.goose_pdu import IECGoosePDU 33 | from goose.goose_pdu import UtcTime 34 | 35 | ############################### 36 | # Global Variables 37 | ############################### 38 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 39 | UTC = 1 # 0: local time 1: UTC Time 40 | 41 | ############################### 42 | # Import packets into SCAPY 43 | ############################### 44 | inf = sys.argv[1] 45 | packets = rdpcap(inf) 46 | 47 | ############################### 48 | # Identify packets containing GOOSE messages. 49 | # Sometimes these will be hidden within VLAN packets, so account for these 50 | ############################### 51 | 52 | GOOSE_TYPE = 0x88b8 53 | def gooseTest(pkt): 54 | isGoose = False 55 | # Test for a Goose Ether Type 56 | if pkt.haslayer('Dot1Q'): 57 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 58 | if pkt.haslayer('Ether'): 59 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 60 | return isGoose 61 | 62 | 63 | ############################### 64 | # Display contents 65 | ############################### 66 | def gooseASN1_DataPrint(data): 67 | for e in list(IECGoosePDU()): 68 | if not data[e].hasValue(): 69 | continue 70 | if type(data[e]) == char.VisibleString: 71 | print('%s: %s'%(e,str(data[e]))) 72 | continue 73 | if type(data[e]) == univ.Integer: 74 | print('%s: %s'%(e,int(data[e]))) 75 | continue 76 | if type(data[e]) == UtcTime: 77 | #print('%s: %s'%(e,datetime.datetime.fromtimestamp(int.from_bytes(bytearray(data[e])[:4],'big')).strftime('%Y-%m-%d %H:%M:%S'))) 78 | print('%s: %s'%(e,timeStrFrom64bits(bytearray(data[e])[:4]))) 79 | continue 80 | if type(data[e]) == univ.Boolean: 81 | print('%s: %s'%(e,str(data[e]))) 82 | continue 83 | if type(data[e]) == AllData: 84 | print('%s'%(e)) 85 | for e in data.getComponentByName('allData'): 86 | for v in e.values(): 87 | print(' %s'%(v)) 88 | continue 89 | if type(data[e]) == univ.OctetString: 90 | print('%s: %s'%(e,str(data[e]))) 91 | continue 92 | 93 | ############################### 94 | # Process GOOSE PDU by decoding it with PYASN1 95 | ############################### 96 | def goose_pdu_decode(encoded_data): 97 | 98 | # Debugging on 99 | if DEBUG > 2: 100 | from pyasn1 import debug 101 | debug.setLogger(debug.Debug('all')) 102 | 103 | g = IECGoosePDU().subtype( 104 | implicitTag=tag.Tag( 105 | tag.tagClassApplication, 106 | tag.tagFormatConstructed, 107 | 1 108 | ) 109 | ) 110 | decoded_data, unprocessed_trail = decoder.decode( 111 | encoded_data, 112 | asn1Spec=g 113 | ) 114 | # This should work, but not sure. 115 | return decoded_data 116 | 117 | ############################### 118 | # Time has to be 64-bits or 8-bytes 119 | ############################### 120 | def curTime64Bits(utc=False): 121 | # Microsecond Resolution Ignored 122 | if UTC: 123 | curTime = time.mktime(datetime.datetime.utcnow().timetuple()) 124 | else: 125 | curTime = time.mktime(datetime.datetime.now().timetuple()) 126 | 127 | curTimeInt = int(curTime) 128 | curTimeInt64 = (curTimeInt << 32) 129 | curTimeInt64Str = curTimeInt64.to_bytes(8,'big') 130 | return curTimeInt64Str 131 | 132 | def timeStrFrom64bits(t): 133 | # Microsecond Resolution Ignored 134 | # Convert goose.pdu.UTCTime to bytes 135 | time32Int = int.from_bytes(bytes(t)[:4],'big') 136 | time32Str = datetime.datetime.utcfromtimestamp(time32Int).strftime('%Y-%m-%d %H:%M:%S') 137 | return time32Str 138 | 139 | ############################### 140 | # Process packets and search for GOOSE 141 | ############################### 142 | for p in packets: 143 | # Only process Goose Packets 144 | if gooseTest(p): 145 | # Use SCAPY to parse the Goose header and the Goose PDU header 146 | d = GOOSE(p.load) 147 | 148 | # Grab the Goose PDU for processing 149 | gpdu = d[GOOSEPDU].original 150 | 151 | # Use PYASN1 to parse the Goose PDU 152 | gd = goose_pdu_decode(gpdu) 153 | 154 | if DEBUG: 155 | print("Raw Load:\n%s\n\n"%d) 156 | if DEBUG > 1: 157 | print("Goose Length: %s\n"%d.length) 158 | print("Goose Load Length: %s\n"%len(d.load)) 159 | print("GPDU:\n%s\n\n"%gpdu) 160 | 161 | # Only process one packet 162 | break 163 | 164 | ############################### 165 | # Modify Contents 166 | ############################### 167 | tmpT = Data() 168 | tmpF = Data() 169 | tmpT.setComponentByName('boolean',True) 170 | tmpF.setComponentByName('boolean',False) 171 | 172 | # Get copy of packet and store the start and end of the payload 173 | mod_p = p.copy() 174 | mod_p_load_start = mod_p.load[:8] 175 | mod_p_load_end = mod_p.load[-6:] 176 | 177 | # Parse Goose Data 178 | mod_d = GOOSE(mod_p.load) 179 | mod_gpdu = mod_d[GOOSEPDU].original 180 | mod_gd = goose_pdu_decode(mod_gpdu) 181 | 182 | # Modify Goose Header 183 | tmpSTNUM = mod_gd.getComponentByName('stNum') 184 | tmpSQNUM = mod_gd.getComponentByName('sqNum') 185 | 186 | # Toggle Boolean Values 187 | for e in range(mod_gd['numDatSetEntries']): 188 | if mod_gd['allData'].getComponentByPosition(e) == False: 189 | mod_gd['allData'].setComponentByPosition(e,tmpT) 190 | continue 191 | elif mod_gd['allData'].getComponentByPosition(e) == True: 192 | mod_gd['allData'].setComponentByPosition(e,tmpF) 193 | continue 194 | 195 | ## Increment stNum 196 | mod_gd.setComponentByName('stNum', (int(tmpSTNUM) + 1)) 197 | ## Reset sqNum, note that we will need to increment this or increment stNum and keep this 0 198 | mod_gd.setComponentByName('sqNum', 0) 199 | new_time = curTime64Bits() 200 | mod_gd.setComponentByName('t',new_time) 201 | 202 | 203 | # Encode the modified data 204 | en_gd = encoder.encode(mod_gd) 205 | print('EN_GD: %s'%(en_gd)) 206 | 207 | # Rebuild the packet payload 208 | mod_p.load = mod_p_load_start + en_gd + mod_p_load_end 209 | 210 | ############################### 211 | # Show original packet 212 | ############################### 213 | if DEBUG > 1: 214 | print('###############################') 215 | print('Original Data') 216 | print('###############################') 217 | gooseASN1_DataPrint(gd) 218 | print('\n\n###############') 219 | print('Original Packet:') 220 | print('###############') 221 | p.show() 222 | 223 | ############################### 224 | # Show update packet 225 | ############################### 226 | if DEBUG: 227 | print('###############################') 228 | print('Updated Data') 229 | print('###############################') 230 | gooseASN1_DataPrint(mod_gd) 231 | print('\n\n###############') 232 | print('New Packet:') 233 | print('###############') 234 | mod_p.show() -------------------------------------------------------------------------------- /scripts/goose_parser.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect 5 | 6 | ############################### 7 | # Import SCAPY and ASN1 modules 8 | ############################### 9 | from pyasn1.codec.ber import decoder 10 | from pyasn1.codec.ber import encoder 11 | from pyasn1.type import char 12 | from pyasn1.type import tag 13 | from pyasn1.type import univ 14 | from scapy.layers.l2 import Ether 15 | from scapy.layers.l2 import Dot1Q 16 | from scapy.compat import raw 17 | from scapy.all import rdpcap 18 | 19 | ############################### 20 | # Import Goose module 21 | ############################### 22 | # We have to tell script where to find the Goose module in parent directory 23 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 24 | parentdir = os.path.dirname(currentdir) 25 | sys.path.insert(0, parentdir) 26 | 27 | from goose.goose import GOOSE 28 | from goose.goose import GOOSEPDU 29 | from goose.goose_pdu import AllData 30 | from goose.goose_pdu import Data 31 | from goose.goose_pdu import IECGoosePDU 32 | from goose.goose_pdu import UtcTime 33 | 34 | ############################### 35 | # Global Variables 36 | ############################### 37 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 38 | 39 | ############################### 40 | # Import packets into SCAPY 41 | ############################### 42 | inf = sys.argv[1] 43 | packets = rdpcap(inf) 44 | 45 | ############################### 46 | # Identify packets containing GOOSE messages. 47 | # Sometimes these will be hidden within VLAN packets, so account for these 48 | ############################### 49 | 50 | GOOSE_TYPE = 0x88b8 51 | def gooseTest(pkt): 52 | isGoose = False 53 | # Test for a Goose Ether Type 54 | if pkt.haslayer('Dot1Q'): 55 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 56 | if pkt.haslayer('Ether'): 57 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 58 | return isGoose 59 | 60 | 61 | ############################### 62 | # Display contents 63 | ############################### 64 | def gooseASN1_DataPrint(data): 65 | print('\n\n') 66 | for e in list(IECGoosePDU()): 67 | if not data[e].hasValue(): 68 | continue 69 | if type(data[e]) == char.VisibleString: 70 | print('%s: %s'%(e,str(data[e]))) 71 | continue 72 | if type(data[e]) == univ.Integer: 73 | print('%s: %s'%(e,int(data[e]))) 74 | continue 75 | if type(data[e]) == UtcTime: 76 | print('%s: %s'%(e,datetime.datetime.fromtimestamp(int.from_bytes(bytearray(gd[e])[:4],'big')).strftime('%Y-%m-%d %H:%M:%S'))) 77 | continue 78 | if type(data[e]) == univ.Boolean: 79 | print('%s: %s'%(e,str(data[e]))) 80 | continue 81 | if type(data[e]) == AllData: 82 | print('%s'%(e)) 83 | for e in data.getComponentByName('allData'): 84 | for v in e.values(): 85 | print(' %s'%(v)) 86 | continue 87 | if type(data[e]) == univ.OctetString: 88 | print('%s: %s'%(e,str(data[e]))) 89 | continue 90 | 91 | def gooseASN1_DataPrint_vendorA(data): 92 | print('\n\n') 93 | for e in list(IECGoosePDU()): 94 | if not gd[e].hasValue(): 95 | continue 96 | if type(gd[e]) == char.VisibleString: 97 | print('%s: %s'%(e,str(gd[e]))) 98 | continue 99 | if type(gd[e]) == univ.Integer: 100 | print('%s: %s'%(e,int(gd[e]))) 101 | continue 102 | if type(gd[e]) == UtcTime: 103 | print('%s: %s'%(e,datetime.datetime.fromtimestamp(int.from_bytes(bytearray(gd[e])[:4],'big')).strftime('%Y-%m-%d %H:%M:%S'))) 104 | continue 105 | if type(gd[e]) == univ.Boolean: 106 | print('%s: %s'%(e,str(gd[e]))) 107 | continue 108 | if type(gd[e]) == AllData: 109 | print('%s'%(e)) 110 | tmpstr = [] 111 | for e in gd.getComponentByName('allData'): 112 | for v in e.values(): 113 | tmpstr.append(str(v)) 114 | # Some vendors send two values for each value. Value1) the actual value Value2) quality 115 | # Join these into colon seperated values for readability 116 | for e in [': '.join(x) for x in zip(tmpstr[0::2],tmpstr[1::2])]: 117 | print('%s%s'%(' ',e)) 118 | continue 119 | if type(gd[e]) == univ.OctetString: 120 | print('%s: %s'%(e,str(gd[e]))) 121 | continue 122 | 123 | ############################### 124 | # Process GOOSE PDU by decoding it with PYASN1 125 | ############################### 126 | def goose_pdu_decode(encoded_data): 127 | 128 | # Debugging on 129 | if DEBUG > 2: 130 | from pyasn1 import debug 131 | debug.setLogger(debug.Debug('all')) 132 | 133 | g = IECGoosePDU().subtype( 134 | implicitTag=tag.Tag( 135 | tag.tagClassApplication, 136 | tag.tagFormatConstructed, 137 | 1 138 | ) 139 | ) 140 | decoded_data, unprocessed_trail = decoder.decode( 141 | encoded_data, 142 | asn1Spec=g 143 | ) 144 | # This should work, but not sure. 145 | return decoded_data 146 | 147 | 148 | ############################### 149 | # Process packets and search for GOOSE 150 | ############################### 151 | for p in packets: 152 | # Only process Goose Packets 153 | if gooseTest(p): 154 | # Use SCAPY to parse the Goose header and the Goose PDU header 155 | d = GOOSE(p.load) 156 | 157 | # Grab the Goose PDU for processing 158 | gpdu = d[GOOSEPDU].original 159 | 160 | # Use PYASN1 to parse the Goose PDU 161 | gd = goose_pdu_decode(gpdu) 162 | 163 | if DEBUG: 164 | print("Raw Load:\n%s\n\n"%d) 165 | if DEBUG > 1: 166 | print("Goose Length: %s\n"%d.length) 167 | print("Goose Load Length: %s\n"%len(d.load)) 168 | print("GPDU:\n%s\n\n"%gpdu) 169 | 170 | ############################### 171 | # Print Statements and Functions 172 | ############################### 173 | ## Normal Print Statement 174 | #print("Decoded Data:\n%s\n\n"%gd) 175 | 176 | # ANS1 Print Function 177 | #gooseASN1_DataPrint_vendorA(gd) 178 | gooseASN1_DataPrint(gd) 179 | 180 | if DEBUG > 2: break -------------------------------------------------------------------------------- /scripts/goose_routable_checker.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys 5 | 6 | ############################### 7 | # Import Scapy and Goose Modules 8 | ############################### 9 | from scapy.layers.l2 import Ether 10 | from scapy.layers.l2 import Dot1Q 11 | from scapy.compat import raw 12 | from scapy.all import rdpcap 13 | 14 | ############################### 15 | # Global Variables 16 | ############################### 17 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 18 | 19 | ############################### 20 | # Import packets into SCAPY 21 | ############################### 22 | inf = sys.argv[1] 23 | packets = rdpcap(inf) 24 | 25 | ############################### 26 | # Process packets and search for GOOSE 27 | ############################### 28 | # datasets = {src_mac:{'gocbref':GoCBRef, 'dataset':DataSet, 'goid':GoID}} 29 | routable_goose = b'\x01\x40' 30 | tunneled_goose = b'\xa0' 31 | non_tunnel_goose = b'\xa1' 32 | non_tunnel_sv = b'\xa2' 33 | non_tunnel_man = b'\xa3' 34 | 35 | cnt_routable_goose = 0 36 | cnt_tunneled_goose = 0 37 | cnt_non_tunnel_goose = 0 38 | cnt_non_tunnel_sv = 0 39 | cnt_non_tunnel_man = 0 40 | 41 | for p in packets: 42 | # Only process UDP Packets 43 | if p.haslayer('UDP'): 44 | if p.load[0:2] == routable_goose: 45 | cnt_routable_goose = cnt_routable_goose + 1 46 | gsi = p.load[2:3] 47 | if gsi == tunneled_goose: 48 | cnt_tunneled_goose = cnt_tunneled_goose + 1 49 | if gsi == non_tunnel_goose: 50 | cnt_non_tunnel_goose = cnt_non_tunnel_goose + 1 51 | if gsi == non_tunnel_sv: 52 | cnt_non_tunnel_sv = cnt_non_tunnel_sv + 1 53 | if gsi == non_tunnel_man: 54 | cnt_non_tunnel_man = cnt_non_tunnel_man + 1 55 | 56 | 57 | ############################### 58 | # Print Statements and Functions 59 | ############################### 60 | ## Normal Print Statement 61 | print('Routable Goose and Sampled Values') 62 | indent = ' ' 63 | if cnt_routable_goose: 64 | print('%sTotal Routable Goose Packets: %d'%(cnt_routable_goose)) 65 | print('%sTotal Tunneled Goose: %d'%(cnt_tunneled_goose)) 66 | print('%sTotal Non-tunneled Goose: %d'%(cnt_non_tunnel_goose)) 67 | print('%sTotal Non-tunneled Sampled Values: %d'%(cnt_non_tunnel_sv)) 68 | print('%sTotal Non-tunneled Management: %d'%(cnt_non_tunnel_man)) 69 | else: 70 | print('%sNo routable Goose or Sampled Values detected.'%(indent)) 71 | -------------------------------------------------------------------------------- /scripts/goose_security_checker.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect 5 | 6 | ############################### 7 | # Import Scapy and Goose Modules 8 | ############################### 9 | # We have to tell script where to find the Goose module in parent directory 10 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 11 | parentdir = os.path.dirname(currentdir) 12 | sys.path.insert(0, parentdir) 13 | 14 | from scapy.layers.l2 import Ether 15 | from scapy.layers.l2 import Dot1Q 16 | from scapy.compat import raw 17 | from scapy.all import rdpcap 18 | from goose.goose import GOOSE 19 | from goose.goose import GOOSEPDU 20 | from goose.goose_pdu import AllData 21 | from goose.goose_pdu import Data 22 | from goose.goose_pdu import IECGoosePDU 23 | from goose.goose_pdu import UtcTime 24 | 25 | ############################### 26 | # Global Variables 27 | ############################### 28 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 29 | 30 | ############################### 31 | # Import packets into SCAPY 32 | ############################### 33 | inf = sys.argv[1] 34 | packets = rdpcap(inf) 35 | 36 | ############################### 37 | # Identify packets containing GOOSE messages. 38 | # Sometimes these will be hidden within VLAN packets, so account for these 39 | ############################### 40 | 41 | GOOSE_TYPE = 0x88b8 42 | def gooseTest(pkt): 43 | isGoose = False 44 | # Test for a Goose Ether Type 45 | if pkt.haslayer('Dot1Q'): 46 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 47 | if pkt.haslayer('Ether'): 48 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 49 | return isGoose 50 | 51 | ############################### 52 | # Process packets and search for GOOSE 53 | ############################### 54 | cnt_sec = 0 55 | cnt_nosec = 0 56 | indent = ' ' 57 | for p in packets: 58 | # Only process Goose Packets 59 | if gooseTest(p): 60 | # Use SCAPY to parse the Goose header and the Goose PDU header 61 | d = GOOSE(p.load) 62 | 63 | # Test Goose Reserve1 Byte 64 | # FIXME: This byte also contains bits for simulation and future standardization. 65 | # Ignore until a mask can be made for security bits. 66 | 67 | # Test Goose Reserved2 Byte - this will contain a CRC if security enabled, else 0x0000 68 | if d.reserved2 > 0x0000: 69 | cnt_sec = cnt_sec + 1 70 | else: 71 | cnt_nosec = cnt_nosec + 1 72 | 73 | # Test Goose PDU for Security information 74 | # TODO: Add this test if this can be implemented without setting Reserved1 and / or Reserved2 bytes 75 | # TODO: Add this test or a separate script to collect and report useful security information. 76 | 77 | ############################### 78 | # Print Statements and Functions 79 | ############################### 80 | ## Normal Print Statement 81 | print('Goose Packets: %d'%(cnt_sec + cnt_nosec)) 82 | print('%sSecurity: %d'%(indent,cnt_sec)) 83 | print('%sNo Security: %d'%(indent,cnt_nosec)) 84 | -------------------------------------------------------------------------------- /scripts/goose_send_mod_packet.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect, struct, time 5 | from copy import deepcopy 6 | 7 | ############################### 8 | # Import SCAPY and ASN1 modules 9 | ############################### 10 | from pyasn1.codec.ber import decoder 11 | from pyasn1.codec.ber import encoder 12 | from pyasn1.type import char 13 | from pyasn1.type import tag 14 | from pyasn1.type import univ 15 | from scapy.layers.l2 import Ether 16 | from scapy.layers.l2 import Dot1Q 17 | from scapy.compat import raw 18 | from scapy.all import rdpcap 19 | 20 | ############################### 21 | # Import Goose module 22 | ############################### 23 | # We have to tell script where to find the Goose module in parent directory 24 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 25 | parentdir = os.path.dirname(currentdir) 26 | sys.path.insert(0, parentdir) 27 | 28 | from goose.goose import GOOSE 29 | from goose.goose import GOOSEPDU 30 | from goose.goose_pdu import AllData 31 | from goose.goose_pdu import Data 32 | from goose.goose_pdu import IECGoosePDU 33 | from goose.goose_pdu import UtcTime 34 | 35 | ############################### 36 | # Global Variables 37 | ############################### 38 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 39 | UTC = 1 # 0: local time 1: UTC Time 40 | 41 | ############################### 42 | # Import packets into SCAPY 43 | ############################### 44 | inf = sys.argv[1] 45 | packets = rdpcap(inf) 46 | 47 | ############################### 48 | # Identify packets containing GOOSE messages. 49 | # Sometimes these will be hidden within VLAN packets, so account for these 50 | ############################### 51 | 52 | GOOSE_TYPE = 0x88b8 53 | def gooseTest(pkt): 54 | isGoose = False 55 | # Test for a Goose Ether Type 56 | if pkt.haslayer('Dot1Q'): 57 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 58 | if pkt.haslayer('Ether'): 59 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 60 | return isGoose 61 | 62 | 63 | ############################### 64 | # Display contents 65 | ############################### 66 | def gooseASN1_DataPrint(data): 67 | for e in list(IECGoosePDU()): 68 | if not data[e].hasValue(): 69 | continue 70 | if type(data[e]) == char.VisibleString: 71 | print('%s: %s'%(e,str(data[e]))) 72 | continue 73 | if type(data[e]) == univ.Integer: 74 | print('%s: %s'%(e,int(data[e]))) 75 | continue 76 | if type(data[e]) == UtcTime: 77 | #print('%s: %s'%(e,datetime.datetime.fromtimestamp(int.from_bytes(bytearray(data[e])[:4],'big')).strftime('%Y-%m-%d %H:%M:%S'))) 78 | print('%s: %s'%(e,timeStrFrom64bits(bytearray(data[e])[:4]))) 79 | continue 80 | if type(data[e]) == univ.Boolean: 81 | print('%s: %s'%(e,str(data[e]))) 82 | continue 83 | if type(data[e]) == AllData: 84 | print('%s'%(e)) 85 | for e in data.getComponentByName('allData'): 86 | for v in e.values(): 87 | print(' %s'%(v)) 88 | continue 89 | if type(data[e]) == univ.OctetString: 90 | print('%s: %s'%(e,str(data[e]))) 91 | continue 92 | 93 | ############################### 94 | # Process GOOSE PDU by decoding it with PYASN1 95 | ############################### 96 | def goose_pdu_decode(encoded_data): 97 | 98 | # Debugging on 99 | if DEBUG > 2: 100 | from pyasn1 import debug 101 | debug.setLogger(debug.Debug('all')) 102 | 103 | g = IECGoosePDU().subtype( 104 | implicitTag=tag.Tag( 105 | tag.tagClassApplication, 106 | tag.tagFormatConstructed, 107 | 1 108 | ) 109 | ) 110 | decoded_data, unprocessed_trail = decoder.decode( 111 | encoded_data, 112 | asn1Spec=g 113 | ) 114 | # This should work, but not sure. 115 | return decoded_data 116 | 117 | ############################### 118 | # Time has to be 64-bits or 8-bytes 119 | ############################### 120 | def curTime64Bits(utc=False): 121 | # Microsecond Resolution Ignored 122 | if UTC: 123 | curTime = time.mktime(datetime.datetime.utcnow().timetuple()) 124 | else: 125 | curTime = time.mktime(datetime.datetime.now().timetuple()) 126 | 127 | curTimeInt = int(curTime) 128 | curTimeInt64 = (curTimeInt << 32) 129 | curTimeInt64Str = curTimeInt64.to_bytes(8,'big') 130 | return curTimeInt64Str 131 | 132 | def timeStrFrom64bits(t): 133 | # Microsecond Resolution Ignored 134 | time32Int = int.from_bytes(t[:4],'big') 135 | time32Str = datetime.datetime.fromtimestamp(time32Int).strftime('%Y-%m-%d %H:%M:%S') 136 | return time32Str 137 | 138 | ############################### 139 | # Process packets and search for GOOSE 140 | ############################### 141 | datSet = 'datSet' 142 | datSetList = [] 143 | g_packets = [] 144 | for p in packets: 145 | # Only process Goose Packets 146 | if gooseTest(p): 147 | # Store for processing later 148 | g_packets.append(p) 149 | 150 | # Use SCAPY to parse the Goose header and the Goose PDU header 151 | d = GOOSE(p.load) 152 | 153 | # Grab the Goose PDU for processing 154 | gpdu = d[GOOSEPDU].original 155 | 156 | # Use PYASN1 to parse the Goose PDU 157 | gd = goose_pdu_decode(gpdu) 158 | 159 | if DEBUG: 160 | print("Raw Load:\n%s\n\n"%d) 161 | if DEBUG > 1: 162 | print("Goose Length: %s\n"%d.length) 163 | print("Goose Load Length: %s\n"%len(d.load)) 164 | print("GPDU:\n%s\n\n"%gpdu) 165 | 166 | # Append the datSet value to a list for selection 167 | datSetList.append(str(gd[datSet])) 168 | 169 | ############################### 170 | # Have user select the packet to modify 171 | ############################### 172 | print('Select Goose packet to modify and send.') 173 | for e in range(len(datSetList)): 174 | print('[%d] %s'%(e,datSetList[e])) 175 | pnum = input('Select number:') 176 | pnum = int(pnum) 177 | if DEBUG: print('User Selected: %d'%(pnum)) 178 | #sys.exit() 179 | 180 | ############################### 181 | # Modify Contents 182 | ############################### 183 | tmpT = Data() 184 | tmpF = Data() 185 | tmpT.setComponentByName('boolean',True) 186 | tmpF.setComponentByName('boolean',False) 187 | 188 | # Get copy of packet and store the start and end of the payload 189 | p = g_packets[pnum] 190 | mod_p = p.copy() 191 | mod_p_load_start = mod_p.load[:8] 192 | mod_p_load_end = mod_p.load[-6:] 193 | 194 | # Parse Goose Data 195 | mod_d = GOOSE(mod_p.load) 196 | mod_gpdu = mod_d[GOOSEPDU].original 197 | mod_gd = goose_pdu_decode(mod_gpdu) 198 | 199 | # Modify Goose Header 200 | tmpSTNUM = mod_gd.getComponentByName('stNum') 201 | tmpSQNUM = mod_gd.getComponentByName('sqNum') 202 | 203 | # Toggle Boolean Values 204 | for e in range(mod_gd['numDatSetEntries']): 205 | if mod_gd['allData'].getComponentByPosition(e) == False: 206 | mod_gd['allData'].setComponentByPosition(e,tmpT) 207 | continue 208 | elif mod_gd['allData'].getComponentByPosition(e) == True: 209 | mod_gd['allData'].setComponentByPosition(e,tmpF) 210 | continue 211 | 212 | ## Increment stNum 213 | mod_gd.setComponentByName('stNum', (int(tmpSTNUM) + 1)) 214 | ## Reset sqNum, note that we will need to increment this or increment stNum and keep this 0 215 | mod_gd.setComponentByName('sqNum', 0) 216 | new_time = curTime64Bits() 217 | mod_gd.setComponentByName('t',new_time) 218 | 219 | 220 | # Encode the modified data 221 | en_gd = encoder.encode(mod_gd) 222 | print('\n\n###############') 223 | print('ENCODED Goose Data: %s'%(en_gd)) 224 | print('###############') 225 | 226 | # Rebuild the packet payload 227 | mod_p.load = mod_p_load_start + en_gd + mod_p_load_end 228 | 229 | ############################### 230 | # Show original packet 231 | ############################### 232 | if DEBUG > 1: 233 | print('###############################') 234 | print('Original Data') 235 | print('###############################') 236 | gooseASN1_DataPrint(gd) 237 | print('\n\n###############') 238 | print('Original Packet:') 239 | print('###############') 240 | p.show() 241 | 242 | ############################### 243 | # Show update packet 244 | ############################### 245 | if DEBUG: 246 | print('###############################') 247 | print('Updated Data') 248 | print('###############################') 249 | gooseASN1_DataPrint(mod_gd) 250 | print('\n\n###############') 251 | print('New Packet:') 252 | print('###############') 253 | mod_p.show() 254 | 255 | print('Resend this packet (time will be updated)?') 256 | send_p = input('Send [y/N]:') 257 | if send_p in ['Y','y']: print('Not Implemented Yet, But Nice Try!!!') 258 | if DEBUG: print('User Selected: %d'%(send_p)) 259 | -------------------------------------------------------------------------------- /scripts/goose_time_sync_checker.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect 5 | 6 | ############################### 7 | # Import ASN1 modules 8 | ############################### 9 | from pyasn1.codec.ber import decoder 10 | from pyasn1.codec.ber import encoder 11 | from pyasn1.type import char 12 | from pyasn1.type import tag 13 | from pyasn1.type import univ 14 | 15 | ############################### 16 | # Import Scapy and Goose Modules 17 | ############################### 18 | # We have to tell script where to find the Goose module in parent directory 19 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.insert(0, parentdir) 22 | 23 | from scapy.layers.l2 import Ether 24 | from scapy.layers.l2 import Dot1Q 25 | from scapy.compat import raw 26 | from scapy.all import rdpcap 27 | from goose.goose import GOOSE 28 | from goose.goose import GOOSEPDU 29 | from goose.goose_pdu import AllData 30 | from goose.goose_pdu import Data 31 | from goose.goose_pdu import IECGoosePDU 32 | from goose.goose_pdu import UtcTime 33 | 34 | ############################### 35 | # Global Variables 36 | ############################### 37 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 38 | 39 | ############################### 40 | # Import packets into SCAPY 41 | ############################### 42 | inf = sys.argv[1] 43 | packets = rdpcap(inf) 44 | 45 | ############################### 46 | # Identify packets containing GOOSE messages. 47 | # Sometimes these will be hidden within VLAN packets, so account for these 48 | ############################### 49 | 50 | GOOSE_TYPE = 0x88b8 51 | def gooseTest(pkt): 52 | isGoose = False 53 | # Test for a Goose Ether Type 54 | if pkt.haslayer('Dot1Q'): 55 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 56 | if pkt.haslayer('Ether'): 57 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 58 | return isGoose 59 | 60 | 61 | ############################### 62 | # Process GOOSE PDU by decoding it with PYASN1 63 | ############################### 64 | def goose_pdu_decode(encoded_data): 65 | 66 | # Debugging on 67 | if DEBUG > 2: 68 | from pyasn1 import debug 69 | debug.setLogger(debug.Debug('all')) 70 | 71 | g = IECGoosePDU().subtype( 72 | implicitTag=tag.Tag( 73 | tag.tagClassApplication, 74 | tag.tagFormatConstructed, 75 | 1 76 | ) 77 | ) 78 | decoded_data, unprocessed_trail = decoder.decode( 79 | encoded_data, 80 | asn1Spec=g 81 | ) 82 | # This should work, but not sure. 83 | return decoded_data 84 | 85 | 86 | ############################### 87 | # Time has to be 64-bits or 8-bytes 88 | ############################### 89 | def timeStrFrom64bits(t): 90 | # Microsecond Resolution Ignored 91 | # Convert goose.pdu.UTCTime to bytes 92 | time32Int = int.from_bytes(bytes(t)[:4],'big') 93 | time32Str = datetime.datetime.utcfromtimestamp(time32Int).strftime('%Y-%m-%d %H:%M:%S') 94 | return time32Str 95 | 96 | ############################### 97 | # Process packets and search for GOOSE 98 | ############################### 99 | # msgdates = {goid:(gostnum,gotime,gottl)} 100 | msgdates = {} 101 | for p in packets: 102 | # Only process Goose Packets 103 | if gooseTest(p): 104 | # Use SCAPY to parse the Goose header and the Goose PDU header 105 | d = GOOSE(p.load) 106 | 107 | # Grab the Goose PDU for processing 108 | gpdu = d[GOOSEPDU].original 109 | 110 | # Use PYASN1 to parse the Goose PDU 111 | gd = goose_pdu_decode(gpdu) 112 | 113 | # Grab Goose ID and check stNums 114 | goid = str(gd['goID']) 115 | gostnum = str(gd['stNum']) 116 | gottl = str(gd['timeAllowedtoLive']) 117 | gotime = '%s'%(timeStrFrom64bits(gd['t'])) 118 | # Combine stNum and t as they are related 119 | msgtime = (gostnum, gotime, gottl) 120 | if goid in msgdates.keys(): 121 | if msgtime not in msgdates[goid]: 122 | msgdates[goid].append(msgtime) 123 | else: 124 | msgdates[goid] = [msgtime] 125 | 126 | ############################### 127 | # Print Statements and Functions 128 | ############################### 129 | ## Normal Print Statement 130 | print('##################################################') 131 | print('### Goose Timestamps and TTL by Goose ID and stNum') 132 | print('### NOTE: Devices times can be different and not ') 133 | print('### have a negative impact on operations. ') 134 | print('### Goose devices are more interested in ') 135 | print('### message timing than device time. ') 136 | print('##################################################') 137 | 138 | indent = ' ' 139 | for goID in msgdates.keys(): 140 | print('Source Device: %s'%(goID)) 141 | 142 | # Print all 143 | for e in msgdates[goID]: 144 | # Each stNum should have a unique timestamp 145 | # Incrementing sqNum does NOT affect timestamp 146 | # TTL does not seem to be tied to stNum, included only for reference 147 | print('%s%s : %s : %s'%(indent,e[0],e[1],e[2])) 148 | -------------------------------------------------------------------------------- /scripts/goose_type_checker.py: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Import Python modules 3 | ############################### 4 | import os, sys, datetime, inspect 5 | 6 | ############################### 7 | # Import Scapy and Goose Modules 8 | ############################### 9 | # We have to tell script where to find the Goose module in parent directory 10 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 11 | parentdir = os.path.dirname(currentdir) 12 | sys.path.insert(0, parentdir) 13 | 14 | from scapy.layers.l2 import Ether 15 | from scapy.layers.l2 import Dot1Q 16 | from scapy.compat import raw 17 | from scapy.all import rdpcap 18 | from goose.goose import GOOSE 19 | from goose.goose import GOOSEPDU 20 | from goose.goose_pdu import AllData 21 | from goose.goose_pdu import Data 22 | from goose.goose_pdu import IECGoosePDU 23 | from goose.goose_pdu import UtcTime 24 | 25 | ############################### 26 | # Global Variables 27 | ############################### 28 | DEBUG = 0 # 0: off 1: Show Goose Payload 2: Full Debug 29 | 30 | ############################### 31 | # Import packets into SCAPY 32 | ############################### 33 | inf = sys.argv[1] 34 | packets = rdpcap(inf) 35 | 36 | ############################### 37 | # Identify packets containing GOOSE messages. 38 | # Sometimes these will be hidden within VLAN packets, so account for these 39 | ############################### 40 | 41 | GOOSE_TYPE = 0x88b8 42 | def gooseTest(pkt): 43 | isGoose = False 44 | # Test for a Goose Ether Type 45 | if pkt.haslayer('Dot1Q'): 46 | if pkt['Dot1Q'].type == GOOSE_TYPE: isGoose = True 47 | if pkt.haslayer('Ether'): 48 | if pkt['Ether'].type == GOOSE_TYPE: isGoose = True 49 | return isGoose 50 | 51 | GOOSE_TYPE1 = 0x88b8 52 | GOOSE_MAN = 0x88b9 53 | GOOSE_SVALS = 0x88ba 54 | GOOSE_TYPES = [GOOSE_TYPE1,GOOSE_MAN,GOOSE_SVALS] 55 | def gooseTypeTest(pkt): 56 | typeGoose = 0 57 | # Test for a Goose Ether Type 58 | if pkt.haslayer('Dot1Q'): 59 | if pkt['Dot1Q'].type in GOOSE_TYPES: typeGoose = pkt['Dot1Q'].type 60 | if pkt.haslayer('Ether'): 61 | if pkt['Ether'].type in GOOSE_TYPES: typeGoose = pkt['Ether'].type 62 | return typeGoose 63 | 64 | ############################### 65 | # Process packets and search for GOOSE 66 | ############################### 67 | goose_type1 = 0 68 | goose_type1a = 0 69 | goose_manage = 0 70 | goose_svalues = 0 71 | goose_sv_appid = 0x4000 72 | goose_type1a_appid = 0x8000 73 | indent = ' ' 74 | for p in packets: 75 | # Only process Goose Packets 76 | if gooseTest(p): 77 | # Use SCAPY to parse the Goose header and the Goose PDU header 78 | d = GOOSE(p.load) 79 | 80 | # Test Ethertype 81 | gtype = gooseTypeTest(p) 82 | gappid = d.appid 83 | if gtype == GOOSE_TYPE1: 84 | if gappid >= goose_type1a_appid: 85 | goose_type1a = goose_type1a + 1 86 | else: 87 | goose_type1 = goose_type1 + 1 88 | # TODO: Determine if Sampled Values requires APPID between 0x4000 and 0x7fff and EtherType 0x88ba 89 | if gtype == GOOSE_SVALS: 90 | goose_svalues = goose_svalues + 1 91 | if gtype == GOOSE_MAN: 92 | goose_manage = goose_manage + 1 93 | 94 | 95 | ############################### 96 | # Print Statements and Functions 97 | ############################### 98 | ## Normal Print Statement 99 | print('Goose Packets: %d'%(goose_type1 + goose_type1a + goose_manage + goose_svalues)) 100 | print('%sType 1 : %d'%(indent,goose_type1)) 101 | print('%sType 1a : %d'%(indent,goose_type1a)) 102 | print('%sGSE Management: %d'%(indent,goose_manage)) 103 | print('%sSampled Values: %d'%(indent,goose_svalues)) --------------------------------------------------------------------------------