1# This file is part of Scapy 2# See http://www.secdev.org/projects/scapy for more informations 3# Copyright (C) Santiago Hernandez Ramos <shramos@protonmail.com> 4# This program is published under GPLv2 license 5 6 7from scapy.packet import Packet, bind_layers 8from scapy.fields import FieldLenField, BitEnumField, StrLenField, \ 9 ShortField, ConditionalField, ByteEnumField, ByteField, StrNullField 10from scapy.layers.inet import TCP 11from scapy.error import Scapy_Exception 12from scapy.compat import orb, chb 13 14 15# CUSTOM FIELDS 16 17# source: http://stackoverflow.com/a/43717630 18class VariableFieldLenField(FieldLenField): 19 def addfield(self, pkt, s, val): 20 val = self.i2m(pkt, val) 21 data = [] 22 while val: 23 if val > 127: 24 data.append(val & 127) 25 val /= 127 26 else: 27 data.append(val) 28 lastoffset = len(data) - 1 29 data = b"".join(chb(val | (0 if i == lastoffset else 128)) 30 for i, val in enumerate(data)) 31 return s + data 32 if len(data) > 3: 33 raise Scapy_Exception("%s: malformed length field" % 34 self.__class__.__name__) 35 36 def getfield(self, pkt, s): 37 value = 0 38 for offset, curbyte in enumerate(s): 39 curbyte = orb(curbyte) 40 value += (curbyte & 127) * (128 ** offset) 41 if curbyte & 128 == 0: 42 return s[offset + 1:], value 43 if offset > 2: 44 raise Scapy_Exception("%s: malformed length field" % 45 self.__class__.__name__) 46 47 48# LAYERS 49 50CONTROL_PACKET_TYPE = {1: 'CONNECT', 51 2: 'CONNACK', 52 3: 'PUBLISH', 53 4: 'PUBACK', 54 5: 'PUBREC', 55 6: 'PUBREL', 56 7: 'PUBCOMP', 57 8: 'SUBSCRIBE', 58 9: 'SUBACK', 59 10: 'UNSUBSCRIBE', 60 11: 'UNSUBACK', 61 12: 'PINGREQ', 62 13: 'PINGRESP', 63 14: 'DISCONNECT'} 64 65 66QOS_LEVEL = {0: 'At most once delivery', 67 1: 'At least once delivery', 68 2: 'Exactly once delivery'} 69 70 71# source: http://stackoverflow.com/a/43722441 72class MQTT(Packet): 73 name = "MQTT fixed header" 74 fields_desc = [ 75 BitEnumField("type", 1, 4, CONTROL_PACKET_TYPE), 76 BitEnumField("DUP", 0, 1, {0: 'Disabled', 77 1: 'Enabled'}), 78 BitEnumField("QOS", 0, 2, QOS_LEVEL), 79 BitEnumField("RETAIN", 0, 1, {0: 'Disabled', 80 1: 'Enabled'}), 81 # Since the size of the len field depends on the next layer, we need 82 # to "cheat" with the length_of parameter and use adjust parameter to 83 # calculate the value. 84 VariableFieldLenField("len", None, length_of="len", 85 adjust=lambda pkt, x: len(pkt.payload),), 86 ] 87 88 89class MQTTConnect(Packet): 90 name = "MQTT connect" 91 fields_desc = [ 92 FieldLenField("length", None, length_of="protoname"), 93 StrLenField("protoname", "", 94 length_from=lambda pkt: pkt.length), 95 ByteField("protolevel", 0), 96 BitEnumField("usernameflag", 0, 1, {0: 'Disabled', 97 1: 'Enabled'}), 98 BitEnumField("passwordflag", 0, 1, {0: 'Disabled', 99 1: 'Enabled'}), 100 BitEnumField("willretainflag", 0, 1, {0: 'Disabled', 101 1: 'Enabled'}), 102 BitEnumField("willQOSflag", 0, 2, QOS_LEVEL), 103 BitEnumField("willflag", 0, 1, {0: 'Disabled', 104 1: 'Enabled'}), 105 BitEnumField("cleansess", 0, 1, {0: 'Disabled', 106 1: 'Enabled'}), 107 BitEnumField("reserved", 0, 1, {0: 'Disabled', 108 1: 'Enabled'}), 109 ShortField("klive", 0), 110 FieldLenField("clientIdlen", None, length_of="clientId"), 111 StrLenField("clientId", "", 112 length_from=lambda pkt: pkt.clientIdlen), 113 # Payload with optional fields depending on the flags 114 ConditionalField(FieldLenField("wtoplen", None, length_of="willtopic"), 115 lambda pkt: pkt.willflag == 1), 116 ConditionalField(StrLenField("willtopic", "", 117 length_from=lambda pkt: pkt.wtoplen), 118 lambda pkt: pkt.willflag == 1), 119 ConditionalField(FieldLenField("wmsglen", None, length_of="willmsg"), 120 lambda pkt: pkt.willflag == 1), 121 ConditionalField(StrLenField("willmsg", "", 122 length_from=lambda pkt: pkt.wmsglen), 123 lambda pkt: pkt.willflag == 1), 124 ConditionalField(FieldLenField("userlen", None, length_of="username"), 125 lambda pkt: pkt.usernameflag == 1), 126 ConditionalField(StrLenField("username", "", 127 length_from=lambda pkt: pkt.userlen), 128 lambda pkt: pkt.usernameflag == 1), 129 ConditionalField(FieldLenField("passlen", None, length_of="password"), 130 lambda pkt: pkt.passwordflag == 1), 131 ConditionalField(StrLenField("password", "", 132 length_from=lambda pkt: pkt.passlen), 133 lambda pkt: pkt.passwordflag == 1), 134 ] 135 136 137RETURN_CODE = {0: 'Connection Accepted', 138 1: 'Unacceptable protocol version', 139 2: 'Identifier rejected', 140 3: 'Server unavailable', 141 4: 'Bad username/password', 142 5: 'Not authorized'} 143 144 145class MQTTConnack(Packet): 146 name = "MQTT connack" 147 fields_desc = [ 148 ByteField("sessPresentFlag", 0), 149 ByteEnumField("retcode", 0, RETURN_CODE), 150 # this package has not payload 151 ] 152 153 154class MQTTPublish(Packet): 155 name = "MQTT publish" 156 fields_desc = [ 157 FieldLenField("length", None, length_of="topic"), 158 StrLenField("topic", "", 159 length_from=lambda pkt: pkt.length), 160 ConditionalField(ShortField("msgid", None), 161 lambda pkt: (pkt.underlayer.QOS == 1 162 or pkt.underlayer.QOS == 2)), 163 StrLenField("value", "", 164 length_from=lambda pkt: (pkt.underlayer.len - 165 pkt.length - 2)), 166 ] 167 168 169class MQTTPuback(Packet): 170 name = "MQTT puback" 171 fields_desc = [ 172 ShortField("msgid", None), 173 ] 174 175 176class MQTTPubrec(Packet): 177 name = "MQTT pubrec" 178 fields_desc = [ 179 ShortField("msgid", None), 180 ] 181 182 183class MQTTPubrel(Packet): 184 name = "MQTT pubrel" 185 fields_desc = [ 186 ShortField("msgid", None), 187 ] 188 189 190class MQTTPubcomp(Packet): 191 name = "MQTT pubcomp" 192 fields_desc = [ 193 ShortField("msgid", None), 194 ] 195 196 197class MQTTSubscribe(Packet): 198 name = "MQTT subscribe" 199 fields_desc = [ 200 ShortField("msgid", None), 201 FieldLenField("length", None, length_of="topic"), 202 StrLenField("topic", "", 203 length_from=lambda pkt: pkt.length), 204 ByteEnumField("QOS", 0, QOS_LEVEL), 205 ] 206 207 208ALLOWED_RETURN_CODE = {0: 'Success', 209 1: 'Success', 210 2: 'Success', 211 128: 'Failure'} 212 213 214class MQTTSuback(Packet): 215 name = "MQTT suback" 216 fields_desc = [ 217 ShortField("msgid", None), 218 ByteEnumField("retcode", None, ALLOWED_RETURN_CODE) 219 ] 220 221 222class MQTTUnsubscribe(Packet): 223 name = "MQTT unsubscribe" 224 fields_desc = [ 225 ShortField("msgid", None), 226 StrNullField("payload", "") 227 ] 228 229 230class MQTTUnsuback(Packet): 231 name = "MQTT unsuback" 232 fields_desc = [ 233 ShortField("msgid", None) 234 ] 235 236 237# LAYERS BINDINGS 238 239bind_layers(TCP, MQTT, sport=1883) 240bind_layers(TCP, MQTT, dport=1883) 241bind_layers(MQTT, MQTTConnect, type=1) 242bind_layers(MQTT, MQTTConnack, type=2) 243bind_layers(MQTT, MQTTPublish, type=3) 244bind_layers(MQTT, MQTTPuback, type=4) 245bind_layers(MQTT, MQTTPubrec, type=5) 246bind_layers(MQTT, MQTTPubrel, type=6) 247bind_layers(MQTT, MQTTPubcomp, type=7) 248bind_layers(MQTT, MQTTSubscribe, type=8) 249bind_layers(MQTT, MQTTSuback, type=9) 250bind_layers(MQTT, MQTTUnsubscribe, type=10) 251bind_layers(MQTT, MQTTUnsuback, type=11) 252bind_layers(MQTTConnect, MQTT) 253bind_layers(MQTTConnack, MQTT) 254bind_layers(MQTTPublish, MQTT) 255bind_layers(MQTTPuback, MQTT) 256bind_layers(MQTTPubrec, MQTT) 257bind_layers(MQTTPubrel, MQTT) 258bind_layers(MQTTPubcomp, MQTT) 259bind_layers(MQTTSubscribe, MQTT) 260bind_layers(MQTTSuback, MQTT) 261bind_layers(MQTTUnsubscribe, MQTT) 262bind_layers(MQTTUnsuback, MQTT) 263