├── BACnet └── BACnet-discover-enumerate.nse ├── EtherNet-IP └── enip-enumerate.nse ├── IEC-60870-5-104 └── iec-identify.nse ├── IEC-61850-8-1 └── mms-identify.nse ├── README.md ├── S7 └── s7-enumerate.nse ├── Siemens ├── README.md ├── Siemens-CommunicationsProcessor.nse ├── Siemens-HMI-miniweb.nse ├── Siemens-SIMATIC-PLC-S7.nse ├── Siemens-Scalance-module.nse └── Siemens-WINCC.nse ├── all ├── BACnet-discover-enumerate.nse ├── Siemens-CommunicationsProcessor.nse ├── Siemens-HMI-miniweb.nse ├── Siemens-SIMATIC-PLC-S7.nse ├── Siemens-Scalance-module.nse ├── Siemens-WINCC.nse ├── enip-enumerate.nse ├── iec-identify.nse ├── mms-identify.nse ├── modbus-discover.nse ├── modbus-enum.nse └── s7-enumerate.nse └── modbus ├── modbus-discover.nse └── modbus-enum.nse /BACnet/BACnet-discover-enumerate.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | local unicode = require "unicode" 8 | local ipOps = require "ipOps" 9 | 10 | description = [[ 11 | Discovers and enumerates BACNet Devices collects device information based off 12 | standard requests. In some cases, devices may not strictly follow the 13 | specifications, or may comply with older versions of the specifications, and 14 | will result in a BACNET error response. Presence of this error positively 15 | identifies the device as a BACNet device, but no enumeration is possible. 16 | 17 | This Nmap Script will also attempt to enumerate the BBMD (BACnet Broadcast 18 | Management Device). This allows a device on one network to communicate with a 19 | device on another network by using the BBMD to forward and route the messages. 20 | Also the NSE will attempt to pull the FDT (Foreign-Device-Table), as well as the 21 | (TTL) Time To Live, and time-out until the device willbe removed from the foreign 22 | device table. To utilize this feature, run with --script-args full=yes. 23 | 24 | This process was submitted via Jeff Meden via the original 25 | BACnet-discover-enumerate.nse script on github, it was determined to create a 26 | new script with the submitted methods. 27 | 28 | Note: Requests and responses are via UDP 47808, ensure scanner will receive UDP 29 | 47808 source and destination responses. 30 | 31 | http://digitalbond.com 32 | 33 | ]] 34 | 35 | --- 36 | -- @usage 37 | -- nmap --script BACnet-discover-enumerate.nse -sU -p 47808 38 | -- 39 | -- @args full If set yes the script will run the FDT and BBMD Checks 40 | -- 41 | -- @output 42 | --47808/udp open BACNet -- Building Automation and Control Networks 43 | --| BACnet-discover-enumerate.nse: 44 | --| Vendor ID: BACnet Stack at SourceForge (260) 45 | --| Instance Number: 260001 46 | --| Firmware: 0.8.2 47 | --| Application Software: 1.0 48 | --| Object Name: SimpleServer 49 | --| Model Name: GNU 50 | --| Description: server 51 | --| Location: USA 52 | --| BACnet Broadcast Management Device (BBMD): 53 | --| 192.168.0.100:47808 54 | --| Foreign Device Table (FDT): 55 | --|_ 192.168.1.101:47809:ttl=60:timeout=37 56 | 57 | -- 58 | -- @xmloutput 59 | --BACnet Stack at SourceForge (260) 60 | --260001 61 | --0.8.2 62 | --1.0 63 | --SimpleServer 64 | --GNU 65 | --server 66 | --USA 67 | --192.168.0.100:47808 68 | --192.168.1.101:47809:ttl=60:timeout=37 69 | 70 | author = "Stephen Hilt(Digital Bond)" 71 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 72 | categories = {"discovery", "intrusive"} 73 | 74 | -- 75 | -- Function to define the portrule as per nmap standards 76 | -- 77 | -- 78 | -- 79 | 80 | portrule = shortport.port_or_service(47808, "bacnet", "udp") 81 | 82 | --- 83 | -- Function to determine if a string starts with the parameter that is passed in 84 | -- 85 | -- First argument is the string to be evaluated, the second argument is 86 | -- the character(s) to be tested if the string starts with this argument. Uses Lua 87 | -- string.sub and string.len 88 | -- @param String String to be passed in. 89 | -- @param Start The char you want to test to see the string starts with. 90 | function string.starts(String,Start) 91 | return string.sub(String,1,string.len(Start))==Start 92 | end 93 | 94 | --- 95 | -- Table to look up the Vendor Name based on Vendor ID 96 | -- Table data from http://www.bacnet.org/VendorID/BACnet%20Vendor%20IDs.htm 97 | -- Fetched on 3/18/2014 98 | -- 99 | -- @key vennum Vendor number parsed out of the BACNet packet 100 | local vendor_id = { 101 | [0] = "ASHRAE", 102 | [1] = "NIST", 103 | [2] = "The Trane Company", 104 | [3] = "McQuay International", 105 | [4] = "PolarSoft", 106 | [5] = "Johnson Controls Inc.", 107 | [6] = "American Auto-Matrix", 108 | [7] = "Siemens Schweiz AG (Formerly: Landis & Staefa Division Europe)", 109 | [8] = "Delta Controls", 110 | [9] = "Siemens Schweiz AG", 111 | [10] = "Schneider Electric", 112 | [11] = "TAC", 113 | [12] = "Orion Analysis Corporation", 114 | [13] = "Teletrol Systems Inc.", 115 | [14] = "Cimetrics Technology", 116 | [15] = "Cornell University", 117 | [16] = "United Technologies Carrier", 118 | [17] = "Honeywell Inc.", 119 | [18] = "Alerton / Honeywell", 120 | [19] = "TAC AB", 121 | [20] = "Hewlett-Packard Company", 122 | [21] = "Dorsette.s Inc.", 123 | [22] = "Siemens Schweiz AG (Formerly: Cerberus AG)", 124 | [23] = "York Controls Group", 125 | [24] = "Automated Logic Corporation", 126 | [25] = "CSI Control Systems International", 127 | [26] = "Phoenix Controls Corporation", 128 | [27] = "Innovex Technologies Inc.", 129 | [28] = "KMC Controls Inc.", 130 | [29] = "Xn Technologies Inc.", 131 | [30] = "Hyundai Information Technology Co. Ltd.", 132 | [31] = "Tokimec Inc.", 133 | [32] = "Simplex", 134 | [33] = "North Building Technologies Limited", 135 | [34] = "Notifier", 136 | [35] = "Reliable Controls Corporation", 137 | [36] = "Tridium Inc.", 138 | [37] = "Sierra Monitor Corporation/FieldServer Technologies", 139 | [38] = "Silicon Energy", 140 | [39] = "Kieback & Peter GmbH & Co KG", 141 | [40] = "Anacon Systems Inc.", 142 | [41] = "Systems Controls & Instruments LLC", 143 | [42] = "Lithonia Lighting", 144 | [43] = "Micropower Manufacturing", 145 | [44] = "Matrix Controls", 146 | [45] = "METALAIRE", 147 | [46] = "ESS Engineering", 148 | [47] = "Sphere Systems Pty Ltd.", 149 | [48] = "Walker Technologies Corporation", 150 | [49] = "H I Solutions Inc.", 151 | [50] = "MBS GmbH", 152 | [51] = "SAMSON AG", 153 | [52] = "Badger Meter Inc.", 154 | [53] = "DAIKIN Industries Ltd.", 155 | [54] = "NARA Controls Inc.", 156 | [55] = "Mammoth Inc.", 157 | [56] = "Liebert Corporation", 158 | [57] = "SEMCO Incorporated", 159 | [58] = "Air Monitor Corporation", 160 | [59] = "TRIATEK LLC", 161 | [60] = "NexLight", 162 | [61] = "Multistack", 163 | [62] = "TSI Incorporated", 164 | [63] = "Weather-Rite Inc.", 165 | [64] = "Dunham-Bush", 166 | [65] = "Reliance Electric", 167 | [66] = "LCS Inc.", 168 | [67] = "Regulator Australia PTY Ltd.", 169 | [68] = "Touch-Plate Lighting Controls", 170 | [69] = "Amann GmbH", 171 | [70] = "RLE Technologies", 172 | [71] = "Cardkey Systems", 173 | [72] = "SECOM Co. Ltd.", 174 | [73] = "ABB Gebäetechnik AG Bereich NetServ", 175 | [74] = "KNX Association cvba", 176 | [75] = "Institute of Electrical Installation Engineers of Japan (IEIEJ)", 177 | [76] = "Nohmi Bosai Ltd.", 178 | [77] = "Carel S.p.A.", 179 | [78] = "AirSense Technology Inc.", 180 | [79] = "Hochiki Corporation", 181 | [80] = "Fr. Sauter AG", 182 | [81] = "Matsushita Electric Works Ltd.", 183 | [82] = "Mitsubishi Electric Corporation Inazawa Works", 184 | [83] = "Mitsubishi Heavy Industries Ltd.", 185 | [84] = "ITT Bell & Gossett", 186 | [85] = "Yamatake Building Systems Co. Ltd.", 187 | [86] = "The Watt Stopper Inc.", 188 | [87] = "Aichi Tokei Denki Co. Ltd.", 189 | [88] = "Activation Technologies LLC", 190 | [89] = "Saia-Burgess Controls Ltd.", 191 | [90] = "Hitachi Ltd.", 192 | [91] = "Novar Corp./Trend Control Systems Ltd.", 193 | [92] = "Mitsubishi Electric Lighting Corporation", 194 | [93] = "Argus Control Systems Ltd.", 195 | [94] = "Kyuki Corporation", 196 | [95] = "Richards-Zeta Building Intelligence Inc.", 197 | [96] = "Scientech R&D Inc.", 198 | [97] = "VCI Controls Inc.", 199 | [98] = "Toshiba Corporation", 200 | [99] = "Mitsubishi Electric Corporation Air Conditioning & Refrigeration Systems Works", 201 | [100] = "Custom Mechanical Equipment LLC", 202 | [101] = "ClimateMaster", 203 | [102] = "ICP Panel-Tec Inc.", 204 | [103] = "D-Tek Controls", 205 | [104] = "NEC Engineering Ltd.", 206 | [105] = "PRIVA BV", 207 | [106] = "Meidensha Corporation", 208 | [107] = "JCI Systems Integration Services", 209 | [108] = "Freedom Corporation", 210 | [109] = "Neuberger Gebäeautomation GmbH", 211 | [110] = "Sitronix", 212 | [111] = "Leviton Manufacturing", 213 | [112] = "Fujitsu Limited", 214 | [113] = "Emerson Network Power", 215 | [114] = "S. A. Armstrong Ltd.", 216 | [115] = "Visonet AG", 217 | [116] = "M&M Systems Inc.", 218 | [117] = "Custom Software Engineering", 219 | [118] = "Nittan Company Limited", 220 | [119] = "Elutions Inc. (Wizcon Systems SAS)", 221 | [120] = "Pacom Systems Pty. Ltd.", 222 | [121] = "Unico Inc.", 223 | [122] = "Ebtron Inc.", 224 | [123] = "Scada Engine", 225 | [124] = "AC Technology Corporation", 226 | [125] = "Eagle Technology", 227 | [126] = "Data Aire Inc.", 228 | [127] = "ABB Inc.", 229 | [128] = "Transbit Sp. z o. o.", 230 | [129] = "Toshiba Carrier Corporation", 231 | [130] = "Shenzhen Junzhi Hi-Tech Co. Ltd.", 232 | [131] = "Tokai Soft", 233 | [132] = "Blue Ridge Technologies", 234 | [133] = "Veris Industries", 235 | [134] = "Centaurus Prime", 236 | [135] = "Sand Network Systems", 237 | [136] = "Regulvar Inc.", 238 | [137] = "AFDtek Division of Fastek International Inc.", 239 | [138] = "PowerCold Comfort Air Solutions Inc.", 240 | [139] = "I Controls", 241 | [140] = "Viconics Electronics Inc.", 242 | [141] = "Yaskawa America Inc.", 243 | [142] = "DEOS control systems GmbH", 244 | [143] = "Digitale Mess- und Steuersysteme AG", 245 | [144] = "Fujitsu General Limited", 246 | [145] = "Project Engineering S.r.l.", 247 | [146] = "Sanyo Electric Co. Ltd.", 248 | [147] = "Integrated Information Systems Inc.", 249 | [148] = "Temco Controls Ltd.", 250 | [149] = "Airtek International Inc.", 251 | [150] = "Advantech Corporation", 252 | [151] = "Titan Products Ltd.", 253 | [152] = "Regel Partners", 254 | [153] = "National Environmental Product", 255 | [154] = "Unitec Corporation", 256 | [155] = "Kanden Engineering Company", 257 | [156] = "Messner Gebäetechnik GmbH", 258 | [157] = "Integrated.CH", 259 | [158] = "Price Industries", 260 | [159] = "SE-Elektronic GmbH", 261 | [160] = "Rockwell Automation", 262 | [161] = "Enflex Corp.", 263 | [162] = "ASI Controls", 264 | [163] = "SysMik GmbH Dresden", 265 | [164] = "HSC Regelungstechnik GmbH", 266 | [165] = "Smart Temp Australia Pty. Ltd.", 267 | [166] = "Cooper Controls", 268 | [167] = "Duksan Mecasys Co. Ltd.", 269 | [168] = "Fuji IT Co. Ltd.", 270 | [169] = "Vacon Plc", 271 | [170] = "Leader Controls", 272 | [171] = "Cylon Controls Ltd.", 273 | [172] = "Compas", 274 | [173] = "Mitsubishi Electric Building Techno-Service Co. Ltd.", 275 | [174] = "Building Control Integrators", 276 | [175] = "ITG Worldwide (M) Sdn Bhd", 277 | [176] = "Lutron Electronics Co. Inc.", 278 | [178] = "LOYTEC Electronics GmbH", 279 | [179] = "ProLon", 280 | [180] = "Mega Controls Limited", 281 | [181] = "Micro Control Systems Inc.", 282 | [182] = "Kiyon Inc.", 283 | [183] = "Dust Networks", 284 | [184] = "Advanced Building Automation Systems", 285 | [185] = "Hermos AG", 286 | [186] = "CEZIM", 287 | [187] = "Softing", 288 | [188] = "Lynxspring", 289 | [189] = "Schneider Toshiba Inverter Europe", 290 | [190] = "Danfoss Drives A/S", 291 | [191] = "Eaton Corporation", 292 | [192] = "Matyca S.A.", 293 | [193] = "Botech AB", 294 | [194] = "Noveo Inc.", 295 | [195] = "AMEV", 296 | [196] = "Yokogawa Electric Corporation", 297 | [197] = "GFR Gesellschaft füelungstechnik", 298 | [198] = "Exact Logic", 299 | [199] = "Mass Electronics Pty Ltd dba Innotech Control Systems Australia", 300 | [200] = "Kandenko Co. Ltd.", 301 | [201] = "DTF Daten-Technik Fries", 302 | [202] = "Klimasoft Ltd.", 303 | [203] = "Toshiba Schneider Inverter Corporation", 304 | [204] = "Control Applications Ltd.", 305 | [205] = "KDT Systems Co. Ltd.", 306 | [206] = "Onicon Incorporated", 307 | [207] = "Automation Displays Inc.", 308 | [208] = "Control Solutions Inc.", 309 | [209] = "Remsdaq Limited", 310 | [210] = "NTT Facilities Inc.", 311 | [211] = "VIPA GmbH", 312 | [212] = "TSC21 Association of Japan", 313 | [213] = "Strato Automation", 314 | [214] = "HRW Limited", 315 | [215] = "Lighting Control & Design Inc.", 316 | [216] = "Mercy Electronic and Electrical Industries", 317 | [217] = "Samsung SDS Co.Ltd", 318 | [218] = "Impact Facility Solutions Inc.", 319 | [219] = "Aircuity", 320 | [220] = "Control Techniques Ltd.", 321 | [221] = "OpenGeneral Pty. Ltd.", 322 | [222] = "WAGO Kontakttechnik GmbH & Co. KG", 323 | [223] = "Cerus Industrial", 324 | [224] = "Chloride Power Protection Company", 325 | [225] = "Computrols Inc.", 326 | [226] = "Phoenix Contact GmbH & Co. KG", 327 | [227] = "Grundfos Management A/S", 328 | [228] = "Ridder Drive Systems", 329 | [229] = "Soft Device SDN BHD", 330 | [230] = "Integrated Control Technology Limited", 331 | [231] = "AIRxpert Systems Inc.", 332 | [232] = "Microtrol Limited", 333 | [233] = "Red Lion Controls", 334 | [234] = "Digital Electronics Corporation", 335 | [235] = "Ennovatis GmbH", 336 | [236] = "Serotonin Software Technologies Inc.", 337 | [237] = "LS Industrial Systems Co. Ltd.", 338 | [238] = "Square D Company", 339 | [239] = "S Squared Innovations Inc.", 340 | [240] = "Aricent Ltd.", 341 | [241] = "EtherMetrics LLC", 342 | [242] = "Industrial Control Communications Inc.", 343 | [243] = "Paragon Controls Inc.", 344 | [244] = "A. O. Smith Corporation", 345 | [245] = "Contemporary Control Systems Inc.", 346 | [246] = "Intesis Software SL", 347 | [247] = "Ingenieurgesellschaft N. Hartleb mbH", 348 | [248] = "Heat-Timer Corporation", 349 | [249] = "Ingrasys Technology Inc.", 350 | [250] = "Costerm Building Automation", 351 | [251] = "WILO SE", 352 | [252] = "Embedia Technologies Corp.", 353 | [253] = "Technilog", 354 | [254] = "HR Controls Ltd. & Co. KG", 355 | [255] = "Lennox International Inc.", 356 | [256] = "RK-Tec Rauchklappen-Steuerungssysteme GmbH & Co. KG", 357 | [257] = "Thermomax Ltd.", 358 | [258] = "ELCON Electronic Control Ltd.", 359 | [259] = "Larmia Control AB", 360 | [260] = "BACnet Stack at SourceForge", 361 | [261] = "G4S Security Services A/S", 362 | [262] = "Exor International S.p.A.", 363 | [263] = "Cristal Controles", 364 | [264] = "Regin AB", 365 | [265] = "Dimension Software Inc.", 366 | [266] = "SynapSense Corporation", 367 | [267] = "Beijing Nantree Electronic Co. Ltd.", 368 | [268] = "Camus Hydronics Ltd.", 369 | [269] = "Kawasaki Heavy Industries Ltd.", 370 | [270] = "Critical Environment Technologies", 371 | [271] = "ILSHIN IBS Co. Ltd.", 372 | [272] = "ELESTA Energy Control AG", 373 | [273] = "KROPMAN Installatietechniek", 374 | [274] = "Baldor Electric Company", 375 | [275] = "INGA mbH", 376 | [276] = "GE Consumer & Industrial", 377 | [277] = "Functional Devices Inc.", 378 | [278] = "ESAC", 379 | [279] = "M-System Co. Ltd.", 380 | [280] = "Yokota Co. Ltd.", 381 | [281] = "Hitranse Technology Co.LTD", 382 | [282] = "Federspiel Controls", 383 | [283] = "Kele Inc.", 384 | [284] = "Opera Electronics Inc.", 385 | [285] = "Gentec", 386 | [286] = "Embedded Science Labs LLC", 387 | [287] = "Parker Hannifin Corporation", 388 | [288] = "MaCaPS International Limited", 389 | [289] = "Link4 Corporation", 390 | [290] = "Romutec Steuer-u. Regelsysteme GmbH", 391 | [291] = "Pribusin Inc.", 392 | [292] = "Advantage Controls", 393 | [293] = "Critical Room Control", 394 | [294] = "LEGRAND", 395 | [295] = "Tongdy Control Technology Co. Ltd.", 396 | [296] = "ISSARO Integrierte Systemtechnik", 397 | [297] = "Pro-Dev Industries", 398 | [298] = "DRI-STEEM", 399 | [299] = "Creative Electronic GmbH", 400 | [300] = "Swegon AB", 401 | [301] = "Jan Brachacek", 402 | [302] = "Hitachi Appliances Inc.", 403 | [303] = "Real Time Automation Inc.", 404 | [304] = "ITEC Hankyu-Hanshin Co.", 405 | [305] = "Cyrus E&M Engineering Co. Ltd.", 406 | [306] = "Racine Federated Inc.", 407 | [307] = "Cirrascale Corporation", 408 | [308] = "Elesta GmbH Building Automation", 409 | [309] = "Securiton", 410 | [310] = "OSlsoft Inc.", 411 | [311] = "Hanazeder Electronic GmbH", 412 | [312] = "Honeywell Security DeutschlandNovar GmbH", 413 | [313] = "Siemens Energy & Automation Inc.", 414 | [314] = "ETM Professional Control GmbH", 415 | [315] = "Meitav-tec Ltd.", 416 | [316] = "Janitza Electronics GmbH", 417 | [317] = "MKS Nordhausen", 418 | [318] = "De Gier Drive Systems B.V.", 419 | [319] = "Cypress Envirosystems", 420 | [320] = "SMARTron s.r.o.", 421 | [321] = "Verari Systems Inc.", 422 | [322] = "K-W Electronic Service Inc.", 423 | [323] = "ALFA-SMART Energy Management", 424 | [324] = "Telkonet Inc.", 425 | [325] = "Securiton GmbH", 426 | [326] = "Cemtrex Inc.", 427 | [327] = "Performance Technologies Inc.", 428 | [328] = "Xtralis (Aust) Pty Ltd", 429 | [329] = "TROX GmbH", 430 | [330] = "Beijing Hysine Technology Co.Ltd", 431 | [331] = "RCK Controls Inc.", 432 | [332] = "Distech Controls SAS", 433 | [333] = "Novar/Honeywell", 434 | [334] = "The S4 Group Inc.", 435 | [335] = "Schneider Electric", 436 | [336] = "LHA Systems", 437 | [337] = "GHM engineering Group Inc.", 438 | [338] = "Cllimalux S.A.", 439 | [339] = "VAISALA Oyj", 440 | [340] = "COMPLEX (Beijing) TechnologyCo. Ltd.", 441 | [341] = "SCADAmetrics", 442 | [342] = "POWERPEG NSI Limited", 443 | [343] = "BACnet Interoperability Testing Services Inc.", 444 | [344] = "Teco a.s.", 445 | [345] = "Plexus Technology Inc.", 446 | [346] = "Energy Focus Inc.", 447 | [347] = "Powersmiths International Corp.", 448 | [348] = "Nichibei Co. Ltd.", 449 | [349] = "HKC Technology Ltd.", 450 | [350] = "Ovation Networks Inc.", 451 | [351] = "Setra Systems", 452 | [352] = "AVG Automation", 453 | [353] = "ZXC Ltd.", 454 | [354] = "Byte Sphere", 455 | [355] = "Generiton Co. Ltd.", 456 | [356] = "Holter Regelarmaturen GmbH & Co. KG", 457 | [357] = "Bedford Instruments LLC", 458 | [358] = "Standair Inc.", 459 | [359] = "WEG Automation - R&D", 460 | [360] = "Prolon Control Systems ApS", 461 | [361] = "Inneasoft", 462 | [362] = "ConneXSoft GmbH", 463 | [363] = "CEAG Notlichtsysteme GmbH", 464 | [364] = "Distech Controls Inc.", 465 | [365] = "Industrial Technology Research Institute", 466 | [366] = "ICONICS Inc.", 467 | [367] = "IQ Controls s.c.", 468 | [368] = "OJ Electronics A/S", 469 | [369] = "Rolbit Ltd.", 470 | [370] = "Synapsys Solutions Ltd.", 471 | [371] = "ACME Engineering Prod. Ltd.", 472 | [372] = "Zener Electric Pty Ltd.", 473 | [373] = "Selectronix Inc.", 474 | [374] = "Gorbet & Banerjee LLC.", 475 | [375] = "IME", 476 | [376] = "Stephen H. Dawson Computer Service", 477 | [377] = "Accutrol LLC", 478 | [378] = "Schneider Elektronik GmbH", 479 | [379] = "Alpha-Inno Tec GmbH", 480 | [380] = "ADMMicro Inc.", 481 | [381] = "Greystone Energy Systems Inc.", 482 | [382] = "CAP Technologie", 483 | [383] = "KeRo Systems", 484 | [384] = "Domat Control System s.r.o.", 485 | [385] = "Efektronics Pty. Ltd.", 486 | [386] = "Hekatron Vertriebs GmbH", 487 | [387] = "Securiton AG", 488 | [388] = "Carlo Gavazzi Controls SpA", 489 | [389] = "Chipkin Automation Systems", 490 | [390] = "Savant Systems LLC", 491 | [391] = "Simmtronic Lighting Controls", 492 | [392] = "Abelko Innovation AB", 493 | [393] = "Seresco Technologies Inc.", 494 | [394] = "IT Watchdogs", 495 | [395] = "Automation Assist Japan Corp.", 496 | [396] = "Thermokon Sensortechnik GmbH", 497 | [397] = "EGauge Systems LLC", 498 | [398] = "Quantum Automation (ASIA) PTE Ltd.", 499 | [399] = "Toshiba Lighting & Technology Corp.", 500 | [400] = "SPIN Engenharia de Automaç Ltda.", 501 | [401] = "Logistics Systems & Software Services India PVT. Ltd.", 502 | [402] = "Delta Controls Integration Products", 503 | [403] = "Focus Media", 504 | [404] = "LUMEnergi Inc.", 505 | [405] = "Kara Systems", 506 | [406] = "RF Code Inc.", 507 | [407] = "Fatek Automation Corp.", 508 | [408] = "JANDA Software Company LLC", 509 | [409] = "Open System Solutions Limited", 510 | [410] = "Intelec Systems PTY Ltd.", 511 | [411] = "Ecolodgix LLC", 512 | [412] = "Douglas Lighting Controls", 513 | [413] = "iSAtech GmbH", 514 | [414] = "AREAL", 515 | [415] = "Beckhoff Automation GmbH", 516 | [416] = "IPAS GmbH", 517 | [417] = "KE2 Therm Solutions", 518 | [418] = "Base2Products", 519 | [419] = "DTL Controls LLC", 520 | [420] = "INNCOM International Inc.", 521 | [421] = "BTR Netcom GmbH", 522 | [422] = "Greentrol AutomationInc", 523 | [423] = "BELIMO Automation AG", 524 | [424] = "Samsung Heavy Industries CoLtd", 525 | [425] = "Triacta Power Technologies Inc.", 526 | [426] = "Globestar Systems", 527 | [427] = "MLB Advanced MediaLP", 528 | [428] = "SWG Stuckmann Wirtschaftliche Gebäesysteme GmbH", 529 | [429] = "SensorSwitch", 530 | [430] = "Multitek Power Limited", 531 | [431] = "Aquametro AG", 532 | [432] = "LG Electronics Inc.", 533 | [433] = "Electronic Theatre Controls Inc.", 534 | [434] = "Mitsubishi Electric Corporation Nagoya Works", 535 | [435] = "Delta Electronics Inc.", 536 | [436] = "Elma Kurtalj Ltd.", 537 | [437] = "ADT Fire and Security Sp. A.o.o.", 538 | [438] = "Nedap Security Management", 539 | [439] = "ESC Automation Inc.", 540 | [440] = "DSP4YOU Ltd.", 541 | [441] = "GE Sensing and Inspection Technologies", 542 | [442] = "Embedded Systems SIA", 543 | [443] = "BEFEGA GmbH", 544 | [444] = "Baseline Inc.", 545 | [445] = "M2M Systems Integrators", 546 | [446] = "OEMCtrl", 547 | [447] = "Clarkson Controls Limited", 548 | [448] = "Rogerwell Control System Limited", 549 | [449] = "SCL Elements", 550 | [450] = "Hitachi Ltd.", 551 | [451] = "Newron System SA", 552 | [452] = "BEVECO Gebouwautomatisering BV", 553 | [453] = "Streamside Solutions", 554 | [454] = "Yellowstone Soft", 555 | [455] = "Oztech Intelligent Systems Pty Ltd.", 556 | [456] = "Novelan GmbH", 557 | [457] = "Flexim Americas Corporation", 558 | [458] = "ICP DAS Co. Ltd.", 559 | [459] = "CARMA Industries Inc.", 560 | [460] = "Log-One Ltd.", 561 | [461] = "TECO Electric & Machinery Co. Ltd.", 562 | [462] = "ConnectEx Inc.", 563 | [463] = "Turbo DDC Sü", 564 | [464] = "Quatrosense Environmental Ltd.", 565 | [465] = "Fifth Light Technology Ltd.", 566 | [466] = "Scientific Solutions Ltd.", 567 | [467] = "Controller Area Network Solutions (M) Sdn Bhd", 568 | [468] = "RESOL - Elektronische Regelungen GmbH", 569 | [469] = "RPBUS LLC", 570 | [470] = "BRS Sistemas Eletronicos", 571 | [471] = "WindowMaster A/S", 572 | [472] = "Sunlux Technologies Ltd.", 573 | [473] = "Measurlogic", 574 | [474] = "Frimat GmbH", 575 | [475] = "Spirax Sarco", 576 | [476] = "Luxtron", 577 | [477] = "Raypak Inc", 578 | [478] = "Air Monitor Corporation", 579 | [479] = "Regler Och Webbteknik Sverige (ROWS)", 580 | [480] = "Intelligent Lighting Controls Inc.", 581 | [481] = "Sanyo Electric Industry Co.Ltd", 582 | [482] = "E-Mon Energy Monitoring Products", 583 | [483] = "Digital Control Systems", 584 | [484] = "ATI Airtest Technologies Inc.", 585 | [485] = "SCS SA", 586 | [486] = "HMS Industrial Networks AB", 587 | [487] = "Shenzhen Universal Intellisys Co Ltd", 588 | [488] = "EK Intellisys Sdn Bhd", 589 | [489] = "SysCom", 590 | [490] = "Firecom Inc.", 591 | [491] = "ESA Elektroschaltanlagen Grimma GmbH", 592 | [492] = "Kumahira Co Ltd", 593 | [493] = "Hotraco", 594 | [494] = "SABO Elektronik GmbH", 595 | [495] = "Equip'Trans", 596 | [496] = "TCS Basys Controls", 597 | [497] = "FlowCon International A/S", 598 | [498] = "ThyssenKrupp Elevator Americas", 599 | [499] = "Abatement Technologies", 600 | [500] = "Continental Control Systems LLC", 601 | [501] = "WISAG Automatisierungstechnik GmbH & Co KG", 602 | [502] = "EasyIO", 603 | [503] = "EAP-Electric GmbH", 604 | [504] = "Hardmeier", 605 | [505] = "Mircom Group of Companies", 606 | [506] = "Quest Controls", 607 | [507] = "MestekInc", 608 | [508] = "Pulse Energy", 609 | [509] = "Tachikawa Corporation", 610 | [510] = "University of Nebraska-Lincoln", 611 | [511] = "Redwood Systems", 612 | [512] = "PASStec Industrie-Elektronik GmbH", 613 | [513] = "NgEK Inc.", 614 | [514] = "FAW Electronics Ltd", 615 | [515] = "Jireh Energy Tech Co. Ltd.", 616 | [516] = "Enlighted Inc.", 617 | [517] = "El-Piast Sp. Z o.o", 618 | [518] = "NetxAutomation Software GmbH", 619 | [519] = "Invertek Drives", 620 | [520] = "Deutschmann Automation GmbH & Co. KG", 621 | [521] = "EMU Electronic AG", 622 | [522] = "Phaedrus Limited", 623 | [523] = "Sigmatek GmbH & Co KG", 624 | [524] = "Marlin Controls", 625 | [525] = "CircutorSA", 626 | [526] = "UTC Fire & Security", 627 | [527] = "DENT Instruments Inc.", 628 | [528] = "FHP Manufacturing Company - Bosch Group", 629 | [529] = "GE Intelligent Platforms", 630 | [530] = "Inner Range Pty Ltd", 631 | [531] = "GLAS Energy Technology", 632 | [532] = "MSR-Electronic-GmbH", 633 | [533] = "Energy Control Systems Inc.", 634 | [534] = "EMT Controls", 635 | [535] = "Daintree Networks Inc.", 636 | [536] = "EURO ICC d.o.o", 637 | [537] = "TE Connectivity Energy", 638 | [538] = "GEZE GmbH", 639 | [539] = "NEC Corporation", 640 | [540] = "Ho Cheung International Company Limited", 641 | [541] = "Sharp Manufacturing Systems Corporation", 642 | [542] = "DOT CONTROLS a.s.", 643 | [543] = "BeaconMedæ0220", 644 | [544] = "Midea Commercial Aircon", 645 | [545] = "WattMaster Controls", 646 | [546] = "Kamstrup A/S", 647 | [547] = "CA Computer Automation GmbH", 648 | [548] = "Laars Heating Systems Company", 649 | [549] = "Hitachi Systems Ltd.", 650 | [550] = "Fushan AKE Electronic Engineering Co. Ltd.", 651 | [551] = "Toshiba International Corporation", 652 | [552] = "Starman Systems LLC", 653 | [553] = "Samsung Techwin Co. Ltd.", 654 | [554] = "ISAS-Integrated Switchgear and Systems P/L", 655 | [556] = "Obvius", 656 | [557] = "Marek Guzik", 657 | [558] = "Vortek Instruments LLC", 658 | [559] = "Universal Lighting Technologies", 659 | [560] = "Myers Power Products Inc.", 660 | [561] = "Vector Controls GmbH", 661 | [562] = "Crestron Electronics Inc.", 662 | [563] = "A&E Controls Limited", 663 | [564] = "Projektomontaza A.D.", 664 | [565] = "Freeaire Refrigeration", 665 | [566] = "Aqua Cooler Pty Limited", 666 | [567] = "Basic Controls", 667 | [568] = "GE Measurement and Control Solutions Advanced Sensors", 668 | [569] = "EQUAL Networks", 669 | [570] = "Millennial Net", 670 | [571] = "APLI Ltd", 671 | [572] = "Electro Industries/GaugeTech", 672 | [573] = "SangMyung University", 673 | [574] = "Coppertree Analytics Inc.", 674 | [575] = "CoreNetiX GmbH", 675 | [576] = "Acutherm", 676 | [577] = "Dr. Riedel Automatisierungstechnik GmbH", 677 | [578] = "Shina System Co.Ltd", 678 | [579] = "Iqapertus", 679 | [580] = "PSE Technology", 680 | [581] = "BA Systems", 681 | [582] = "BTICINO", 682 | [583] = "Monico Inc.", 683 | [584] = "iCue", 684 | [585] = "tekmar Control Systems Ltd.", 685 | [586] = "Control Technology Corporation", 686 | [587] = "GFAE GmbH", 687 | [588] = "BeKa Software GmbH", 688 | [589] = "Isoil Industria SpA", 689 | [590] = "Home Systems Consulting SpA", 690 | [591] = "Socomec", 691 | [592] = "Everex Communications Inc.", 692 | [593] = "Ceiec Electric Technology", 693 | [594] = "Atrila GmbH", 694 | [595] = "WingTechs", 695 | [596] = "Shenzhen Mek Intellisys Pte Ltd.", 696 | [597] = "Nestfield Co. Ltd.", 697 | [598] = "Swissphone Telecom AG", 698 | [599] = "PNTECH JSC", 699 | [600] = "Horner APG LLC", 700 | [601] = "PVI Industries LLC", 701 | [602] = "Ela-compil", 702 | [603] = "Pegasus Automation International LLC", 703 | [604] = "Wight Electronic Services Ltd.", 704 | [605] = "Marcom", 705 | [606] = "Exhausto A/S", 706 | [607] = "Dwyer Instruments Inc.", 707 | [608] = "Link GmbH", 708 | [609] = "Oppermann Regelgerate GmbH", 709 | [610] = "NuAire Inc.", 710 | [611] = "Nortec Humidity Inc.", 711 | [612] = "Bigwood Systems Inc.", 712 | [613] = "Enbala Power Networks", 713 | [614] = "Inter Energy Co. Ltd.", 714 | [615] = "ETC", 715 | [616] = "COMELEC S.A.R.L", 716 | [617] = "Pythia Technologies", 717 | [618] = "TrendPoint Systems Inc.", 718 | [619] = "AWEX", 719 | [620] = "Eurevia", 720 | [621] = "Kongsberg E-lon AS", 721 | [622] = "FlaktWoods", 722 | [623] = "E + E Elektronik GES M.B.H.", 723 | [624] = "ARC Informatique", 724 | [625] = "SKIDATA AG", 725 | [626] = "WSW Solutions", 726 | [627] = "Trefon Electronic GmbH", 727 | [628] = "Dongseo System", 728 | [629] = "Kanontec Intelligence Technology Co. Ltd.", 729 | [630] = "EVCO S.p.A.", 730 | [631] = "Accuenergy (CANADA) Inc.", 731 | [632] = "SoftDEL", 732 | [633] = "Orion Energy Systems Inc.", 733 | [634] = "Roboticsware", 734 | [635] = "DOMIQ Sp. z o.o.", 735 | [636] = "Solidyne", 736 | [637] = "Elecsys Corporation", 737 | [638] = "Conditionaire International Pty. Limited", 738 | [639] = "Quebec Inc.", 739 | [640] = "Homerun Holdings", 740 | [641] = "RFM Inc.", 741 | [642] = "Comptek", 742 | [643] = "Westco Systems Inc.", 743 | [644] = "Advancis Software & Services GmbH", 744 | [645] = "Intergrid LLC", 745 | [646] = "Markerr Controls Inc.", 746 | [647] = "Toshiba Elevator and Building Systems Corporation", 747 | [648] = "Spectrum Controls Inc.", 748 | [649] = "Mkservice", 749 | [650] = "Fox Thermal Instruments", 750 | [651] = "SyxthSense Ltd", 751 | [652] = "DUHA System S R.O.", 752 | [653] = "NIBE", 753 | [654] = "Melink Corporation", 754 | [655] = "Fritz-Haber-Institut", 755 | [656] = "MTU Onsite Energy GmbHGas Power Systems", 756 | [657] = "Omega Engineering Inc.", 757 | [658] = "Avelon", 758 | [659] = "Ywire Technologies Inc.", 759 | [660] = "M.R. Engineering Co. Ltd.", 760 | [661] = "Lochinvar LLC", 761 | [662] = "Sontay Limited", 762 | [663] = "GRUPA Slawomir Chelminski", 763 | [664] = "Arch Meter Corporation", 764 | [665] = "Senva Inc.", 765 | [667] = "FM-Tec", 766 | [668] = "Systems Specialists Inc.", 767 | [669] = "SenseAir", 768 | [670] = "AB IndustrieTechnik Srl", 769 | [671] = "Cortland Research LLC", 770 | [672] = "MediaView", 771 | [673] = "VDA Elettronica", 772 | [674] = "CSS Inc.", 773 | [675] = "Tek-Air Systems Inc.", 774 | [676] = "ICDT", 775 | [677] = "The Armstrong Monitoring Corporation", 776 | [678] = "DIXELL S.r.l", 777 | [679] = "Lead System Inc.", 778 | [680] = "ISM EuroCenter S.A.", 779 | [681] = "TDIS", 780 | [682] = "Trade FIDES", 781 | [683] = "KnübH (Emerson Network Power)", 782 | [684] = "Resource Data Management", 783 | [685] = "Abies Technology Inc.", 784 | [686] = "Amalva", 785 | [687] = "MIRAE Electrical Mfg. Co. Ltd.", 786 | [688] = "HunterDouglas Architectural Projects Scandinavia ApS", 787 | [689] = "RUNPAQ Group Co.Ltd", 788 | [690] = "Unicard SA", 789 | [691] = "IE Technologies", 790 | [692] = "Ruskin Manufacturing", 791 | [693] = "Calon Associates Limited", 792 | [694] = "Contec Co. Ltd.", 793 | [695] = "iT GmbH", 794 | [696] = "Autani Corporation", 795 | [697] = "Christian Fortin", 796 | [698] = "HDL", 797 | [699] = "IPID Sp. Z.O.O Limited", 798 | [700] = "Fuji Electric Co.Ltd", 799 | [701] = "View Inc.", 800 | [702] = "Samsung S1 Corporation", 801 | [703] = "New Lift", 802 | [704] = "VRT Systems", 803 | [705] = "Motion Control Engineering Inc.", 804 | [706] = "Weiss Klimatechnik GmbH", 805 | [707] = "Elkon", 806 | [708] = "Eliwell Controls S.r.l.", 807 | [709] = "Japan Computer Technos Corp", 808 | [710] = "Rational Network ehf", 809 | [711] = "Magnum Energy Solutions LLC", 810 | [712] = "MelRok", 811 | [713] = "VAE Group", 812 | [714] = "LGCNS", 813 | [715] = "Berghof Automationstechnik GmbH", 814 | [716] = "Quark Communications Inc.", 815 | [717] = "Sontex", 816 | [718] = "mivune AG", 817 | [719] = "Panduit", 818 | [720] = "Smart Controls LLC", 819 | [721] = "Compu-Aire Inc.", 820 | [722] = "Sierra", 821 | [723] = "ProtoSense Technologies", 822 | [724] = "Eltrac Technologies Pvt Ltd", 823 | [725] = "Bektas Invisible Controls GmbH", 824 | [726] = "Entelec", 825 | [727] = "Innexiv", 826 | [728] = "Covenant" 827 | } 828 | --return vendor information 829 | function vendor_lookup(vennum) 830 | local vendorname = vendor_id[vennum] or "Unknown Vendor Number" 831 | return string.format("%s (%d)", vendorname, vennum) 832 | end 833 | 834 | --- 835 | -- Function to lookup the length of the Field to be used for Vendor ID, Firmware 836 | -- Object Name, Software Version, and Location. It will then return the Value 837 | -- that is stored inside the packet for this information as a String Value. 838 | -- The field is located in the 18th byte of the data field of a valid packet. 839 | -- Depending on this field the information will be stored in field 20 + length 840 | -- or in field 22 + length. 841 | -- 842 | -- @param packet The packet that was received and is ready to be parsed 843 | function field_size(packet) 844 | local info 845 | 846 | -- read the Length field from the packet data byte 18 847 | local offset 848 | -- Verify the field from byte 18 to determine if the vendor number is one byte or two bytes? 849 | local value = string.byte(packet, 18) 850 | if ( value % 0x10 < 5 ) then 851 | value = value % 0x10 - 1 852 | offset = 19 853 | else 854 | value = string.byte(packet, 19) - 1 855 | offset = 20 856 | end 857 | -- unpack a string of length 858 | offset, charset, info = bin.unpack("CA" .. tostring(value), packet, offset) 859 | -- return information that was found in the packet 860 | if charset == 0 then -- UTF-8 861 | return info 862 | elseif charset == 4 then -- UCS-2 big-endian 863 | return unicode.transcode(info, unicode.utf16_dec, unicode.utf8_enc, true, nil) 864 | else -- TODO: other encodings not supported by unicode.lua 865 | return info 866 | end 867 | end 868 | --- 869 | -- Function to set the nmap output for the host, if a valid BACNet packet 870 | -- is received then the output will show that the port is open instead of 871 | -- open|filtered 872 | -- 873 | -- @param host Host that was passed in via nmap 874 | -- @param port port that BACNet is running on (Default UDP/47808) 875 | function set_nmap(host, port) 876 | 877 | --set port Open 878 | port.state = "open" 879 | -- set version name to BACNet 880 | port.version.name = "BACNet -- Building Automation and Control Networks" 881 | nmap.set_port_version(host, port) 882 | nmap.set_port_state(host, port, "open") 883 | 884 | end 885 | 886 | --- 887 | -- Function to send a query to the discovered BACNet devices. This will pull extra 888 | -- information to help identify the device. Information such as firmware, application software 889 | -- object name, description, and location parameters configured inside of the device. 890 | -- 891 | -- @param socket The socket that was created in the action function 892 | -- @param type Type is the type of packet to send, this can be firmware, application, object, description, or location 893 | function standard_query(socket, type) 894 | 895 | 896 | -- set the query for vendor name 897 | local vendor_query = bin.pack("H","810a001101040005010c0c023FFFFF1979") 898 | -- set the firmware version query data for sending 899 | local firmware_query = bin.pack("H","810a001101040005010c0c023FFFFF192c") 900 | -- set the application version query data for sending 901 | local appsoft_query = bin.pack("H","810a001101040005010c0c023FFFFF190c") 902 | -- set the object name query data for sending 903 | local object_query = bin.pack("H","810a001101040005010c0c023FFFFF194d") 904 | -- set the model name query data for sending 905 | local model_query = bin.pack("H","810a001101040005010c0c023FFFFF1946") 906 | -- set the desc name query data for sending 907 | local desc_query = bin.pack("H","810a001101040005010c0c023FFFFF191c") 908 | -- set the location name query data for sending 909 | local location_query = bin.pack("H","810a001101040005010c0c023FFFFF193A") 910 | local query 911 | 912 | -- 913 | -- determine what type of packet to send 914 | if (type == "firmware") then 915 | query = firmware_query 916 | elseif (type == "application") then 917 | query = appsoft_query 918 | elseif (type == "model") then 919 | query = model_query 920 | elseif (type == "object") then 921 | query = object_query 922 | elseif (type == "description") then 923 | query = desc_query 924 | elseif (type == "location") then 925 | query = location_query 926 | elseif (type == "vendor") then 927 | query = vendor_query 928 | end 929 | 930 | --try to pull the information 931 | local status, result = socket:send(query) 932 | if(status == false) then 933 | stdnse.debug1("Socket error sending query: %s", result) 934 | return nil 935 | end 936 | -- receive packet from response 937 | local rcvstatus, response = socket:receive() 938 | if(rcvstatus == false) then 939 | stdnse.debug1("Socket error receiving: %s", response) 940 | return nil 941 | end 942 | -- validate valid BACNet Packet 943 | if( string.starts(response, "\x81")) then 944 | -- Lookup byte 7 (pakcet type) 945 | local pos, value = bin.unpack("C", response, 7) 946 | -- verify that the response packet was not an error packet 947 | if( value ~= 0x50) then 948 | --collect information by looping thru the packet 949 | return field_size(response) 950 | -- if it was an error packet, set the string to error for later purposes 951 | else 952 | stdnse.debug1("Error receiving: BACNet Error") 953 | return nil 954 | end 955 | -- else ERROR 956 | else 957 | stdnse.debug1("Error receiving Vendor ID: Invalid BACNet packet") 958 | return nil 959 | end 960 | 961 | end 962 | --- 963 | -- Function to send a query to the discovered BACNet devices. This function queries extra 964 | -- information to help identify the device. Vendor ID query is sent with this 965 | -- function and the Vendor ID number is parsed out of the packet. 966 | -- 967 | -- @param socket The socket that was created in the action function 968 | function vendornum_query(socket) 969 | 970 | -- set the vendor query data for sending 971 | local vendor_query = bin.pack("H","810a001101040005010c0c023FFFFF1978") 972 | 973 | 974 | --send the vendor information 975 | local status, result = socket:send(vendor_query) 976 | if(status == false) then 977 | stdnse.debug1("Socket error sending vendor query: %s", result) 978 | return nil 979 | end 980 | -- receive vendor information packet 981 | local rcvstatus, response = socket:receive() 982 | if(rcvstatus == false) then 983 | stdnse.debug1("Socket error receiving vendor query: %s", response) 984 | return nil 985 | end 986 | -- validate valid BACNet Packet 987 | if( string.starts(response, "\x81")) then 988 | local pos, value = bin.unpack("C", response, 7) 989 | --if the vendor query resulted in an error 990 | if( value ~= 0x50) then 991 | -- read values for byte 18 in the packet data 992 | -- this value determines if vendor number is 1 or 2 bytes 993 | pos, value = bin.unpack("C", response, 18) 994 | else 995 | stdnse.debug1("Error receiving Vendor ID: BACNet Error") 996 | return nil 997 | end 998 | -- if value is 21 (byte 18) 999 | if( value == 0x21 ) then 1000 | -- convert hex to decimal 1001 | local vendornum = string.byte(response, 19) 1002 | -- look up vendor name from table 1003 | return vendor_lookup(vendornum) 1004 | -- if value is 22 (byte 18) 1005 | elseif( value == 0x22 ) then 1006 | -- convert hex to decimal 1007 | local vendornum 1008 | pos, vendornum = bin.unpack(">S", response, 19) 1009 | -- look up vendor name from table 1010 | return vendor_lookup(vendornum) 1011 | else 1012 | -- set return value to an Error if byte 18 was not 21/22 1013 | stdnse.debug1("Error receiving Vendor ID: Invalid BACNet packet") 1014 | return nil 1015 | end 1016 | end 1017 | 1018 | end 1019 | 1020 | --- 1021 | -- Function to send a request for BVLC info to the discovered BACNet devices. 1022 | -- This includes the BBMD and the FDT queries. These are read only and do not 1023 | -- attempt to join the router as a Foreign Device. 1024 | -- 1025 | -- @param socket The socket that was created in the action function 1026 | -- @param type Type is the type of packet to send, this can be bbmd or fdt 1027 | function bvlc_query(socket, type) 1028 | 1029 | -- set the BVLC query data for sending 1030 | -- BBMD = 0x02 1031 | local bbmd_query = bin.pack("H","81020004") 1032 | -- FDT = 0x06 1033 | local fdt_query = bin.pack("H","81060004") 1034 | -- initialize query var 1035 | local query 1036 | 1037 | -- Based on type parameter passed in from Action 1038 | if (type == "bbmd") then 1039 | query = bbmd_query 1040 | elseif (type == "fdt") then 1041 | query = fdt_query 1042 | end 1043 | 1044 | -- Send the query that was set by the type 1045 | local status, result = socket:send(query) 1046 | if(status == false) then 1047 | stdnse.debug1("BVLC-" .. type .. ": Socket error sending query: %s", result) 1048 | return nil 1049 | end 1050 | -- Recive response from the query 1051 | local rcvstatus, response = socket:receive() 1052 | if(rcvstatus == false) then 1053 | stdnse.debug1("BVLC-" .. type .. ": Socket error receiving: %s", response) 1054 | return nil 1055 | end 1056 | 1057 | -- Validate that packet is BACNet, if it is then we will start parsing more response 1058 | if( string.starts(response, "\x81")) then 1059 | 1060 | -- init up vars 1061 | local info = "" 1062 | local ips = {} 1063 | local length 1064 | local mask 1065 | local resptype 1066 | 1067 | -- unpack response type, this will be used to determine BBMD vs FDT 1068 | local pos, resptype = bin.unpack("C", response, 2) 1069 | 1070 | -- unpack length, this will be the length of the information to be parsed 1071 | pos, length = bin.unpack(">S", response, 3) 1072 | -- add one to length since Lua starts at 1 not 0 1073 | length = length + 1 1074 | stdnse.debug1("BVLC-" .. type .. ": starting on bacnet bytes: " .. length) 1075 | -- if length is 7(packet size 6), then we will test to see if it was NAK response 1076 | if length == 7 then 1077 | -- response type will be BVLC-Result 1078 | if resptype == 0 then 1079 | -- unpack two bytes of interest 1080 | pos, byte1 = bin.unpack("C", response, 4) 1081 | pos, byte2 = bin.unpack("C", response, 6) 1082 | if byte1 == 0x06 and byte2 == 0x40 then 1083 | return "Non-Acknowledgement (NAK)" 1084 | elseif byte1 == 0x06 and byte2 == 0x20 then 1085 | return "Non-Acknowledgement (NAK)" 1086 | end 1087 | end 1088 | -- if the packet length is 5(packet size 4) then check to see if a Empty response 1089 | elseif length == 5 then 1090 | -- validate the response is for the FDT query 1091 | if resptype == 7 then 1092 | return "Empty Table" 1093 | end 1094 | -- if packet is not long enough then we will exit 1095 | elseif length < 15 then 1096 | stdnse.debug1( 1097 | "BVLC-" .. type .. ": stopping, this response had not enough bytes: " .. length .. " < 15") 1098 | return nil 1099 | end 1100 | -- While loop for the length of the packet as determined from above. 1101 | while pos < length do 1102 | local ipaddr = "" 1103 | --Unpack and the IP Address from the response 1104 | pos, info = bin.unpack("S", response, pos) 1110 | -- Make string to be stored in output table to be returned to Nmap 1111 | ipaddr = ipaddr .. ":" .. info 1112 | -- shift by 4 bytes 1113 | pos = pos + 4 1114 | 1115 | -- else if the type is FDT 1116 | elseif resptype == 7 then 1117 | --Unpack port number 1118 | pos, info = bin.unpack(">S", response, pos) 1119 | ipaddr = ipaddr .. ":" .. info 1120 | --Unpack TTL field 1121 | pos, info = bin.unpack(">S", response, pos) 1122 | ipaddr = ipaddr .. ":ttl=" .. info 1123 | --Unpack the timeout field 1124 | pos, info = bin.unpack(">S", response, pos) 1125 | ipaddr = ipaddr .. ":timeout=" .. info 1126 | stdnse.debug1("BVLC-" .. type .. ": found this: " .. ipaddr) 1127 | -- else the type was not something we were asking for 1128 | --we don't know what response type this is! 1129 | else 1130 | stdnse.debug1("BVLC-" .. type .. ": unknown response type encountered!") 1131 | return nil 1132 | end 1133 | -- insert to the ips table for output to Nmap 1134 | table.insert(ips, ipaddr) 1135 | 1136 | -- consider if its time to quit based on the last pos from the last 1137 | -- unpack was the end of the packet 1138 | if pos == length then 1139 | stdnse.debug1("BVLC-" .. type .. ": bailing because we are at the end: " .. pos) 1140 | return ips 1141 | end 1142 | stdnse.debug1("BVLC-" .. type .. ": done with loop") 1143 | end 1144 | -- else ERROR 1145 | else 1146 | stdnse.debug1("Invalid BACNet packet in response to: " .. type) 1147 | return nil 1148 | end 1149 | 1150 | end 1151 | 1152 | --- 1153 | -- Action Function that is used to run the NSE. This function will send the initial query to the 1154 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 1155 | -- is a BACNet device. If it is then more actions are taken to gather extra information. 1156 | -- 1157 | -- @param host Host that was scanned via nmap 1158 | -- @param port port that was scanned via nmap 1159 | action = function(host, port) 1160 | --set the first query data for sending 1161 | local orig_query = bin.pack("H","810a001101040005010c0c023FFFFF194b" ) 1162 | 1163 | local to_return = nil 1164 | -- create new socket 1165 | local sock = nmap.new_socket() 1166 | -- Bind to port for niceness with BACNet this may need to be commented out if 1167 | -- scanning more than one host at a time, may fix some issues seen on Windows 1168 | -- 1169 | local status, err = sock:bind(nil, 47808) 1170 | if(status == false) then 1171 | stdnse.debug1( 1172 | "Couldn't bind to 47808/udp. Continuing anyway, results may vary") 1173 | end 1174 | -- connect to the remote host 1175 | local constatus, conerr = sock:connect(host, port) 1176 | if not constatus then 1177 | stdnse.debug1( 1178 | 'Error establishing a UDP connection for %s - %s', host, conerr 1179 | ) 1180 | return nil 1181 | end 1182 | -- send the original query to see if it is a valid BACNet Device 1183 | local sendstatus, senderr = sock:send(orig_query) 1184 | if not sendstatus then 1185 | stdnse.debug1( 1186 | 'Error sending BACNet request to %s:%d - %s', 1187 | host.ip, port.number, senderr 1188 | ) 1189 | return nil 1190 | end 1191 | 1192 | -- receive response 1193 | local rcvstatus, response = sock:receive() 1194 | if(rcvstatus == false) then 1195 | stdnse.debug1("Receive error: %s", response) 1196 | return nil 1197 | end 1198 | 1199 | -- if the response starts with 0x81 then its BACNet 1200 | if( string.starts(response, "\x81")) then 1201 | local pos, value = bin.unpack("C", response, 7) 1202 | --if the first query resulted in an error 1203 | -- 1204 | if( value == 0x50) then 1205 | -- set the nmap output for the port and version 1206 | set_nmap(host, port) 1207 | -- return that BACNet Error was received 1208 | to_return = "\nBACNet ADPU Type: Error (5) \n\t" .. stdnse.tohex(response) 1209 | --else pull the InstanceNumber and move onto the pulling more information 1210 | -- 1211 | else 1212 | to_return = stdnse.output_table() 1213 | -- set the nmap output for the port and version 1214 | set_nmap(host, port) 1215 | 1216 | -- Vendor Number to Name lookup 1217 | to_return["Vendor ID"] = vendornum_query(sock) 1218 | 1219 | -- vendor name 1220 | to_return["Vendor Name"] = standard_query(sock, "vendor") 1221 | 1222 | -- Instance Number (object number) 1223 | local instance_upper, instance 1224 | pos, instance_upper, instance = bin.unpack("C>S", response, 20) 1225 | to_return["Object-identifier"] = instance_upper * 0x10000 + instance 1226 | 1227 | --Firmware Verson 1228 | to_return["Firmware"] = standard_query(sock, "firmware") 1229 | 1230 | -- Application Software Version 1231 | to_return["Application Software"] = standard_query(sock, "application") 1232 | 1233 | -- Object Name 1234 | to_return["Object Name"] = standard_query(sock, "object") 1235 | 1236 | -- Model Name 1237 | to_return["Model Name"] = standard_query(sock, "model") 1238 | 1239 | -- Description 1240 | to_return["Description"] = standard_query(sock, "description") 1241 | 1242 | -- Location 1243 | to_return["Location"] = standard_query(sock, "location") 1244 | 1245 | -- for each element in the table, if it is nil, then remove the information from the table 1246 | for key,value in pairs(to_return) do 1247 | if(string.len(to_return[key]) == 0) then 1248 | to_return[key] = nil 1249 | end 1250 | end 1251 | -- check for script-args, if its set to yes, then run this additional queries 1252 | local arguments = stdnse.get_script_args('full') 1253 | if ( arguments == "yes" ) then 1254 | -- BACnet Broadcast Management Device Query/Response 1255 | to_return["Broadcast Distribution Table (BDT)"] = bvlc_query(sock, "bbmd") 1256 | 1257 | -- Foreign Device Table Query/Response 1258 | to_return["Foreign Device Table (FDT)"] = bvlc_query(sock, "fdt") 1259 | end 1260 | end 1261 | else 1262 | -- return nothing, no BACNet was detected 1263 | -- close socket 1264 | sock:close() 1265 | return nil 1266 | end 1267 | -- close socket 1268 | sock:close() 1269 | -- return all information that was found 1270 | return to_return 1271 | 1272 | end 1273 | -------------------------------------------------------------------------------- /IEC-60870-5-104/iec-identify.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | IEC-60870-5-104 (IEC 104) protocol discovery tool. Power of Community 2013 conference release. 3 | Attemts to check tcp/2404 port supporting IEC 60870-5-104 ICS protocol. 4 | ]] 5 | 6 | --- 7 | -- @usage 8 | -- nmap -Pn -n -d --script iec-identify.nse --script-args='iec-identify.timeout=500' -p 2404 9 | -- 10 | -- @args iec-identify.timeout 11 | -- Set the timeout in milliseconds. The default value is 500. 12 | -- 13 | -- @output 14 | -- PORT STATE SERVICE REASON 15 | -- 2404/tcp open IEC 60870-5-104 syn-ack 16 | -- | iec-identify: 17 | -- | testfr sent / recv: 680443000000 / 680483000000 18 | -- | startdt sent / recv: 680407000000 / 68040b000000 19 | -- | c_ic_na_1 sent / recv: 680e0000000064010600ffff00000000 / 680e0000020064014700ffff00000014 20 | -- |_ asdu address: 65535 21 | -- 22 | -- Version 0.1 23 | -- 24 | --- 25 | 26 | author = "Aleksandr Timorin" 27 | copyright = "Aleksandr Timorin" 28 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 29 | categories = {"discovery", "intrusive"} 30 | 31 | local shortport = require("shortport") 32 | local bin = require("bin") 33 | local comm = require("comm") 34 | local stdnse = require("stdnse") 35 | 36 | portrule = shortport.portnumber(2404, "tcp") 37 | 38 | local function hex2str(str) 39 | local x = {} 40 | local char_int 41 | for y in str:gmatch('(..)') do 42 | char_int = tonumber(y, 16) 43 | if char_int>=32 and char_int<=126 then 44 | x[#x+1] = string.char( char_int ) 45 | else 46 | x[#x+1] = y 47 | end 48 | end 49 | return table.concat( x ) 50 | end 51 | 52 | action = function(host, port) 53 | 54 | local timeout = stdnse.get_script_args("iec-identify.timeout") 55 | timeout = tonumber(timeout) or 500 56 | 57 | local asdu_address 58 | local pos 59 | local status, recv 60 | local output = {} 61 | local socket = nmap.new_socket() 62 | 63 | socket:set_timeout(timeout) 64 | 65 | -- attempt to connect tp 2404 tcp port 66 | stdnse.print_debug(1, "try to connect to port 2404" ) 67 | status, result = socket:connect(host, port, "tcp") 68 | --stdnse.print_debug(1, "connect status %s", status ) 69 | if not status then 70 | return nil 71 | end 72 | 73 | -- send TESTFR command 74 | local TESTFR = string.char(0x68, 0x04, 0x43, 0x00, 0x00, 0x00) 75 | status = socket:send( TESTFR ) 76 | stdnse.print_debug(1, "testfr status %s", status ) 77 | if not status then 78 | return nil 79 | end 80 | 81 | -- receive TESTFR answer 82 | status, recv = socket:receive_bytes(1024) 83 | stdnse.print_debug(1, "testfr recv: %s", stdnse.tohex(recv) ) 84 | --table.insert(output, string.format("testfr sent / recv: %s / %s", hex2str( stdnse.tohex(TESTFR)), hex2str( stdnse.tohex(recv)))) 85 | table.insert(output, string.format("testfr sent / recv: %s / %s", stdnse.tohex(TESTFR), stdnse.tohex(recv))) 86 | 87 | -- send STARTDT command 88 | local STARTDT = string.char(0x68, 0x04, 0x07, 0x00, 0x00, 0x00) 89 | status = socket:send( STARTDT ) 90 | if not status then 91 | return nil 92 | end 93 | 94 | -- receive STARTDT answer 95 | status, recv = socket:receive_bytes(0) 96 | stdnse.print_debug(1, "startd recv len: %d", #recv ) 97 | stdnse.print_debug(1, "startdt recv: %s", stdnse.tohex(recv) ) 98 | --table.insert(output, string.format("startdt sent / recv: %s / %s", hex2str( stdnse.tohex(STARTDT)), hex2str( stdnse.tohex(recv)))) 99 | table.insert(output, string.format("startdt sent / recv: %s / %s", stdnse.tohex(STARTDT), stdnse.tohex(recv))) 100 | 101 | -- if received 2 packets - STARTDT con + ME_EI_NA_1 Init -> full length should be 6+6+10 bytes 102 | if #recv == 22 then 103 | pos, asdu_address = bin.unpack(" 8 | -- 9 | -- @args mms-identify.timeout 10 | -- Set the timeout in milliseconds. The default value is 500. 11 | -- 12 | -- @output 13 | -- PORT STATE SERVICE 14 | -- 102/tcp open IEC 61850-8-1 MMS 15 | -- | mms-identify: 16 | -- | Raw answer: 030000>02f08001000100a10/020103a0*a1(020101a2#800flibiec61850.com810blibiec6185082030.5 17 | -- | Vendor name: libiec61850.com 18 | -- | Model name: libiec61850 19 | -- |_ Revision: 0.5 20 | -- 21 | -- Version 0.1 22 | -- 23 | --- 24 | 25 | author = "Aleksandr Timorin" 26 | copyright = "Aleksandr Timorin" 27 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 28 | categories = {"discovery", "intrusive"} 29 | 30 | local shortport = require("shortport") 31 | local bin = require("bin") 32 | local comm = require("comm") 33 | local stdnse = require("stdnse") 34 | 35 | portrule = shortport.portnumber(102, "tcp") 36 | 37 | local function hex2str(str) 38 | local x = {} 39 | local char_int 40 | for y in str:gmatch('(..)') do 41 | char_int = tonumber(y, 16) 42 | if char_int>=32 and char_int<=126 then 43 | x[#x+1] = string.char( char_int ) 44 | else 45 | x[#x+1] = y 46 | end 47 | end 48 | return table.concat( x ) 49 | end 50 | 51 | action = function(host, port) 52 | 53 | local timeout = stdnse.get_script_args("mms-identify.timeout") 54 | timeout = tonumber(timeout) or 500 55 | 56 | local status, recv 57 | local output = {} 58 | local socket = nmap.new_socket() 59 | 60 | socket:set_timeout(timeout) 61 | 62 | status, result = socket:connect(host, port, "tcp") 63 | if not status then 64 | return nil 65 | end 66 | 67 | local CR_TPDU = string.char(0x03, 0x00, 0x00, 0x0b, 0x06, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x00) 68 | -- status, recv = comm.exchange(host, port, CR_TPDU, {timeout=timeout}) 69 | status = socket:send( CR_TPDU ) 70 | if not status then 71 | return nil 72 | end 73 | status, recv = socket:receive_bytes(1024) 74 | stdnse.print_debug(1, "cr_tpdu recv: %s", stdnse.tohex(recv) ) 75 | table.insert(output, string.format("cr_tpdu send / recv: %s / %s", hex2str( stdnse.tohex(CR_TPDU)), hex2str( stdnse.tohex(recv)))) 76 | 77 | local MMS_INITIATE = string.char( 78 | 0x03, 0x00, 0x00, 0xc5, 0x02, 0xf0, 0x80, 0x0d, 79 | 0xbc, 0x05, 0x06, 0x13, 80 | 0x01, 0x00, 0x16, 0x01, 0x02, 0x14, 0x02, 0x00, 81 | 0x02, 0x33, 0x02, 0x00, 0x01, 0x34, 0x02, 0x00, 82 | 0x02, 0xc1, 0xa6, 0x31, 0x81, 0xa3, 0xa0, 0x03, 83 | 0x80, 0x01, 0x01, 0xa2, 0x81, 0x9b, 0x80, 0x02, 84 | 0x07, 0x80, 0x81, 0x04, 0x00, 0x00, 0x00, 0x01, 85 | 0x82, 0x04, 0x00, 0x00, 0x00, 0x02, 0xa4, 0x23, 86 | 0x30, 0x0f, 0x02, 0x01, 0x01, 0x06, 0x04, 0x52, 87 | 0x01, 0x00, 0x01, 0x30, 0x04, 0x06, 0x02, 0x51, 88 | 0x01, 0x30, 0x10, 0x02, 0x01, 0x03, 0x06, 0x05, 89 | 0x28, 0xca, 0x22, 0x02, 0x01, 0x30, 0x04, 0x06, 90 | 0x02, 0x51, 0x01, 0x88, 0x02, 0x06, 0x00, 0x61, 91 | 0x60, 0x30, 0x5e, 0x02, 0x01, 0x01, 0xa0, 0x59, 92 | 0x60, 0x57, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x07, 93 | 0x06, 0x05, 0x28, 0xca, 0x22, 0x01, 0x01, 0xa2, 94 | 0x04, 0x06, 0x02, 0x29, 0x02, 0xa3, 0x03, 0x02, 95 | 0x01, 0x02, 0xa6, 0x04, 0x06, 0x02, 0x29, 0x01, 96 | 0xa7, 0x03, 0x02, 0x01, 0x01, 0xbe, 0x32, 0x28, 97 | 0x30, 0x06, 0x02, 0x51, 0x01, 0x02, 0x01, 0x03, 98 | 0xa0, 0x27, 0xa8, 0x25, 0x80, 0x02, 0x7d, 0x00, 99 | 0x81, 0x01, 0x14, 0x82, 0x01, 0x14, 0x83, 0x01, 100 | 0x04, 0xa4, 0x16, 0x80, 0x01, 0x01, 0x81, 0x03, 101 | 0x05, 0xfb, 0x00, 0x82, 0x0c, 0x03, 0x6e, 0x1d, 102 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 103 | 0x98 104 | ) 105 | 106 | status = socket:send( MMS_INITIATE ) 107 | if not status then 108 | return nil 109 | end 110 | status, recv = socket:receive_bytes(1024) 111 | stdnse.print_debug(1, "mms_initiate recv: %s", stdnse.tohex(recv) ) 112 | table.insert(output, string.format("mms_initiate send / recv: %s / %s", hex2str( stdnse.tohex(MMS_INITIATE)), hex2str( stdnse.tohex(recv)))) 113 | 114 | local MMS_IDENTIFY = string.char( 115 | 0x03, 0x00, 0x00, 0x1b, 0x02, 0xf0, 0x80, 0x01, 116 | 0x00, 0x01, 0x00, 0x61, 0x0e, 0x30, 0x0c, 0x02, 117 | 0x01, 0x03, 0xa0, 0x07, 0xa0, 0x05, 0x02, 0x01, 118 | 0x01, 0x82, 0x00 119 | ) 120 | 121 | status = socket:send( MMS_IDENTIFY ) 122 | if not status then 123 | return nil 124 | end 125 | status, recv = socket:receive_bytes(1024) 126 | stdnse.print_debug(1, "mms_identify recv: %s", stdnse.tohex(recv) ) 127 | table.insert(output, string.format("mms_identify send / recv: %s / %s", hex2str( stdnse.tohex(MMS_IDENTIFY)), hex2str( stdnse.tohex(recv)))) 128 | 129 | local parse_err_catch = function() 130 | stdnse.print_debug(1, "error while parsing answer" ) 131 | end 132 | 133 | local try = nmap.new_try(parse_err_catch) 134 | 135 | if ( status and recv ) then 136 | -- damn! rewrite with bin.unpack! 137 | table.insert(output, string.format("raw answer: %s", hex2str( stdnse.tohex(recv)))) 138 | local tmp_recv = stdnse.tohex(recv) 139 | local invokeID_size = tonumber(string.sub(tmp_recv, 47, 48), 16) 140 | stdnse.print_debug(1, "invokeID_size: %d", invokeID_size ) 141 | 142 | local mms_identify_info = string.sub(tmp_recv, 52 + 2*invokeID_size +1) 143 | local vendor_name_size = tonumber(string.sub(mms_identify_info, 3, 4), 16) 144 | local vendor_name = string.sub(mms_identify_info, 5, 5 + 2*vendor_name_size -1) 145 | table.insert(output, string.format("vendor name: %s", hex2str( vendor_name))) 146 | 147 | mms_identify_info = string.sub(mms_identify_info, 5 + 2*vendor_name_size) 148 | local model_name_size = tonumber(string.sub(mms_identify_info, 3, 4), 16) 149 | local model_name = string.sub(mms_identify_info, 5, 5 + 2*model_name_size -1) 150 | table.insert(output, string.format("model name: %s", hex2str( model_name))) 151 | 152 | mms_identify_info = string.sub(mms_identify_info, 5 + 2*model_name_size) 153 | local revision_size = tonumber(string.sub(mms_identify_info, 3, 4), 16) 154 | local revision = string.sub(mms_identify_info, 5, 5 + 2*revision_size -1) 155 | table.insert(output, string.format("revision: %s", hex2str( revision))) 156 | else 157 | return nil 158 | end 159 | 160 | if(#output > 0) then 161 | port.version.name = "IEC 61850-8-1 MMS" 162 | nmap.set_port_state(host, port, "open") 163 | nmap.set_port_version(host, port, "hardmatched") 164 | return stdnse.format_output(true, output) 165 | else 166 | return nil 167 | end 168 | end 169 | 170 | 171 | --[[ 172 | 173 | python parsing implementation 174 | 175 | tpkt = struct.unpack('!I', r[:4]) 176 | iso8073 = struct.unpack('!I', '\x00' + r[4:7]) 177 | iso8327 = struct.unpack('!I', r[7:11]) 178 | iso8823 = struct.unpack('!II', '\x00' + r[11:18]) 179 | mms = r[18:] 180 | a0, a0_packetsize = struct.unpack('!BB', mms[:2]) 181 | a1, a1_packetsize = struct.unpack('!BB', mms[2:4]) 182 | invokeID, invokeID_size = struct.unpack('!BB', mms[4:6]) 183 | a2, a2_packetsize = struct.unpack('!BB', mms[6+invokeID_size:6+invokeID_size+2]) 184 | mms_identify_info = mms[6+invokeID_size+2:] 185 | vendor_name_size, = struct.unpack('!B', mms_identify_info[1:2]) 186 | vendor_name = ''.join(struct.unpack('!%dc' % vendor_name_size, mms_identify_info[2:2+vendor_name_size])) 187 | mms_identify_info = mms_identify_info[2+vendor_name_size:] 188 | model_name_size, = struct.unpack('!B', mms_identify_info[1:2]) 189 | model_name = ''.join(struct.unpack('!%dc' % model_name_size, mms_identify_info[2:2+model_name_size])) 190 | mms_identify_info = mms_identify_info[2+model_name_size:] 191 | revision_size, = struct.unpack('!B', mms_identify_info[1:2]) 192 | revision = ''.join(struct.unpack('!%dc' % revision_size, mms_identify_info[2:2+revision_size])) 193 | 194 | print "vendor name: {0}, model name: {1}, revision: {2}".format(vendor_name, model_name, revision) 195 | 196 | 197 | 198 | --]] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nmap-ICS-SCADA 2 | ICS/SCADA nmap script. 3 | 4 | [modbus-discover.nse](https://nmap.org/nsedoc/scripts/modbus-discover.html) -Modbus TCP设备发现脚本,该脚本可以调用Modbus 43(2B功能码)功能码读取设备信息 5 | 6 | [modbus-enum.nse](http://seclists.org/nmap-dev/2010/q4/att-489/) - Modbus TCP设备枚举脚本 7 | 8 | [BACnet-discover-enumerate.nse](https://github.com/digitalbond/Redpoint#bacnet-discover-enumeratense) - 读取BACnet设备的基本信息 9 | 10 | [enip-enumerate.nse]() - 读取EtherNet/IP设备的基本信息 11 | 12 | [s7-enumerate.nse](https://github.com/abusy/ICS-Discovery-Tools) - 西门子S7 PLC设备发现脚本,可以枚举PLC的一些基本信息 13 | 14 | [iec-identify.nse](https://github.com/abusy/scada-tools) - IEC-60870-5-104协议asdu address枚举脚本 15 | 16 | [mms-identify.nse](https://github.com/abusy/scada-tools) - IEC-61850-8-1协议信息枚举脚本 17 | 18 | [Simens](https://github.com/abusy/nmap-scada) - Simens目录下的nmap脚本 19 | -------------------------------------------------------------------------------- /S7/s7-enumerate.nse: -------------------------------------------------------------------------------- 1 | -- 2 | -- required packages for this script 3 | -- 4 | -- plcscan.org Fix 5 | -- Fix Support S7-300/400 and S7-1200 and S7 Series Unknown Devices 6 | -- Last change 2014-11-14 add Support S7-1200 7 | -- Last change 2014-11-26 add enumerates block functions number 8 | -- 9 | local bin = require "bin" 10 | local nmap = require "nmap" 11 | local shortport = require "shortport" 12 | local stdnse = require "stdnse" 13 | local string = require "string" 14 | local table = require "table" 15 | 16 | description = [[ 17 | Enumerates Siemens S7 PLC Devices and collects their device information. This 18 | NSE is based off PLCScan that was developed by Positive Research and 19 | Scadastrangelove (https://code.google.com/p/plcscan/). This script is meant to 20 | provide the same functionality as PLCScan inside of Nmap. Some of the 21 | information that is collected by PLCScan was not ported over to this NSE, this 22 | information can be parsed out of the packets that are received. 23 | 24 | Thanks to Positive Research, and Dmitry Efanov for creating PLCScan 25 | ]] 26 | 27 | author = "Stephen Hilt (Digital Bond)" 28 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 29 | categories = {"discovery","intrusive"} 30 | 31 | --- 32 | -- @usage 33 | -- nmap -sP --script s7-discover.nse -p 102 34 | -- 35 | -- @output 36 | --102/tcp open Siemens S7 315 PLC 37 | --| s7-discover: 38 | --| Basic Hardware: 6ES7 315-2AG10-0AB0 39 | --| System Name: SIMATIC 300(1) 40 | --| Copyright: Original Siemens Equipment 41 | --| Version: 2.6.9 42 | --| Module Type: CPU 315-2 DP 43 | --| Module: 6ES7 315-2AG10-0AB0 44 | --|_ Serial Number: S C-X4U421302009 45 | --| ---------------------------------- 46 | --| Blocks Name: Count(Num) 47 | --| OB: 9 48 | --| FB: 0 49 | --| FC: 19 50 | --| DB: 18 51 | --| SDB: 17 52 | --| SFC: 71 53 | --| SFB: 20 54 | -- 55 | -- 56 | -- @output 57 | --102/tcp open Siemens S7 1200 PLC 58 | -- s7-enumerate: 59 | -- Module: 6ES7 214-1AE30-0XB0 60 | -- Basic Hardware: 6ES7 214-1AE30-0XB0 61 | -- Version: 2.2.0 62 | -- 63 | -- @xmloutput 64 | --6ES7 315-2AG10-0AB0 65 | --SIMATIC 300(1) 66 | --Original Siemens Equipment 67 | --2.6.9 68 | --SimpleServer 69 | --CPU 315-2 DP 70 | --6ES7 315-2AG10-0AB0 71 | --S C-X4U421302009 72 | -- 73 | 74 | 75 | -- port rule for devices running on TCP/102 76 | portrule = shortport.port_or_service(102, "iso-tsap", "tcp") 77 | 78 | --- 79 | -- Function to send and receive the S7COMM Packet 80 | -- 81 | -- First argument is the socket that was created inside of the main Action 82 | -- this will be utilized to send and receive the packets from the host. 83 | -- the second argument is the query to be sent, this is passed in and is created 84 | -- inside of the main action. 85 | -- @param socket the socket that was created in Action. 86 | -- @param query the specific query that you want to send/receive on. 87 | function send_receive(socket, query) 88 | local sendstatus, senderr = socket:send(query) 89 | if(sendstatus == false) then 90 | return "Error Sending S7COMM" 91 | end 92 | -- receive response 93 | local rcvstatus,response = socket:receive() 94 | if(rcvstatus == false) then 95 | return "Error Reading S7COMM" 96 | end 97 | return response 98 | end 99 | 100 | --- 101 | -- Function to parse the first SZL Request response that was received from the S7 PLCC 102 | -- 103 | -- First argument is the socket that was created inside of the main Action 104 | -- this will be utilized to send and receive the packets from the host. 105 | -- the second argument is the query to be sent, this is passed in and is created 106 | -- inside of the main action. 107 | -- @param response Packet response that was received from S7 host. 108 | -- @param host The host hat was passed in via Nmap, this is to change output of host/port 109 | -- @param port The port that was passed in via Nmap, this is to change output of host/port 110 | -- @param output Table used for output for return to Nmap 111 | function parse_response(response, host, port, output) 112 | -- unpack the protocol ID 113 | local pos, value = bin.unpack("C", response, 8) 114 | -- unpack the second byte of the SZL-ID 115 | local pos, szl_id = bin.unpack("C", response, 31) 116 | -- set the offset to 0 117 | local offset = 0 118 | -- if the protocol ID is 0x32 119 | if (value == 0x32) then 120 | local pos 121 | -- unpack the module information 122 | pos, output["Module"] = bin.unpack("z", response, 44) 123 | -- unpack the basic hardware information 124 | pos, output["Basic Hardware"] = bin.unpack("z", response, 72) 125 | -- set version number to 0 126 | local version = 0 127 | -- parse version number 128 | local pos, char1,char2,char3 = bin.unpack("CCC", response, 123) 129 | -- concatenate string, or if string is nil make version number 0.0 130 | output["Version"] = table.concat({char1 or "0.0", char2, char3}, ".") 131 | -- return the output table 132 | return output 133 | else 134 | output = DescFlag(S7DescFlag) 135 | return output 136 | end 137 | end 138 | 139 | --- 140 | -- Function to parse the second SZL Request response that was received from the S7 PLC 141 | -- 142 | -- First argument is the socket that was created inside of the main Action 143 | -- this will be utilized to send and receive the packets from the host. 144 | -- the second argument is the query to be sent, this is passed in and is created 145 | -- inside of the main action. 146 | -- @param response Packet response that was received from S7 host. 147 | -- @param output Table used for output for return to Nmap 148 | function second_parse_response(response, output) 149 | local offset = 0 150 | -- unpack the protocol ID 151 | local pos, value = bin.unpack("C", response, 8) 152 | -- unpack the second byte of the SZL-ID 153 | local pos, szl_id = bin.unpack("C", response, 31) 154 | -- if the protocol ID is 0x32 155 | if (value == 0x32) then 156 | -- if the szl-ID is not 0x1c 157 | if( szl_id ~= 0x1c ) then 158 | -- change offset to 4, this is where most ov valid PLCs will fall 159 | offset = 4 160 | end 161 | -- parse system name 162 | pos, output["System Name"] = bin.unpack("z", response, 40 + offset) 163 | -- parse module type 164 | pos, output["Module Type"] = bin.unpack("z", response, 74 + offset) 165 | -- parse serial number 166 | pos, output["Serial Number"] = bin.unpack("z", response, 176 + offset) 167 | -- parse plant identification 168 | pos, output["Plant Identification"] = bin.unpack("z", response, 108 + offset) 169 | -- parse copyright 170 | pos, output["Copyright"] = bin.unpack("z", response, 142 + offset) 171 | 172 | -- for each element in the table, if it is nil, then remove the information from the table 173 | for key,value in pairs(output) do 174 | if(string.len(output[key]) == 0) then 175 | output[key] = nil 176 | end 177 | end 178 | -- return output 179 | return output 180 | else 181 | output = DescFlag(S7DescFlag) 182 | return output 183 | end 184 | end 185 | --- 186 | -- Function to set the nmap output for the host, if a valid S7COMM packet 187 | -- is received then the output will show that the port is open 188 | -- and change the output to reflect an S7 PLC 189 | -- 190 | -- @param host Host that was passed in via nmap 191 | -- @param port port that S7COMM is running on 192 | function set_nmap(host, port) 193 | --set port Open 194 | port.state = "open" 195 | -- set that detected an Siemens S7 196 | port.version.name = "iso-tsap" 197 | port.version.devicetype = "specialized" 198 | port.version.product = "Siemens S7 PLC" 199 | nmap.set_port_version(host, port) 200 | nmap.set_port_state(host, port, "open") 201 | 202 | end 203 | --- 204 | -- 205 | -- if get fail SZL info output S7 protocol Flag 206 | -- 207 | -- add S7 protocol Flag 208 | -- 209 | -- 210 | function DescFlag(S7DescFlag) 211 | output = stdnse.output_table() 212 | local pos, protocol_head = bin.unpack("C", S7DescFlag, 1) 213 | if (protocol_head == 0x03) then 214 | output["Devices Type"] = 'Siemens S7 Series Devices' 215 | return output 216 | end 217 | end 218 | -- 219 | -- 220 | --- 221 | -- to parse the list block response 222 | -- 223 | -- 224 | function parse_listblock_response(response, output) 225 | local block_type = { 226 | [56] = "OB", 227 | [69] = "FB", 228 | [67] = "FC", 229 | [65] = "DB", 230 | [66] = "SDB", 231 | [68] = "SFC", 232 | [70] = "SFB" 233 | } 234 | -- print "dev debug1" 235 | local pos, protocol_id = bin.unpack("C", response, 8) 236 | local pos, listlength = bin.unpack("C", response, 33) 237 | if (protocol_id == 0x32) then 238 | -- print "dev debug2" 239 | if (listlength == 0x1c) then 240 | -- print "dev debug3" 241 | output["Blocks Name"] = "Count(Num)" 242 | local pos, fuc1 = bin.unpack("C", response, 35) 243 | local pos, count1 = bin.unpack("C", response, 37) 244 | output[block_type[fuc1]] = count1 245 | local pos, fuc2 = bin.unpack("C", response, 39) 246 | local pos, count2 = bin.unpack("C", response, 41) 247 | output[block_type[fuc2]] = count2 248 | local pos, fuc3 = bin.unpack("C", response, 43) 249 | local pos, count3 = bin.unpack("C", response, 45) 250 | output[block_type[fuc3]] = count3 251 | local pos, fuc4 = bin.unpack("C", response, 47) 252 | local pos, count4 = bin.unpack("C", response, 49) 253 | output[block_type[fuc4]] = count4 254 | local pos, fuc5 = bin.unpack("C", response, 51) 255 | local pos, count5 = bin.unpack("C", response, 53) 256 | output[block_type[fuc5]] = count5 257 | local pos, fuc6 = bin.unpack("C", response, 55) 258 | local pos, count6 = bin.unpack("C", response, 57) 259 | output[block_type[fuc6]] = count6 260 | local pos, fuc7 = bin.unpack("C", response, 59) 261 | local pos, count7 = bin.unpack("C", response, 61) 262 | output[block_type[fuc7]] = count7 263 | return output 264 | else 265 | return output 266 | end 267 | else 268 | return output 269 | end 270 | end 271 | 272 | -- 273 | -- 274 | -- 275 | --- 276 | --- 277 | -- Action Function that is used to run the NSE. This function will send the initial query to the 278 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 279 | -- is a S7COMM device. If it is then more actions are taken to gather extra information. 280 | -- 281 | -- @param host Host that was scanned via nmap 282 | -- @param port port that was scanned via nmap 283 | action = function(host,port) 284 | -- COTP packet with a dst of 102 285 | local COTP = bin.pack("H","0300001611e00000001400c1020100c2020" .. "102" .. "c0010a") 286 | -- COTP packet with a dst of 200 287 | local alt_COTP = bin.pack("H","0300001611e00000000500c1020100c2020" .. "200" .. "c0010a") 288 | -- setup the ROSCTR Packet 289 | local ROSCTR_Setup = bin.pack("H","0300001902f08032010000000000080000f0000001000101e0") 290 | -- setup the Read SZL information packet 291 | local Read_SZL = bin.pack("H","0300002102f080320700000000000800080001120411440100ff09000400110001") 292 | -- setup the first SZL request (gather the basic hardware and version number) 293 | local first_SZL_Request = bin.pack("H","0300002102f080320700000000000800080001120411440100ff09000400110001") 294 | -- setup the second SZL request 295 | local second_SZL_Request = bin.pack("H","0300002102f080320700000000000800080001120411440100ff090004001c0001") 296 | --- 297 | -- add S7-1200 packet and Block Functions Enumerates 298 | -- by Z-0ne plcscan.org 299 | -- Based on S7COMM Protocol analysis plugin. 300 | -- 301 | --- 302 | -- S7-1200 PLC usage Rack 0 Slot 1 303 | local COTP_0x0000 = bin.pack("H","0300001611e00000000100c0010ac1020100c2020301") 304 | -- Setup communication 0xf0 305 | local Setup_comm = bin.pack("H","0300001902f080320100000c0000080000f0000001000101e0") 306 | -- Request SZL functions Read SZL ID=0X0011 307 | local Req_SZL_0x0011 = bin.pack("H","0300002102f080320700000d00000800080001120411440100ff09000400110000") 308 | -- Request Block Functions -> List blocks 309 | local Req_Block_fuc_list = bin.pack("H","0300001d02f0803207000025000008000400011204114301000a000000") 310 | -- 311 | --- 312 | -- response is used to collect the packet responses 313 | local response 314 | -- output table for Nmap 315 | local output = stdnse.output_table() 316 | -- create socket for communications 317 | local sock = nmap.new_socket() 318 | -- connect to host 319 | local constatus,conerr = sock:connect(host,port) 320 | if not constatus then 321 | stdnse.print_debug(1, 322 | 'Error establishing connection for %s - %s', host,conerr 323 | ) 324 | return nil 325 | end 326 | -- send and receive the COTP Packet 327 | S7DescFlag = send_receive(sock, COTP) 328 | -- unpack the PDU Type 329 | local pos, CC_connect_confirm = bin.unpack("C", S7DescFlag, 6) 330 | -- if PDU type is not 0xd0, then not a successful COTP connection 331 | --- 332 | -- if ( CC_connect_confirm ~= 0xd0) then 333 | -- return nil 334 | -- end 335 | --- 336 | if ( CC_connect_confirm ~= 0xd0) then 337 | --- 338 | -- add support S7 1200 packet send 339 | --- 340 | output = stdnse.output_table() 341 | local constatus,conerr = sock:connect(host,port) 342 | if not constatus then 343 | stdnse.print_debug(1, 344 | 'Error establishing connection for %s - %s', host,conerr 345 | ) 346 | return nil 347 | end 348 | S7DescFlag = send_receive(sock, COTP_0x0000) 349 | local pos, CC_connect_confirm = bin.unpack("C", S7DescFlag, 6) 350 | if ( CC_connect_confirm ~= 0xd0) then 351 | stdnse.print_debug(1, "Not a successful COTP Packet_1200") 352 | output = DescFlag(S7DescFlag) 353 | return output 354 | end 355 | response = send_receive(sock, Setup_comm) 356 | local pos, protocol_id = bin.unpack("C", response, 8) 357 | if ( protocol_id ~= 0x32) then 358 | stdnse.print_debug(1, "Not a successful S7COMM Packet_1200") 359 | output = DescFlag(S7DescFlag) 360 | return output 361 | end 362 | response = send_receive(sock, Req_SZL_0x0011) 363 | local pos, protocol_id = bin.unpack("C", response, 8) 364 | if ( protocol_id ~= 0x32) then 365 | stdnse.print_debug(1, "Not a successful S7COMM Packet_1200") 366 | output = DescFlag(S7DescFlag) 367 | return output 368 | end 369 | output = parse_response(response, host, port, output) 370 | response = send_receive(sock, Req_Block_fuc_list) 371 | output = parse_listblock_response(response, output) 372 | return output 373 | -- 374 | --- 375 | end 376 | -- send and receive the ROSCTR Setup Packet 377 | response = send_receive(sock, ROSCTR_Setup) 378 | -- unpack the protocol ID 379 | local pos, protocol_id = bin.unpack("C", response, 8) 380 | -- if protocol ID is not 0x32 then return nil 381 | --- 382 | if ( protocol_id ~= 0x32) then 383 | stdnse.print_debug(1, "Not a successful S7COMM Packet") 384 | -- return nil 385 | end 386 | --- 387 | -- send and receive the READ_SZL packet 388 | response = send_receive(sock, Read_SZL) 389 | local pos, protocol_id = bin.unpack("C", response, 8) 390 | -- if protocol ID is not 0x32 then return nil 391 | --- 392 | if ( protocol_id ~= 0x32) then 393 | stdnse.print_debug(1, "Not a successful S7COMM Packet") 394 | -- return nil 395 | end 396 | --- 397 | -- send and receive the first SZL Request packet 398 | response = send_receive(sock, first_SZL_Request) 399 | -- parse the response for basic hardware information 400 | output = parse_response(response, host, port, output) 401 | -- send and receive the second SZL Request packet 402 | response = send_receive(sock, second_SZL_Request) 403 | -- parse the response for more information 404 | output = second_parse_response(response, output) 405 | --- 406 | -- send and receive the list block request 407 | response = send_receive(sock, Req_Block_fuc_list) 408 | -- parse the response 409 | output = parse_listblock_response(response, output) 410 | --- 411 | -- if nothing was parsed from the previous two responses 412 | if(output == nil) then 413 | -- re initialize the table 414 | output = stdnse.output_table() 415 | -- re connect to the device ( a RST packet was sent in the previous attempts) 416 | local constatus,conerr = sock:connect(host,port) 417 | if not constatus then 418 | stdnse.print_debug(1, 419 | 'Error establishing connection for %s - %s', host,conerr 420 | ) 421 | return nil 422 | end 423 | -- send and receive the alternate COTP Packet, the dst is 200 instead of 102( do nothing with result) 424 | S7DescFlag = send_receive(sock, alt_COTP) 425 | local pos, CC_connect_confirm = bin.unpack("C", S7DescFlag, 6) 426 | -- if PDU type is not 0xd0, then not a successful COTP connection 427 | --- 428 | if ( CC_connect_confirm ~= 0xd0) then 429 | stdnse.print_debug(1, "Not a successful COTP Packet") 430 | -- return nil 431 | end 432 | --- 433 | -- send and receive the packets as before. 434 | response = send_receive(sock, ROSCTR_Setup) 435 | -- unpack the protocol ID 436 | local pos, protocol_id = bin.unpack("C", response, 8) 437 | -- if protocol ID is not 0x32 then return nil 438 | --- 439 | if ( protocol_id ~= 0x32) then 440 | stdnse.print_debug(1, "Not a successful S7COMM Packet") 441 | -- return nil 442 | end 443 | --- 444 | response = send_receive(sock, Read_SZL) 445 | -- unpack the protocol ID 446 | local pos, protocol_id = bin.unpack("C", response, 8) 447 | -- if protocol ID is not 0x32 then return nil 448 | --- 449 | if ( protocol_id ~= 0x32) then 450 | stdnse.print_debug(1, "Not a successful S7COMM Packet") 451 | -- return nil 452 | end 453 | --- 454 | response = send_receive(sock, first_SZL_Request) 455 | output = parse_response(response, host, port, "ONE", output) 456 | response = send_receive(sock, second_SZL_Request) 457 | output = parse_response(response, host, port, "TWO", output) 458 | --- 459 | response = send_receive(sock, Req_Block_fuc_list) 460 | output = parse_listblock_response(response, output) 461 | --- 462 | end 463 | -- close the socket 464 | sock:close() 465 | 466 | -- If we parsed anything, then set the version info for Nmap 467 | if #output > 0 then 468 | set_nmap(host, port) 469 | end 470 | -- return output to Nmap 471 | return output 472 | 473 | end 474 | 475 | -------------------------------------------------------------------------------- /Siemens/README.md: -------------------------------------------------------------------------------- 1 | nmap-scada 2 | ========== 3 | 4 | nse scripts for scada identification 5 | 6 | nmap --script ./Siemens-CommunicationsProcessors.nse -p 80 7 | 8 | nmap -sU --script ./Siemens-SCALANCE-module.nse -p 161 9 | 10 | nmap -sU --script ./Siemens-WINCC.nse -p 137 -------------------------------------------------------------------------------- /Siemens/Siemens-CommunicationsProcessor.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens S7 Communications Processor devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_Siemens-CommunicationsProcessor: CP 343-1 CX10 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | 31 | if string.find (body, "/S7Web.css") then 32 | version = body:match("(.-)") 33 | if version == nil then 34 | version = "Unknown version" 35 | end 36 | output = output .. version 37 | return true 38 | elseif string.find (body, "examples/visual_key.htm") then 39 | version = body:match("(.-)") 40 | if version == nil then 41 | version = "Unknown version" 42 | end 43 | output = output .. version 44 | return true 45 | elseif string.find (body, "__FSys_Root") then 46 | version = body:match("(.-)") 47 | version = version:gsub(" ", " ") 48 | if version == nil then 49 | version = "Unknown version" 50 | end 51 | output = output .. version 52 | return true 53 | else 54 | return nil 55 | end 56 | end 57 | 58 | action = function(host, port) 59 | local verified, noun 60 | 61 | local answer1 = http.get(host, port, "/Portal0000.htm" ) 62 | local answer2 = http.get(host, port, "/__Additional" ) 63 | local answer3 = http.get(host, port, "/" ) 64 | 65 | if answer1.status ~= 200 and answer2.status ~= 200 and answer3.status ~= 200 then 66 | return nil 67 | end 68 | 69 | if answer1.status == 200 then 70 | answer = answer1 71 | elseif answer2.status == 200 then 72 | answer = answer2 73 | elseif answer3.status == 200 then 74 | answer = answer3 75 | end 76 | 77 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 78 | local detail = 15 79 | local output = strbuf.new() 80 | 81 | 82 | verified = verify_version(answer.body, output) 83 | 84 | 85 | if verified == nil then 86 | return 87 | end 88 | 89 | -- verbose/debug mode, print 50 entries 90 | if v_level > 1 and v_level < 5 then 91 | detail = 40 92 | -- double debug mode, print everything 93 | elseif v_level >= 5 then 94 | detail = verified 95 | end 96 | 97 | 98 | return output 99 | end -------------------------------------------------------------------------------- /Siemens/Siemens-HMI-miniweb.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens SIMATIC S7- devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_SIEMENS-HMI-miniweb: Not implemented verify_version 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | if string.find (body, "ad_header_form_sprachauswahl") then 31 | version = body:match("") 32 | if version == nil then 33 | version = "Not implemented verify_version" 34 | end 35 | output = output .. version 36 | return true 37 | else 38 | return nil 39 | end 40 | end 41 | 42 | action = function(host, port) 43 | local verified, noun 44 | local answer = http.get(host, port, "/CSS/Miniweb.css" ) 45 | 46 | if answer.status ~= 200 then 47 | return nil 48 | end 49 | 50 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 51 | local detail = 15 52 | local output = strbuf.new() 53 | 54 | 55 | verified = verify_version(answer.body, output) 56 | 57 | 58 | if verified == nil then 59 | return 60 | end 61 | 62 | -- verbose/debug mode, print 50 entries 63 | if v_level > 1 and v_level < 5 then 64 | detail = 40 65 | -- double debug mode, print everything 66 | elseif v_level >= 5 then 67 | detail = verified 68 | end 69 | 70 | 71 | return output 72 | end -------------------------------------------------------------------------------- /Siemens/Siemens-SIMATIC-PLC-S7.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens Simatic S7 devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_Siemens-Simatic-S7: SIMATIC 300 (MPI2)/CPU 315-2 PN/DP 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | if string.find (body, "/S7Web.css") then 31 | version = body:match("(.-)") 32 | version = version:gsub(" ", " ") 33 | if version == nil then 34 | version = "Unknown version" 35 | end 36 | output = output .. version 37 | return true 38 | elseif string.find (body, "snCplugPresent") then 39 | version = body:match("Siemens, (.-) !!!") 40 | version = version:gsub(" ", " ") 41 | if version == nil then 42 | version = "Unknown version" 43 | end 44 | output = output .. version 45 | return true 46 | 47 | else 48 | return nil 49 | end 50 | end 51 | 52 | action = function(host, port) 53 | local verified, noun 54 | local answer1 = http.get(host, port, "/Portal/Portal.mwsl" ) 55 | local answer2 = http.get(host, port, "/docs/cplugError.html/" ) 56 | 57 | if answer1.status ~= 200 and answer2.status ~= 200 then 58 | return nil 59 | end 60 | 61 | if answer1.status == 200 then 62 | answer = answer1 63 | elseif answer2.status == 200 then 64 | answer = answer2 65 | end 66 | 67 | 68 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 69 | local detail = 15 70 | local output = strbuf.new() 71 | 72 | 73 | verified = verify_version(answer.body, output) 74 | 75 | 76 | if verified == nil then 77 | return 78 | end 79 | 80 | -- verbose/debug mode, print 50 entries 81 | if v_level > 1 and v_level < 5 then 82 | detail = 40 83 | -- double debug mode, print everything 84 | elseif v_level >= 5 then 85 | detail = verified 86 | end 87 | 88 | 89 | return output 90 | end -------------------------------------------------------------------------------- /Siemens/Siemens-Scalance-module.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local shortport = require "shortport" 3 | local snmp = require "snmp" 4 | local stdnse = require "stdnse" 5 | local table = require "table" 6 | 7 | description = [[ 8 | Checks for SCADA Siemens SCALANCE modules. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | -- @output 15 | -- | Siemens-Scalance-module: 16 | -- |_ SCALANCE W788-1PRO 17 | 18 | 19 | author = "Jose Ramon Palanco, drainware" 20 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 21 | categories = {"default", "discovery", "safe"} 22 | dependencies = {"snmp-brute"} 23 | 24 | 25 | portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) 26 | 27 | 28 | function process_answer( tbl ) 29 | 30 | local new_tab = {} 31 | 32 | for _, v in ipairs( tbl ) do 33 | if string.find (v.value, "SCALANCE") then 34 | version = v.value:gsub("SCALANCE", "VERSION:") 35 | model = version:match("%W+ %s*(.-)%d%d%d") 36 | if model == "W" then 37 | version = version .. " (wireless device)" 38 | elseif model == "X" then 39 | version = version .. " (network switch)" 40 | elseif model == "S" then 41 | version = version .. " (firewall)" 42 | end 43 | else 44 | return nil 45 | end 46 | table.insert( new_tab, version) 47 | end 48 | 49 | table.sort( new_tab ) 50 | 51 | return new_tab 52 | 53 | end 54 | 55 | action = function(host, port) 56 | 57 | local socket = nmap.new_socket() 58 | local catch = function() socket:close() end 59 | local try = nmap.new_try(catch) 60 | local snmpoid = "1.3.6.1.2.1.1.1" 61 | local services = {} 62 | local status 63 | 64 | socket:set_timeout(5000) 65 | try(socket:connect(host, port)) 66 | 67 | status, services = snmp.snmpWalk( socket, snmpoid ) 68 | socket:close() 69 | 70 | 71 | if ( not(status) ) or ( services == nil ) or ( #services == 0 ) then 72 | return 73 | end 74 | 75 | services = process_answer(services) 76 | 77 | if services == nil then 78 | return 79 | end 80 | 81 | nmap.set_port_state(host, port, "open") 82 | 83 | return stdnse.format_output( true, services ) 84 | end 85 | -------------------------------------------------------------------------------- /Siemens/Siemens-WINCC.nse: -------------------------------------------------------------------------------- 1 | local datafiles = require "datafiles" 2 | local netbios = require "netbios" 3 | local nmap = require "nmap" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | description = [[ 9 | Checks for SCADA Siemens WINCC server. 10 | 11 | The higher the verbosity or debug level, the more disallowed entries are shown. 12 | ]] 13 | 14 | --- 15 | -- @usage 16 | -- sudo nmap -sU --script Siemens-WINCC.nse -p137 17 | -- 18 | -- @output 19 | -- Host script results: 20 | -- | Siemens-WINCC: 21 | -- |_ Detected Siemens WINCC_SRV 22 | 23 | 24 | author = "Jose Ramon Palanco, drainware" 25 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 26 | 27 | -- Current version of this script was based entirly on Implementing CIFS, by 28 | -- Christopher R. Hertel. 29 | categories = {"default", "discovery", "safe"} 30 | 31 | 32 | hostrule = function(host) 33 | 34 | -- The following is an attempt to only run this script against hosts 35 | -- that will probably respond to a UDP 137 probe. One might argue 36 | -- that sending a single UDP packet and waiting for a response is no 37 | -- big deal and that it should be done for every host. In that case 38 | -- simply change this rule to always return true. 39 | 40 | local port_t135 = nmap.get_port_state(host, 41 | {number=135, protocol="tcp"}) 42 | local port_t139 = nmap.get_port_state(host, 43 | {number=139, protocol="tcp"}) 44 | local port_t445 = nmap.get_port_state(host, 45 | {number=445, protocol="tcp"}) 46 | local port_u137 = nmap.get_port_state(host, 47 | {number=137, protocol="udp"}) 48 | 49 | return (port_t135 ~= nil and port_t135.state == "open") or 50 | (port_t139 ~= nil and port_t139.state == "open") or 51 | (port_t445 ~= nil and port_t445.state == "open") or 52 | (port_u137 ~= nil and 53 | (port_u137.state == "open" or 54 | port_u137.state == "open|filtered")) 55 | end 56 | 57 | 58 | action = function(host) 59 | 60 | local i 61 | local status 62 | local names, statistics 63 | local server_name 64 | local mac, prefix, manuf 65 | local response = {} 66 | local catch = function() return end 67 | local try = nmap.new_try(catch) 68 | 69 | 70 | -- Get the list of NetBIOS names 71 | status, names, statistics = netbios.do_nbstat(host) 72 | status, names, statistics = netbios.do_nbstat(host) 73 | status, names, statistics = netbios.do_nbstat(host) 74 | status, names, statistics = netbios.do_nbstat(host) 75 | if(status == false) then 76 | return stdnse.format_output(false, names) 77 | end 78 | 79 | -- Get the server name 80 | status, server_name = netbios.get_server_name(host, names) 81 | if(status == false) then 82 | return stdnse.format_output(false, server_name) 83 | end 84 | 85 | local step1, step2, step3, step4, step5 = nil 86 | 87 | for i = 1, #names, 1 do 88 | local padding = string.rep(" ", 17 - #names[i]['name']) 89 | local flags_str = netbios.flags_to_string(names[i]['flags']) 90 | 91 | 92 | 93 | if string.find(names[i]['name'], "WINCC_SRV") then 94 | if names[i]['suffix'] == 0x0 then 95 | step1 = true 96 | elseif names[i]['suffix'] == 0x20 then 97 | step2 = true 98 | end 99 | end 100 | 101 | if names[i]['name'] == "SIEMENS" then 102 | if names[i]['suffix'] == 0x0 then 103 | step3 = true 104 | elseif names[i]['suffix'] == 0x1e then 105 | step4 = true 106 | elseif names[i]['suffix'] == 0x1d then 107 | step5 = true 108 | end 109 | end 110 | 111 | end 112 | 113 | if step1 and step2 and step3 and step4 and step5 then 114 | info = string.format("Detected Siemens %s", server_name) 115 | table.insert(response, info) 116 | end 117 | 118 | 119 | return stdnse.format_output(true, response) 120 | 121 | 122 | end -------------------------------------------------------------------------------- /all/BACnet-discover-enumerate.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | local unicode = require "unicode" 8 | local ipOps = require "ipOps" 9 | 10 | description = [[ 11 | Discovers and enumerates BACNet Devices collects device information based off 12 | standard requests. In some cases, devices may not strictly follow the 13 | specifications, or may comply with older versions of the specifications, and 14 | will result in a BACNET error response. Presence of this error positively 15 | identifies the device as a BACNet device, but no enumeration is possible. 16 | 17 | This Nmap Script will also attempt to enumerate the BBMD (BACnet Broadcast 18 | Management Device). This allows a device on one network to communicate with a 19 | device on another network by using the BBMD to forward and route the messages. 20 | Also the NSE will attempt to pull the FDT (Foreign-Device-Table), as well as the 21 | (TTL) Time To Live, and time-out until the device willbe removed from the foreign 22 | device table. To utilize this feature, run with --script-args full=yes. 23 | 24 | This process was submitted via Jeff Meden via the original 25 | BACnet-discover-enumerate.nse script on github, it was determined to create a 26 | new script with the submitted methods. 27 | 28 | Note: Requests and responses are via UDP 47808, ensure scanner will receive UDP 29 | 47808 source and destination responses. 30 | 31 | http://digitalbond.com 32 | 33 | ]] 34 | 35 | --- 36 | -- @usage 37 | -- nmap --script BACnet-discover-enumerate.nse -sU -p 47808 38 | -- 39 | -- @args full If set yes the script will run the FDT and BBMD Checks 40 | -- 41 | -- @output 42 | --47808/udp open BACNet -- Building Automation and Control Networks 43 | --| BACnet-discover-enumerate.nse: 44 | --| Vendor ID: BACnet Stack at SourceForge (260) 45 | --| Instance Number: 260001 46 | --| Firmware: 0.8.2 47 | --| Application Software: 1.0 48 | --| Object Name: SimpleServer 49 | --| Model Name: GNU 50 | --| Description: server 51 | --| Location: USA 52 | --| BACnet Broadcast Management Device (BBMD): 53 | --| 192.168.0.100:47808 54 | --| Foreign Device Table (FDT): 55 | --|_ 192.168.1.101:47809:ttl=60:timeout=37 56 | 57 | -- 58 | -- @xmloutput 59 | --BACnet Stack at SourceForge (260) 60 | --260001 61 | --0.8.2 62 | --1.0 63 | --SimpleServer 64 | --GNU 65 | --server 66 | --USA 67 | --192.168.0.100:47808 68 | --192.168.1.101:47809:ttl=60:timeout=37 69 | 70 | author = "Stephen Hilt(Digital Bond)" 71 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 72 | categories = {"discovery", "intrusive"} 73 | 74 | -- 75 | -- Function to define the portrule as per nmap standards 76 | -- 77 | -- 78 | -- 79 | 80 | portrule = shortport.port_or_service(47808, "bacnet", "udp") 81 | 82 | --- 83 | -- Function to determine if a string starts with the parameter that is passed in 84 | -- 85 | -- First argument is the string to be evaluated, the second argument is 86 | -- the character(s) to be tested if the string starts with this argument. Uses Lua 87 | -- string.sub and string.len 88 | -- @param String String to be passed in. 89 | -- @param Start The char you want to test to see the string starts with. 90 | function string.starts(String,Start) 91 | return string.sub(String,1,string.len(Start))==Start 92 | end 93 | 94 | --- 95 | -- Table to look up the Vendor Name based on Vendor ID 96 | -- Table data from http://www.bacnet.org/VendorID/BACnet%20Vendor%20IDs.htm 97 | -- Fetched on 3/18/2014 98 | -- 99 | -- @key vennum Vendor number parsed out of the BACNet packet 100 | local vendor_id = { 101 | [0] = "ASHRAE", 102 | [1] = "NIST", 103 | [2] = "The Trane Company", 104 | [3] = "McQuay International", 105 | [4] = "PolarSoft", 106 | [5] = "Johnson Controls Inc.", 107 | [6] = "American Auto-Matrix", 108 | [7] = "Siemens Schweiz AG (Formerly: Landis & Staefa Division Europe)", 109 | [8] = "Delta Controls", 110 | [9] = "Siemens Schweiz AG", 111 | [10] = "Schneider Electric", 112 | [11] = "TAC", 113 | [12] = "Orion Analysis Corporation", 114 | [13] = "Teletrol Systems Inc.", 115 | [14] = "Cimetrics Technology", 116 | [15] = "Cornell University", 117 | [16] = "United Technologies Carrier", 118 | [17] = "Honeywell Inc.", 119 | [18] = "Alerton / Honeywell", 120 | [19] = "TAC AB", 121 | [20] = "Hewlett-Packard Company", 122 | [21] = "Dorsette.s Inc.", 123 | [22] = "Siemens Schweiz AG (Formerly: Cerberus AG)", 124 | [23] = "York Controls Group", 125 | [24] = "Automated Logic Corporation", 126 | [25] = "CSI Control Systems International", 127 | [26] = "Phoenix Controls Corporation", 128 | [27] = "Innovex Technologies Inc.", 129 | [28] = "KMC Controls Inc.", 130 | [29] = "Xn Technologies Inc.", 131 | [30] = "Hyundai Information Technology Co. Ltd.", 132 | [31] = "Tokimec Inc.", 133 | [32] = "Simplex", 134 | [33] = "North Building Technologies Limited", 135 | [34] = "Notifier", 136 | [35] = "Reliable Controls Corporation", 137 | [36] = "Tridium Inc.", 138 | [37] = "Sierra Monitor Corporation/FieldServer Technologies", 139 | [38] = "Silicon Energy", 140 | [39] = "Kieback & Peter GmbH & Co KG", 141 | [40] = "Anacon Systems Inc.", 142 | [41] = "Systems Controls & Instruments LLC", 143 | [42] = "Lithonia Lighting", 144 | [43] = "Micropower Manufacturing", 145 | [44] = "Matrix Controls", 146 | [45] = "METALAIRE", 147 | [46] = "ESS Engineering", 148 | [47] = "Sphere Systems Pty Ltd.", 149 | [48] = "Walker Technologies Corporation", 150 | [49] = "H I Solutions Inc.", 151 | [50] = "MBS GmbH", 152 | [51] = "SAMSON AG", 153 | [52] = "Badger Meter Inc.", 154 | [53] = "DAIKIN Industries Ltd.", 155 | [54] = "NARA Controls Inc.", 156 | [55] = "Mammoth Inc.", 157 | [56] = "Liebert Corporation", 158 | [57] = "SEMCO Incorporated", 159 | [58] = "Air Monitor Corporation", 160 | [59] = "TRIATEK LLC", 161 | [60] = "NexLight", 162 | [61] = "Multistack", 163 | [62] = "TSI Incorporated", 164 | [63] = "Weather-Rite Inc.", 165 | [64] = "Dunham-Bush", 166 | [65] = "Reliance Electric", 167 | [66] = "LCS Inc.", 168 | [67] = "Regulator Australia PTY Ltd.", 169 | [68] = "Touch-Plate Lighting Controls", 170 | [69] = "Amann GmbH", 171 | [70] = "RLE Technologies", 172 | [71] = "Cardkey Systems", 173 | [72] = "SECOM Co. Ltd.", 174 | [73] = "ABB Gebäetechnik AG Bereich NetServ", 175 | [74] = "KNX Association cvba", 176 | [75] = "Institute of Electrical Installation Engineers of Japan (IEIEJ)", 177 | [76] = "Nohmi Bosai Ltd.", 178 | [77] = "Carel S.p.A.", 179 | [78] = "AirSense Technology Inc.", 180 | [79] = "Hochiki Corporation", 181 | [80] = "Fr. Sauter AG", 182 | [81] = "Matsushita Electric Works Ltd.", 183 | [82] = "Mitsubishi Electric Corporation Inazawa Works", 184 | [83] = "Mitsubishi Heavy Industries Ltd.", 185 | [84] = "ITT Bell & Gossett", 186 | [85] = "Yamatake Building Systems Co. Ltd.", 187 | [86] = "The Watt Stopper Inc.", 188 | [87] = "Aichi Tokei Denki Co. Ltd.", 189 | [88] = "Activation Technologies LLC", 190 | [89] = "Saia-Burgess Controls Ltd.", 191 | [90] = "Hitachi Ltd.", 192 | [91] = "Novar Corp./Trend Control Systems Ltd.", 193 | [92] = "Mitsubishi Electric Lighting Corporation", 194 | [93] = "Argus Control Systems Ltd.", 195 | [94] = "Kyuki Corporation", 196 | [95] = "Richards-Zeta Building Intelligence Inc.", 197 | [96] = "Scientech R&D Inc.", 198 | [97] = "VCI Controls Inc.", 199 | [98] = "Toshiba Corporation", 200 | [99] = "Mitsubishi Electric Corporation Air Conditioning & Refrigeration Systems Works", 201 | [100] = "Custom Mechanical Equipment LLC", 202 | [101] = "ClimateMaster", 203 | [102] = "ICP Panel-Tec Inc.", 204 | [103] = "D-Tek Controls", 205 | [104] = "NEC Engineering Ltd.", 206 | [105] = "PRIVA BV", 207 | [106] = "Meidensha Corporation", 208 | [107] = "JCI Systems Integration Services", 209 | [108] = "Freedom Corporation", 210 | [109] = "Neuberger Gebäeautomation GmbH", 211 | [110] = "Sitronix", 212 | [111] = "Leviton Manufacturing", 213 | [112] = "Fujitsu Limited", 214 | [113] = "Emerson Network Power", 215 | [114] = "S. A. Armstrong Ltd.", 216 | [115] = "Visonet AG", 217 | [116] = "M&M Systems Inc.", 218 | [117] = "Custom Software Engineering", 219 | [118] = "Nittan Company Limited", 220 | [119] = "Elutions Inc. (Wizcon Systems SAS)", 221 | [120] = "Pacom Systems Pty. Ltd.", 222 | [121] = "Unico Inc.", 223 | [122] = "Ebtron Inc.", 224 | [123] = "Scada Engine", 225 | [124] = "AC Technology Corporation", 226 | [125] = "Eagle Technology", 227 | [126] = "Data Aire Inc.", 228 | [127] = "ABB Inc.", 229 | [128] = "Transbit Sp. z o. o.", 230 | [129] = "Toshiba Carrier Corporation", 231 | [130] = "Shenzhen Junzhi Hi-Tech Co. Ltd.", 232 | [131] = "Tokai Soft", 233 | [132] = "Blue Ridge Technologies", 234 | [133] = "Veris Industries", 235 | [134] = "Centaurus Prime", 236 | [135] = "Sand Network Systems", 237 | [136] = "Regulvar Inc.", 238 | [137] = "AFDtek Division of Fastek International Inc.", 239 | [138] = "PowerCold Comfort Air Solutions Inc.", 240 | [139] = "I Controls", 241 | [140] = "Viconics Electronics Inc.", 242 | [141] = "Yaskawa America Inc.", 243 | [142] = "DEOS control systems GmbH", 244 | [143] = "Digitale Mess- und Steuersysteme AG", 245 | [144] = "Fujitsu General Limited", 246 | [145] = "Project Engineering S.r.l.", 247 | [146] = "Sanyo Electric Co. Ltd.", 248 | [147] = "Integrated Information Systems Inc.", 249 | [148] = "Temco Controls Ltd.", 250 | [149] = "Airtek International Inc.", 251 | [150] = "Advantech Corporation", 252 | [151] = "Titan Products Ltd.", 253 | [152] = "Regel Partners", 254 | [153] = "National Environmental Product", 255 | [154] = "Unitec Corporation", 256 | [155] = "Kanden Engineering Company", 257 | [156] = "Messner Gebäetechnik GmbH", 258 | [157] = "Integrated.CH", 259 | [158] = "Price Industries", 260 | [159] = "SE-Elektronic GmbH", 261 | [160] = "Rockwell Automation", 262 | [161] = "Enflex Corp.", 263 | [162] = "ASI Controls", 264 | [163] = "SysMik GmbH Dresden", 265 | [164] = "HSC Regelungstechnik GmbH", 266 | [165] = "Smart Temp Australia Pty. Ltd.", 267 | [166] = "Cooper Controls", 268 | [167] = "Duksan Mecasys Co. Ltd.", 269 | [168] = "Fuji IT Co. Ltd.", 270 | [169] = "Vacon Plc", 271 | [170] = "Leader Controls", 272 | [171] = "Cylon Controls Ltd.", 273 | [172] = "Compas", 274 | [173] = "Mitsubishi Electric Building Techno-Service Co. Ltd.", 275 | [174] = "Building Control Integrators", 276 | [175] = "ITG Worldwide (M) Sdn Bhd", 277 | [176] = "Lutron Electronics Co. Inc.", 278 | [178] = "LOYTEC Electronics GmbH", 279 | [179] = "ProLon", 280 | [180] = "Mega Controls Limited", 281 | [181] = "Micro Control Systems Inc.", 282 | [182] = "Kiyon Inc.", 283 | [183] = "Dust Networks", 284 | [184] = "Advanced Building Automation Systems", 285 | [185] = "Hermos AG", 286 | [186] = "CEZIM", 287 | [187] = "Softing", 288 | [188] = "Lynxspring", 289 | [189] = "Schneider Toshiba Inverter Europe", 290 | [190] = "Danfoss Drives A/S", 291 | [191] = "Eaton Corporation", 292 | [192] = "Matyca S.A.", 293 | [193] = "Botech AB", 294 | [194] = "Noveo Inc.", 295 | [195] = "AMEV", 296 | [196] = "Yokogawa Electric Corporation", 297 | [197] = "GFR Gesellschaft füelungstechnik", 298 | [198] = "Exact Logic", 299 | [199] = "Mass Electronics Pty Ltd dba Innotech Control Systems Australia", 300 | [200] = "Kandenko Co. Ltd.", 301 | [201] = "DTF Daten-Technik Fries", 302 | [202] = "Klimasoft Ltd.", 303 | [203] = "Toshiba Schneider Inverter Corporation", 304 | [204] = "Control Applications Ltd.", 305 | [205] = "KDT Systems Co. Ltd.", 306 | [206] = "Onicon Incorporated", 307 | [207] = "Automation Displays Inc.", 308 | [208] = "Control Solutions Inc.", 309 | [209] = "Remsdaq Limited", 310 | [210] = "NTT Facilities Inc.", 311 | [211] = "VIPA GmbH", 312 | [212] = "TSC21 Association of Japan", 313 | [213] = "Strato Automation", 314 | [214] = "HRW Limited", 315 | [215] = "Lighting Control & Design Inc.", 316 | [216] = "Mercy Electronic and Electrical Industries", 317 | [217] = "Samsung SDS Co.Ltd", 318 | [218] = "Impact Facility Solutions Inc.", 319 | [219] = "Aircuity", 320 | [220] = "Control Techniques Ltd.", 321 | [221] = "OpenGeneral Pty. Ltd.", 322 | [222] = "WAGO Kontakttechnik GmbH & Co. KG", 323 | [223] = "Cerus Industrial", 324 | [224] = "Chloride Power Protection Company", 325 | [225] = "Computrols Inc.", 326 | [226] = "Phoenix Contact GmbH & Co. KG", 327 | [227] = "Grundfos Management A/S", 328 | [228] = "Ridder Drive Systems", 329 | [229] = "Soft Device SDN BHD", 330 | [230] = "Integrated Control Technology Limited", 331 | [231] = "AIRxpert Systems Inc.", 332 | [232] = "Microtrol Limited", 333 | [233] = "Red Lion Controls", 334 | [234] = "Digital Electronics Corporation", 335 | [235] = "Ennovatis GmbH", 336 | [236] = "Serotonin Software Technologies Inc.", 337 | [237] = "LS Industrial Systems Co. Ltd.", 338 | [238] = "Square D Company", 339 | [239] = "S Squared Innovations Inc.", 340 | [240] = "Aricent Ltd.", 341 | [241] = "EtherMetrics LLC", 342 | [242] = "Industrial Control Communications Inc.", 343 | [243] = "Paragon Controls Inc.", 344 | [244] = "A. O. Smith Corporation", 345 | [245] = "Contemporary Control Systems Inc.", 346 | [246] = "Intesis Software SL", 347 | [247] = "Ingenieurgesellschaft N. Hartleb mbH", 348 | [248] = "Heat-Timer Corporation", 349 | [249] = "Ingrasys Technology Inc.", 350 | [250] = "Costerm Building Automation", 351 | [251] = "WILO SE", 352 | [252] = "Embedia Technologies Corp.", 353 | [253] = "Technilog", 354 | [254] = "HR Controls Ltd. & Co. KG", 355 | [255] = "Lennox International Inc.", 356 | [256] = "RK-Tec Rauchklappen-Steuerungssysteme GmbH & Co. KG", 357 | [257] = "Thermomax Ltd.", 358 | [258] = "ELCON Electronic Control Ltd.", 359 | [259] = "Larmia Control AB", 360 | [260] = "BACnet Stack at SourceForge", 361 | [261] = "G4S Security Services A/S", 362 | [262] = "Exor International S.p.A.", 363 | [263] = "Cristal Controles", 364 | [264] = "Regin AB", 365 | [265] = "Dimension Software Inc.", 366 | [266] = "SynapSense Corporation", 367 | [267] = "Beijing Nantree Electronic Co. Ltd.", 368 | [268] = "Camus Hydronics Ltd.", 369 | [269] = "Kawasaki Heavy Industries Ltd.", 370 | [270] = "Critical Environment Technologies", 371 | [271] = "ILSHIN IBS Co. Ltd.", 372 | [272] = "ELESTA Energy Control AG", 373 | [273] = "KROPMAN Installatietechniek", 374 | [274] = "Baldor Electric Company", 375 | [275] = "INGA mbH", 376 | [276] = "GE Consumer & Industrial", 377 | [277] = "Functional Devices Inc.", 378 | [278] = "ESAC", 379 | [279] = "M-System Co. Ltd.", 380 | [280] = "Yokota Co. Ltd.", 381 | [281] = "Hitranse Technology Co.LTD", 382 | [282] = "Federspiel Controls", 383 | [283] = "Kele Inc.", 384 | [284] = "Opera Electronics Inc.", 385 | [285] = "Gentec", 386 | [286] = "Embedded Science Labs LLC", 387 | [287] = "Parker Hannifin Corporation", 388 | [288] = "MaCaPS International Limited", 389 | [289] = "Link4 Corporation", 390 | [290] = "Romutec Steuer-u. Regelsysteme GmbH", 391 | [291] = "Pribusin Inc.", 392 | [292] = "Advantage Controls", 393 | [293] = "Critical Room Control", 394 | [294] = "LEGRAND", 395 | [295] = "Tongdy Control Technology Co. Ltd.", 396 | [296] = "ISSARO Integrierte Systemtechnik", 397 | [297] = "Pro-Dev Industries", 398 | [298] = "DRI-STEEM", 399 | [299] = "Creative Electronic GmbH", 400 | [300] = "Swegon AB", 401 | [301] = "Jan Brachacek", 402 | [302] = "Hitachi Appliances Inc.", 403 | [303] = "Real Time Automation Inc.", 404 | [304] = "ITEC Hankyu-Hanshin Co.", 405 | [305] = "Cyrus E&M Engineering Co. Ltd.", 406 | [306] = "Racine Federated Inc.", 407 | [307] = "Cirrascale Corporation", 408 | [308] = "Elesta GmbH Building Automation", 409 | [309] = "Securiton", 410 | [310] = "OSlsoft Inc.", 411 | [311] = "Hanazeder Electronic GmbH", 412 | [312] = "Honeywell Security DeutschlandNovar GmbH", 413 | [313] = "Siemens Energy & Automation Inc.", 414 | [314] = "ETM Professional Control GmbH", 415 | [315] = "Meitav-tec Ltd.", 416 | [316] = "Janitza Electronics GmbH", 417 | [317] = "MKS Nordhausen", 418 | [318] = "De Gier Drive Systems B.V.", 419 | [319] = "Cypress Envirosystems", 420 | [320] = "SMARTron s.r.o.", 421 | [321] = "Verari Systems Inc.", 422 | [322] = "K-W Electronic Service Inc.", 423 | [323] = "ALFA-SMART Energy Management", 424 | [324] = "Telkonet Inc.", 425 | [325] = "Securiton GmbH", 426 | [326] = "Cemtrex Inc.", 427 | [327] = "Performance Technologies Inc.", 428 | [328] = "Xtralis (Aust) Pty Ltd", 429 | [329] = "TROX GmbH", 430 | [330] = "Beijing Hysine Technology Co.Ltd", 431 | [331] = "RCK Controls Inc.", 432 | [332] = "Distech Controls SAS", 433 | [333] = "Novar/Honeywell", 434 | [334] = "The S4 Group Inc.", 435 | [335] = "Schneider Electric", 436 | [336] = "LHA Systems", 437 | [337] = "GHM engineering Group Inc.", 438 | [338] = "Cllimalux S.A.", 439 | [339] = "VAISALA Oyj", 440 | [340] = "COMPLEX (Beijing) TechnologyCo. Ltd.", 441 | [341] = "SCADAmetrics", 442 | [342] = "POWERPEG NSI Limited", 443 | [343] = "BACnet Interoperability Testing Services Inc.", 444 | [344] = "Teco a.s.", 445 | [345] = "Plexus Technology Inc.", 446 | [346] = "Energy Focus Inc.", 447 | [347] = "Powersmiths International Corp.", 448 | [348] = "Nichibei Co. Ltd.", 449 | [349] = "HKC Technology Ltd.", 450 | [350] = "Ovation Networks Inc.", 451 | [351] = "Setra Systems", 452 | [352] = "AVG Automation", 453 | [353] = "ZXC Ltd.", 454 | [354] = "Byte Sphere", 455 | [355] = "Generiton Co. Ltd.", 456 | [356] = "Holter Regelarmaturen GmbH & Co. KG", 457 | [357] = "Bedford Instruments LLC", 458 | [358] = "Standair Inc.", 459 | [359] = "WEG Automation - R&D", 460 | [360] = "Prolon Control Systems ApS", 461 | [361] = "Inneasoft", 462 | [362] = "ConneXSoft GmbH", 463 | [363] = "CEAG Notlichtsysteme GmbH", 464 | [364] = "Distech Controls Inc.", 465 | [365] = "Industrial Technology Research Institute", 466 | [366] = "ICONICS Inc.", 467 | [367] = "IQ Controls s.c.", 468 | [368] = "OJ Electronics A/S", 469 | [369] = "Rolbit Ltd.", 470 | [370] = "Synapsys Solutions Ltd.", 471 | [371] = "ACME Engineering Prod. Ltd.", 472 | [372] = "Zener Electric Pty Ltd.", 473 | [373] = "Selectronix Inc.", 474 | [374] = "Gorbet & Banerjee LLC.", 475 | [375] = "IME", 476 | [376] = "Stephen H. Dawson Computer Service", 477 | [377] = "Accutrol LLC", 478 | [378] = "Schneider Elektronik GmbH", 479 | [379] = "Alpha-Inno Tec GmbH", 480 | [380] = "ADMMicro Inc.", 481 | [381] = "Greystone Energy Systems Inc.", 482 | [382] = "CAP Technologie", 483 | [383] = "KeRo Systems", 484 | [384] = "Domat Control System s.r.o.", 485 | [385] = "Efektronics Pty. Ltd.", 486 | [386] = "Hekatron Vertriebs GmbH", 487 | [387] = "Securiton AG", 488 | [388] = "Carlo Gavazzi Controls SpA", 489 | [389] = "Chipkin Automation Systems", 490 | [390] = "Savant Systems LLC", 491 | [391] = "Simmtronic Lighting Controls", 492 | [392] = "Abelko Innovation AB", 493 | [393] = "Seresco Technologies Inc.", 494 | [394] = "IT Watchdogs", 495 | [395] = "Automation Assist Japan Corp.", 496 | [396] = "Thermokon Sensortechnik GmbH", 497 | [397] = "EGauge Systems LLC", 498 | [398] = "Quantum Automation (ASIA) PTE Ltd.", 499 | [399] = "Toshiba Lighting & Technology Corp.", 500 | [400] = "SPIN Engenharia de Automaç Ltda.", 501 | [401] = "Logistics Systems & Software Services India PVT. Ltd.", 502 | [402] = "Delta Controls Integration Products", 503 | [403] = "Focus Media", 504 | [404] = "LUMEnergi Inc.", 505 | [405] = "Kara Systems", 506 | [406] = "RF Code Inc.", 507 | [407] = "Fatek Automation Corp.", 508 | [408] = "JANDA Software Company LLC", 509 | [409] = "Open System Solutions Limited", 510 | [410] = "Intelec Systems PTY Ltd.", 511 | [411] = "Ecolodgix LLC", 512 | [412] = "Douglas Lighting Controls", 513 | [413] = "iSAtech GmbH", 514 | [414] = "AREAL", 515 | [415] = "Beckhoff Automation GmbH", 516 | [416] = "IPAS GmbH", 517 | [417] = "KE2 Therm Solutions", 518 | [418] = "Base2Products", 519 | [419] = "DTL Controls LLC", 520 | [420] = "INNCOM International Inc.", 521 | [421] = "BTR Netcom GmbH", 522 | [422] = "Greentrol AutomationInc", 523 | [423] = "BELIMO Automation AG", 524 | [424] = "Samsung Heavy Industries CoLtd", 525 | [425] = "Triacta Power Technologies Inc.", 526 | [426] = "Globestar Systems", 527 | [427] = "MLB Advanced MediaLP", 528 | [428] = "SWG Stuckmann Wirtschaftliche Gebäesysteme GmbH", 529 | [429] = "SensorSwitch", 530 | [430] = "Multitek Power Limited", 531 | [431] = "Aquametro AG", 532 | [432] = "LG Electronics Inc.", 533 | [433] = "Electronic Theatre Controls Inc.", 534 | [434] = "Mitsubishi Electric Corporation Nagoya Works", 535 | [435] = "Delta Electronics Inc.", 536 | [436] = "Elma Kurtalj Ltd.", 537 | [437] = "ADT Fire and Security Sp. A.o.o.", 538 | [438] = "Nedap Security Management", 539 | [439] = "ESC Automation Inc.", 540 | [440] = "DSP4YOU Ltd.", 541 | [441] = "GE Sensing and Inspection Technologies", 542 | [442] = "Embedded Systems SIA", 543 | [443] = "BEFEGA GmbH", 544 | [444] = "Baseline Inc.", 545 | [445] = "M2M Systems Integrators", 546 | [446] = "OEMCtrl", 547 | [447] = "Clarkson Controls Limited", 548 | [448] = "Rogerwell Control System Limited", 549 | [449] = "SCL Elements", 550 | [450] = "Hitachi Ltd.", 551 | [451] = "Newron System SA", 552 | [452] = "BEVECO Gebouwautomatisering BV", 553 | [453] = "Streamside Solutions", 554 | [454] = "Yellowstone Soft", 555 | [455] = "Oztech Intelligent Systems Pty Ltd.", 556 | [456] = "Novelan GmbH", 557 | [457] = "Flexim Americas Corporation", 558 | [458] = "ICP DAS Co. Ltd.", 559 | [459] = "CARMA Industries Inc.", 560 | [460] = "Log-One Ltd.", 561 | [461] = "TECO Electric & Machinery Co. Ltd.", 562 | [462] = "ConnectEx Inc.", 563 | [463] = "Turbo DDC Sü", 564 | [464] = "Quatrosense Environmental Ltd.", 565 | [465] = "Fifth Light Technology Ltd.", 566 | [466] = "Scientific Solutions Ltd.", 567 | [467] = "Controller Area Network Solutions (M) Sdn Bhd", 568 | [468] = "RESOL - Elektronische Regelungen GmbH", 569 | [469] = "RPBUS LLC", 570 | [470] = "BRS Sistemas Eletronicos", 571 | [471] = "WindowMaster A/S", 572 | [472] = "Sunlux Technologies Ltd.", 573 | [473] = "Measurlogic", 574 | [474] = "Frimat GmbH", 575 | [475] = "Spirax Sarco", 576 | [476] = "Luxtron", 577 | [477] = "Raypak Inc", 578 | [478] = "Air Monitor Corporation", 579 | [479] = "Regler Och Webbteknik Sverige (ROWS)", 580 | [480] = "Intelligent Lighting Controls Inc.", 581 | [481] = "Sanyo Electric Industry Co.Ltd", 582 | [482] = "E-Mon Energy Monitoring Products", 583 | [483] = "Digital Control Systems", 584 | [484] = "ATI Airtest Technologies Inc.", 585 | [485] = "SCS SA", 586 | [486] = "HMS Industrial Networks AB", 587 | [487] = "Shenzhen Universal Intellisys Co Ltd", 588 | [488] = "EK Intellisys Sdn Bhd", 589 | [489] = "SysCom", 590 | [490] = "Firecom Inc.", 591 | [491] = "ESA Elektroschaltanlagen Grimma GmbH", 592 | [492] = "Kumahira Co Ltd", 593 | [493] = "Hotraco", 594 | [494] = "SABO Elektronik GmbH", 595 | [495] = "Equip'Trans", 596 | [496] = "TCS Basys Controls", 597 | [497] = "FlowCon International A/S", 598 | [498] = "ThyssenKrupp Elevator Americas", 599 | [499] = "Abatement Technologies", 600 | [500] = "Continental Control Systems LLC", 601 | [501] = "WISAG Automatisierungstechnik GmbH & Co KG", 602 | [502] = "EasyIO", 603 | [503] = "EAP-Electric GmbH", 604 | [504] = "Hardmeier", 605 | [505] = "Mircom Group of Companies", 606 | [506] = "Quest Controls", 607 | [507] = "MestekInc", 608 | [508] = "Pulse Energy", 609 | [509] = "Tachikawa Corporation", 610 | [510] = "University of Nebraska-Lincoln", 611 | [511] = "Redwood Systems", 612 | [512] = "PASStec Industrie-Elektronik GmbH", 613 | [513] = "NgEK Inc.", 614 | [514] = "FAW Electronics Ltd", 615 | [515] = "Jireh Energy Tech Co. Ltd.", 616 | [516] = "Enlighted Inc.", 617 | [517] = "El-Piast Sp. Z o.o", 618 | [518] = "NetxAutomation Software GmbH", 619 | [519] = "Invertek Drives", 620 | [520] = "Deutschmann Automation GmbH & Co. KG", 621 | [521] = "EMU Electronic AG", 622 | [522] = "Phaedrus Limited", 623 | [523] = "Sigmatek GmbH & Co KG", 624 | [524] = "Marlin Controls", 625 | [525] = "CircutorSA", 626 | [526] = "UTC Fire & Security", 627 | [527] = "DENT Instruments Inc.", 628 | [528] = "FHP Manufacturing Company - Bosch Group", 629 | [529] = "GE Intelligent Platforms", 630 | [530] = "Inner Range Pty Ltd", 631 | [531] = "GLAS Energy Technology", 632 | [532] = "MSR-Electronic-GmbH", 633 | [533] = "Energy Control Systems Inc.", 634 | [534] = "EMT Controls", 635 | [535] = "Daintree Networks Inc.", 636 | [536] = "EURO ICC d.o.o", 637 | [537] = "TE Connectivity Energy", 638 | [538] = "GEZE GmbH", 639 | [539] = "NEC Corporation", 640 | [540] = "Ho Cheung International Company Limited", 641 | [541] = "Sharp Manufacturing Systems Corporation", 642 | [542] = "DOT CONTROLS a.s.", 643 | [543] = "BeaconMedæ0220", 644 | [544] = "Midea Commercial Aircon", 645 | [545] = "WattMaster Controls", 646 | [546] = "Kamstrup A/S", 647 | [547] = "CA Computer Automation GmbH", 648 | [548] = "Laars Heating Systems Company", 649 | [549] = "Hitachi Systems Ltd.", 650 | [550] = "Fushan AKE Electronic Engineering Co. Ltd.", 651 | [551] = "Toshiba International Corporation", 652 | [552] = "Starman Systems LLC", 653 | [553] = "Samsung Techwin Co. Ltd.", 654 | [554] = "ISAS-Integrated Switchgear and Systems P/L", 655 | [556] = "Obvius", 656 | [557] = "Marek Guzik", 657 | [558] = "Vortek Instruments LLC", 658 | [559] = "Universal Lighting Technologies", 659 | [560] = "Myers Power Products Inc.", 660 | [561] = "Vector Controls GmbH", 661 | [562] = "Crestron Electronics Inc.", 662 | [563] = "A&E Controls Limited", 663 | [564] = "Projektomontaza A.D.", 664 | [565] = "Freeaire Refrigeration", 665 | [566] = "Aqua Cooler Pty Limited", 666 | [567] = "Basic Controls", 667 | [568] = "GE Measurement and Control Solutions Advanced Sensors", 668 | [569] = "EQUAL Networks", 669 | [570] = "Millennial Net", 670 | [571] = "APLI Ltd", 671 | [572] = "Electro Industries/GaugeTech", 672 | [573] = "SangMyung University", 673 | [574] = "Coppertree Analytics Inc.", 674 | [575] = "CoreNetiX GmbH", 675 | [576] = "Acutherm", 676 | [577] = "Dr. Riedel Automatisierungstechnik GmbH", 677 | [578] = "Shina System Co.Ltd", 678 | [579] = "Iqapertus", 679 | [580] = "PSE Technology", 680 | [581] = "BA Systems", 681 | [582] = "BTICINO", 682 | [583] = "Monico Inc.", 683 | [584] = "iCue", 684 | [585] = "tekmar Control Systems Ltd.", 685 | [586] = "Control Technology Corporation", 686 | [587] = "GFAE GmbH", 687 | [588] = "BeKa Software GmbH", 688 | [589] = "Isoil Industria SpA", 689 | [590] = "Home Systems Consulting SpA", 690 | [591] = "Socomec", 691 | [592] = "Everex Communications Inc.", 692 | [593] = "Ceiec Electric Technology", 693 | [594] = "Atrila GmbH", 694 | [595] = "WingTechs", 695 | [596] = "Shenzhen Mek Intellisys Pte Ltd.", 696 | [597] = "Nestfield Co. Ltd.", 697 | [598] = "Swissphone Telecom AG", 698 | [599] = "PNTECH JSC", 699 | [600] = "Horner APG LLC", 700 | [601] = "PVI Industries LLC", 701 | [602] = "Ela-compil", 702 | [603] = "Pegasus Automation International LLC", 703 | [604] = "Wight Electronic Services Ltd.", 704 | [605] = "Marcom", 705 | [606] = "Exhausto A/S", 706 | [607] = "Dwyer Instruments Inc.", 707 | [608] = "Link GmbH", 708 | [609] = "Oppermann Regelgerate GmbH", 709 | [610] = "NuAire Inc.", 710 | [611] = "Nortec Humidity Inc.", 711 | [612] = "Bigwood Systems Inc.", 712 | [613] = "Enbala Power Networks", 713 | [614] = "Inter Energy Co. Ltd.", 714 | [615] = "ETC", 715 | [616] = "COMELEC S.A.R.L", 716 | [617] = "Pythia Technologies", 717 | [618] = "TrendPoint Systems Inc.", 718 | [619] = "AWEX", 719 | [620] = "Eurevia", 720 | [621] = "Kongsberg E-lon AS", 721 | [622] = "FlaktWoods", 722 | [623] = "E + E Elektronik GES M.B.H.", 723 | [624] = "ARC Informatique", 724 | [625] = "SKIDATA AG", 725 | [626] = "WSW Solutions", 726 | [627] = "Trefon Electronic GmbH", 727 | [628] = "Dongseo System", 728 | [629] = "Kanontec Intelligence Technology Co. Ltd.", 729 | [630] = "EVCO S.p.A.", 730 | [631] = "Accuenergy (CANADA) Inc.", 731 | [632] = "SoftDEL", 732 | [633] = "Orion Energy Systems Inc.", 733 | [634] = "Roboticsware", 734 | [635] = "DOMIQ Sp. z o.o.", 735 | [636] = "Solidyne", 736 | [637] = "Elecsys Corporation", 737 | [638] = "Conditionaire International Pty. Limited", 738 | [639] = "Quebec Inc.", 739 | [640] = "Homerun Holdings", 740 | [641] = "RFM Inc.", 741 | [642] = "Comptek", 742 | [643] = "Westco Systems Inc.", 743 | [644] = "Advancis Software & Services GmbH", 744 | [645] = "Intergrid LLC", 745 | [646] = "Markerr Controls Inc.", 746 | [647] = "Toshiba Elevator and Building Systems Corporation", 747 | [648] = "Spectrum Controls Inc.", 748 | [649] = "Mkservice", 749 | [650] = "Fox Thermal Instruments", 750 | [651] = "SyxthSense Ltd", 751 | [652] = "DUHA System S R.O.", 752 | [653] = "NIBE", 753 | [654] = "Melink Corporation", 754 | [655] = "Fritz-Haber-Institut", 755 | [656] = "MTU Onsite Energy GmbHGas Power Systems", 756 | [657] = "Omega Engineering Inc.", 757 | [658] = "Avelon", 758 | [659] = "Ywire Technologies Inc.", 759 | [660] = "M.R. Engineering Co. Ltd.", 760 | [661] = "Lochinvar LLC", 761 | [662] = "Sontay Limited", 762 | [663] = "GRUPA Slawomir Chelminski", 763 | [664] = "Arch Meter Corporation", 764 | [665] = "Senva Inc.", 765 | [667] = "FM-Tec", 766 | [668] = "Systems Specialists Inc.", 767 | [669] = "SenseAir", 768 | [670] = "AB IndustrieTechnik Srl", 769 | [671] = "Cortland Research LLC", 770 | [672] = "MediaView", 771 | [673] = "VDA Elettronica", 772 | [674] = "CSS Inc.", 773 | [675] = "Tek-Air Systems Inc.", 774 | [676] = "ICDT", 775 | [677] = "The Armstrong Monitoring Corporation", 776 | [678] = "DIXELL S.r.l", 777 | [679] = "Lead System Inc.", 778 | [680] = "ISM EuroCenter S.A.", 779 | [681] = "TDIS", 780 | [682] = "Trade FIDES", 781 | [683] = "KnübH (Emerson Network Power)", 782 | [684] = "Resource Data Management", 783 | [685] = "Abies Technology Inc.", 784 | [686] = "Amalva", 785 | [687] = "MIRAE Electrical Mfg. Co. Ltd.", 786 | [688] = "HunterDouglas Architectural Projects Scandinavia ApS", 787 | [689] = "RUNPAQ Group Co.Ltd", 788 | [690] = "Unicard SA", 789 | [691] = "IE Technologies", 790 | [692] = "Ruskin Manufacturing", 791 | [693] = "Calon Associates Limited", 792 | [694] = "Contec Co. Ltd.", 793 | [695] = "iT GmbH", 794 | [696] = "Autani Corporation", 795 | [697] = "Christian Fortin", 796 | [698] = "HDL", 797 | [699] = "IPID Sp. Z.O.O Limited", 798 | [700] = "Fuji Electric Co.Ltd", 799 | [701] = "View Inc.", 800 | [702] = "Samsung S1 Corporation", 801 | [703] = "New Lift", 802 | [704] = "VRT Systems", 803 | [705] = "Motion Control Engineering Inc.", 804 | [706] = "Weiss Klimatechnik GmbH", 805 | [707] = "Elkon", 806 | [708] = "Eliwell Controls S.r.l.", 807 | [709] = "Japan Computer Technos Corp", 808 | [710] = "Rational Network ehf", 809 | [711] = "Magnum Energy Solutions LLC", 810 | [712] = "MelRok", 811 | [713] = "VAE Group", 812 | [714] = "LGCNS", 813 | [715] = "Berghof Automationstechnik GmbH", 814 | [716] = "Quark Communications Inc.", 815 | [717] = "Sontex", 816 | [718] = "mivune AG", 817 | [719] = "Panduit", 818 | [720] = "Smart Controls LLC", 819 | [721] = "Compu-Aire Inc.", 820 | [722] = "Sierra", 821 | [723] = "ProtoSense Technologies", 822 | [724] = "Eltrac Technologies Pvt Ltd", 823 | [725] = "Bektas Invisible Controls GmbH", 824 | [726] = "Entelec", 825 | [727] = "Innexiv", 826 | [728] = "Covenant" 827 | } 828 | --return vendor information 829 | function vendor_lookup(vennum) 830 | local vendorname = vendor_id[vennum] or "Unknown Vendor Number" 831 | return string.format("%s (%d)", vendorname, vennum) 832 | end 833 | 834 | --- 835 | -- Function to lookup the length of the Field to be used for Vendor ID, Firmware 836 | -- Object Name, Software Version, and Location. It will then return the Value 837 | -- that is stored inside the packet for this information as a String Value. 838 | -- The field is located in the 18th byte of the data field of a valid packet. 839 | -- Depending on this field the information will be stored in field 20 + length 840 | -- or in field 22 + length. 841 | -- 842 | -- @param packet The packet that was received and is ready to be parsed 843 | function field_size(packet) 844 | local info 845 | 846 | -- read the Length field from the packet data byte 18 847 | local offset 848 | -- Verify the field from byte 18 to determine if the vendor number is one byte or two bytes? 849 | local value = string.byte(packet, 18) 850 | if ( value % 0x10 < 5 ) then 851 | value = value % 0x10 - 1 852 | offset = 19 853 | else 854 | value = string.byte(packet, 19) - 1 855 | offset = 20 856 | end 857 | -- unpack a string of length 858 | offset, charset, info = bin.unpack("CA" .. tostring(value), packet, offset) 859 | -- return information that was found in the packet 860 | if charset == 0 then -- UTF-8 861 | return info 862 | elseif charset == 4 then -- UCS-2 big-endian 863 | return unicode.transcode(info, unicode.utf16_dec, unicode.utf8_enc, true, nil) 864 | else -- TODO: other encodings not supported by unicode.lua 865 | return info 866 | end 867 | end 868 | --- 869 | -- Function to set the nmap output for the host, if a valid BACNet packet 870 | -- is received then the output will show that the port is open instead of 871 | -- open|filtered 872 | -- 873 | -- @param host Host that was passed in via nmap 874 | -- @param port port that BACNet is running on (Default UDP/47808) 875 | function set_nmap(host, port) 876 | 877 | --set port Open 878 | port.state = "open" 879 | -- set version name to BACNet 880 | port.version.name = "BACNet -- Building Automation and Control Networks" 881 | nmap.set_port_version(host, port) 882 | nmap.set_port_state(host, port, "open") 883 | 884 | end 885 | 886 | --- 887 | -- Function to send a query to the discovered BACNet devices. This will pull extra 888 | -- information to help identify the device. Information such as firmware, application software 889 | -- object name, description, and location parameters configured inside of the device. 890 | -- 891 | -- @param socket The socket that was created in the action function 892 | -- @param type Type is the type of packet to send, this can be firmware, application, object, description, or location 893 | function standard_query(socket, type) 894 | 895 | 896 | -- set the query for vendor name 897 | local vendor_query = bin.pack("H","810a001101040005010c0c023FFFFF1979") 898 | -- set the firmware version query data for sending 899 | local firmware_query = bin.pack("H","810a001101040005010c0c023FFFFF192c") 900 | -- set the application version query data for sending 901 | local appsoft_query = bin.pack("H","810a001101040005010c0c023FFFFF190c") 902 | -- set the object name query data for sending 903 | local object_query = bin.pack("H","810a001101040005010c0c023FFFFF194d") 904 | -- set the model name query data for sending 905 | local model_query = bin.pack("H","810a001101040005010c0c023FFFFF1946") 906 | -- set the desc name query data for sending 907 | local desc_query = bin.pack("H","810a001101040005010c0c023FFFFF191c") 908 | -- set the location name query data for sending 909 | local location_query = bin.pack("H","810a001101040005010c0c023FFFFF193A") 910 | local query 911 | 912 | -- 913 | -- determine what type of packet to send 914 | if (type == "firmware") then 915 | query = firmware_query 916 | elseif (type == "application") then 917 | query = appsoft_query 918 | elseif (type == "model") then 919 | query = model_query 920 | elseif (type == "object") then 921 | query = object_query 922 | elseif (type == "description") then 923 | query = desc_query 924 | elseif (type == "location") then 925 | query = location_query 926 | elseif (type == "vendor") then 927 | query = vendor_query 928 | end 929 | 930 | --try to pull the information 931 | local status, result = socket:send(query) 932 | if(status == false) then 933 | stdnse.debug1("Socket error sending query: %s", result) 934 | return nil 935 | end 936 | -- receive packet from response 937 | local rcvstatus, response = socket:receive() 938 | if(rcvstatus == false) then 939 | stdnse.debug1("Socket error receiving: %s", response) 940 | return nil 941 | end 942 | -- validate valid BACNet Packet 943 | if( string.starts(response, "\x81")) then 944 | -- Lookup byte 7 (pakcet type) 945 | local pos, value = bin.unpack("C", response, 7) 946 | -- verify that the response packet was not an error packet 947 | if( value ~= 0x50) then 948 | --collect information by looping thru the packet 949 | return field_size(response) 950 | -- if it was an error packet, set the string to error for later purposes 951 | else 952 | stdnse.debug1("Error receiving: BACNet Error") 953 | return nil 954 | end 955 | -- else ERROR 956 | else 957 | stdnse.debug1("Error receiving Vendor ID: Invalid BACNet packet") 958 | return nil 959 | end 960 | 961 | end 962 | --- 963 | -- Function to send a query to the discovered BACNet devices. This function queries extra 964 | -- information to help identify the device. Vendor ID query is sent with this 965 | -- function and the Vendor ID number is parsed out of the packet. 966 | -- 967 | -- @param socket The socket that was created in the action function 968 | function vendornum_query(socket) 969 | 970 | -- set the vendor query data for sending 971 | local vendor_query = bin.pack("H","810a001101040005010c0c023FFFFF1978") 972 | 973 | 974 | --send the vendor information 975 | local status, result = socket:send(vendor_query) 976 | if(status == false) then 977 | stdnse.debug1("Socket error sending vendor query: %s", result) 978 | return nil 979 | end 980 | -- receive vendor information packet 981 | local rcvstatus, response = socket:receive() 982 | if(rcvstatus == false) then 983 | stdnse.debug1("Socket error receiving vendor query: %s", response) 984 | return nil 985 | end 986 | -- validate valid BACNet Packet 987 | if( string.starts(response, "\x81")) then 988 | local pos, value = bin.unpack("C", response, 7) 989 | --if the vendor query resulted in an error 990 | if( value ~= 0x50) then 991 | -- read values for byte 18 in the packet data 992 | -- this value determines if vendor number is 1 or 2 bytes 993 | pos, value = bin.unpack("C", response, 18) 994 | else 995 | stdnse.debug1("Error receiving Vendor ID: BACNet Error") 996 | return nil 997 | end 998 | -- if value is 21 (byte 18) 999 | if( value == 0x21 ) then 1000 | -- convert hex to decimal 1001 | local vendornum = string.byte(response, 19) 1002 | -- look up vendor name from table 1003 | return vendor_lookup(vendornum) 1004 | -- if value is 22 (byte 18) 1005 | elseif( value == 0x22 ) then 1006 | -- convert hex to decimal 1007 | local vendornum 1008 | pos, vendornum = bin.unpack(">S", response, 19) 1009 | -- look up vendor name from table 1010 | return vendor_lookup(vendornum) 1011 | else 1012 | -- set return value to an Error if byte 18 was not 21/22 1013 | stdnse.debug1("Error receiving Vendor ID: Invalid BACNet packet") 1014 | return nil 1015 | end 1016 | end 1017 | 1018 | end 1019 | 1020 | --- 1021 | -- Function to send a request for BVLC info to the discovered BACNet devices. 1022 | -- This includes the BBMD and the FDT queries. These are read only and do not 1023 | -- attempt to join the router as a Foreign Device. 1024 | -- 1025 | -- @param socket The socket that was created in the action function 1026 | -- @param type Type is the type of packet to send, this can be bbmd or fdt 1027 | function bvlc_query(socket, type) 1028 | 1029 | -- set the BVLC query data for sending 1030 | -- BBMD = 0x02 1031 | local bbmd_query = bin.pack("H","81020004") 1032 | -- FDT = 0x06 1033 | local fdt_query = bin.pack("H","81060004") 1034 | -- initialize query var 1035 | local query 1036 | 1037 | -- Based on type parameter passed in from Action 1038 | if (type == "bbmd") then 1039 | query = bbmd_query 1040 | elseif (type == "fdt") then 1041 | query = fdt_query 1042 | end 1043 | 1044 | -- Send the query that was set by the type 1045 | local status, result = socket:send(query) 1046 | if(status == false) then 1047 | stdnse.debug1("BVLC-" .. type .. ": Socket error sending query: %s", result) 1048 | return nil 1049 | end 1050 | -- Recive response from the query 1051 | local rcvstatus, response = socket:receive() 1052 | if(rcvstatus == false) then 1053 | stdnse.debug1("BVLC-" .. type .. ": Socket error receiving: %s", response) 1054 | return nil 1055 | end 1056 | 1057 | -- Validate that packet is BACNet, if it is then we will start parsing more response 1058 | if( string.starts(response, "\x81")) then 1059 | 1060 | -- init up vars 1061 | local info = "" 1062 | local ips = {} 1063 | local length 1064 | local mask 1065 | local resptype 1066 | 1067 | -- unpack response type, this will be used to determine BBMD vs FDT 1068 | local pos, resptype = bin.unpack("C", response, 2) 1069 | 1070 | -- unpack length, this will be the length of the information to be parsed 1071 | pos, length = bin.unpack(">S", response, 3) 1072 | -- add one to length since Lua starts at 1 not 0 1073 | length = length + 1 1074 | stdnse.debug1("BVLC-" .. type .. ": starting on bacnet bytes: " .. length) 1075 | -- if length is 7(packet size 6), then we will test to see if it was NAK response 1076 | if length == 7 then 1077 | -- response type will be BVLC-Result 1078 | if resptype == 0 then 1079 | -- unpack two bytes of interest 1080 | pos, byte1 = bin.unpack("C", response, 4) 1081 | pos, byte2 = bin.unpack("C", response, 6) 1082 | if byte1 == 0x06 and byte2 == 0x40 then 1083 | return "Non-Acknowledgement (NAK)" 1084 | elseif byte1 == 0x06 and byte2 == 0x20 then 1085 | return "Non-Acknowledgement (NAK)" 1086 | end 1087 | end 1088 | -- if the packet length is 5(packet size 4) then check to see if a Empty response 1089 | elseif length == 5 then 1090 | -- validate the response is for the FDT query 1091 | if resptype == 7 then 1092 | return "Empty Table" 1093 | end 1094 | -- if packet is not long enough then we will exit 1095 | elseif length < 15 then 1096 | stdnse.debug1( 1097 | "BVLC-" .. type .. ": stopping, this response had not enough bytes: " .. length .. " < 15") 1098 | return nil 1099 | end 1100 | -- While loop for the length of the packet as determined from above. 1101 | while pos < length do 1102 | local ipaddr = "" 1103 | --Unpack and the IP Address from the response 1104 | pos, info = bin.unpack("S", response, pos) 1110 | -- Make string to be stored in output table to be returned to Nmap 1111 | ipaddr = ipaddr .. ":" .. info 1112 | -- shift by 4 bytes 1113 | pos = pos + 4 1114 | 1115 | -- else if the type is FDT 1116 | elseif resptype == 7 then 1117 | --Unpack port number 1118 | pos, info = bin.unpack(">S", response, pos) 1119 | ipaddr = ipaddr .. ":" .. info 1120 | --Unpack TTL field 1121 | pos, info = bin.unpack(">S", response, pos) 1122 | ipaddr = ipaddr .. ":ttl=" .. info 1123 | --Unpack the timeout field 1124 | pos, info = bin.unpack(">S", response, pos) 1125 | ipaddr = ipaddr .. ":timeout=" .. info 1126 | stdnse.debug1("BVLC-" .. type .. ": found this: " .. ipaddr) 1127 | -- else the type was not something we were asking for 1128 | --we don't know what response type this is! 1129 | else 1130 | stdnse.debug1("BVLC-" .. type .. ": unknown response type encountered!") 1131 | return nil 1132 | end 1133 | -- insert to the ips table for output to Nmap 1134 | table.insert(ips, ipaddr) 1135 | 1136 | -- consider if its time to quit based on the last pos from the last 1137 | -- unpack was the end of the packet 1138 | if pos == length then 1139 | stdnse.debug1("BVLC-" .. type .. ": bailing because we are at the end: " .. pos) 1140 | return ips 1141 | end 1142 | stdnse.debug1("BVLC-" .. type .. ": done with loop") 1143 | end 1144 | -- else ERROR 1145 | else 1146 | stdnse.debug1("Invalid BACNet packet in response to: " .. type) 1147 | return nil 1148 | end 1149 | 1150 | end 1151 | 1152 | --- 1153 | -- Action Function that is used to run the NSE. This function will send the initial query to the 1154 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 1155 | -- is a BACNet device. If it is then more actions are taken to gather extra information. 1156 | -- 1157 | -- @param host Host that was scanned via nmap 1158 | -- @param port port that was scanned via nmap 1159 | action = function(host, port) 1160 | --set the first query data for sending 1161 | local orig_query = bin.pack("H","810a001101040005010c0c023FFFFF194b" ) 1162 | 1163 | local to_return = nil 1164 | -- create new socket 1165 | local sock = nmap.new_socket() 1166 | -- Bind to port for niceness with BACNet this may need to be commented out if 1167 | -- scanning more than one host at a time, may fix some issues seen on Windows 1168 | -- 1169 | local status, err = sock:bind(nil, 47808) 1170 | if(status == false) then 1171 | stdnse.debug1( 1172 | "Couldn't bind to 47808/udp. Continuing anyway, results may vary") 1173 | end 1174 | -- connect to the remote host 1175 | local constatus, conerr = sock:connect(host, port) 1176 | if not constatus then 1177 | stdnse.debug1( 1178 | 'Error establishing a UDP connection for %s - %s', host, conerr 1179 | ) 1180 | return nil 1181 | end 1182 | -- send the original query to see if it is a valid BACNet Device 1183 | local sendstatus, senderr = sock:send(orig_query) 1184 | if not sendstatus then 1185 | stdnse.debug1( 1186 | 'Error sending BACNet request to %s:%d - %s', 1187 | host.ip, port.number, senderr 1188 | ) 1189 | return nil 1190 | end 1191 | 1192 | -- receive response 1193 | local rcvstatus, response = sock:receive() 1194 | if(rcvstatus == false) then 1195 | stdnse.debug1("Receive error: %s", response) 1196 | return nil 1197 | end 1198 | 1199 | -- if the response starts with 0x81 then its BACNet 1200 | if( string.starts(response, "\x81")) then 1201 | local pos, value = bin.unpack("C", response, 7) 1202 | --if the first query resulted in an error 1203 | -- 1204 | if( value == 0x50) then 1205 | -- set the nmap output for the port and version 1206 | set_nmap(host, port) 1207 | -- return that BACNet Error was received 1208 | to_return = "\nBACNet ADPU Type: Error (5) \n\t" .. stdnse.tohex(response) 1209 | --else pull the InstanceNumber and move onto the pulling more information 1210 | -- 1211 | else 1212 | to_return = stdnse.output_table() 1213 | -- set the nmap output for the port and version 1214 | set_nmap(host, port) 1215 | 1216 | -- Vendor Number to Name lookup 1217 | to_return["Vendor ID"] = vendornum_query(sock) 1218 | 1219 | -- vendor name 1220 | to_return["Vendor Name"] = standard_query(sock, "vendor") 1221 | 1222 | -- Instance Number (object number) 1223 | local instance_upper, instance 1224 | pos, instance_upper, instance = bin.unpack("C>S", response, 20) 1225 | to_return["Object-identifier"] = instance_upper * 0x10000 + instance 1226 | 1227 | --Firmware Verson 1228 | to_return["Firmware"] = standard_query(sock, "firmware") 1229 | 1230 | -- Application Software Version 1231 | to_return["Application Software"] = standard_query(sock, "application") 1232 | 1233 | -- Object Name 1234 | to_return["Object Name"] = standard_query(sock, "object") 1235 | 1236 | -- Model Name 1237 | to_return["Model Name"] = standard_query(sock, "model") 1238 | 1239 | -- Description 1240 | to_return["Description"] = standard_query(sock, "description") 1241 | 1242 | -- Location 1243 | to_return["Location"] = standard_query(sock, "location") 1244 | 1245 | -- for each element in the table, if it is nil, then remove the information from the table 1246 | for key,value in pairs(to_return) do 1247 | if(string.len(to_return[key]) == 0) then 1248 | to_return[key] = nil 1249 | end 1250 | end 1251 | -- check for script-args, if its set to yes, then run this additional queries 1252 | local arguments = stdnse.get_script_args('full') 1253 | if ( arguments == "yes" ) then 1254 | -- BACnet Broadcast Management Device Query/Response 1255 | to_return["Broadcast Distribution Table (BDT)"] = bvlc_query(sock, "bbmd") 1256 | 1257 | -- Foreign Device Table Query/Response 1258 | to_return["Foreign Device Table (FDT)"] = bvlc_query(sock, "fdt") 1259 | end 1260 | end 1261 | else 1262 | -- return nothing, no BACNet was detected 1263 | -- close socket 1264 | sock:close() 1265 | return nil 1266 | end 1267 | -- close socket 1268 | sock:close() 1269 | -- return all information that was found 1270 | return to_return 1271 | 1272 | end 1273 | -------------------------------------------------------------------------------- /all/Siemens-CommunicationsProcessor.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens S7 Communications Processor devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_Siemens-CommunicationsProcessor: CP 343-1 CX10 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | 31 | if string.find (body, "/S7Web.css") then 32 | version = body:match("(.-)") 33 | if version == nil then 34 | version = "Unknown version" 35 | end 36 | output = output .. version 37 | return true 38 | elseif string.find (body, "examples/visual_key.htm") then 39 | version = body:match("(.-)") 40 | if version == nil then 41 | version = "Unknown version" 42 | end 43 | output = output .. version 44 | return true 45 | elseif string.find (body, "__FSys_Root") then 46 | version = body:match("(.-)") 47 | version = version:gsub(" ", " ") 48 | if version == nil then 49 | version = "Unknown version" 50 | end 51 | output = output .. version 52 | return true 53 | else 54 | return nil 55 | end 56 | end 57 | 58 | action = function(host, port) 59 | local verified, noun 60 | 61 | local answer1 = http.get(host, port, "/Portal0000.htm" ) 62 | local answer2 = http.get(host, port, "/__Additional" ) 63 | local answer3 = http.get(host, port, "/" ) 64 | 65 | if answer1.status ~= 200 and answer2.status ~= 200 and answer3.status ~= 200 then 66 | return nil 67 | end 68 | 69 | if answer1.status == 200 then 70 | answer = answer1 71 | elseif answer2.status == 200 then 72 | answer = answer2 73 | elseif answer3.status == 200 then 74 | answer = answer3 75 | end 76 | 77 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 78 | local detail = 15 79 | local output = strbuf.new() 80 | 81 | 82 | verified = verify_version(answer.body, output) 83 | 84 | 85 | if verified == nil then 86 | return 87 | end 88 | 89 | -- verbose/debug mode, print 50 entries 90 | if v_level > 1 and v_level < 5 then 91 | detail = 40 92 | -- double debug mode, print everything 93 | elseif v_level >= 5 then 94 | detail = verified 95 | end 96 | 97 | 98 | return output 99 | end -------------------------------------------------------------------------------- /all/Siemens-HMI-miniweb.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens SIMATIC S7- devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_SIEMENS-HMI-miniweb: Not implemented verify_version 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | if string.find (body, "ad_header_form_sprachauswahl") then 31 | version = body:match("") 32 | if version == nil then 33 | version = "Not implemented verify_version" 34 | end 35 | output = output .. version 36 | return true 37 | else 38 | return nil 39 | end 40 | end 41 | 42 | action = function(host, port) 43 | local verified, noun 44 | local answer = http.get(host, port, "/CSS/Miniweb.css" ) 45 | 46 | if answer.status ~= 200 then 47 | return nil 48 | end 49 | 50 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 51 | local detail = 15 52 | local output = strbuf.new() 53 | 54 | 55 | verified = verify_version(answer.body, output) 56 | 57 | 58 | if verified == nil then 59 | return 60 | end 61 | 62 | -- verbose/debug mode, print 50 entries 63 | if v_level > 1 and v_level < 5 then 64 | detail = 40 65 | -- double debug mode, print everything 66 | elseif v_level >= 5 then 67 | detail = verified 68 | end 69 | 70 | 71 | return output 72 | end -------------------------------------------------------------------------------- /all/Siemens-SIMATIC-PLC-S7.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | 6 | 7 | description = [[ 8 | Checks for SCADA Siemens Simatic S7 devices. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- |_Siemens-Simatic-S7: SIMATIC 300 (MPI2)/CPU 315-2 PN/DP 17 | 18 | 19 | 20 | author = "Jose Ramon Palanco, drainware" 21 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | 28 | local function verify_version(body, output) 29 | local version = nil 30 | if string.find (body, "/S7Web.css") then 31 | version = body:match("(.-)") 32 | version = version:gsub(" ", " ") 33 | if version == nil then 34 | version = "Unknown version" 35 | end 36 | output = output .. version 37 | return true 38 | elseif string.find (body, "snCplugPresent") then 39 | version = body:match("Siemens, (.-) !!!") 40 | version = version:gsub(" ", " ") 41 | if version == nil then 42 | version = "Unknown version" 43 | end 44 | output = output .. version 45 | return true 46 | 47 | else 48 | return nil 49 | end 50 | end 51 | 52 | action = function(host, port) 53 | local verified, noun 54 | local answer1 = http.get(host, port, "/Portal/Portal.mwsl" ) 55 | local answer2 = http.get(host, port, "/docs/cplugError.html/" ) 56 | 57 | if answer1.status ~= 200 and answer2.status ~= 200 then 58 | return nil 59 | end 60 | 61 | if answer1.status == 200 then 62 | answer = answer1 63 | elseif answer2.status == 200 then 64 | answer = answer2 65 | end 66 | 67 | 68 | local v_level = nmap.verbosity() + (nmap.debugging()*2) 69 | local detail = 15 70 | local output = strbuf.new() 71 | 72 | 73 | verified = verify_version(answer.body, output) 74 | 75 | 76 | if verified == nil then 77 | return 78 | end 79 | 80 | -- verbose/debug mode, print 50 entries 81 | if v_level > 1 and v_level < 5 then 82 | detail = 40 83 | -- double debug mode, print everything 84 | elseif v_level >= 5 then 85 | detail = verified 86 | end 87 | 88 | 89 | return output 90 | end -------------------------------------------------------------------------------- /all/Siemens-Scalance-module.nse: -------------------------------------------------------------------------------- 1 | local nmap = require "nmap" 2 | local shortport = require "shortport" 3 | local snmp = require "snmp" 4 | local stdnse = require "stdnse" 5 | local table = require "table" 6 | 7 | description = [[ 8 | Checks for SCADA Siemens SCALANCE modules. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | -- @output 15 | -- | Siemens-Scalance-module: 16 | -- |_ SCALANCE W788-1PRO 17 | 18 | 19 | author = "Jose Ramon Palanco, drainware" 20 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 21 | categories = {"default", "discovery", "safe"} 22 | dependencies = {"snmp-brute"} 23 | 24 | 25 | portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) 26 | 27 | 28 | function process_answer( tbl ) 29 | 30 | local new_tab = {} 31 | 32 | for _, v in ipairs( tbl ) do 33 | if string.find (v.value, "SCALANCE") then 34 | version = v.value:gsub("SCALANCE", "VERSION:") 35 | model = version:match("%W+ %s*(.-)%d%d%d") 36 | if model == "W" then 37 | version = version .. " (wireless device)" 38 | elseif model == "X" then 39 | version = version .. " (network switch)" 40 | elseif model == "S" then 41 | version = version .. " (firewall)" 42 | end 43 | else 44 | return nil 45 | end 46 | table.insert( new_tab, version) 47 | end 48 | 49 | table.sort( new_tab ) 50 | 51 | return new_tab 52 | 53 | end 54 | 55 | action = function(host, port) 56 | 57 | local socket = nmap.new_socket() 58 | local catch = function() socket:close() end 59 | local try = nmap.new_try(catch) 60 | local snmpoid = "1.3.6.1.2.1.1.1" 61 | local services = {} 62 | local status 63 | 64 | socket:set_timeout(5000) 65 | try(socket:connect(host, port)) 66 | 67 | status, services = snmp.snmpWalk( socket, snmpoid ) 68 | socket:close() 69 | 70 | 71 | if ( not(status) ) or ( services == nil ) or ( #services == 0 ) then 72 | return 73 | end 74 | 75 | services = process_answer(services) 76 | 77 | if services == nil then 78 | return 79 | end 80 | 81 | nmap.set_port_state(host, port, "open") 82 | 83 | return stdnse.format_output( true, services ) 84 | end 85 | -------------------------------------------------------------------------------- /all/Siemens-WINCC.nse: -------------------------------------------------------------------------------- 1 | local datafiles = require "datafiles" 2 | local netbios = require "netbios" 3 | local nmap = require "nmap" 4 | local stdnse = require "stdnse" 5 | local string = require "string" 6 | local table = require "table" 7 | 8 | description = [[ 9 | Checks for SCADA Siemens WINCC server. 10 | 11 | The higher the verbosity or debug level, the more disallowed entries are shown. 12 | ]] 13 | 14 | --- 15 | -- @usage 16 | -- sudo nmap -sU --script Siemens-WINCC.nse -p137 17 | -- 18 | -- @output 19 | -- Host script results: 20 | -- | Siemens-WINCC: 21 | -- |_ Detected Siemens WINCC_SRV 22 | 23 | 24 | author = "Jose Ramon Palanco, drainware" 25 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 26 | 27 | -- Current version of this script was based entirly on Implementing CIFS, by 28 | -- Christopher R. Hertel. 29 | categories = {"default", "discovery", "safe"} 30 | 31 | 32 | hostrule = function(host) 33 | 34 | -- The following is an attempt to only run this script against hosts 35 | -- that will probably respond to a UDP 137 probe. One might argue 36 | -- that sending a single UDP packet and waiting for a response is no 37 | -- big deal and that it should be done for every host. In that case 38 | -- simply change this rule to always return true. 39 | 40 | local port_t135 = nmap.get_port_state(host, 41 | {number=135, protocol="tcp"}) 42 | local port_t139 = nmap.get_port_state(host, 43 | {number=139, protocol="tcp"}) 44 | local port_t445 = nmap.get_port_state(host, 45 | {number=445, protocol="tcp"}) 46 | local port_u137 = nmap.get_port_state(host, 47 | {number=137, protocol="udp"}) 48 | 49 | return (port_t135 ~= nil and port_t135.state == "open") or 50 | (port_t139 ~= nil and port_t139.state == "open") or 51 | (port_t445 ~= nil and port_t445.state == "open") or 52 | (port_u137 ~= nil and 53 | (port_u137.state == "open" or 54 | port_u137.state == "open|filtered")) 55 | end 56 | 57 | 58 | action = function(host) 59 | 60 | local i 61 | local status 62 | local names, statistics 63 | local server_name 64 | local mac, prefix, manuf 65 | local response = {} 66 | local catch = function() return end 67 | local try = nmap.new_try(catch) 68 | 69 | 70 | -- Get the list of NetBIOS names 71 | status, names, statistics = netbios.do_nbstat(host) 72 | status, names, statistics = netbios.do_nbstat(host) 73 | status, names, statistics = netbios.do_nbstat(host) 74 | status, names, statistics = netbios.do_nbstat(host) 75 | if(status == false) then 76 | return stdnse.format_output(false, names) 77 | end 78 | 79 | -- Get the server name 80 | status, server_name = netbios.get_server_name(host, names) 81 | if(status == false) then 82 | return stdnse.format_output(false, server_name) 83 | end 84 | 85 | local step1, step2, step3, step4, step5 = nil 86 | 87 | for i = 1, #names, 1 do 88 | local padding = string.rep(" ", 17 - #names[i]['name']) 89 | local flags_str = netbios.flags_to_string(names[i]['flags']) 90 | 91 | 92 | 93 | if string.find(names[i]['name'], "WINCC_SRV") then 94 | if names[i]['suffix'] == 0x0 then 95 | step1 = true 96 | elseif names[i]['suffix'] == 0x20 then 97 | step2 = true 98 | end 99 | end 100 | 101 | if names[i]['name'] == "SIEMENS" then 102 | if names[i]['suffix'] == 0x0 then 103 | step3 = true 104 | elseif names[i]['suffix'] == 0x1e then 105 | step4 = true 106 | elseif names[i]['suffix'] == 0x1d then 107 | step5 = true 108 | end 109 | end 110 | 111 | end 112 | 113 | if step1 and step2 and step3 and step4 and step5 then 114 | info = string.format("Detected Siemens %s", server_name) 115 | table.insert(response, info) 116 | end 117 | 118 | 119 | return stdnse.format_output(true, response) 120 | 121 | 122 | end -------------------------------------------------------------------------------- /all/iec-identify.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | IEC-60870-5-104 (IEC 104) protocol discovery tool. Power of Community 2013 conference release. 3 | Attemts to check tcp/2404 port supporting IEC 60870-5-104 ICS protocol. 4 | ]] 5 | 6 | --- 7 | -- @usage 8 | -- nmap -Pn -n -d --script iec-identify.nse --script-args='iec-identify.timeout=500' -p 2404 9 | -- 10 | -- @args iec-identify.timeout 11 | -- Set the timeout in milliseconds. The default value is 500. 12 | -- 13 | -- @output 14 | -- PORT STATE SERVICE REASON 15 | -- 2404/tcp open IEC 60870-5-104 syn-ack 16 | -- | iec-identify: 17 | -- | testfr sent / recv: 680443000000 / 680483000000 18 | -- | startdt sent / recv: 680407000000 / 68040b000000 19 | -- | c_ic_na_1 sent / recv: 680e0000000064010600ffff00000000 / 680e0000020064014700ffff00000014 20 | -- |_ asdu address: 65535 21 | -- 22 | -- Version 0.1 23 | -- 24 | --- 25 | 26 | author = "Aleksandr Timorin" 27 | copyright = "Aleksandr Timorin" 28 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 29 | categories = {"discovery", "intrusive"} 30 | 31 | local shortport = require("shortport") 32 | local bin = require("bin") 33 | local comm = require("comm") 34 | local stdnse = require("stdnse") 35 | 36 | portrule = shortport.portnumber(2404, "tcp") 37 | 38 | local function hex2str(str) 39 | local x = {} 40 | local char_int 41 | for y in str:gmatch('(..)') do 42 | char_int = tonumber(y, 16) 43 | if char_int>=32 and char_int<=126 then 44 | x[#x+1] = string.char( char_int ) 45 | else 46 | x[#x+1] = y 47 | end 48 | end 49 | return table.concat( x ) 50 | end 51 | 52 | action = function(host, port) 53 | 54 | local timeout = stdnse.get_script_args("iec-identify.timeout") 55 | timeout = tonumber(timeout) or 500 56 | 57 | local asdu_address 58 | local pos 59 | local status, recv 60 | local output = {} 61 | local socket = nmap.new_socket() 62 | 63 | socket:set_timeout(timeout) 64 | 65 | -- attempt to connect tp 2404 tcp port 66 | stdnse.print_debug(1, "try to connect to port 2404" ) 67 | status, result = socket:connect(host, port, "tcp") 68 | --stdnse.print_debug(1, "connect status %s", status ) 69 | if not status then 70 | return nil 71 | end 72 | 73 | -- send TESTFR command 74 | local TESTFR = string.char(0x68, 0x04, 0x43, 0x00, 0x00, 0x00) 75 | status = socket:send( TESTFR ) 76 | stdnse.print_debug(1, "testfr status %s", status ) 77 | if not status then 78 | return nil 79 | end 80 | 81 | -- receive TESTFR answer 82 | status, recv = socket:receive_bytes(1024) 83 | stdnse.print_debug(1, "testfr recv: %s", stdnse.tohex(recv) ) 84 | --table.insert(output, string.format("testfr sent / recv: %s / %s", hex2str( stdnse.tohex(TESTFR)), hex2str( stdnse.tohex(recv)))) 85 | table.insert(output, string.format("testfr sent / recv: %s / %s", stdnse.tohex(TESTFR), stdnse.tohex(recv))) 86 | 87 | -- send STARTDT command 88 | local STARTDT = string.char(0x68, 0x04, 0x07, 0x00, 0x00, 0x00) 89 | status = socket:send( STARTDT ) 90 | if not status then 91 | return nil 92 | end 93 | 94 | -- receive STARTDT answer 95 | status, recv = socket:receive_bytes(0) 96 | stdnse.print_debug(1, "startd recv len: %d", #recv ) 97 | stdnse.print_debug(1, "startdt recv: %s", stdnse.tohex(recv) ) 98 | --table.insert(output, string.format("startdt sent / recv: %s / %s", hex2str( stdnse.tohex(STARTDT)), hex2str( stdnse.tohex(recv)))) 99 | table.insert(output, string.format("startdt sent / recv: %s / %s", stdnse.tohex(STARTDT), stdnse.tohex(recv))) 100 | 101 | -- if received 2 packets - STARTDT con + ME_EI_NA_1 Init -> full length should be 6+6+10 bytes 102 | if #recv == 22 then 103 | pos, asdu_address = bin.unpack(" 8 | -- 9 | -- @args mms-identify.timeout 10 | -- Set the timeout in milliseconds. The default value is 500. 11 | -- 12 | -- @output 13 | -- PORT STATE SERVICE 14 | -- 102/tcp open IEC 61850-8-1 MMS 15 | -- | mms-identify: 16 | -- | Raw answer: 030000>02f08001000100a10/020103a0*a1(020101a2#800flibiec61850.com810blibiec6185082030.5 17 | -- | Vendor name: libiec61850.com 18 | -- | Model name: libiec61850 19 | -- |_ Revision: 0.5 20 | -- 21 | -- Version 0.1 22 | -- 23 | --- 24 | 25 | author = "Aleksandr Timorin" 26 | copyright = "Aleksandr Timorin" 27 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 28 | categories = {"discovery", "intrusive"} 29 | 30 | local shortport = require("shortport") 31 | local bin = require("bin") 32 | local comm = require("comm") 33 | local stdnse = require("stdnse") 34 | 35 | portrule = shortport.portnumber(102, "tcp") 36 | 37 | local function hex2str(str) 38 | local x = {} 39 | local char_int 40 | for y in str:gmatch('(..)') do 41 | char_int = tonumber(y, 16) 42 | if char_int>=32 and char_int<=126 then 43 | x[#x+1] = string.char( char_int ) 44 | else 45 | x[#x+1] = y 46 | end 47 | end 48 | return table.concat( x ) 49 | end 50 | 51 | action = function(host, port) 52 | 53 | local timeout = stdnse.get_script_args("mms-identify.timeout") 54 | timeout = tonumber(timeout) or 500 55 | 56 | local status, recv 57 | local output = {} 58 | local socket = nmap.new_socket() 59 | 60 | socket:set_timeout(timeout) 61 | 62 | status, result = socket:connect(host, port, "tcp") 63 | if not status then 64 | return nil 65 | end 66 | 67 | local CR_TPDU = string.char(0x03, 0x00, 0x00, 0x0b, 0x06, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x00) 68 | -- status, recv = comm.exchange(host, port, CR_TPDU, {timeout=timeout}) 69 | status = socket:send( CR_TPDU ) 70 | if not status then 71 | return nil 72 | end 73 | status, recv = socket:receive_bytes(1024) 74 | stdnse.print_debug(1, "cr_tpdu recv: %s", stdnse.tohex(recv) ) 75 | table.insert(output, string.format("cr_tpdu send / recv: %s / %s", hex2str( stdnse.tohex(CR_TPDU)), hex2str( stdnse.tohex(recv)))) 76 | 77 | local MMS_INITIATE = string.char( 78 | 0x03, 0x00, 0x00, 0xc5, 0x02, 0xf0, 0x80, 0x0d, 79 | 0xbc, 0x05, 0x06, 0x13, 80 | 0x01, 0x00, 0x16, 0x01, 0x02, 0x14, 0x02, 0x00, 81 | 0x02, 0x33, 0x02, 0x00, 0x01, 0x34, 0x02, 0x00, 82 | 0x02, 0xc1, 0xa6, 0x31, 0x81, 0xa3, 0xa0, 0x03, 83 | 0x80, 0x01, 0x01, 0xa2, 0x81, 0x9b, 0x80, 0x02, 84 | 0x07, 0x80, 0x81, 0x04, 0x00, 0x00, 0x00, 0x01, 85 | 0x82, 0x04, 0x00, 0x00, 0x00, 0x02, 0xa4, 0x23, 86 | 0x30, 0x0f, 0x02, 0x01, 0x01, 0x06, 0x04, 0x52, 87 | 0x01, 0x00, 0x01, 0x30, 0x04, 0x06, 0x02, 0x51, 88 | 0x01, 0x30, 0x10, 0x02, 0x01, 0x03, 0x06, 0x05, 89 | 0x28, 0xca, 0x22, 0x02, 0x01, 0x30, 0x04, 0x06, 90 | 0x02, 0x51, 0x01, 0x88, 0x02, 0x06, 0x00, 0x61, 91 | 0x60, 0x30, 0x5e, 0x02, 0x01, 0x01, 0xa0, 0x59, 92 | 0x60, 0x57, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x07, 93 | 0x06, 0x05, 0x28, 0xca, 0x22, 0x01, 0x01, 0xa2, 94 | 0x04, 0x06, 0x02, 0x29, 0x02, 0xa3, 0x03, 0x02, 95 | 0x01, 0x02, 0xa6, 0x04, 0x06, 0x02, 0x29, 0x01, 96 | 0xa7, 0x03, 0x02, 0x01, 0x01, 0xbe, 0x32, 0x28, 97 | 0x30, 0x06, 0x02, 0x51, 0x01, 0x02, 0x01, 0x03, 98 | 0xa0, 0x27, 0xa8, 0x25, 0x80, 0x02, 0x7d, 0x00, 99 | 0x81, 0x01, 0x14, 0x82, 0x01, 0x14, 0x83, 0x01, 100 | 0x04, 0xa4, 0x16, 0x80, 0x01, 0x01, 0x81, 0x03, 101 | 0x05, 0xfb, 0x00, 0x82, 0x0c, 0x03, 0x6e, 0x1d, 102 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 103 | 0x98 104 | ) 105 | 106 | status = socket:send( MMS_INITIATE ) 107 | if not status then 108 | return nil 109 | end 110 | status, recv = socket:receive_bytes(1024) 111 | stdnse.print_debug(1, "mms_initiate recv: %s", stdnse.tohex(recv) ) 112 | table.insert(output, string.format("mms_initiate send / recv: %s / %s", hex2str( stdnse.tohex(MMS_INITIATE)), hex2str( stdnse.tohex(recv)))) 113 | 114 | local MMS_IDENTIFY = string.char( 115 | 0x03, 0x00, 0x00, 0x1b, 0x02, 0xf0, 0x80, 0x01, 116 | 0x00, 0x01, 0x00, 0x61, 0x0e, 0x30, 0x0c, 0x02, 117 | 0x01, 0x03, 0xa0, 0x07, 0xa0, 0x05, 0x02, 0x01, 118 | 0x01, 0x82, 0x00 119 | ) 120 | 121 | status = socket:send( MMS_IDENTIFY ) 122 | if not status then 123 | return nil 124 | end 125 | status, recv = socket:receive_bytes(1024) 126 | stdnse.print_debug(1, "mms_identify recv: %s", stdnse.tohex(recv) ) 127 | table.insert(output, string.format("mms_identify send / recv: %s / %s", hex2str( stdnse.tohex(MMS_IDENTIFY)), hex2str( stdnse.tohex(recv)))) 128 | 129 | local parse_err_catch = function() 130 | stdnse.print_debug(1, "error while parsing answer" ) 131 | end 132 | 133 | local try = nmap.new_try(parse_err_catch) 134 | 135 | if ( status and recv ) then 136 | -- damn! rewrite with bin.unpack! 137 | table.insert(output, string.format("raw answer: %s", hex2str( stdnse.tohex(recv)))) 138 | local tmp_recv = stdnse.tohex(recv) 139 | local invokeID_size = tonumber(string.sub(tmp_recv, 47, 48), 16) 140 | stdnse.print_debug(1, "invokeID_size: %d", invokeID_size ) 141 | 142 | local mms_identify_info = string.sub(tmp_recv, 52 + 2*invokeID_size +1) 143 | local vendor_name_size = tonumber(string.sub(mms_identify_info, 3, 4), 16) 144 | local vendor_name = string.sub(mms_identify_info, 5, 5 + 2*vendor_name_size -1) 145 | table.insert(output, string.format("vendor name: %s", hex2str( vendor_name))) 146 | 147 | mms_identify_info = string.sub(mms_identify_info, 5 + 2*vendor_name_size) 148 | local model_name_size = tonumber(string.sub(mms_identify_info, 3, 4), 16) 149 | local model_name = string.sub(mms_identify_info, 5, 5 + 2*model_name_size -1) 150 | table.insert(output, string.format("model name: %s", hex2str( model_name))) 151 | 152 | mms_identify_info = string.sub(mms_identify_info, 5 + 2*model_name_size) 153 | local revision_size = tonumber(string.sub(mms_identify_info, 3, 4), 16) 154 | local revision = string.sub(mms_identify_info, 5, 5 + 2*revision_size -1) 155 | table.insert(output, string.format("revision: %s", hex2str( revision))) 156 | else 157 | return nil 158 | end 159 | 160 | if(#output > 0) then 161 | port.version.name = "IEC 61850-8-1 MMS" 162 | nmap.set_port_state(host, port, "open") 163 | nmap.set_port_version(host, port, "hardmatched") 164 | return stdnse.format_output(true, output) 165 | else 166 | return nil 167 | end 168 | end 169 | 170 | 171 | --[[ 172 | 173 | python parsing implementation 174 | 175 | tpkt = struct.unpack('!I', r[:4]) 176 | iso8073 = struct.unpack('!I', '\x00' + r[4:7]) 177 | iso8327 = struct.unpack('!I', r[7:11]) 178 | iso8823 = struct.unpack('!II', '\x00' + r[11:18]) 179 | mms = r[18:] 180 | a0, a0_packetsize = struct.unpack('!BB', mms[:2]) 181 | a1, a1_packetsize = struct.unpack('!BB', mms[2:4]) 182 | invokeID, invokeID_size = struct.unpack('!BB', mms[4:6]) 183 | a2, a2_packetsize = struct.unpack('!BB', mms[6+invokeID_size:6+invokeID_size+2]) 184 | mms_identify_info = mms[6+invokeID_size+2:] 185 | vendor_name_size, = struct.unpack('!B', mms_identify_info[1:2]) 186 | vendor_name = ''.join(struct.unpack('!%dc' % vendor_name_size, mms_identify_info[2:2+vendor_name_size])) 187 | mms_identify_info = mms_identify_info[2+vendor_name_size:] 188 | model_name_size, = struct.unpack('!B', mms_identify_info[1:2]) 189 | model_name = ''.join(struct.unpack('!%dc' % model_name_size, mms_identify_info[2:2+model_name_size])) 190 | mms_identify_info = mms_identify_info[2+model_name_size:] 191 | revision_size, = struct.unpack('!B', mms_identify_info[1:2]) 192 | revision = ''.join(struct.unpack('!%dc' % revision_size, mms_identify_info[2:2+revision_size])) 193 | 194 | print "vendor name: {0}, model name: {1}, revision: {2}".format(vendor_name, model_name, revision) 195 | 196 | 197 | 198 | --]] -------------------------------------------------------------------------------- /all/modbus-discover.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local comm = require "comm" 3 | local nmap = require "nmap" 4 | local shortport = require "shortport" 5 | local stdnse = require "stdnse" 6 | local string = require "string" 7 | local table = require "table" 8 | 9 | description = [[ 10 | Enumerates SCADA Modbus slave ids (sids) and collects their device information. 11 | 12 | Modbus is one of the popular SCADA protocols. This script does Modbus device 13 | information disclosure. It tries to find legal sids (slave ids) of Modbus 14 | devices and to get additional information about the vendor and firmware. This 15 | script is improvement of modscan python utility written by Mark Bristow. 16 | 17 | Information about MODBUS protocol and security issues: 18 | * MODBUS application protocol specification: http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf 19 | * Defcon 16 Modscan presentation: https://www.defcon.org/images/defcon-16/dc16-presentations/defcon-16-bristow.pdf 20 | * Modscan utility is hosted at google code: http://code.google.com/p/modscan/ 21 | ]] 22 | 23 | --- 24 | -- @usage 25 | -- nmap --script modbus-discover.nse --script-args='modbus-discover.aggressive=true' -p 502 26 | -- 27 | -- @args aggressive - boolean value defines find all or just first sid 28 | -- 29 | -- @output 30 | -- PORT STATE SERVICE 31 | -- 502/tcp open modbus 32 | -- | modbus-discover: 33 | -- | sid 0x64: 34 | -- | Slave ID data: \xFA\xFFPM710PowerMeter 35 | -- | Device identification: Schneider Electric PM710 v03.110 36 | -- | sid 0x96: 37 | -- |_ error: GATEWAY TARGET DEVICE FAILED TO RESPONSE 38 | -- 39 | -- @xmloutput 40 | -- 41 | -- \xFA\xFFPM710PowerMeter 42 | -- Schneider Electric PM710 v03.110 43 | --
44 | -- 45 | -- GATEWAY TARGET DEVICE FAILED TO RESPONSE 46 | --
47 | 48 | -- Version 0.2 - /12.12.10/ - script cleanup 49 | -- Version 0.3 - /13.12.10/ - several bugfixes 50 | 51 | author = "Alexander Rudakov" 52 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 53 | categories = {"discovery", "intrusive"} 54 | 55 | 56 | portrule = shortport.port_or_service(502, "modbus") 57 | 58 | local form_rsid = function(sid, functionId, data) 59 | local payload_len = 2 60 | if ( #data > 0 ) then 61 | payload_len = payload_len + #data 62 | end 63 | return "\0\0\0\0\0" .. bin.pack('CCC', payload_len, sid, functionId) .. data 64 | end 65 | 66 | discover_device_id_recursive = function(host, port, sid, start_id, objects_table) 67 | local rsid = form_rsid(sid, 0x2B, "\x0E\x01" .. bin.pack('C', start_id)) 68 | local status, result = comm.exchange(host, port, rsid) 69 | if ( status and (#result >= 8)) then 70 | local ret_code = string.byte(result, 8) 71 | if ( ret_code == 0x2B and #result >= 15 ) then 72 | local more_follows = string.byte(result, 12) 73 | local next_object_id = string.byte(result, 13) 74 | local number_of_objects = string.byte(result, 14) 75 | stdnse.debug1("more = 0x%x, next_id = 0x%x, obj_number = 0x%x", more_follows, next_object_id, number_of_objects) 76 | local offset = 15 77 | for i = start_id, (number_of_objects - 1) do 78 | local object_id = string.byte(result, offset) 79 | local object_len = string.byte(result, offset + 1) 80 | -- error data format -- 81 | if object_len == nil then break end 82 | local object_value = string.sub(result, offset + 2, offset + 1 + object_len) 83 | stdnse.debug1("Object id = 0x%x, value = %s", object_id, object_value) 84 | table.insert(objects_table, object_id + 1, object_value) 85 | offset = offset + 2 + object_len 86 | end 87 | if ( more_follows == 0xFF and next_object_id ~= 0x00 ) then 88 | stdnse.debug1("Has more objects") 89 | return discover_device_id_recursive(host, port, sid, next_object_id, objects_table) 90 | end 91 | end 92 | end 93 | return objects_table 94 | end 95 | 96 | local discover_device_id = function(host, port, sid) 97 | return discover_device_id_recursive(host, port, sid, 0x0, {}) 98 | end 99 | 100 | local extract_slave_id = function(response) 101 | local byte_count = string.byte(response, 9) 102 | if ( byte_count == nil or byte_count == 0) then return nil end 103 | local offset, slave_id = bin.unpack("A"..byte_count, response, 10) 104 | return slave_id 105 | end 106 | 107 | modbus_exception_codes = { 108 | [1] = "ILLEGAL FUNCTION", 109 | [2] = "ILLEGAL DATA ADDRESS", 110 | [3] = "ILLEGAL DATA VALUE", 111 | [4] = "SLAVE DEVICE FAILURE", 112 | [5] = "ACKNOWLEDGE", 113 | [6] = "SLAVE DEVICE BUSY", 114 | [8] = "MEMORY PARITY ERROR", 115 | [10] = "GATEWAY PATH UNAVAILABLE", 116 | [11] = "GATEWAY TARGET DEVICE FAILED TO RESPOND" 117 | } 118 | 119 | action = function(host, port) 120 | -- If false, stop after first sid. 121 | local aggressive = stdnse.get_script_args('modbus-discover.aggressive') 122 | 123 | local opts = {request_timeout=2000} 124 | local results = stdnse.output_table() 125 | 126 | for sid = 1, 246 do 127 | stdnse.debug3("Sending command with sid = %d", sid) 128 | local rsid = form_rsid(sid, 0x11, "") 129 | 130 | local status, result = comm.exchange(host, port, rsid, opts) 131 | if ( status and (#result >= 8) ) then 132 | local ret_code = string.byte(result, 8) 133 | if ( ret_code == (0x11) or ret_code == (0x11 + 128) ) then 134 | local sid_table = stdnse.output_table() 135 | if ret_code == (0x11) then 136 | local slave_id = extract_slave_id(result) 137 | sid_table["Slave ID data"] = slave_id or "" 138 | elseif ret_code == (0x11 + 128) then 139 | local exception_code = string.byte(result, 9) 140 | local exception_string = modbus_exception_codes[exception_code] 141 | if ( exception_string == nil ) then 142 | exception_string = ("Unknown exception (0x%x)"):format(exception_code) 143 | end 144 | sid_table["error"] = exception_string 145 | end 146 | 147 | local device_table = discover_device_id(host, port, sid) 148 | if ( #device_table > 0 ) then 149 | sid_table["Device identification"] = table.concat(device_table, " ") 150 | end 151 | if ( #sid_table > 0 ) then 152 | results[("sid 0x%x"):format(sid)] = sid_table 153 | end 154 | if ( not aggressive ) then break end 155 | end 156 | end 157 | end 158 | 159 | if ( #results > 0 ) then 160 | port.state = "open" 161 | port.version.name = "modbus" 162 | nmap.set_port_version(host, port) 163 | return results 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /all/modbus-enum.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Attemts to find valid sid for tcp modbus server. 3 | ]] 4 | 5 | --- 6 | -- @usage 7 | -- nmap --script modbus-enum.nse --script-args='functionId=8, aggressive=true, data="00 AA BB CC"' -p 502 8 | -- 9 | -- @args functionId - modbus request function id. Valid function codes are 1-247 10 | -- @args aggressive - boolean value defines find all or just first sid 11 | -- @args data - payload data for modbus request in hex form. Example: "AA BB CC DD" 12 | -- 13 | -- @output 14 | -- PORT STATE SERVICE 15 | -- 502/tcp open modbus 16 | -- | modbus-enum: 17 | -- | Positive response for sid = 0x64 18 | -- | Positive error response for sid = 0x96 19 | -- |_ Positive response for sid = 0xc8 20 | -- 21 | -- Version 0.1 22 | -- 23 | -- This script is a NSE port of modscan utility written by Mark Bristow. 24 | -- MODBUS TCP protocol has no any authentication and allow to find information 25 | -- about legal sids by bruteforse. 26 | -- Presentation about tcp modbus protocol and modscan utility from Defcon 16 27 | -- can be found here https://www.defcon.org/images/defcon-16/dc16-presentations/defcon-16-bristow.pdf 28 | -- Modscan utility is hosted at google code: http://code.google.com/p/modscan/ 29 | -- 30 | --- 31 | 32 | author = "Alexander Rudakov" 33 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 34 | categories = {"discovery", "intrusive"} 35 | 36 | local bin = require "bin" 37 | local comm = require "comm" 38 | local stdnse = require "stdnse" 39 | local shortport = require "shortport" 40 | 41 | portrule = shortport.portnumber(502, "tcp") 42 | 43 | local form_rsid = function(sid, functionId, data) 44 | local payload_len = 2 45 | if ( #data > 0 ) then 46 | payload_len = payload_len + #data 47 | end 48 | return bin.pack('CCCCC', 0x00, 0x00, 0x00, 0x00, 0x00) .. bin.pack('C', payload_len) .. bin.pack('C', sid) .. bin.pack('C', functionId) .. data 49 | end 50 | 51 | action = function(host, port) 52 | local functionId = 8 -- default function code for diagnostics 53 | local aggressive = false -- stop on first founded sid 54 | local data = "00 00 AA BB" -- diagnostic code, just return data 55 | 56 | if (nmap.registry.args['functionId']) then 57 | functionId = nmap.registry.args['functionId'] 58 | end 59 | 60 | if (nmap.registry.args['aggressive']) then 61 | aggressive = nmap.registry.args['aggressive'] 62 | end 63 | 64 | if (nmap.registry.args['data']) then 65 | data = bin.pack('H', nmap.registry.args['data']) 66 | end 67 | 68 | 69 | local results = {} 70 | for sid = 1, 246 do 71 | stdnse.print_debug(3, "Sending command with sid = %d", sid) 72 | rsid = form_rsid(sid, functionId, data) 73 | 74 | local status, result = comm.exchange(host, port, rsid) 75 | if ( status and #result >= 8 ) then 76 | local ret_code = string.byte(result, 8) 77 | if ret_code == (functionId + 0) then 78 | table.insert(results, ("Positive response for sid = 0x%x"):format(sid)) 79 | if ( not aggressive ) then break end 80 | elseif ret_code == (functionId + 128) then 81 | table.insert(results, ("Positive error response for sid = 0x%x"):format(sid)) 82 | if ( not aggressive ) then break end 83 | end 84 | end 85 | end 86 | if ( #results > 0 ) then 87 | port.state = "open" 88 | port.version.name = "modbus" 89 | nmap.set_port_version(host, port, "hardmatched") 90 | end 91 | 92 | return stdnse.format_output(true, results) 93 | end 94 | -------------------------------------------------------------------------------- /all/s7-enumerate.nse: -------------------------------------------------------------------------------- 1 | -- 2 | -- required packages for this script 3 | -- 4 | -- plcscan.org Fix 5 | -- Fix Support S7-300/400 and S7-1200 and S7 Series Unknown Devices 6 | -- Last change 2014-11-14 add Support S7-1200 7 | -- Last change 2014-11-26 add enumerates block functions number 8 | -- 9 | local bin = require "bin" 10 | local nmap = require "nmap" 11 | local shortport = require "shortport" 12 | local stdnse = require "stdnse" 13 | local string = require "string" 14 | local table = require "table" 15 | 16 | description = [[ 17 | Enumerates Siemens S7 PLC Devices and collects their device information. This 18 | NSE is based off PLCScan that was developed by Positive Research and 19 | Scadastrangelove (https://code.google.com/p/plcscan/). This script is meant to 20 | provide the same functionality as PLCScan inside of Nmap. Some of the 21 | information that is collected by PLCScan was not ported over to this NSE, this 22 | information can be parsed out of the packets that are received. 23 | 24 | Thanks to Positive Research, and Dmitry Efanov for creating PLCScan 25 | ]] 26 | 27 | author = "Stephen Hilt (Digital Bond)" 28 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 29 | categories = {"discovery","intrusive"} 30 | 31 | --- 32 | -- @usage 33 | -- nmap -sP --script s7-discover.nse -p 102 34 | -- 35 | -- @output 36 | --102/tcp open Siemens S7 315 PLC 37 | --| s7-discover: 38 | --| Basic Hardware: 6ES7 315-2AG10-0AB0 39 | --| System Name: SIMATIC 300(1) 40 | --| Copyright: Original Siemens Equipment 41 | --| Version: 2.6.9 42 | --| Module Type: CPU 315-2 DP 43 | --| Module: 6ES7 315-2AG10-0AB0 44 | --|_ Serial Number: S C-X4U421302009 45 | --| ---------------------------------- 46 | --| Blocks Name: Count(Num) 47 | --| OB: 9 48 | --| FB: 0 49 | --| FC: 19 50 | --| DB: 18 51 | --| SDB: 17 52 | --| SFC: 71 53 | --| SFB: 20 54 | -- 55 | -- 56 | -- @output 57 | --102/tcp open Siemens S7 1200 PLC 58 | -- s7-enumerate: 59 | -- Module: 6ES7 214-1AE30-0XB0 60 | -- Basic Hardware: 6ES7 214-1AE30-0XB0 61 | -- Version: 2.2.0 62 | -- 63 | -- @xmloutput 64 | --6ES7 315-2AG10-0AB0 65 | --SIMATIC 300(1) 66 | --Original Siemens Equipment 67 | --2.6.9 68 | --SimpleServer 69 | --CPU 315-2 DP 70 | --6ES7 315-2AG10-0AB0 71 | --S C-X4U421302009 72 | -- 73 | 74 | 75 | -- port rule for devices running on TCP/102 76 | portrule = shortport.port_or_service(102, "iso-tsap", "tcp") 77 | 78 | --- 79 | -- Function to send and receive the S7COMM Packet 80 | -- 81 | -- First argument is the socket that was created inside of the main Action 82 | -- this will be utilized to send and receive the packets from the host. 83 | -- the second argument is the query to be sent, this is passed in and is created 84 | -- inside of the main action. 85 | -- @param socket the socket that was created in Action. 86 | -- @param query the specific query that you want to send/receive on. 87 | function send_receive(socket, query) 88 | local sendstatus, senderr = socket:send(query) 89 | if(sendstatus == false) then 90 | return "Error Sending S7COMM" 91 | end 92 | -- receive response 93 | local rcvstatus,response = socket:receive() 94 | if(rcvstatus == false) then 95 | return "Error Reading S7COMM" 96 | end 97 | return response 98 | end 99 | 100 | --- 101 | -- Function to parse the first SZL Request response that was received from the S7 PLCC 102 | -- 103 | -- First argument is the socket that was created inside of the main Action 104 | -- this will be utilized to send and receive the packets from the host. 105 | -- the second argument is the query to be sent, this is passed in and is created 106 | -- inside of the main action. 107 | -- @param response Packet response that was received from S7 host. 108 | -- @param host The host hat was passed in via Nmap, this is to change output of host/port 109 | -- @param port The port that was passed in via Nmap, this is to change output of host/port 110 | -- @param output Table used for output for return to Nmap 111 | function parse_response(response, host, port, output) 112 | -- unpack the protocol ID 113 | local pos, value = bin.unpack("C", response, 8) 114 | -- unpack the second byte of the SZL-ID 115 | local pos, szl_id = bin.unpack("C", response, 31) 116 | -- set the offset to 0 117 | local offset = 0 118 | -- if the protocol ID is 0x32 119 | if (value == 0x32) then 120 | local pos 121 | -- unpack the module information 122 | pos, output["Module"] = bin.unpack("z", response, 44) 123 | -- unpack the basic hardware information 124 | pos, output["Basic Hardware"] = bin.unpack("z", response, 72) 125 | -- set version number to 0 126 | local version = 0 127 | -- parse version number 128 | local pos, char1,char2,char3 = bin.unpack("CCC", response, 123) 129 | -- concatenate string, or if string is nil make version number 0.0 130 | output["Version"] = table.concat({char1 or "0.0", char2, char3}, ".") 131 | -- return the output table 132 | return output 133 | else 134 | output = DescFlag(S7DescFlag) 135 | return output 136 | end 137 | end 138 | 139 | --- 140 | -- Function to parse the second SZL Request response that was received from the S7 PLC 141 | -- 142 | -- First argument is the socket that was created inside of the main Action 143 | -- this will be utilized to send and receive the packets from the host. 144 | -- the second argument is the query to be sent, this is passed in and is created 145 | -- inside of the main action. 146 | -- @param response Packet response that was received from S7 host. 147 | -- @param output Table used for output for return to Nmap 148 | function second_parse_response(response, output) 149 | local offset = 0 150 | -- unpack the protocol ID 151 | local pos, value = bin.unpack("C", response, 8) 152 | -- unpack the second byte of the SZL-ID 153 | local pos, szl_id = bin.unpack("C", response, 31) 154 | -- if the protocol ID is 0x32 155 | if (value == 0x32) then 156 | -- if the szl-ID is not 0x1c 157 | if( szl_id ~= 0x1c ) then 158 | -- change offset to 4, this is where most ov valid PLCs will fall 159 | offset = 4 160 | end 161 | -- parse system name 162 | pos, output["System Name"] = bin.unpack("z", response, 40 + offset) 163 | -- parse module type 164 | pos, output["Module Type"] = bin.unpack("z", response, 74 + offset) 165 | -- parse serial number 166 | pos, output["Serial Number"] = bin.unpack("z", response, 176 + offset) 167 | -- parse plant identification 168 | pos, output["Plant Identification"] = bin.unpack("z", response, 108 + offset) 169 | -- parse copyright 170 | pos, output["Copyright"] = bin.unpack("z", response, 142 + offset) 171 | 172 | -- for each element in the table, if it is nil, then remove the information from the table 173 | for key,value in pairs(output) do 174 | if(string.len(output[key]) == 0) then 175 | output[key] = nil 176 | end 177 | end 178 | -- return output 179 | return output 180 | else 181 | output = DescFlag(S7DescFlag) 182 | return output 183 | end 184 | end 185 | --- 186 | -- Function to set the nmap output for the host, if a valid S7COMM packet 187 | -- is received then the output will show that the port is open 188 | -- and change the output to reflect an S7 PLC 189 | -- 190 | -- @param host Host that was passed in via nmap 191 | -- @param port port that S7COMM is running on 192 | function set_nmap(host, port) 193 | --set port Open 194 | port.state = "open" 195 | -- set that detected an Siemens S7 196 | port.version.name = "iso-tsap" 197 | port.version.devicetype = "specialized" 198 | port.version.product = "Siemens S7 PLC" 199 | nmap.set_port_version(host, port) 200 | nmap.set_port_state(host, port, "open") 201 | 202 | end 203 | --- 204 | -- 205 | -- if get fail SZL info output S7 protocol Flag 206 | -- 207 | -- add S7 protocol Flag 208 | -- 209 | -- 210 | function DescFlag(S7DescFlag) 211 | output = stdnse.output_table() 212 | local pos, protocol_head = bin.unpack("C", S7DescFlag, 1) 213 | if (protocol_head == 0x03) then 214 | output["Devices Type"] = 'Siemens S7 Series Devices' 215 | return output 216 | end 217 | end 218 | -- 219 | -- 220 | --- 221 | -- to parse the list block response 222 | -- 223 | -- 224 | function parse_listblock_response(response, output) 225 | local block_type = { 226 | [56] = "OB", 227 | [69] = "FB", 228 | [67] = "FC", 229 | [65] = "DB", 230 | [66] = "SDB", 231 | [68] = "SFC", 232 | [70] = "SFB" 233 | } 234 | -- print "dev debug1" 235 | local pos, protocol_id = bin.unpack("C", response, 8) 236 | local pos, listlength = bin.unpack("C", response, 33) 237 | if (protocol_id == 0x32) then 238 | -- print "dev debug2" 239 | if (listlength == 0x1c) then 240 | -- print "dev debug3" 241 | output["Blocks Name"] = "Count(Num)" 242 | local pos, fuc1 = bin.unpack("C", response, 35) 243 | local pos, count1 = bin.unpack("C", response, 37) 244 | output[block_type[fuc1]] = count1 245 | local pos, fuc2 = bin.unpack("C", response, 39) 246 | local pos, count2 = bin.unpack("C", response, 41) 247 | output[block_type[fuc2]] = count2 248 | local pos, fuc3 = bin.unpack("C", response, 43) 249 | local pos, count3 = bin.unpack("C", response, 45) 250 | output[block_type[fuc3]] = count3 251 | local pos, fuc4 = bin.unpack("C", response, 47) 252 | local pos, count4 = bin.unpack("C", response, 49) 253 | output[block_type[fuc4]] = count4 254 | local pos, fuc5 = bin.unpack("C", response, 51) 255 | local pos, count5 = bin.unpack("C", response, 53) 256 | output[block_type[fuc5]] = count5 257 | local pos, fuc6 = bin.unpack("C", response, 55) 258 | local pos, count6 = bin.unpack("C", response, 57) 259 | output[block_type[fuc6]] = count6 260 | local pos, fuc7 = bin.unpack("C", response, 59) 261 | local pos, count7 = bin.unpack("C", response, 61) 262 | output[block_type[fuc7]] = count7 263 | return output 264 | else 265 | return output 266 | end 267 | else 268 | return output 269 | end 270 | end 271 | 272 | -- 273 | -- 274 | -- 275 | --- 276 | --- 277 | -- Action Function that is used to run the NSE. This function will send the initial query to the 278 | -- host and port that were passed in via nmap. The initial response is parsed to determine if host 279 | -- is a S7COMM device. If it is then more actions are taken to gather extra information. 280 | -- 281 | -- @param host Host that was scanned via nmap 282 | -- @param port port that was scanned via nmap 283 | action = function(host,port) 284 | -- COTP packet with a dst of 102 285 | local COTP = bin.pack("H","0300001611e00000001400c1020100c2020" .. "102" .. "c0010a") 286 | -- COTP packet with a dst of 200 287 | local alt_COTP = bin.pack("H","0300001611e00000000500c1020100c2020" .. "200" .. "c0010a") 288 | -- setup the ROSCTR Packet 289 | local ROSCTR_Setup = bin.pack("H","0300001902f08032010000000000080000f0000001000101e0") 290 | -- setup the Read SZL information packet 291 | local Read_SZL = bin.pack("H","0300002102f080320700000000000800080001120411440100ff09000400110001") 292 | -- setup the first SZL request (gather the basic hardware and version number) 293 | local first_SZL_Request = bin.pack("H","0300002102f080320700000000000800080001120411440100ff09000400110001") 294 | -- setup the second SZL request 295 | local second_SZL_Request = bin.pack("H","0300002102f080320700000000000800080001120411440100ff090004001c0001") 296 | --- 297 | -- add S7-1200 packet and Block Functions Enumerates 298 | -- by Z-0ne plcscan.org 299 | -- Based on S7COMM Protocol analysis plugin. 300 | -- 301 | --- 302 | -- S7-1200 PLC usage Rack 0 Slot 1 303 | local COTP_0x0000 = bin.pack("H","0300001611e00000000100c0010ac1020100c2020301") 304 | -- Setup communication 0xf0 305 | local Setup_comm = bin.pack("H","0300001902f080320100000c0000080000f0000001000101e0") 306 | -- Request SZL functions Read SZL ID=0X0011 307 | local Req_SZL_0x0011 = bin.pack("H","0300002102f080320700000d00000800080001120411440100ff09000400110000") 308 | -- Request Block Functions -> List blocks 309 | local Req_Block_fuc_list = bin.pack("H","0300001d02f0803207000025000008000400011204114301000a000000") 310 | -- 311 | --- 312 | -- response is used to collect the packet responses 313 | local response 314 | -- output table for Nmap 315 | local output = stdnse.output_table() 316 | -- create socket for communications 317 | local sock = nmap.new_socket() 318 | -- connect to host 319 | local constatus,conerr = sock:connect(host,port) 320 | if not constatus then 321 | stdnse.print_debug(1, 322 | 'Error establishing connection for %s - %s', host,conerr 323 | ) 324 | return nil 325 | end 326 | -- send and receive the COTP Packet 327 | S7DescFlag = send_receive(sock, COTP) 328 | -- unpack the PDU Type 329 | local pos, CC_connect_confirm = bin.unpack("C", S7DescFlag, 6) 330 | -- if PDU type is not 0xd0, then not a successful COTP connection 331 | --- 332 | -- if ( CC_connect_confirm ~= 0xd0) then 333 | -- return nil 334 | -- end 335 | --- 336 | if ( CC_connect_confirm ~= 0xd0) then 337 | --- 338 | -- add support S7 1200 packet send 339 | --- 340 | output = stdnse.output_table() 341 | local constatus,conerr = sock:connect(host,port) 342 | if not constatus then 343 | stdnse.print_debug(1, 344 | 'Error establishing connection for %s - %s', host,conerr 345 | ) 346 | return nil 347 | end 348 | S7DescFlag = send_receive(sock, COTP_0x0000) 349 | local pos, CC_connect_confirm = bin.unpack("C", S7DescFlag, 6) 350 | if ( CC_connect_confirm ~= 0xd0) then 351 | stdnse.print_debug(1, "Not a successful COTP Packet_1200") 352 | output = DescFlag(S7DescFlag) 353 | return output 354 | end 355 | response = send_receive(sock, Setup_comm) 356 | local pos, protocol_id = bin.unpack("C", response, 8) 357 | if ( protocol_id ~= 0x32) then 358 | stdnse.print_debug(1, "Not a successful S7COMM Packet_1200") 359 | output = DescFlag(S7DescFlag) 360 | return output 361 | end 362 | response = send_receive(sock, Req_SZL_0x0011) 363 | local pos, protocol_id = bin.unpack("C", response, 8) 364 | if ( protocol_id ~= 0x32) then 365 | stdnse.print_debug(1, "Not a successful S7COMM Packet_1200") 366 | output = DescFlag(S7DescFlag) 367 | return output 368 | end 369 | output = parse_response(response, host, port, output) 370 | response = send_receive(sock, Req_Block_fuc_list) 371 | output = parse_listblock_response(response, output) 372 | return output 373 | -- 374 | --- 375 | end 376 | -- send and receive the ROSCTR Setup Packet 377 | response = send_receive(sock, ROSCTR_Setup) 378 | -- unpack the protocol ID 379 | local pos, protocol_id = bin.unpack("C", response, 8) 380 | -- if protocol ID is not 0x32 then return nil 381 | --- 382 | if ( protocol_id ~= 0x32) then 383 | stdnse.print_debug(1, "Not a successful S7COMM Packet") 384 | -- return nil 385 | end 386 | --- 387 | -- send and receive the READ_SZL packet 388 | response = send_receive(sock, Read_SZL) 389 | local pos, protocol_id = bin.unpack("C", response, 8) 390 | -- if protocol ID is not 0x32 then return nil 391 | --- 392 | if ( protocol_id ~= 0x32) then 393 | stdnse.print_debug(1, "Not a successful S7COMM Packet") 394 | -- return nil 395 | end 396 | --- 397 | -- send and receive the first SZL Request packet 398 | response = send_receive(sock, first_SZL_Request) 399 | -- parse the response for basic hardware information 400 | output = parse_response(response, host, port, output) 401 | -- send and receive the second SZL Request packet 402 | response = send_receive(sock, second_SZL_Request) 403 | -- parse the response for more information 404 | output = second_parse_response(response, output) 405 | --- 406 | -- send and receive the list block request 407 | response = send_receive(sock, Req_Block_fuc_list) 408 | -- parse the response 409 | output = parse_listblock_response(response, output) 410 | --- 411 | -- if nothing was parsed from the previous two responses 412 | if(output == nil) then 413 | -- re initialize the table 414 | output = stdnse.output_table() 415 | -- re connect to the device ( a RST packet was sent in the previous attempts) 416 | local constatus,conerr = sock:connect(host,port) 417 | if not constatus then 418 | stdnse.print_debug(1, 419 | 'Error establishing connection for %s - %s', host,conerr 420 | ) 421 | return nil 422 | end 423 | -- send and receive the alternate COTP Packet, the dst is 200 instead of 102( do nothing with result) 424 | S7DescFlag = send_receive(sock, alt_COTP) 425 | local pos, CC_connect_confirm = bin.unpack("C", S7DescFlag, 6) 426 | -- if PDU type is not 0xd0, then not a successful COTP connection 427 | --- 428 | if ( CC_connect_confirm ~= 0xd0) then 429 | stdnse.print_debug(1, "Not a successful COTP Packet") 430 | -- return nil 431 | end 432 | --- 433 | -- send and receive the packets as before. 434 | response = send_receive(sock, ROSCTR_Setup) 435 | -- unpack the protocol ID 436 | local pos, protocol_id = bin.unpack("C", response, 8) 437 | -- if protocol ID is not 0x32 then return nil 438 | --- 439 | if ( protocol_id ~= 0x32) then 440 | stdnse.print_debug(1, "Not a successful S7COMM Packet") 441 | -- return nil 442 | end 443 | --- 444 | response = send_receive(sock, Read_SZL) 445 | -- unpack the protocol ID 446 | local pos, protocol_id = bin.unpack("C", response, 8) 447 | -- if protocol ID is not 0x32 then return nil 448 | --- 449 | if ( protocol_id ~= 0x32) then 450 | stdnse.print_debug(1, "Not a successful S7COMM Packet") 451 | -- return nil 452 | end 453 | --- 454 | response = send_receive(sock, first_SZL_Request) 455 | output = parse_response(response, host, port, "ONE", output) 456 | response = send_receive(sock, second_SZL_Request) 457 | output = parse_response(response, host, port, "TWO", output) 458 | --- 459 | response = send_receive(sock, Req_Block_fuc_list) 460 | output = parse_listblock_response(response, output) 461 | --- 462 | end 463 | -- close the socket 464 | sock:close() 465 | 466 | -- If we parsed anything, then set the version info for Nmap 467 | if #output > 0 then 468 | set_nmap(host, port) 469 | end 470 | -- return output to Nmap 471 | return output 472 | 473 | end 474 | 475 | -------------------------------------------------------------------------------- /modbus/modbus-discover.nse: -------------------------------------------------------------------------------- 1 | local bin = require "bin" 2 | local comm = require "comm" 3 | local nmap = require "nmap" 4 | local shortport = require "shortport" 5 | local stdnse = require "stdnse" 6 | local string = require "string" 7 | local table = require "table" 8 | 9 | description = [[ 10 | Enumerates SCADA Modbus slave ids (sids) and collects their device information. 11 | 12 | Modbus is one of the popular SCADA protocols. This script does Modbus device 13 | information disclosure. It tries to find legal sids (slave ids) of Modbus 14 | devices and to get additional information about the vendor and firmware. This 15 | script is improvement of modscan python utility written by Mark Bristow. 16 | 17 | Information about MODBUS protocol and security issues: 18 | * MODBUS application protocol specification: http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf 19 | * Defcon 16 Modscan presentation: https://www.defcon.org/images/defcon-16/dc16-presentations/defcon-16-bristow.pdf 20 | * Modscan utility is hosted at google code: http://code.google.com/p/modscan/ 21 | ]] 22 | 23 | --- 24 | -- @usage 25 | -- nmap --script modbus-discover.nse --script-args='modbus-discover.aggressive=true' -p 502 26 | -- 27 | -- @args aggressive - boolean value defines find all or just first sid 28 | -- 29 | -- @output 30 | -- PORT STATE SERVICE 31 | -- 502/tcp open modbus 32 | -- | modbus-discover: 33 | -- | sid 0x64: 34 | -- | Slave ID data: \xFA\xFFPM710PowerMeter 35 | -- | Device identification: Schneider Electric PM710 v03.110 36 | -- | sid 0x96: 37 | -- |_ error: GATEWAY TARGET DEVICE FAILED TO RESPONSE 38 | -- 39 | -- @xmloutput 40 | -- 41 | -- \xFA\xFFPM710PowerMeter 42 | -- Schneider Electric PM710 v03.110 43 | --
44 | -- 45 | -- GATEWAY TARGET DEVICE FAILED TO RESPONSE 46 | --
47 | 48 | -- Version 0.2 - /12.12.10/ - script cleanup 49 | -- Version 0.3 - /13.12.10/ - several bugfixes 50 | 51 | author = "Alexander Rudakov" 52 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 53 | categories = {"discovery", "intrusive"} 54 | 55 | 56 | portrule = shortport.port_or_service(502, "modbus") 57 | 58 | local form_rsid = function(sid, functionId, data) 59 | local payload_len = 2 60 | if ( #data > 0 ) then 61 | payload_len = payload_len + #data 62 | end 63 | return "\0\0\0\0\0" .. bin.pack('CCC', payload_len, sid, functionId) .. data 64 | end 65 | 66 | discover_device_id_recursive = function(host, port, sid, start_id, objects_table) 67 | local rsid = form_rsid(sid, 0x2B, "\x0E\x01" .. bin.pack('C', start_id)) 68 | local status, result = comm.exchange(host, port, rsid) 69 | if ( status and (#result >= 8)) then 70 | local ret_code = string.byte(result, 8) 71 | if ( ret_code == 0x2B and #result >= 15 ) then 72 | local more_follows = string.byte(result, 12) 73 | local next_object_id = string.byte(result, 13) 74 | local number_of_objects = string.byte(result, 14) 75 | stdnse.debug1("more = 0x%x, next_id = 0x%x, obj_number = 0x%x", more_follows, next_object_id, number_of_objects) 76 | local offset = 15 77 | for i = start_id, (number_of_objects - 1) do 78 | local object_id = string.byte(result, offset) 79 | local object_len = string.byte(result, offset + 1) 80 | -- error data format -- 81 | if object_len == nil then break end 82 | local object_value = string.sub(result, offset + 2, offset + 1 + object_len) 83 | stdnse.debug1("Object id = 0x%x, value = %s", object_id, object_value) 84 | table.insert(objects_table, object_id + 1, object_value) 85 | offset = offset + 2 + object_len 86 | end 87 | if ( more_follows == 0xFF and next_object_id ~= 0x00 ) then 88 | stdnse.debug1("Has more objects") 89 | return discover_device_id_recursive(host, port, sid, next_object_id, objects_table) 90 | end 91 | end 92 | end 93 | return objects_table 94 | end 95 | 96 | local discover_device_id = function(host, port, sid) 97 | return discover_device_id_recursive(host, port, sid, 0x0, {}) 98 | end 99 | 100 | local extract_slave_id = function(response) 101 | local byte_count = string.byte(response, 9) 102 | if ( byte_count == nil or byte_count == 0) then return nil end 103 | local offset, slave_id = bin.unpack("A"..byte_count, response, 10) 104 | return slave_id 105 | end 106 | 107 | modbus_exception_codes = { 108 | [1] = "ILLEGAL FUNCTION", 109 | [2] = "ILLEGAL DATA ADDRESS", 110 | [3] = "ILLEGAL DATA VALUE", 111 | [4] = "SLAVE DEVICE FAILURE", 112 | [5] = "ACKNOWLEDGE", 113 | [6] = "SLAVE DEVICE BUSY", 114 | [8] = "MEMORY PARITY ERROR", 115 | [10] = "GATEWAY PATH UNAVAILABLE", 116 | [11] = "GATEWAY TARGET DEVICE FAILED TO RESPOND" 117 | } 118 | 119 | action = function(host, port) 120 | -- If false, stop after first sid. 121 | local aggressive = stdnse.get_script_args('modbus-discover.aggressive') 122 | 123 | local opts = {request_timeout=2000} 124 | local results = stdnse.output_table() 125 | 126 | for sid = 1, 246 do 127 | stdnse.debug3("Sending command with sid = %d", sid) 128 | local rsid = form_rsid(sid, 0x11, "") 129 | 130 | local status, result = comm.exchange(host, port, rsid, opts) 131 | if ( status and (#result >= 8) ) then 132 | local ret_code = string.byte(result, 8) 133 | if ( ret_code == (0x11) or ret_code == (0x11 + 128) ) then 134 | local sid_table = stdnse.output_table() 135 | if ret_code == (0x11) then 136 | local slave_id = extract_slave_id(result) 137 | sid_table["Slave ID data"] = slave_id or "" 138 | elseif ret_code == (0x11 + 128) then 139 | local exception_code = string.byte(result, 9) 140 | local exception_string = modbus_exception_codes[exception_code] 141 | if ( exception_string == nil ) then 142 | exception_string = ("Unknown exception (0x%x)"):format(exception_code) 143 | end 144 | sid_table["error"] = exception_string 145 | end 146 | 147 | local device_table = discover_device_id(host, port, sid) 148 | if ( #device_table > 0 ) then 149 | sid_table["Device identification"] = table.concat(device_table, " ") 150 | end 151 | if ( #sid_table > 0 ) then 152 | results[("sid 0x%x"):format(sid)] = sid_table 153 | end 154 | if ( not aggressive ) then break end 155 | end 156 | end 157 | end 158 | 159 | if ( #results > 0 ) then 160 | port.state = "open" 161 | port.version.name = "modbus" 162 | nmap.set_port_version(host, port) 163 | return results 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /modbus/modbus-enum.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Attemts to find valid sid for tcp modbus server. 3 | ]] 4 | 5 | --- 6 | -- @usage 7 | -- nmap --script modbus-enum.nse --script-args='functionId=8, aggressive=true, data="00 AA BB CC"' -p 502 8 | -- 9 | -- @args functionId - modbus request function id. Valid function codes are 1-247 10 | -- @args aggressive - boolean value defines find all or just first sid 11 | -- @args data - payload data for modbus request in hex form. Example: "AA BB CC DD" 12 | -- 13 | -- @output 14 | -- PORT STATE SERVICE 15 | -- 502/tcp open modbus 16 | -- | modbus-enum: 17 | -- | Positive response for sid = 0x64 18 | -- | Positive error response for sid = 0x96 19 | -- |_ Positive response for sid = 0xc8 20 | -- 21 | -- Version 0.1 22 | -- 23 | -- This script is a NSE port of modscan utility written by Mark Bristow. 24 | -- MODBUS TCP protocol has no any authentication and allow to find information 25 | -- about legal sids by bruteforse. 26 | -- Presentation about tcp modbus protocol and modscan utility from Defcon 16 27 | -- can be found here https://www.defcon.org/images/defcon-16/dc16-presentations/defcon-16-bristow.pdf 28 | -- Modscan utility is hosted at google code: http://code.google.com/p/modscan/ 29 | -- 30 | --- 31 | 32 | author = "Alexander Rudakov" 33 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 34 | categories = {"discovery", "intrusive"} 35 | 36 | local bin = require "bin" 37 | local comm = require "comm" 38 | local stdnse = require "stdnse" 39 | local shortport = require "shortport" 40 | 41 | portrule = shortport.portnumber(502, "tcp") 42 | 43 | local form_rsid = function(sid, functionId, data) 44 | local payload_len = 2 45 | if ( #data > 0 ) then 46 | payload_len = payload_len + #data 47 | end 48 | return bin.pack('CCCCC', 0x00, 0x00, 0x00, 0x00, 0x00) .. bin.pack('C', payload_len) .. bin.pack('C', sid) .. bin.pack('C', functionId) .. data 49 | end 50 | 51 | action = function(host, port) 52 | local functionId = 8 -- default function code for diagnostics 53 | local aggressive = false -- stop on first founded sid 54 | local data = "00 00 AA BB" -- diagnostic code, just return data 55 | 56 | if (nmap.registry.args['functionId']) then 57 | functionId = nmap.registry.args['functionId'] 58 | end 59 | 60 | if (nmap.registry.args['aggressive']) then 61 | aggressive = nmap.registry.args['aggressive'] 62 | end 63 | 64 | if (nmap.registry.args['data']) then 65 | data = bin.pack('H', nmap.registry.args['data']) 66 | end 67 | 68 | 69 | local results = {} 70 | for sid = 1, 246 do 71 | stdnse.print_debug(3, "Sending command with sid = %d", sid) 72 | rsid = form_rsid(sid, functionId, data) 73 | 74 | local status, result = comm.exchange(host, port, rsid) 75 | if ( status and #result >= 8 ) then 76 | local ret_code = string.byte(result, 8) 77 | if ret_code == (functionId + 0) then 78 | table.insert(results, ("Positive response for sid = 0x%x"):format(sid)) 79 | if ( not aggressive ) then break end 80 | elseif ret_code == (functionId + 128) then 81 | table.insert(results, ("Positive error response for sid = 0x%x"):format(sid)) 82 | if ( not aggressive ) then break end 83 | end 84 | end 85 | end 86 | if ( #results > 0 ) then 87 | port.state = "open" 88 | port.version.name = "modbus" 89 | nmap.set_port_version(host, port, "hardmatched") 90 | end 91 | 92 | return stdnse.format_output(true, results) 93 | end 94 | --------------------------------------------------------------------------------