├── README.md ├── mms_stack_detector.py ├── mms_structs.py ├── requirements.txt └── resources └── MMS_stack_detector.gif /README.md: -------------------------------------------------------------------------------- 1 | # MMS protocol stack detector 2 | 3 | ![](./resources/MMS_stack_detector.gif) 4 | 5 | > Manufacturing Message Specification (MMS) is an international standard (ISO 9506) dealing with messaging systems for transferring real time process data and supervisory control information between networked devices or computer applications. From [Wikipedia](https://en.wikipedia.org/wiki/Manufacturing_Message_Specification) 6 | 7 | MMS is popular protocol and multiple implementations exists in the market. It may be desirable to be able to indentify the specific implementation when performing a vulnerability research in order to better understand the attack surface. This tool is able to detect the most common MMS implementation stacks. 8 | 9 | ## Installation: 10 | ``` 11 | git clone https://github.com/claroty/MMS-Stack-Detector.git 12 | cd MMS-Stack-Detector 13 | python3 -m pip install -r requirements.txt 14 | ``` 15 | 16 | ## Usage: 17 | ``` 18 | python3 mms_stack_detector.py --help 19 | usage: mms_stack_detector.py [-h] [--server_ip SERVER_IP] [--from_file FROM_FILE] [--print_all_services] 20 | 21 | MMS Stack Detector 22 | 23 | options: 24 | -h, --help show this help message and exit 25 | --server_ip SERVER_IP 26 | Server/Device IP address 27 | --from_file FROM_FILE 28 | Specify file path of IP addresses separated by newline 29 | --print_all_services Whether to print the services along with the detected MMS stack 30 | ``` 31 | 32 | ## Examples: 33 | ``` 34 | python3 mms_stack_detector.py --server_ip 10.10.10.10 --print_all_services 35 | 36 | python3 mms_stack_detector.py --from_file /path/to/list_of_ip_addr > results.txt 37 | ``` 38 | 39 | ### Example output: 40 | ``` 41 | -------------------------- MMS Stack Detector --------------------------- 42 | ---------------------------[ 10.10.10.10 ]----------------------------- 43 | 44 | [*] Server Identification 45 | Vendor Name: libiec61850.com 46 | Model: NxD 47 | Version: 1.5.0 48 | ------------------------------------------------------------------------- 49 | [*] MMS Stack: LibIEC61850 Sig [0xee1c000000000000000118] 50 | ========================================================================= 51 | ``` 52 | 53 | ## Supported stacks 54 | - [LibIEC61850](https://libiec61850.com/) 55 | - [Triangle MicroWorks MMSd](https://www.trianglemicroworks.com/products/source-code-libraries/iec-61850-scl-pages) 56 | - [Sisco MMS Lite](https://sisconet.com/wp-content/uploads/2019/04/MktLit_MMS-EASE-Lite_082014.pdf) (PDF) 57 | - [INFO TECH S61850](https://www.infotech.pl/products/iec-61850/iec-61850-source-code-library) 58 | - [Vizimax](https://www.vizimax.com/iec-61850/) 59 | - [Bitronics](https://www.novatechautomation.com/products/orion-substation-automation#modal_extensive-protocol-library) 60 | 61 | ## How to add additional stack to the detector 62 | The stack detection is based on a subset of supported services. Those services are sent in `InitiateResponse` packet. 63 | If this subset of services is unique for this server, the the server can be identified. 64 | The following are the steps to be done to add new server to the detector: 65 | - Send a proper MMS `InitiateRequest` to the newly configured server 66 | - Review the services in the `InitiateResponse` 67 | - Convert the services to the sequental bitmap 68 | - If the bit sequence do not exist in `known_signatures` (the new server can be identified!), add the new sequence to the `known_signatures` struct . 69 | 70 | 71 | ## Dependencies 72 | - [construct](https://construct.readthedocs.io/en/latest/) 73 | - [bitarray](https://pypi.org/project/bitarray/) 74 | -------------------------------------------------------------------------------- /mms_stack_detector.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import socket 3 | import os 4 | from construct import * 5 | from bitarray import bitarray 6 | from enum import Enum 7 | from struct import pack 8 | from mms_structs import * 9 | 10 | 11 | class MMSDetectionFailed(Exception): 12 | pass 13 | 14 | 15 | # Some hard coded values 16 | DEFAULT_MMS_PORT = 102 17 | 18 | MMS_PDU_TYPE_INIT_REQ = 0xa9 19 | MMS_PDU_TYPE_IDENTIFY_REQ = 0xa2 20 | 21 | TPKT_LAYER_LEN = 4 22 | 23 | 24 | class TPDU_Types(Enum): 25 | TPDU_CONNECTION_REQUEST = 1 26 | TPDU_DATA = 2 27 | 28 | 29 | # The signarure data base 30 | known_signatures = [ 31 | { 32 | "Name": "LibIEC61850", 33 | "Required": bitarray('1110111000011100000000000000000000000000000000000000000000000000000000000000000100011000'), 34 | "Optional": bitarray('0000000000000000000000000000000000000000000000100000000000000000010000001111110000000000') 35 | }, 36 | { 37 | "Name": "Triangle MicroWorks MMSd", 38 | "Required": bitarray('1110111000011100000000000000000000000100000000000000000000000000011110010000001100011000'), 39 | "Optional": bitarray('0000000000000000000000000011100000000000000000100000000000000000000000001110110000000000') 40 | }, 41 | { 42 | "Name": "Sisco MMS Lite", 43 | "Required": bitarray('1110111000011100000000000000000000000100000000000000000000000000010110011110010100011000'), 44 | "Optional": bitarray('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 45 | }, 46 | { 47 | "Name": "INFO TECH S61850", 48 | "Required": bitarray('1110111000011100000000000000000000000100000000000000000000000000000000011110110100010000'), 49 | "Optional": bitarray('0000000000000000000000000000000000000000000000100000000000000000000000000000000000001000') 50 | }, 51 | { 52 | "Name": "Vizimax", 53 | "Required": bitarray('1110111000011100000000000000000000000100000000000000000000000000000000001110110100011000'), 54 | "Optional": bitarray('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 55 | }, 56 | { 57 | "Name": "Bitronics", 58 | "Required": bitarray('1110111000011100000000000000000000000100000000000000000000000000000000011110110000011000'), 59 | "Optional": bitarray('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 60 | } 61 | ] 62 | 63 | 64 | def print_prologue(server_ip): 65 | print("") 66 | print("-------------------------- MMS Stack Detector ---------------------------") 67 | print( 68 | f"---------------------------[{server_ip:^15}]-----------------------------") 69 | print("") 70 | 71 | 72 | def print_epilogue(): 73 | print("=========================================================================\n\n\n") 74 | 75 | 76 | def print_separator(): 77 | print("-------------------------------------------------------------------------") 78 | 79 | 80 | def print_services(services_bytes): 81 | print("[*] Supported services:\n") 82 | services = SERVICES_SUPPORTED.parse(services_bytes) 83 | for service, flag in services.items(): 84 | if '_io' not in service: 85 | print(f" [{'*' if flag else ' '}] {service}") 86 | print("") 87 | 88 | 89 | def exit_detection(): 90 | print_epilogue() 91 | raise MMSDetectionFailed 92 | 93 | 94 | def get_rfc1006_payload(tpdu_type, higher_layers_lenght): 95 | if tpdu_type == TPDU_Types.TPDU_CONNECTION_REQUEST: 96 | ISO_8073_LAYER = b'\x11' # Length 97 | ISO_8073_LAYER += b'\xe0' # PDU Type: CR Connect Request 98 | ISO_8073_LAYER += b'\x00\x00' # Destination reference 99 | ISO_8073_LAYER += b'\x00\x01' # Source reference 100 | ISO_8073_LAYER += b'\x00' # class + COTP flags 101 | ISO_8073_LAYER += b'\xc0\x01\x0a' # tpdu-size 102 | ISO_8073_LAYER += b'\xc1\x02\x00\x01' # src-tsap 103 | ISO_8073_LAYER += b'\xc2\x02\x00\x01' # dst-tsap 104 | 105 | elif tpdu_type == TPDU_Types.TPDU_DATA: 106 | ISO_8073_LAYER = b'\x02' # Length 107 | ISO_8073_LAYER += b'\xf0' # PDU Type: DT Data (0x0f) 108 | ISO_8073_LAYER += b'\x80' # TPDU number + Is Last 109 | 110 | else: 111 | print("ERROR TPDU Type") 112 | exit_detection() 113 | 114 | TPKT = b'\x03\x00' # Version + reserved 115 | TPKT += pack(">H", TPKT_LAYER_LEN + 116 | len(ISO_8073_LAYER) + higher_layers_lenght) 117 | return TPKT + ISO_8073_LAYER 118 | 119 | 120 | def get_osi_layers_initiatePDU(include_acse=False): 121 | 122 | # Session layer 123 | IS0_8327_LAYER = b'\x0d' # SPDU Type 124 | IS0_8327_LAYER += b'\xb6' # Length 125 | IS0_8327_LAYER += b'\x05\x06\x13\x01\x00\x16\x01\x02' # Connect Accept Item 126 | IS0_8327_LAYER += b'\x14\x02\x00\x02' # Session Requirement 127 | IS0_8327_LAYER += b'\x33\x02\x00\x01' # Calling Session Selector 128 | IS0_8327_LAYER += b'\x34\x02\x00\x01' # Called Session Selector 129 | IS0_8327_LAYER += b'\xc1\xa0' # Session user data\ 130 | 131 | # Presentation Layer ASN.1 BER Encoded 132 | ISO_8823_LAYER = b'\x31\x81' 133 | ISO_8823_LAYER += b'\x9d' 134 | ISO_8823_LAYER += b'\xa0\x03\x80\x01\x01' # mode-value 135 | ISO_8823_LAYER += b'\xa2\x81\x95' 136 | ISO_8823_LAYER += b'\x81\x04\x00\x00\x00\x01' # calling-presentation-selector 137 | ISO_8823_LAYER += b'\x82\x04\x00\x00\x00\x01' # called-presentation-selector 138 | ISO_8823_LAYER += b'\xa4\x23' 139 | ISO_8823_LAYER += b'\x30\x0f' 140 | ISO_8823_LAYER += b'\x02\x01\x01' # presentation-context-identifier 141 | ISO_8823_LAYER += b'\x06\x04\x52\x01\x00\x01' # abstract-syntax-name 142 | ISO_8823_LAYER += b'\x30\x04\x06\x02\x51\x01' # transfer-syntax-name-list 143 | ISO_8823_LAYER += b'\x30\x10' 144 | ISO_8823_LAYER += b'\x02\x01\x03' # presentation-context-identifier 145 | ISO_8823_LAYER += b'\x06\x05\x28\xca\x22\x02\x01' # abstract-syntax-name 146 | ISO_8823_LAYER += b'\x30\x04\x06\x02\x51\x01' # transfer-syntax-name-list 147 | ISO_8823_LAYER += b'\x61\x62\x30\x60\x02\x01\x01\xa0\x5b' # user-data 148 | 149 | # Application Layer ASN.1 BER Encoded 150 | ISO_8650_LAYER = b'\x60\x59' 151 | ISO_8650_LAYER += b'\xa1\x07\x06\x05\x28\xca\x22\x02\x03' # aSO-context-name 152 | ISO_8650_LAYER += b'\xa2\x07\x06\x05\x29\x01\x87\x67\x01' # called-AP-title 153 | ISO_8650_LAYER += b'\xa3\x03\x02\x01\x0c' # called-AE-qualifier 154 | ISO_8650_LAYER += b'\xa6\x06\x06\x04\x29\x01\x87\x67' # calling-AP-title 155 | ISO_8650_LAYER += b'\xa7\x03\x02\x01\x0c' # calling-AE-qualifier 156 | ISO_8650_LAYER += b'\xbe\x33\x28\x31\x06\x02\x51\x01\x02\x01\x03' # user-information: Association-data 157 | ISO_8650_LAYER += b'\xa0\x28' # MMS data 158 | 159 | if include_acse: 160 | return IS0_8327_LAYER + ISO_8823_LAYER + ISO_8650_LAYER 161 | else: 162 | return IS0_8327_LAYER + ISO_8823_LAYER 163 | 164 | 165 | def get_osi_layers_confirmedPDU(): 166 | 167 | # Session layer 168 | IS0_8327_LAYER_1 = b'\x01\x00' #SPDU Type + lenght 169 | IS0_8327_LAYER_2 = b'\x01\x00' #SPDU Type + lenght 170 | 171 | # Presentation Layer ASN.1 BER Encoded 172 | ISO_8823_LAYER = b'\x61\x0f' # User Data 173 | ISO_8823_LAYER += b'\x30\x0d' # PVD List 174 | ISO_8823_LAYER += b'\x02\x01\x03' # presentation-context-identifier 175 | ISO_8823_LAYER += b'\xa0\x08' # presentation-data-values 176 | 177 | return IS0_8327_LAYER_1 + IS0_8327_LAYER_2 + ISO_8823_LAYER 178 | 179 | 180 | def get_inititiadeRequest(is_reduced_osi): 181 | 182 | MMS_MSG = b'\xa8\x26' # initiate-RequestPDU 183 | MMS_MSG += b'\x80\x03\x00\xff\x00' # localDetailCalling 184 | MMS_MSG += b'\x81\x01\x01' # proposedMaxServOutstandingCalling 185 | MMS_MSG += b'\x82\x01\x01' # proposedMaxServOutstandingCalled 186 | MMS_MSG += b'\x83\x01\x0a' # proposedDataStructureNestingLevel 187 | MMS_MSG += b'\xa4\x16' # mmsInitRequestDetail 188 | MMS_MSG += b'\x80\x01\x01' # proposedVersionNumber 189 | MMS_MSG += b'\x81\x03\x05\xf1\x00' # padding + proposedParameterCBB 190 | MMS_MSG += b'\x82\x0c\x03\xee\x1c\x00\x00\x04\x02\x00\x00\x01\xed\x18' # padding + servicesSupportedCalling 191 | 192 | if is_reduced_osi: 193 | CONSTRUCTED = MMS_MSG 194 | else: 195 | CONSTRUCTED = get_osi_layers_initiatePDU(True) + MMS_MSG 196 | 197 | RFC1006 = get_rfc1006_payload(TPDU_Types.TPDU_DATA, len(CONSTRUCTED)) 198 | 199 | return RFC1006 + CONSTRUCTED 200 | 201 | 202 | def get_identifyRequest(is_reduced_osi): 203 | 204 | MMS_MSG = b'\xa0\x06' # confirmed-RequestPDU 205 | MMS_MSG += b'\x02\x02\x11\x4f' # invokeID 206 | MMS_MSG += b'\x82\x00' # confirmedServiceRequest 207 | 208 | if is_reduced_osi: 209 | CONSTRUCTED = MMS_MSG 210 | else: 211 | CONSTRUCTED = get_osi_layers_confirmedPDU() + MMS_MSG 212 | 213 | RFC1006 = get_rfc1006_payload(TPDU_Types.TPDU_DATA, len(CONSTRUCTED)) 214 | 215 | return RFC1006 + CONSTRUCTED 216 | 217 | 218 | def get_mms_identification(server, is_reduced_osi): 219 | try: 220 | response = get_identify_response(server, is_reduced_osi) 221 | except Exception as e: 222 | print( 223 | f"ERROR: Something went wrong while sending identification request, got exception: {e}") 224 | exit_detection() 225 | return parse_identify_response(response) 226 | 227 | 228 | def get_initialize_response(server, is_reduced_osi=False): 229 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 230 | sock.settimeout(3) 231 | sock.connect((server, DEFAULT_MMS_PORT)) 232 | sock.send(get_rfc1006_payload(TPDU_Types.TPDU_CONNECTION_REQUEST, 0)) 233 | sock.recv(1024) 234 | sock.send(get_inititiadeRequest(is_reduced_osi)) 235 | resp = sock.recv(1024) 236 | sock.close() 237 | return resp 238 | 239 | 240 | def get_identify_response(server, is_reduced_osi=False): 241 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 242 | sock.settimeout(3) 243 | sock.connect((server, DEFAULT_MMS_PORT)) 244 | sock.send(get_rfc1006_payload(TPDU_Types.TPDU_CONNECTION_REQUEST, 0)) 245 | sock.recv(1024) 246 | sock.send(get_inititiadeRequest(is_reduced_osi)) 247 | resp = sock.recv(1024) 248 | sock.send(get_identifyRequest(is_reduced_osi)) 249 | resp = sock.recv(1024) 250 | sock.close() 251 | return resp 252 | 253 | 254 | def get_mms_stack_signature(server): 255 | sig_done = bitarray(endian='big') 256 | is_reduced_osi = False 257 | response_init = None 258 | try: 259 | response_init = get_initialize_response(server) 260 | except Exception: 261 | is_reduced_osi = True 262 | 263 | if response_init == None or response_init == b'' or is_reduced_osi is True: 264 | is_reduced_osi = True 265 | print("ERROR: Did not get a response, trying ABB mode") 266 | try: 267 | response_init = get_initialize_response( 268 | server, is_reduced_osi) 269 | if response_init == None or response_init == b'': 270 | print("ERROR: Something went wrong, did not get a response, exiting...") 271 | exit_detection() 272 | except ConnectionResetError: 273 | print( 274 | "ERROR: Something went wrong, could not connect to the server, exiting...") 275 | exit_detection() 276 | except socket.timeout: 277 | print("ERROR: Something went wrong, did not get a response, exiting...") 278 | exit_detection() 279 | 280 | signature = get_initiate_response_mms_data(response_init) 281 | 282 | if signature is None: 283 | print("ERROR: Failed to parse the payload, please open an issue so we could fix it") 284 | exit_detection() 285 | 286 | sig_done.frombytes(signature) 287 | 288 | vendor_name, model, version = get_mms_identification( 289 | server, is_reduced_osi) 290 | 291 | return sig_done, vendor_name, model, version 292 | 293 | 294 | def get_initiate_response_mms_data(data): 295 | """ 296 | This function is used to extract the MMS data from the response to the initial initiate-request packet, 297 | as we don't want to deal with parsing the layers before MMS (ISO 8327-1, ISO 8823, ISO 8650-1). 298 | So we're just looking for the first byte of the MMS response and try to parse the data from there. 299 | :param data: the data over COTP returned from after sending initiate-RequestPDU 300 | :return: only the MMS Data, as bytes 301 | """ 302 | offsets = [i for i in range(len(data)) if data[i] == MMS_PDU_TYPE_INIT_REQ] 303 | 304 | if len(offsets) == 0: 305 | return None 306 | 307 | return get_supported_services_from_mms_payload(data[offsets[0]:]) 308 | 309 | 310 | def get_supported_services_from_mms_payload(raw_data): 311 | parsed_raw = MMS_TLV.parse(raw_data) 312 | 313 | if parsed_raw.type != MMS_PDU_TYPE_INIT_REQ: 314 | print( 315 | "ERROR: Something went wrong, this is not an initiate_ResponsePDU, exiting...") 316 | exit_detection() 317 | 318 | parsed_raws = Struct("tlvs" / GreedyRange(MMS_TLV)).parse(parsed_raw.data) 319 | for tlv in parsed_raws.tlvs: 320 | if tlv.type == 0xa4: 321 | return get_supported_services_bitmap(tlv) 322 | 323 | 324 | def get_supported_services_bitmap(main_tlv): 325 | parsed_raws = Struct("tlvs" / GreedyRange(MMS_TLV)).parse(main_tlv.data) 326 | for tlv in parsed_raws.tlvs: 327 | if tlv.type == 0x82: 328 | temp = Struct("padding" / Byte, 329 | "services_supported" / Bytes(11)).parse(tlv.data) 330 | return temp.services_supported 331 | 332 | print("ERROR: Something went wrong, didn't find servicesSupportedCalled, exiting...") 333 | exit_detection() 334 | 335 | # It will much better to parse the prev to MMS layers instead of serching for the pdu type.. 336 | def parse_identify_response(data): 337 | offsets = [i for i in range(len(data)) if data[i] 338 | == MMS_PDU_TYPE_IDENTIFY_REQ] 339 | if len(offsets) == 0: 340 | print("ERROR: Something went wrong, failed to parse identify response") 341 | return None 342 | 343 | parsed_raws = Struct("tlvs" / GreedyRange(MMS_TLV) 344 | ).parse(data[offsets[0]:]) 345 | for tlv in parsed_raws.tlvs: 346 | if tlv.type == 0xa2: 347 | indentify_data = GreedyRange(MMS_TLV).parse(tlv.data) 348 | if len(indentify_data) < 3: 349 | print("ERROR: Something went wrong, failed to parse identify response") 350 | vendor_name = indentify_data[0].data.decode('utf-8') 351 | model_name = indentify_data[1].data.decode('utf-8') 352 | revision_name = indentify_data[2].data.decode('utf-8') 353 | return vendor_name, model_name, revision_name 354 | return None 355 | 356 | 357 | def print_detection_one_server(server_ip, print_all_services): 358 | print_prologue(server_ip) 359 | 360 | stack_signature, vendor_name, model, version = get_mms_stack_signature( 361 | server_ip) 362 | 363 | if vendor_name is not None and model is not None and version is not None: 364 | print( 365 | f"[*] Server Identification \n\tVendor Name: {vendor_name}\n\tModel: {model}\n\tVersion: {version}") 366 | print_separator() 367 | else: 368 | print("[!] Server Identification \n\tParsing failed...") 369 | print_separator() 370 | 371 | guessed_stack = None 372 | for signature in known_signatures: 373 | 374 | # Ignore optional bits by ANDing it with NEG(Optional bit array) 375 | if not ((stack_signature ^ signature['Required']) & (~signature['Optional'])).any(): 376 | guessed_stack = signature['Name'] 377 | break 378 | 379 | if print_all_services: 380 | print_services(stack_signature.tobytes()) 381 | print_separator() 382 | 383 | if guessed_stack is None: 384 | print( 385 | f"[!] Unrecognized Stack: Please open an issue so we can add it. Signature: [0x{stack_signature.tobytes().hex()}]") 386 | else: 387 | print( 388 | f"[*] MMS Stack: {guessed_stack} Sig [0x{stack_signature.tobytes().hex()}]") 389 | print_epilogue() 390 | 391 | 392 | def main(): 393 | # Example: python3 mms_stack_detector.py --server_ip 10.10.10.10 --print_all_services 394 | parser = argparse.ArgumentParser(description='MMS Stack Detector') 395 | parser.add_argument('--server_ip', help='Server/Device IP address') 396 | parser.add_argument('--from_file', 397 | help='Specify file path of IP addresses separated by newline') 398 | parser.add_argument('--print_all_services', action='store_true', 399 | help='Whether to print the services along with the detected MMS stack') 400 | 401 | args = parser.parse_args() 402 | 403 | if args.server_ip is not None: 404 | try: 405 | print_detection_one_server(args.server_ip, args.print_all_services) 406 | except MMSDetectionFailed: 407 | exit() 408 | elif args.from_file is not None and os.path.exists(args.from_file): 409 | with open(args.from_file, 'r') as f: 410 | for line in f.readlines(): 411 | try: 412 | print_detection_one_server( 413 | line.strip(), args.print_all_services) 414 | except MMSDetectionFailed: 415 | continue 416 | else: 417 | parser.print_help() 418 | 419 | 420 | if __name__ == "__main__": 421 | main() 422 | -------------------------------------------------------------------------------- /mms_structs.py: -------------------------------------------------------------------------------- 1 | from struct import Struct 2 | from construct import * 3 | 4 | def parse_length(length_byte_struct, length_bytes): 5 | if not length_byte_struct.length_bytes: 6 | return length_byte_struct.length_value 7 | length = 0 8 | for i, length_byte in enumerate(length_bytes[::-1]): # big endian 9 | length += length_byte * pow(0x100, i) 10 | return length 11 | 12 | # generic MMS TLV 13 | MMS_TLV = Struct( 14 | "type" / Byte, 15 | "_length_byte" / BitStruct("length_bytes" / Flag, 16 | "length_value" / BitsInteger(7)), 17 | "length_bytes" / If(this._length_byte.length_bytes == True, Bytes(this._length_byte.length_value)), 18 | "length" / Computed(lambda x: parse_length(x._length_byte, x.length_bytes)), 19 | "data" / Bytes(this.length) 20 | ) 21 | 22 | SERVICES_SUPPORTED = BitStruct( 23 | "status" / Flag, 24 | "get_name_list" / Flag, 25 | "identify" / Flag, 26 | "rename" / Flag, 27 | "read" / Flag, 28 | "write" / Flag, 29 | "get_variable_access_attributes" / Flag, 30 | "define_named_variable" / Flag, 31 | "define_scattered_access" / Flag, 32 | "get_scattered_access_attributes" / Flag, 33 | "delete_variable_access" / Flag, 34 | "define_named_variable_list" / Flag, 35 | "get_named_variable_list_attributes" / Flag, 36 | "delete_named_variable_list" / Flag, 37 | "define_named_type" / Flag, 38 | "get_named_type_attributes" / Flag, 39 | "delete_named_type" / Flag, 40 | "input" / Flag, 41 | "output" / Flag, 42 | "take_control" / Flag, 43 | "relinquish_control" / Flag, 44 | "define_semaphore" / Flag, 45 | "delete_semaphore" / Flag, 46 | "report_semaphore_status" / Flag, 47 | "report_pool_semaphore_status" / Flag, 48 | "report_semaphore_entry_status" / Flag, 49 | "initiate_download_sequence" / Flag, 50 | "download_segment" / Flag, 51 | "terminate_download_sequence" / Flag, 52 | "initiate_upload_sequence" / Flag, 53 | "upload_segment" / Flag, 54 | "terminate_upload_sequence" / Flag, 55 | "request_domain_download" / Flag, 56 | "request_domain_upload" / Flag, 57 | "load_domain_content" / Flag, 58 | "store_domain_content" / Flag, 59 | "delete_domain" / Flag, 60 | "get_domain_attributes" / Flag, 61 | "create_program_invocation" / Flag, 62 | "delete_program_invocation" / Flag, 63 | "start" / Flag, 64 | "stop" / Flag, 65 | "resume" / Flag, 66 | "reset" / Flag, 67 | "kill" / Flag, 68 | "get_program_invocation_attributes" / Flag, 69 | "obtain_file" / Flag, 70 | "define_event_condition" / Flag, 71 | "delete_event_condition" / Flag, 72 | "get_event_condition_attributes" / Flag, 73 | "report_event_condition_status" / Flag, 74 | "alter_event_condition_monitoring" / Flag, 75 | "trigger_event" / Flag, 76 | "define_event_action" / Flag, 77 | "delete_event_action" / Flag, 78 | "get_event_action_attributes" / Flag, 79 | "report_action_status" / Flag, 80 | "define_event_enrollment" / Flag, 81 | "delete_event_enrollment" / Flag, 82 | "alter_event_enrollment" / Flag, 83 | "report_event_enrollment_status" / Flag, 84 | "get_event_enrollment_attributes" / Flag, 85 | "acknowledge_event_notification" / Flag, 86 | "get_alarm_summary" / Flag, 87 | "get_alarm_enrollment_summary" / Flag, 88 | "read_journal" / Flag, 89 | "write_journal" / Flag, 90 | "initialize_journal" / Flag, 91 | "report_journal_status" / Flag, 92 | "create_journal" / Flag, 93 | "delete_journal" / Flag, 94 | "get_capability_list" / Flag, 95 | "file_open" / Flag, 96 | "file_read" / Flag, 97 | "file_close" / Flag, 98 | "file_rename" / Flag, 99 | "file_delete" / Flag, 100 | "file_directory" / Flag, 101 | "unsolicited_status" / Flag, 102 | "information_report" / Flag, 103 | "event_notification" / Flag, 104 | "attach_to_event_condition" / Flag, 105 | "attach_to_semaphore" / Flag, 106 | "conclude" / Flag, 107 | "cancel" / Flag, 108 | "reserved" / BitsInteger(3) 109 | ) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | construct 2 | bitarray -------------------------------------------------------------------------------- /resources/MMS_stack_detector.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claroty/MMS-Stack-Detector/b91eb5cb00eac7208d2854f7a6b976a89f5204d8/resources/MMS_stack_detector.gif --------------------------------------------------------------------------------