├── .gitignore ├── LICENSE ├── README.md ├── TriStation.lua ├── malware_exec.pcap └── triconex_honeypot.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Nozomi Networks 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tricotools 2 | A collection of utilities and tools related to the **Triconex** hardware manufactured by Schneider Electric. Triconex products are a Safety Instrumented Systems (SIS) based on patented triple modular redundancy (TMR) industrial safety-shutdown technology. 3 | 4 | # TriStation dissector 5 | The Wireshark dissector for the TriStation protocol has been written in Lua to be portable and easy to use. 6 | 7 | ## Installation 8 | The Lua script is natively supported by Wireshark and there are no required dependencies for using it. The script needs to be placed in the right directory depending on the operating system used. Below are the reported working paths used during development: 9 | 10 | * Linux / MacOS: ```~/.config/wireshark/plugins``` 11 | * Windows: ```%appdata%\Wireshark\plugins``` 12 | 13 | Note that in some systems the plug-in folder could be missing. To fix this issue, just create it manually and place the Lua script in it. 14 | 15 | More detailed information about plug-in installation can be found at the official web page: 16 | [https://www.wireshark.org/docs/wsug_html_chunked/ChPluginFolders.html](https://www.wireshark.org/docs/wsug_html_chunked/ChPluginFolders.html) 17 | 18 | ## Features 19 | The dissector interprets the TriStation protocol function codes, populating the description fields for the specific analysed packets. The user can easily investigate the packet stream for: 20 | * The direction of communication 21 | * Function codes translated as descriptive text 22 | * Extraction of transmitted PLC programs 23 | * TRITON malware detection 24 | 25 | The dissector automatically detects TRITON malware using specific indicators obtained during malware analysis performed in the laboratory. We provide a **stripped** PCAP file captured during real execution of the malware to demonstrate the described features. 26 | 27 | We would like to emphasize that the functionality of the dissector is the result of our malware analysis and reflects the attackers’ reverse engineering of the TriStation protocol. 28 | 29 | # Triconex Honeypot 30 | The Triconex Honeypot tool can be used by defense teams to simulate SIS controllers with particular system configurations, using them to detect reconnaissance scans and capture malicious payloads. It can therefore play a useful role in detecting unknown traffic targeting a SIS network. 31 | 32 | ## Dependencies 33 | The python script requires the library ```crcmod``` 34 | 35 | * Linux / MacOS: ```pip install crcmod``` 36 | 37 | ## Features 38 | The tool simulates the controller’s behavior, convincing the Scheider Diagnostic Tool (software used to test it during our analysis) that we are a real controller sending its status, including: 39 | * Controller version 40 | * Controller status 41 | * Controller memory 42 | * Chassis type 43 | * Connected modules 44 | * Status LEDs 45 | * LED type and color 46 | * Hardware key position (RUN/STOP/PROGRAM) 47 | * Project name 48 | * Modules configuration match/mismatch (project <-> chassis) 49 | 50 | Although the script is currently only a proof-of-concept, it can be expanded to support an extensive number of functions. Its realism can be increased to the point where it is indistinguishable from a real controller. In addition, the script can be executed by a regular, inexpensive computer attached to the network. 51 | -------------------------------------------------------------------------------- /TriStation.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------- 2 | -- 3 | -- TriStation Protocol Plug-in for Wireshark 4 | -- 5 | -- date : April, 4th 2018 6 | -- author : Younes Dragoni (@ydragoni) 7 | -- author : Alessandro Di Pinto (@adipinto) 8 | -- contact : secresearch [ @ ] nozominetworks [ . ] com 9 | -- 10 | -------------------------------------------------------------------------- 11 | 12 | ts_proto = Proto("TriStation" , "TriStation Protocol") 13 | -- CRC32 module loading 14 | pkt_table = {} 15 | crc_table = {} 16 | pkt_max_number = 0 17 | 18 | -- TS command list 19 | local TS_names = { 20 | [-1]= 'Not set', 21 | [0]= 'Start download all', 22 | [1]= 'Start download change', 23 | [2]= 'Update configuration', 24 | [3]= 'Upload configuration', 25 | [4]= 'Set I/O addresses', 26 | [5]= 'Allocate network', 27 | [6]= 'Load vector table', 28 | [7]= 'Set calendar', 29 | [8]= 'Get calendar', 30 | [9]= 'Set scan time', 31 | [10]= 'End download all', 32 | [11]= 'End download change', 33 | [12]= 'Cancel download change', 34 | [13]= 'Attach TRICON', 35 | [14]= 'Set I/O address limits', 36 | [15]= 'Configure module', 37 | [16]= 'Set multiple point values', 38 | [17]= 'Enable all points', 39 | [18]= 'Upload vector table', 40 | [19]= 'Get CP status ', 41 | [20]= 'Run program', 42 | [21]= 'Halt program', 43 | [22]= 'Pause program', 44 | [23]= 'Do single scan', 45 | [24]= 'Get chassis status', 46 | [25]= 'Get minimum scan time', 47 | [26]= 'Set node number', 48 | [27]= 'Set I/O point values', 49 | [28]= 'Get I/O point values', 50 | [29]= 'Get MP status', 51 | [30]= 'Set retentive values', 52 | [31]= 'Adjust clock calendar', 53 | [32]= 'Clear module alarms', 54 | [33]= 'Get event log', 55 | [34]= 'Set SOE block', 56 | [35]= 'Record event log', 57 | [36]= 'Get SOE data', 58 | [37]= 'Enable OVD', 59 | [38]= 'Disable OVD', 60 | [39]= 'Enable all OVDs', 61 | [40]= 'Disable all OVDs', 62 | [41]= 'Process MODBUS', 63 | [42]= 'Upload network', 64 | [43]= 'Set lable', 65 | [44]= 'Configure system variables', 66 | [45]= 'Deconfigure module', 67 | [46]= 'Get system variables', 68 | [47]= 'Get module types', 69 | [48]= 'Begin conversion table download', 70 | [49]= 'Continue conversion table download', 71 | [50]= 'End conversion table download', 72 | [51]= 'Get conversion table', 73 | [52]= 'Set ICM status', 74 | [53]= 'Broadcast SOE data available', 75 | [54]= 'Get module versions', 76 | [55]= 'Allocate program', 77 | [56]= 'Allocate function', 78 | [57]= 'Clear retentives', 79 | [58]= 'Set initial values', 80 | [59]= 'Start TS2 program download', 81 | [60]= 'Set TS2 data area', 82 | [61]= 'Get TS2 data', 83 | [62]= 'Set TS2 data', 84 | [63]= 'Set program information', 85 | [64]= 'Get program information', 86 | [65]= 'Upload program', 87 | [66]= 'Upload function', 88 | [67]= 'Get point groups', 89 | [68]= 'Allocate symbol table', 90 | [69]= 'Get I/O address', 91 | [70]= 'Resend I/O address', 92 | [71]= 'Get program timing', 93 | [72]= 'Allocate multiple functions', 94 | [73]= 'Get node number', 95 | [74]= 'Get symbol table', 96 | [75]= 'Unk75', 97 | [76]= 'Unk76', 98 | [77]= 'Unk77', 99 | [78]= 'Unk78', 100 | [79]= 'Unk79', 101 | [80]= 'Go to DOWNLOAD mode', 102 | [81]= 'Unk81', 103 | [83]= 'Unk83', 104 | [100]= 'Command rejected', 105 | [101]= 'Download all permitted', 106 | [102]= 'Download change permitted', 107 | [103]= 'Modification accepted', 108 | [104]= 'Download cancelled', 109 | [105]= 'Program accepted', 110 | [106]= 'TRICON attached', 111 | [107]= 'I/O addresses set', 112 | [108]= 'Get CP status response', 113 | [109]= 'Program is running', 114 | [110]= 'Program is halted', 115 | [111]= 'Program is paused', 116 | [112]= 'End of single scan', 117 | [113]= 'Get chassis configuration response', 118 | [114]= 'Scan period modified', 119 | [115]= '<115>', 120 | [116]= '<116>', 121 | [117]= 'Module configured', 122 | [118]= '<118>', 123 | [119]= 'Get chassis status response', 124 | [120]= 'Vectors response', 125 | [121]= 'Get I/O point values response', 126 | [122]= 'Calendar changed', 127 | [123]= 'Configuration updated', 128 | [124]= 'Get minimum scan time response', 129 | [125]= '<125>', 130 | [126]= 'Node number set', 131 | [127]= 'Get MP status response', 132 | [128]= 'Retentive values set', 133 | [129]= 'SOE block set', 134 | [130]= 'Module alarms cleared', 135 | [131]= 'Get event log response', 136 | [132]= 'Symbol table ccepted', 137 | [133]= 'OVD enable accepted', 138 | [134]= 'OVD disable accepted', 139 | [135]= 'Record event log response', 140 | [136]= 'Upload network response', 141 | [137]= 'Get SOE data response', 142 | [138]= 'Alocate network accepted', 143 | [139]= 'Load vector table accepted', 144 | [140]= 'Get calendar response', 145 | [141]= 'Label set', 146 | [142]= 'Get module types response', 147 | [143]= 'System variables configured', 148 | [144]= 'Module deconfigured', 149 | [145]= '<145>', 150 | [146]= '<146>', 151 | [147]= 'Get conversion table response', 152 | [148]= 'ICM print data sent', 153 | [149]= 'Set ICM status response', 154 | [150]= 'Get system variables response', 155 | [151]= 'Get module versions response', 156 | [152]= 'Process MODBUS response', 157 | [153]= 'Allocate program response', 158 | [154]= 'Allocate function response', 159 | [155]= 'Clear retentives response', 160 | [156]= 'Set initial values response', 161 | [157]= 'Set TS2 data area response', 162 | [158]= 'Get TS2 data response', 163 | [159]= 'Set TS2 data response', 164 | [160]= 'Set program information reponse', 165 | [161]= 'Get program information response', 166 | [162]= 'Upload program response', 167 | [163]= 'Upload function response', 168 | [164]= 'Get point groups response', 169 | [165]= 'Allocate symbol table response', 170 | [166]= 'Program timing response', 171 | [167]= 'Disable points full', 172 | [168]= 'Allocate multiple functions response', 173 | [169]= 'Get node number response', 174 | [170]= 'Symbol table response', 175 | [200]= 'Wrong command', 176 | [201]= 'Load is in progress', 177 | [202]= 'Bad clock calendar data', 178 | [203]= 'Control program not halted', 179 | [204]= 'Control program checksum error', 180 | [205]= 'No memory available', 181 | [206]= 'Control program not valid', 182 | [207]= 'Not loading a control program', 183 | [208]= 'Network is out of range', 184 | [209]= 'Not enough arguments', 185 | [210]= 'A Network is missing', 186 | [211]= 'The download time mismatches', 187 | [212]= 'Key setting prohibits this operation', 188 | [213]= 'Bad control program version', 189 | [214]= 'Command not in correct sequence', 190 | [215]= '<215>', 191 | [216]= 'Bad Index for a module', 192 | [217]= 'Module address is invalid', 193 | [218]= '<218>', 194 | [219]= '<219>', 195 | [220]= 'Bad offset for an I/O point', 196 | [221]= 'Invalid point type', 197 | [222]= 'Invalid Point Location', 198 | [223]= 'Program name is invalid', 199 | [224]= '<224>', 200 | [225]= '<225>', 201 | [226]= '<226>', 202 | [227]= 'Invalid module type', 203 | [228]= '<228>', 204 | [229]= 'Invalid table type', 205 | [230]= '<230>', 206 | [231]= 'Invalid network continuation', 207 | [232]= 'Invalid scan time', 208 | [233]= 'Load is busy', 209 | [234]= 'An MP has re-educated', 210 | [235]= 'Invalid chassis or slot', 211 | [236]= 'Invalid SOE number', 212 | [237]= 'Invalid SOE type', 213 | [238]= 'Invalid SOE state', 214 | [239]= 'The variable is write protected', 215 | [240]= 'Node number mismatch', 216 | [241]= 'Command not allowed', 217 | [242]= 'Invalid sequence number', 218 | [243]= 'Time change on non-master TRICON', 219 | [244]= 'No free Tristation ports', 220 | [245]= 'Invalid Tristation I command', 221 | [246]= 'Invalid TriStation 1131 command', 222 | [247]= 'Only one chassis allowed', 223 | [248]= 'Bad variable address', 224 | [249]= 'Response overflow', 225 | [250]= 'Invalid bus', 226 | [251]= 'Disable is not allowed', 227 | [252]= 'Invalid length', 228 | [253]= 'Point cannot be disabled', 229 | [254]= 'Too many retentive variables', 230 | [255]= 'LOADER_CONNECT', 231 | [256]= 'Unknown reject code' 232 | } 233 | 234 | message_type = ProtoField.uint16("ts.message_type", "TCM_type", base.HEX) 235 | crc16 = ProtoField.uint16("ts.crc16", "crc16", base.HEX) 236 | crc32 = ProtoField.uint32("ts.TScksum", "TScksum", base.HEX) 237 | cid = ProtoField.uint8("ts.ts_cid", "cid", base.DEC) 238 | message_length = ProtoField.int16("ts.message_length", "data_len", base.DEC) 239 | ts_checksum = ProtoField.uint16("ts.ts_chks", "checksum", base.HEX) 240 | ts_function = ProtoField.uint32("ts.ts_function", "func", base.HEX) 241 | ts_program = ProtoField.uint32("ts.ts_program", "program", base.HEX) 242 | ts_full_program = ProtoField.new("Programs","ts.ts_full_program", ftypes.BYTES) 243 | ts_signature = ProtoField.uint32("ts.ts_signature", "triton signature", base.HEX) 244 | ts_sequence = ProtoField.uint8("ts.ts_sequence", "seq_num", base.DEC) 245 | ts_cmd = ProtoField.int16("ts.ts_cmd", "Command", base.DEC) 246 | ts_module = ProtoField.uint8("ts.ts_module", "module_type", base.HEX) 247 | ts_unknown = ProtoField.int64("ts.ts_unk", "unk", base.DEC) 248 | ts_length = ProtoField.int64("ts.ts_len", "data_len", base.DEC) 249 | ts_cp_fstat = ProtoField.uint64("ts.ts_cp_fstat", "fstat", base.DEC) 250 | ts_cp_keyState = ProtoField.uint8("ts.ts_cp_keyState", "keyState", base.HEX) 251 | ts_cp_runState = ProtoField.uint8("ts.ts_cp_runState", "runState", base.HEX) 252 | ts_path = ProtoField.uint8("ts.ts_path", "path", base.DEC) 253 | 254 | ts_proto.fields = { 255 | message_type, 256 | crc16, 257 | crc32, 258 | message_length, 259 | cid, 260 | ts_cmd, 261 | ts_sequence, 262 | ts_unknown, 263 | ts_checksum, 264 | ts_length, 265 | ts_cp_keyState, 266 | ts_cp_runState, 267 | ts_cp_fstat, 268 | ts_function, 269 | ts_program, 270 | ts_signature, 271 | ts_project, 272 | ts_path, 273 | ts_module, 274 | ts_full_program 275 | } 276 | 277 | function ts_proto.dissector(buffer, pinfo, tree) 278 | length = buffer:len() 279 | pinfo.cols.protocol = ts_proto.name 280 | 281 | local subtree = tree:add(ts_proto, buffer(), "TriStation Protocol") 282 | tcm_data = subtree:add(buffer(4), "TCM communication: ") 283 | local opcode = buffer(0,1):uint() 284 | local opcode_name = get_tcm_opcode(opcode) 285 | 286 | tcm_data:add_le(opcode, buffer(0,1)):append_text(" [" .. opcode_name .. "]") 287 | tcm_data:add_le(buffer(1,1), "Channel: ", buffer(1,1):uint()) 288 | tcm_len = tcm_data:add_le(message_length, buffer(2,2)) 289 | 290 | if (buffer(2,2):uint() ~= 0) then 291 | ts_data = subtree:add(buffer(4,buffer(2,2):le_uint()), "TS communication: ") 292 | local direction = buffer(4,1):le_uint() 293 | local direction_type = get_comm_type(direction) 294 | ts_data:add(ts_path, buffer(4,1)):append_text(" [" .. direction_type .. "]") 295 | ts_data:add(cid, buffer(5,1)) 296 | cmd_data = ts_data:add(ts_cmd, buffer(6,1):le_uint()) 297 | cmd_detail = TS_names[buffer(6,1):le_uint()] 298 | if(cmd_detail ~= nil) then 299 | -- Supported command 300 | cmd_data:append_text(" [" .. cmd_detail .. "]") 301 | else 302 | -- Command unknown 303 | cmd_data:append_text(" [unknown TS command]") 304 | end 305 | ts_seq = ts_data:add(ts_sequence, buffer(7,1)) 306 | unknown_value = ts_data:add(ts_unknown, buffer(8,2)) 307 | checksum_value = ts_data:add(ts_checksum, buffer(10,2)):append_text(" (" .. buffer(10,2):uint() .. ")") 308 | len_value = ts_data:add_le(ts_length, buffer(12,2)) 309 | -- Create function for each known response and call based on the reply 310 | if buffer(6,1):le_uint() == 108 then cp_status_resp(buffer, cmd_data) 311 | elseif buffer(6,1):le_uint() == 55 then allocate_program(buffer, cmd_data, pinfo) 312 | elseif buffer(6,1):le_uint() == 56 then allocate_function(buffer, cmd_data) 313 | elseif buffer(6,1):le_uint() == 59 then ts2_program_download(buffer, cmd_data) 314 | elseif buffer(6,1):le_uint() == 151 then get_t2_data_module_version(buffer, cmd_data) 315 | elseif buffer(6,1):le_uint() == 65 then upload_program_req(buffer, cmd_data) 316 | elseif buffer(6,1):le_uint() == 66 then upload_function_req(buffer, cmd_data) 317 | elseif buffer(6,1):le_uint() == 163 then upload_function_res(buffer, cmd_data) 318 | elseif buffer(6,1):le_uint() == 162 then upload_program_res(buffer, cmd_data) 319 | elseif buffer(6,1):le_uint() == 119 then get_chassis_status_resp(buffer, cmd_data) 320 | elseif buffer(6,1):le_uint() == 106 then tricon_attached(buffer, cmd_data) 321 | else 322 | if ((buffer(12,2):le_uint() - 10) ~= 0) then 323 | ts_raw = ts_data:add(buffer(14,buffer(12,2):le_uint() - 10), "Data: " .. buffer(14,buffer(12,2):le_uint() - 10)) 324 | end 325 | end 326 | 327 | crc16_value = tree:add(crc16, buffer(length-2)):append_text(" (" .. buffer(length-2):uint() .. ")") 328 | else -- No TS data 329 | crc16_value = tree:add(crc16, buffer(length-2)):append_text(" (" .. buffer(length-2):uint() .. ")") 330 | end 331 | end 332 | 333 | function get_led_status(bit) 334 | local led_status = "Unknown status" 335 | 336 | if bit == "1" then led_status = "ON" 337 | elseif bit == "0" then led_status = "OFF" end 338 | 339 | return led_status 340 | end 341 | 342 | -- Function: TCM function list 343 | function get_tcm_opcode(opcode) 344 | local opcode_name = "Unknown command" 345 | 346 | if opcode == 1 then opcode_name = "CONNECT REQUEST" 347 | elseif opcode == 2 then opcode_name = "CONNECT REPLY" 348 | elseif opcode == 3 then opcode_name = "DISCONN REPLY" 349 | elseif opcode == 4 then opcode_name = "DISCONN REQUEST" 350 | elseif opcode == 5 then opcode_name = "COMMAND REPLY" 351 | elseif opcode == 6 then opcode_name = "PING" 352 | elseif opcode == 7 then opcode_name = "CONN LIMIT REACHED" 353 | elseif opcode == 8 then opcode_name = "NOT CONNECTED" 354 | elseif opcode == 9 then opcode_name = "MPS ARE DEAD" 355 | elseif opcode == 10 then opcode_name = "ACCESS DENIED" 356 | elseif opcode == 11 then opcode_name = "CONNECTION FAILED" end 357 | 358 | return opcode_name 359 | end 360 | 361 | -- Function: TS modules list 362 | function get_module_type(opcode) 363 | local opcode_name = "Unknown module" 364 | 365 | if opcode == 255 then opcode_name = "3008/N Tricon Enhanced Main Processor" 366 | elseif opcode == 1 then opcode_name = "3501/E/T/TN Discrete Input, 115 V, 32 points" 367 | elseif opcode == 2 then opcode_name = "3502/E/EN Discrete Input, 48 V, 32 points" 368 | elseif opcode == 3 then opcode_name = "3503/E/EN Discrete Input, 24 V, 32 points" 369 | elseif opcode == 7 then opcode_name = "3505/E/EN Discrete Input, 24 V, Low Threshold, 32 points" 370 | elseif opcode == 11 then opcode_name = "3508/E Discrete Input, 230 V, 32 points" 371 | elseif opcode == 17 then opcode_name = "3601/E/T/TN Discrete Output, 115 VAC, 16 points" 372 | elseif opcode == 19 then opcode_name = "3603/B/E/T/TN Discrete Output, 120 VDC, 16 points" 373 | elseif opcode == 20 then opcode_name = "3604/E/EN Discrete Output, 24 VDC, 16 points" 374 | elseif opcode == 23 then opcode_name = "3608/E Discrete Output, 48 VAC, 16 points" 375 | elseif opcode == 24 then opcode_name = "3607/E/EN Discrete Output, 48 VDC, 16 points" 376 | elseif opcode == 29 then opcode_name = "6603 Discrete Output, 24 VDC, 16 points" 377 | elseif opcode == 30 then opcode_name = "6602 Discrete Output, 48 VDC, 16 points" 378 | elseif opcode == 31 then opcode_name = "6601 Discrete Output, 115 VAC, 16 points" 379 | elseif opcode == 32 then opcode_name = "3701/N Analog Input, 10 V input, 32 points" 380 | elseif opcode == 33 then opcode_name = "3700/A/AN Analog Input, 5 V input, 32 points" 381 | elseif opcode == 38 then opcode_name = "3510/N Pulse Input, 8 points" 382 | elseif opcode == 40 then opcode_name = "3801 Analog I/O, 10 V inp, 4-20ma out, 8 inputs, 4 outputs" 383 | elseif opcode == 41 then opcode_name = "3800 Analog I/O, 5 V inp, 4-20ma out, 8 inputs, 4 outputs" 384 | elseif opcode == 42 then opcode_name = "6810 Analog Output, 4-20ma, 4 points; Pulse Input, 4 points" 385 | elseif opcode == 45 then opcode_name = "3511 Enhanced Pulse Input, 8 points" 386 | elseif opcode == 47 then opcode_name = "3515 Pulse Totalizer Input, 32 Data points, 32 Reset points" 387 | elseif opcode == 48 then opcode_name = "4119/A/AN EICM (Intelligent Communications Module)" 388 | elseif opcode == 49 then opcode_name = "420-/N,421-/N Remote Extender Module, Primary/Remote" 389 | elseif opcode == 50 then opcode_name = "6211 ICM (Intelligent Communications Module)" 390 | elseif opcode == 51 then opcode_name = "4509 Honeywell Data Highway Interface Module (HIM)" 391 | elseif opcode == 52 then opcode_name = "4409 Safety Manager Module" 392 | elseif opcode == 53 then opcode_name = "6215 Honeywell Data Highway Interface Module (HIM)" 393 | elseif opcode == 54 then opcode_name = "GPSI Global Positioning System Interface" 394 | elseif opcode == 55 then opcode_name = "4329/N/G NCM (Network Communications Module)" 395 | elseif opcode == 56 then opcode_name = "4609/N ACM (Advanced Communications Module)" 396 | elseif opcode == 57 then opcode_name = "4351B TCM-B (Tricon Communication Module/B - Copper)" 397 | elseif opcode == 58 then opcode_name = "4352B TCM-B (Tricon Communication Module/B - Fiber)" 398 | elseif opcode == 59 then opcode_name = "4353 TCM/OPC (Tricon Communication Module OPC - Copper)" 399 | elseif opcode == 60 then opcode_name = "4354 TCM/OPC (Tricon Communication Module OPC - Fiber)" 400 | elseif opcode == 71 then opcode_name = "3531 Discrete Input (simplx), 115 V, 32 points" 401 | elseif opcode == 72 then opcode_name = "3532 Discrete Input (simplx), 48 V, 32 points" 402 | elseif opcode == 73 then opcode_name = "3533 Discrete Input (simplx), 24 V, 32 points" 403 | elseif opcode == 84 then opcode_name = "4351 TCM (Tricon Communications Module - Copper)" 404 | elseif opcode == 85 then opcode_name = "4352 TCM (Tricon Communications Module - Fiber)" 405 | elseif opcode == 86 then opcode_name = "4351A TCM-A (Tricon Communication Module/A - Copper)" 406 | elseif opcode == 87 then opcode_name = "4352A/N TCM-A (Tricon Communication Module/A - Fiber)" 407 | elseif opcode == 88 then opcode_name = "3664 Dual Discrete Output, 24 V, 32 points, Serial" 408 | elseif opcode == 89 then opcode_name = "3674 Dual Discrete Output, 24 V, 32 points, Fail-Safe" 409 | elseif opcode == 90 then opcode_name = "3667 Dual Discrete Output, 48 V, 32 points, Serial" 410 | elseif opcode == 91 then opcode_name = "3677 Dual Discrete Output, 48 V, 32 points, Parallel" 411 | elseif opcode == 92 then opcode_name = "3663 Dual Discrete Output, 120V, 32 points, Serial" 412 | elseif opcode == 93 then opcode_name = "3673 Dual Discrete Output, 120V, 32 points, Parallel" 413 | elseif opcode == 94 then opcode_name = "3720 Enh Analog Input, 5V, 64 points, Configurable" 414 | elseif opcode == 95 then opcode_name = "3721/N Enh Differential Analog Input, +/-5V, 32 points, Configurable" 415 | elseif opcode == 104 then opcode_name = "3805/E/H/EN Analog Output, 4-20ma, 8 points" 416 | elseif opcode == 105 then opcode_name = "3807 Servo Control Analog Output, -60 to +60mA" 417 | elseif opcode == 107 then opcode_name = "6613 Supv Disc Output, 24 V, 16 points" 418 | elseif opcode == 106 then opcode_name = "3806 Analog Output, 4-20ma (6 pts), 4-320ma (2 pts)" 419 | elseif opcode == 108 then opcode_name = "6612 Supv Disc Output, 48 V, 16 points" 420 | elseif opcode == 109 then opcode_name = "6617 Supv Disc Output, 120 V, 16 points" 421 | elseif opcode == 110 then opcode_name = "6703 Analog Input (isolated), 2 V, 16 points" 422 | elseif opcode == 111 then opcode_name = "3635/E Discrete Output (simplx), Relay Cntct, Norm clsd, 32 pts" 423 | elseif opcode == 112 then opcode_name = "3636/R/T/TN Discrete Output (simplx), Relay Cntct, Norm open, 32 pts" 424 | elseif opcode == 113 then opcode_name = "3611/E Supv Discrete Output, 115 VAC, 8 points" 425 | elseif opcode == 129 then opcode_name = "6507 Discrete Input, 120 VDC, 32 points" 426 | elseif opcode == 130 then opcode_name = "6502 Discrete Input, 48 VDC, 32 points" 427 | elseif opcode == 133 then opcode_name = "6503 Discrete Input, 24 VDC, 32 points" 428 | elseif opcode == 134 then opcode_name = "6501 Discrete Input, 115 VAC, 32 points" 429 | elseif opcode == 136 then opcode_name = "6508 Discrete Input, 48 VAC, 32 points" 430 | elseif opcode == 138 then opcode_name = "6708 Isol Thermocouple Input Type E dgC, 16 points" 431 | elseif opcode == 139 then opcode_name = "6708 Isol Thermocouple Input Type J dgC, 16 points" 432 | elseif opcode == 140 then opcode_name = "6708 Isol Thermocouple Input Type K dgC, 16 points" 433 | elseif opcode == 141 then opcode_name = "6708 Isol Thermocouple Input Type T dgC, 16 points" 434 | elseif opcode == 146 then opcode_name = "3706/A/AN Non-Isol Thermocouple Input Type J dgF 32 points" 435 | elseif opcode == 147 then opcode_name = "3706/A/AN Non-Isol Thermocouple Input Type K dgF 32 points" 436 | elseif opcode == 148 then opcode_name = "3706/A/AN Non-Isol Thermocouple Input Type T dgF 32 points" 437 | elseif opcode == 149 then opcode_name = "3706/A/AN Non-Isol Thermocouple Input Type J dgC 32 points" 438 | elseif opcode == 150 then opcode_name = "3706/A/AN Non-Isol Thermocouple Input Type K dgC 32 points" 439 | elseif opcode == 151 then opcode_name = "3706/A/AN Non-Isol Thermocouple Input Type T dgC 32 points" 440 | elseif opcode == 152 then opcode_name = "3704/E/EN Analog Input, 5 V, DnS, 64 points" 441 | elseif opcode == 153 then opcode_name = "3704/E/EN Analog Input, 10 V, DnS, 64 points" 442 | elseif opcode == 154 then opcode_name = "3704/E/EN Analog Input, 5 V, UpS, 64 points" 443 | elseif opcode == 155 then opcode_name = "3704/E/EN Analog Input, 10 V, UpS, 64 points" 444 | elseif opcode == 156 then opcode_name = "3504/E/EN Discrete Input, 24 VDC, 64 points" 445 | elseif opcode == 157 then opcode_name = "3504/E/EN Discrete Input, 48 VDC, 64 points" 446 | elseif opcode == 158 then opcode_name = "3625/N Supervised Discrete Output, 24V, 32 points, Configurable" 447 | elseif opcode == 160 then opcode_name = "6700 Analog Input, 5 V, DnS, 32 points" 448 | elseif opcode == 161 then opcode_name = "6700 Analog Input, 10 V, DnS, 32 points" 449 | elseif opcode == 162 then opcode_name = "6700 Analog Input, 5 V, UpS, 32 points" 450 | elseif opcode == 163 then opcode_name = "6700 Analog Input, 10 V, UpS, 32 points" 451 | elseif opcode == 180 then opcode_name = "3703/E/EN Enh Isol Analog Input, 5 V, DnS, 16 points" 452 | elseif opcode == 181 then opcode_name = "3703/E/EN Enh Isol Analog Input, 10 V, DnS, 16 points" 453 | elseif opcode == 182 then opcode_name = "3703/E/EN 3Enh Isol Analog Input, 5 V, UpS, 16 points" 454 | elseif opcode == 183 then opcode_name = "3703/E/EN Enh Isol Analog Input, 10 V, UpS, 16 points" 455 | elseif opcode == 184 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type J dgC DnS, 16 points" 456 | elseif opcode == 185 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type K dgC DnS, 16 points" 457 | elseif opcode == 186 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type T dgC DnS, 16 points" 458 | elseif opcode == 187 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type E dgC DnS, 16 points" 459 | elseif opcode == 192 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type J dgF DnS, 16 points" 460 | elseif opcode == 193 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type K dgF DnS, 16 points" 461 | elseif opcode == 194 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type T dgF DnS, 16 points" 462 | elseif opcode == 195 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type E dgF DnS, 16 points" 463 | elseif opcode == 200 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type J dgC UpS, 16 points" 464 | elseif opcode == 201 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type K dgC UpS, 16 points" 465 | elseif opcode == 201 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type T dgC UpS, 16 points" 466 | elseif opcode == 202 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type E dgC UpS, 16 points" 467 | elseif opcode == 208 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type J dgF UpS, 16 points" 468 | elseif opcode == 209 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type K dgF UpS, 16 points" 469 | elseif opcode == 210 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type T dgF UpS, 16 points" 470 | elseif opcode == 211 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type E dgF UpS, 16 points" 471 | elseif opcode == 216 then opcode_name = "3614/E Supv Discrete Output, 24 V, OFF STATE SCD, 8 points" 472 | elseif opcode == 217 then opcode_name = "3614/E Supv Discrete Output, 24 V, 8 points" 473 | elseif opcode == 218 then opcode_name = "3617/E Supv Discrete Output, 48 V, OFF STATE SCD, 8 points" 474 | elseif opcode == 219 then opcode_name = "3617/E Supv Discrete Output, 48 V, 8 points" 475 | elseif opcode == 220 then opcode_name = "3613/E Supv Discrete Output, 120 V, OFF STATE SCD, 8 points" 476 | elseif opcode == 221 then opcode_name = "3613/E Supv Discrete Output, 120 V, 8 points" 477 | elseif opcode == 222 then opcode_name = "3615/E Supv Disc Output, 24 V, OFF STATE SCD, Low Power, 8 points" 478 | elseif opcode == 223 then opcode_name = "3615/E Supv Disc Output, 24 V, Low Power, 8 points" 479 | elseif opcode == 224 then opcode_name = "3564 Single Discrete Input, 24 V, 64 points" 480 | elseif opcode == 225 then opcode_name = "3564 Single Discrete Input, 24 V, 64 points, Non-Critical" 481 | elseif opcode == 226 then opcode_name = "3562 Single Discrete Input, 48 V, 64 points" 482 | elseif opcode == 227 then opcode_name = "3562 Single Discrete Input, 48 V, 64 points, Non-Critical" 483 | elseif opcode == 228 then opcode_name = "3561 Single Discrete Input, 120V, 64 points" 484 | elseif opcode == 229 then opcode_name = "3561 Single Discrete Input, 120V, 64 points, Non-Critical" 485 | elseif opcode == 230 then opcode_name = "356X Single Discrete Input, 115V, 64 points" 486 | elseif opcode == 231 then opcode_name = "356X Single Discrete Input, 115V, 64 points, Non-Critical" 487 | elseif opcode == 232 then opcode_name = "3624/N Supervised Discrete Output, 24 V, 16 points" 488 | elseif opcode == 233 then opcode_name = "3624 Supervised Discrete Output, 24 V, 16 points, Non-Supervised" 489 | elseif opcode == 234 then opcode_name = "3627 Supervised Discrete Output, 48 V, 16 points" 490 | elseif opcode == 235 then opcode_name = "3627 Supervised Discrete Output, 48 V, 16 points, Non-Supervised" 491 | elseif opcode == 236 then opcode_name = "3623/T/TN Supervised Discrete Output, 120V, 16 points" 492 | elseif opcode == 237 then opcode_name = "3623 Supervised Discrete Output, 120V, 16 points, Non-Supervised" 493 | elseif opcode == 238 then opcode_name = "362X Supervised Discrete Output, 115V, 16 points" 494 | elseif opcode == 239 then opcode_name = "362X Supervised Discrete Output, 115V, 16 points, Non-Supervised" 495 | elseif opcode == 208 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type J dgF UpS, 16 points" 496 | elseif opcode == 209 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type K dgF UpS, 16 points" 497 | elseif opcode == 210 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type T dgF UpS, 16 points" 498 | elseif opcode == 211 then opcode_name = "3708/E/EN Enh Isol Thermocouple Input Type E dgF UpS, 16 points" 499 | elseif opcode == 216 then opcode_name = "3614/E Supv Discrete Output, 24 V, OFF STATE SCD, 8 points" 500 | elseif opcode == 217 then opcode_name = "3614/E Supv Discrete Output, 24 V, 8 points" 501 | elseif opcode == 218 then opcode_name = "3617/E Supv Discrete Output, 48 V, OFF STATE SCD, 8 points" 502 | elseif opcode == 219 then opcode_name = "3617/E Supv Discrete Output, 48 V, 8 points" 503 | elseif opcode == 220 then opcode_name = "3613/E Supv Discrete Output, 120 V, OFF STATE SCD, 8 points" 504 | elseif opcode == 221 then opcode_name = "3613/E Supv Discrete Output, 120 V, 8 points" 505 | elseif opcode == 222 then opcode_name = "3615/E Supv Disc Output, 24 V, OFF STATE SCD, Low Power, 8 points" 506 | elseif opcode == 223 then opcode_name = "3615/E Supv Disc Output, 24 V, Low Power, 8 points" 507 | elseif opcode == 224 then opcode_name = "3564 Single Discrete Input, 24 V, 64 points" 508 | elseif opcode == 225 then opcode_name = "3564 Single Discrete Input, 24 V, 64 points, Non-Critical" 509 | elseif opcode == 226 then opcode_name = "3562 Single Discrete Input, 48 V, 64 points" 510 | elseif opcode == 227 then opcode_name = "3562 Single Discrete Input, 48 V, 64 points, Non-Critical" 511 | elseif opcode == 228 then opcode_name = "3561 Single Discrete Input, 120V, 64 points" 512 | elseif opcode == 229 then opcode_name = "3561 Single Discrete Input, 120V, 64 points, Non-Critical" 513 | elseif opcode == 230 then opcode_name = "356X Single Discrete Input, 115V, 64 points" 514 | end 515 | 516 | return opcode_name 517 | end 518 | 519 | -- Function: communication direction 520 | function get_comm_type(direction) 521 | local comm_type = "Unknown direction" 522 | 523 | if direction == 0 then comm_type = "Workstation --> Controller" 524 | elseif direction == 1 then comm_type = "Controller --> Workstation" end 525 | 526 | return comm_type 527 | end 528 | 529 | -- Function: CP Get Status response 530 | function cp_status_resp(buffer, subtree) 531 | 532 | subtree:add(buffer(14,2), "unk:", buffer(14,2):uint()) 533 | subtree:add(buffer(16,1), "loadIn:", buffer(16,1):uint()) 534 | subtree:add(buffer(17,1), "modIn:", buffer(17,1):uint()) 535 | subtree:add(buffer(18,1), "loadState:", buffer(18,1):uint()) 536 | subtree:add(buffer(19,1), "singleScan:", buffer(19,1):uint()) 537 | subtree:add(buffer(20,1), "cpValid:", buffer(20,1):uint()) 538 | if buffer(21,1):uint() == 0 then 539 | subtree:add(ts_cp_keyState, buffer(21,1)):append_text(" [Stop] ") 540 | elseif buffer(21,1):uint() == 1 then 541 | subtree:add(ts_cp_keyState, buffer(21,1)):append_text(" [Program] ") 542 | elseif buffer(21,1):uint() == 2 then 543 | subtree:add(ts_cp_keyState, buffer(21,1)):append_text(" [Run] ") 544 | elseif buffer(21,1):uint() == 3 then 545 | subtree:add(ts_cp_keyState, buffer(21,1)):append_text(" [Remote] ") 546 | end 547 | if buffer(22,1):le_uint() == 0 then 548 | subtree:add(ts_cp_runState, buffer(22,1)):append_text(" [Running] ") 549 | elseif buffer(22,1):le_uint() == 1 then 550 | subtree:add(ts_cp_runState, buffer(22,1)):append_text(" [Stop] ") 551 | elseif buffer(22,1):le_uint() == 2 then 552 | subtree:add(ts_cp_runState, buffer(22,1)):append_text(" [Pause] ") 553 | end 554 | subtree:add_le(buffer(27,4), "my:", buffer(27,4):uint()) 555 | subtree:add_le(buffer(30,4), "us:", buffer(30,4):uint()) 556 | subtree:add_le(buffer(34,4), "ds:", buffer(34,4):uint()) 557 | subtree:add_le(buffer(38,4), "heapMin:", buffer(38,4):uint()) 558 | subtree:add_le(buffer(42,4), "heapMax:", buffer(42,4):uint()) 559 | subtree:add(ts_cp_fstat, buffer(54,4)) 560 | subtree:add(buffer(70,2), "project_minor:", buffer(70,2):uint()) 561 | subtree:add(buffer(72,2), "project_major:", buffer(72,2):uint()) 562 | subtree:add_le(buffer(74,4), "project_timestamp:", buffer(74,4):uint()) 563 | subtree:add(buffer(80,10), "project:", buffer(80,10):string()) 564 | -- Unknown data: to parse 565 | if ((buffer(12,2):le_uint() - 10) ~= 0) then 566 | ts_raw = ts_data:add(buffer(90,buffer(12,2):le_uint() - 84), "Data: " .. buffer(90,buffer(12,2):le_uint() - 84)) 567 | end 568 | end 569 | 570 | -- Function: Allocate Functions 571 | function allocate_function(buffer, subtree) 572 | 573 | subtree:add_le(buffer(14,2), "id:", buffer(14,2):le_uint()) 574 | subtree:add_le(buffer(16,2), "next:", buffer(16,2):le_uint()) 575 | subtree:add_le(buffer(18,2), "full_chunks:", buffer(18,2):le_uint()) 576 | subtree:add_le(buffer(20,2), "offset:", buffer(20,2):le_uint()) 577 | subtree:add_le(buffer(22,2), "func_blocks (4 bytes):", buffer(22,2):le_uint()) 578 | blocks = (buffer(22,2):le_uint())*4 579 | buff = 24 580 | local count = 1 581 | p = 0 582 | ts_func = subtree:add(buffer(24, blocks), "Functions: ", blocks) -- split functions based on the blocks number 583 | while p < blocks-4 do 584 | p = p + 4 585 | function_hex = ts_func:add(ts_function, buffer(buff,4)):append_text(" [" .. count .. "]") 586 | buff = buff + 4 587 | count = count + 1 588 | if (p+4 == blocks) then 589 | crc32_value = ts_func:add(crc32, buffer(buff,4)):append_text(" (" .. buffer(buff,4):uint() .. ")") 590 | end 591 | end 592 | end 593 | 594 | -- Function: Allocate Programs 595 | function allocate_program(buffer, subtree, pinfo) 596 | 597 | subtree:add_le(buffer(14,2), "id:", buffer(14,2):le_uint()) 598 | subtree:add_le(buffer(16,2), "next:", buffer(16,2):le_uint()) 599 | subtree:add_le(buffer(18,2), "full_chunks:", buffer(18,2):le_uint()) 600 | subtree:add_le(buffer(20,2), "offset:", buffer(20,2):le_uint()) 601 | subtree:add_le(buffer(22,2), "program_blocks (4 bytes):", buffer(22,2):le_uint()) 602 | id = buffer(14,2):le_uint() 603 | chunked = buffer(18,2):le_uint() 604 | blocks_num = buffer(22,2):le_uint() 605 | offset = buffer(20,2):le_uint() 606 | 607 | blocks = (buffer(22,2):le_uint())*4 608 | buff = 24 609 | local count = 1 610 | p = 0 611 | ts_prog = subtree:add(ts_full_program, buffer(24, blocks-8)) -- split programs based on the blocks number 612 | while p < blocks-8 do 613 | p = p + 4 614 | program_hex = ts_prog:add(ts_program, buffer(buff,4)):append_text(" [" .. count .. "]") 615 | buff = buff + 4 616 | count = count + 1 617 | if (p+8 == blocks) then 618 | sign = buffer(buff,4):le_uint() 619 | checksum, mlw = crc32_calc(chunked, id, Struct.fromhex(tostring(buffer(24, blocks):bytes())), blocks_num, offset, pinfo, sign) 620 | if checksum~=0 then 621 | if mlw then 622 | malicious_signature = ts_prog:add(ts_signature, buffer(buff,4)) 623 | crc32_value = ts_prog:add(crc32, buffer(buff+4,4)):append_text(" (" .. buffer(buff+4,4):uint() .. ")") 624 | subtree:add_expert_info(PI_MALFORMED, PI_ERROR, "TRITON malware detected! ") 625 | else 626 | program_hex = ts_prog:add(ts_program, buffer(buff,4)):append_text(" [" .. count .. "]") 627 | crc32_value = ts_prog:add(crc32, buffer(buff+4,4)):append_text(" (" .. buffer(buff+4,4):uint() .. ")") 628 | end 629 | else 630 | crc32_value = subtree:add(buffer(buff,4), "crc32: "):append_text(" [ chunked program ]") 631 | end 632 | end 633 | end 634 | end 635 | 636 | -- Functions: TS2 Program Download 637 | function ts2_program_download(buffer, subtree) 638 | 639 | subtree:add(buffer(14,4), "unk:", buffer(14,4):uint()) 640 | subtree:add_le(buffer(18,2), "unk:", buffer(18,2):le_uint()) 641 | subtree:add_le(buffer(20,4), "timestamp:", os.date('%c',buffer(20,4):uint())) 642 | subtree:add(buffer(24,10), "project:", buffer(24,10):string()) 643 | subtree:add_le(buffer(34,2), "func_counter:", buffer(34,2):le_uint()) 644 | subtree:add_le(buffer(36,2), "program_counter:", buffer(36,2):le_uint()) 645 | subtree:add_le(buffer(38,2), "unk:", buffer(38,2):le_uint()) 646 | 647 | end 648 | 649 | -- Functions: Get TS2 data module version 650 | function get_t2_data_module_version(buffer, subtree) 651 | 652 | chassis = subtree:add_le(buffer(14,1), "chassis:", buffer(14,1):le_uint()) 653 | if buffer(15,1):le_uint() == 255 then -- check if modles are present 654 | buff = 8 -- bytes to read for each slots 655 | buff_start = 16 -- buffer location 656 | count = 0 657 | while buff_start < 144 do 658 | local module_type = buffer(buff_start,2):le_uint() 659 | if module_type ~= 0 then 660 | slot = chassis:add_le(buffer(buff_start,8), "slot:"):append_text(" [" .. count .. "]") 661 | local get_module_type = get_module_type(module_type) 662 | 663 | module_name = slot:add_le(ts_module, buffer(buff_start,2)):append_text(" [" .. get_module_type .. "] ") 664 | slot:add_le(buffer(buff_start+2,2), "firmware_1:", buffer(buff_start+2,2):le_uint()) 665 | slot:add_le(buffer(buff_start+4,2), "firmware_2:", buffer(buff_start+4,2):le_uint()) 666 | slot:add_le(buffer(buff_start+6,2), "firmware_3:", buffer(buff_start+6,2):le_uint()) 667 | end 668 | count = count + 1 669 | buff_start = buff_start + buff 670 | end 671 | 672 | pib = subtree:add(buffer(144,17), "PIB") 673 | pib:add(buffer(144,6), "assembly:", buffer(144,3):string(),"-",buffer(147,3):string()) 674 | pib:add(buffer(150,3), "revison:", buffer(150,3):string()) 675 | pib:add(buffer(153,8), "serial:", buffer(153,8):string()) 676 | controller = subtree:add(buffer(464,17), "Controller") 677 | controller:add(buffer(464,6), "assembly:", buffer(464,3):string(),"-",buffer(467,3):string()) 678 | controller:add(buffer(470,3), "revison:", buffer(470,3):string()) 679 | controller:add(buffer(473,8), "serial:", buffer(473,8):string()) 680 | if ((buffer(12,2):le_uint() - 10) ~= 0) then 681 | ts_raw = subtree:add(buffer(481,303), "Data: " .. buffer(481,303)) 682 | end 683 | else 684 | chassis:append_text(" [ no modules attached ]") 685 | end 686 | end 687 | 688 | -- Function: Upload functions request 689 | function upload_function_req(buffer, subtree) 690 | 691 | subtree:add_le(buffer(14,2), "func_id:", buffer(14,2):le_uint()) 692 | subtree:add_le(buffer(16,4), "fixed_values:", buffer(16,4):le_uint()) 693 | subtree:add_le(buffer(20,2), "offset:", buffer(20,2):le_uint()) 694 | end 695 | 696 | -- Function: Upload functions response 697 | function upload_function_res(buffer, subtree) 698 | 699 | subtree:add_le(buffer(14,4), "func_id:", buffer(14,4):le_uint()) 700 | subtree:add_le(buffer(18,2), "full_chunks:", buffer(18,2):le_uint()) 701 | subtree:add_le(buffer(20,2), "offset:", buffer(20,2):le_uint()) 702 | subtree:add_le(buffer(22,2), "function_blocks (4 bytes):", buffer(22,2):le_uint()) 703 | blocks = (buffer(22,2):le_uint()) 704 | buff = 24 705 | local count = 1 706 | p = 0 707 | ts_func = subtree:add(buffer(24, blocks), "Functions: ", blocks) -- split functions based on the blocks number 708 | while p < blocks-1 do 709 | p = p + 1 710 | function_hex = ts_func:add(ts_function, buffer(buff,4)):append_text(" [" .. count .. "]") 711 | buff = buff + 4 712 | count = count + 1 713 | if (p+1 == blocks) then 714 | crc32_value = ts_func:add(crc32, buffer(buff,4)):append_text(" (" .. buffer(buff,4):uint() .. ")") 715 | end 716 | end 717 | end 718 | 719 | -- Function: Upload program request 720 | function upload_program_req(buffer, subtree) 721 | 722 | subtree:add_le(buffer(14,2), "program_id:", buffer(14,2):le_uint()) 723 | subtree:add_le(buffer(16,4), "fixed_values:", buffer(16,4):le_uint()) 724 | subtree:add_le(buffer(20,2), "offset:", buffer(20,2):le_uint()) 725 | end 726 | 727 | -- Function: Upload program response 728 | function upload_program_res(buffer, subtree) 729 | 730 | subtree:add_le(buffer(14,2), "program_id:", buffer(14,2):le_uint()) 731 | subtree:add_le(buffer(18,2), "full_chunks:", buffer(18,2):le_uint()) 732 | subtree:add_le(buffer(20,2), "offset:", buffer(20,2):le_uint()) 733 | subtree:add_le(buffer(22,2), "program_blocks (4 bytes):", buffer(22,2):le_uint()) 734 | id = buffer(14,2):le_uint() 735 | chunked = buffer(18,2):le_uint() 736 | blocks_num = buffer(22,2):le_uint() 737 | offset = buffer(20,2):le_uint() 738 | blocks = (buffer(22,2):le_uint())*4 739 | buff = 24 740 | local count = 1 741 | p = 0 742 | ts_prog = subtree:add(ts_full_program, buffer(24, blocks-8)) -- split programs based on the blocks number 743 | while p < blocks-8 do 744 | p = p + 4 745 | program_hex = ts_prog:add(ts_program, buffer(buff,4)):append_text(" [" .. count .. "]") 746 | buff = buff + 4 747 | count = count + 1 748 | if (p+4 == blocks) then 749 | crc32_value = ts_prog:add(crc32, buffer(buff,4)):append_text(" (" .. buffer(buff,4):uint() .. ")") 750 | end 751 | end 752 | end 753 | 754 | -- Function: Get chassis status response 755 | function get_chassis_status_resp(buffer, subtree) 756 | 757 | subtree:add(buffer(14,2), "TriNode:", buffer(14,2):le_uint()) 758 | if buffer(17,1):le_uint() == 0 then 759 | subtree:add(ts_cp_runState, buffer(17,1)):append_text(" [Running] ") 760 | elseif buffer(17,1):le_uint() == 1 then 761 | subtree:add(ts_cp_runState, buffer(17,1)):append_text(" [Stop] ") 762 | elseif buffer(17,1):le_uint() == 2 then 763 | subtree:add(ts_cp_runState, buffer(17,1)):append_text(" [Pause] ") 764 | end 765 | if buffer(18,1):uint() == 0 then 766 | subtree:add(ts_cp_keyState, buffer(18,1)):append_text(" [Stop] ") 767 | elseif buffer(18,1):uint() == 1 then 768 | subtree:add(ts_cp_keyState, buffer(18,1)):append_text(" [Program] ") 769 | elseif buffer(18,1):uint() == 2 then 770 | subtree:add(ts_cp_keyState, buffer(18,1)):append_text(" [Run] ") 771 | elseif buffer(18,1):uint() == 3 then 772 | subtree:add(ts_cp_keyState, buffer(18,1)):append_text(" [Remote] ") 773 | end 774 | subtree:add(buffer(20,2), "project_minor:", buffer(20,2):uint()) 775 | subtree:add(buffer(22,2), "project_major:", buffer(22,2):uint()) 776 | subtree:add(buffer(24,4), "project_timestamp:", buffer(24,4):uint()) 777 | subtree:add(buffer(28,4), "scan_request:", buffer(28,4):uint()):append_text(" ms") 778 | subtree:add(buffer(32,4), "scan_actual:", buffer(32,4):uint()):append_text(" ms") 779 | subtree:add(buffer(36,2), "scan_surplus:", buffer(36,2):le_uint()):append_text(" ms") 780 | subtree:add(buffer(42,4), "poll_time:", buffer(42,4):le_uint()):append_text(" ms") 781 | subtree:add(buffer(46,10), "project:", buffer(46,10):string()) 782 | subtree:add(buffer(56,4), "calendar:", os.date('%c',buffer(56,4):uint())) 783 | subtree:add(buffer(64,4), "memory_max:", buffer(64,4):le_uint()) 784 | subtree:add(buffer(68,4), "memory_free:", buffer(68,4):le_uint()) 785 | -- Slots 786 | buff = 32 -- bytes to read for each slots 787 | buff_start = 180 -- buffer location 788 | count = 0 789 | while buff_start < 692 do 790 | local module_type = buffer(buff_start+2,1):uint() 791 | if module_type ~= 0 then 792 | slot = subtree:add(buffer(buff_start,32), "slot:"):append_text(" [" .. count .. "]") 793 | local get_module_type = get_module_type(module_type) 794 | 795 | leds = slot:add(buffer(buff_start,1), "LEDs_status:") 796 | led_struct = tobits(buffer(buff_start,1):uint()) -- bit analsys for main LEDs status 797 | if buffer(buff_start,1):uint() > 2 then 798 | leds:add(buffer(buff_start,1), "PASS:"):append_text(" [" .. get_led_status(led_struct:sub(#led_struct)) .. "]") 799 | leds:add(buffer(buff_start,1), "FAULT:"):append_text(" [" .. get_led_status(led_struct:sub(#led_struct-1, #led_struct-1)) .. "]") 800 | leds:add(buffer(buff_start,1), "ACTIVE:"):append_text(" [" .. get_led_status(led_struct:sub(#led_struct-2, #led_struct-2)) .. "]") 801 | else 802 | leds:add(buffer(buff_start,1), "Unable to get LEDs status [ check if module is racked properly ]") 803 | end 804 | if count == 0 then 805 | module_name = slot:add_le(ts_module, buffer(buff_start+2,1)):append_text(" [ Main Processor A ] ") 806 | elseif count == 1 then 807 | module_name = slot:add_le(ts_module, buffer(buff_start+2,1)):append_text(" [ Main Processor B ] ") 808 | elseif count == 2 then 809 | module_name = slot:add_le(ts_module, buffer(buff_start+2,1)):append_text(" [ Main Processor C ] ") 810 | else 811 | module_name = slot:add_le(ts_module, buffer(buff_start+2,1)):append_text(" [" .. get_module_type .. "] ") 812 | end 813 | if buffer(buff_start+3,1):uint() == 1 then 814 | slot:add(buffer(buff_start+3,1), "config_mismatch:", buffer(buff_start+3,1):uint()):append_text(" [Grey: project and hardware configuration are correct] ") 815 | elseif buffer(buff_start+3,1):uint() == 2 then 816 | slot:add(buffer(buff_start+3,1), "config_mismatch:", buffer(buff_start+3,1):uint()):append_text(" [Grey: MP configured in the project is not installed in slot] ") 817 | elseif buffer(buff_start+3,1):uint() == 3 then 818 | slot:add(buffer(buff_start+3,1), "config_mismatch:", buffer(buff_start+3,1):uint()):append_text(" [Blue: spare module is not installed] ") 819 | elseif buffer(buff_start+3,1):uint() == 4 then 820 | slot:add(buffer(buff_start+3,1), "config_mismatch:", buffer(buff_start+3,1):uint()):append_text(" [Red: module configured in the project is not installed in slot] ") 821 | elseif buffer(buff_start+3,1):uint() == 5 then 822 | slot:add(buffer(buff_start+3,1), "config_mismatch:", buffer(buff_start+3,1):uint()):append_text(" [Yellow: module installed in the slot is not configured in the project] ") 823 | end 824 | slot:add(buffer(buff_start+4,28), "padding") 825 | end 826 | count = count + 1 827 | buff_start = buff_start + buff 828 | end 829 | end 830 | 831 | 832 | -- Function: Tricon attached 833 | function tricon_attached(buffer, subtree) 834 | if buffer(17,1):uint() == 0 then 835 | if buffer(18,1):uint() == 0 then 836 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" Tricon v9 - 3008 Main Processor ") 837 | else 838 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" unknown MP model ") 839 | end 840 | elseif buffer(17,1):uint() == 10 then 841 | if buffer(18,1):le_uint() == 0 then 842 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" Tricon v10.0.x - 3008 Main Processor ") 843 | elseif buffer(18,1):le_uint() == 1 then 844 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" Tricon v10.1.x - 3008 Main Processor ") 845 | elseif buffer(18,1):le_uint() == 2 then 846 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" Tricon v10.2.x - 3008 Main Processor ") 847 | elseif buffer(18,1):le_uint() == 3 then 848 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" Tricon v10.3.x - 3008 Main Processor ") 849 | elseif buffer(18,1):le_uint() == 4 then 850 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" Tricon v10.4.x - 3008 Main Processor ") 851 | else 852 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" Tricon v10.x.x - 3008 Main Processor ") 853 | end 854 | else 855 | subtree:add(buffer(17,2), "MP type:", buffer(17,2)):append_text(" unknown MP model ") 856 | end 857 | end 858 | 859 | -- Function: convert value to bit rappresentation 860 | function tobits(num) 861 | local t={} 862 | while num>0 do 863 | rest=num%2 864 | t[#t+1]=rest 865 | num=(num-rest)/2 866 | end 867 | return string.reverse(table.concat(t)) 868 | end 869 | -- Function: crc32 check_implant 870 | function crc32_calc(chunk, id, p_buffer, block_num, offset, pinfo, sign) 871 | local payloads = {} 872 | local triton = false 873 | 874 | if pinfo.number <= pkt_max_number then 875 | if crc_table[pinfo.number] == nil then 876 | return 0, triton 877 | end 878 | return crc_table[pinfo.number].crc, crc_table[pinfo.number].mlw 879 | else 880 | pkt_max_number = pinfo.number 881 | end 882 | 883 | local track = offset+block_num 884 | 885 | if not table.contains_key(pkt_table, id) then 886 | pkt_table[id] = { Payload = { } } 887 | end 888 | 889 | if track == chunk then 890 | table.insert(pkt_table[id].Payload, #pkt_table[id].Payload+1 , Struct.fromhex(Struct.tohex(p_buffer:sub(1, -9)))) 891 | payloads = table.concat(pkt_table[id].Payload) 892 | pkt_table[id] = nil 893 | local checksum = UInt64.lower((bit.bxor(UInt64.lower(CRC32(payloads)), 2068988371))) 894 | if checksum == sign then 895 | triton = true 896 | end 897 | crc_table[pinfo.number] = { crc = UInt64.lower(CRC32(payloads)) , mlw = triton } 898 | return crc_table[pinfo.number].crc, crc_table[pinfo.number].mlw 899 | else 900 | table.insert(pkt_table[id].Payload, #pkt_table[id].Payload+1 ,p_buffer) 901 | return 0 902 | end 903 | end 904 | 905 | function table.contains_key(t, p) 906 | for key, _ in pairs(t) do 907 | if key == p then 908 | return true 909 | end 910 | end 911 | return false 912 | end 913 | 914 | local crc32_table 915 | function CRC32(s,crc) 916 | 917 | if not crc32_table then 918 | crc32_table = {} 919 | for i=0,255 do 920 | local r=i 921 | for j=1,8 do 922 | r = bit.bxor(bit.rshift(r,1),bit.band(0xedb88320,bit.bnot(bit.band(r,1)-1))) 923 | end 924 | crc32_table[i] = r 925 | end 926 | end 927 | crc = bit.bnot(crc or 0) 928 | for i=1,#s do 929 | local c = s:byte(i) 930 | crc = bit.bxor(crc32_table[bit.band(bit.bxor(c,crc),0xff)],bit.rshift(crc,8)) 931 | end 932 | return bit.bnot(crc) 933 | end 934 | 935 | function wlog(s) -- debugging function 936 | file = io.open("/tmp/debug", "a") 937 | file:write("\n debug: ", s) 938 | file:flush() 939 | end 940 | 941 | 942 | local udp_port = DissectorTable.get("udp.port") 943 | udp_port:add(1502, ts_proto) 944 | -------------------------------------------------------------------------------- /malware_exec.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NozomiNetworks/tricotools/2f768c8209d96dbb58dfea40c5a5ba9eb51ad6b0/malware_exec.pcap -------------------------------------------------------------------------------- /triconex_honeypot.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Triconex Honeypot emulating arbitrary modules (PoC) 3 | 4 | date : April, 4th 2018 5 | author : Alessandro Di Pinto (@adipinto) 6 | author : Andrea Arteaga 7 | author : Younes Dragoni (@ydragoni) 8 | 9 | contact : secresearch [ @ ] nozominetworks [ . ] com 10 | ''' 11 | 12 | import sys 13 | import time 14 | import socket 15 | import struct 16 | import argparse 17 | 18 | try: 19 | import crcmod 20 | except ImportError: 21 | print "[-] Please install the module 'crcmod' (eg, pip install crcmod)" 22 | exit(1) 23 | 24 | def build_slot(leds0, leds1, model, color): 25 | slotfmt = '<' + 32*'B' 26 | return struct.pack(slotfmt, leds0, leds1, model, color, 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 28 | 29 | # Construct slots 30 | mps = { 31 | 'active' : build_slot(0x15, 0x21, 0xf0, 0x01), 32 | 'passive': build_slot(0x02, 0x01, 0xf0, 0x02) 33 | } 34 | slotsdesc = { 35 | 'empty': build_slot(0, 0, 0, 0), 36 | 'com' : build_slot(5, 33, 55, 1), 37 | 'do' : build_slot(5, 16, 20, 1), 38 | 'di' : build_slot(5, 32, 11, 1), 39 | 'him' : build_slot(5, 22, 53, 1), 40 | 'ddo' : build_slot(0x4F, 0x21, 0x5C, 0x2) 41 | } 42 | 43 | def build_packet(triconId, funccode, seq, data): 44 | # Subheader without checksum 45 | datalength = len(data)+10 46 | subheader = struct.pack('