├── README.md ├── dnp3-enumerate.nse ├── melsecq-discover-udp.nse ├── melsecq-discover.nse ├── s7-enumerate.nse └── s71200-enumerate-old.nse /README.md: -------------------------------------------------------------------------------- 1 | ICS Discovery Tools 2 | =================== 3 | 4 | ICS Discovery Tools Releases 5 | 6 | [Warning:This is a test project.](http://plcscan.org) 7 | -------------------------------------------------------------------------------- /dnp3-enumerate.nse: -------------------------------------------------------------------------------- 1 | -- Nmap Scripting Engine 2 | -- required packages for this script 3 | -- 4 | -- ICS Discovery Tools Releases 5 | -- ICS Security Workspace(plcscan.org) 6 | --- 7 | -- usage: 8 | -- nmap -sT --script dnp3-enumerate.nse -p 20000 9 | -- 10 | -- Output: 11 | -- PORT STATE SERVICE REASON 12 | -- 20000/tcp open dnp3 syn-ack 13 | -- | dnp3-enumerate: 14 | -- | Source address: 20 15 | -- | Destination address: 0 16 | -- |_ Control code: 68 17 | -- 18 | local bin = require "bin" 19 | local comm = require "comm" 20 | local nmap = require "nmap" 21 | local shortport = require "shortport" 22 | local stdnse = require "stdnse" 23 | local string = require "string" 24 | local table = require "table" 25 | 26 | description = [[ 27 | Discovery DNP3 devices the destination address 28 | From http://plcscan.org/blog/2014/12/dnp3-protocol-overview/ 29 | ]] 30 | author = "Z-0ne" 31 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 32 | categories = {"discovery", "intrusive"} 33 | 34 | function set_nmap(host, port) 35 | port.state = "open" 36 | port.version.name = "dnp3" 37 | port.version.product = "dnp3devices" 38 | nmap.set_port_version(host, port) 39 | nmap.set_port_state(host, port, "open") 40 | end 41 | 42 | portrule = shortport.port_or_service(20000, "dnp3","tcp") 43 | 44 | action = function(host,port) 45 | local output = stdnse.output_table() 46 | local settimeout = {timeout=4000} 47 | local reqlinkstat = bin.pack("H","056405c900000000364c".. 48 | "056405c901000000de8e".. 49 | "056405c9020000009f84".. 50 | "056405c9030000007746".. 51 | "056405c9040000001d90".. 52 | "056405c905000000f552".. 53 | "056405c906000000b458".. 54 | "056405c9070000005c9a".. 55 | "056405c90800000019b9".. 56 | "056405c909000000f17b".. 57 | "056405c90a000000b071".. 58 | "056405c90b00000058b3".. 59 | "056405c90c0000003265".. 60 | "056405c90d000000daa7".. 61 | "056405c90e0000009bad".. 62 | "056405c90f000000736f".. 63 | "056405c91000000011eb".. 64 | "056405c911000000f929".. 65 | "056405c912000000b823".. 66 | "056405c91300000050e1".. 67 | "056405c9140000003a37".. 68 | "056405c915000000d2f5".. 69 | "056405c91600000093ff".. 70 | "056405c9170000007b3d".. 71 | "056405c9180000003e1e".. 72 | "056405c919000000d6dc".. 73 | "056405c91a00000097d6".. 74 | "056405c91b0000007f14".. 75 | "056405c91c00000015c2".. 76 | "056405c91d000000fd00".. 77 | "056405c91e000000bc0a".. 78 | "056405c91f00000054c8".. 79 | "056405c920000000014f".. 80 | "056405c921000000e98d".. 81 | "056405c922000000a887".. 82 | "056405c9230000004045".. 83 | "056405c9240000002a93".. 84 | "056405c925000000c251".. 85 | "056405c926000000835b".. 86 | "056405c9270000006b99".. 87 | "056405c9280000002eba".. 88 | "056405c929000000c678".. 89 | "056405c92a0000008772".. 90 | "056405c92b0000006fb0".. 91 | "056405c92c0000000566".. 92 | "056405c92d000000eda4".. 93 | "056405c92e000000acae".. 94 | "056405c92f000000446c".. 95 | "056405c93000000026e8".. 96 | "056405c931000000ce2a".. 97 | "056405c9320000008f20".. 98 | "056405c93300000067e2".. 99 | "056405c9340000000d34".. 100 | "056405c935000000e5f6".. 101 | "056405c936000000a4fc".. 102 | "056405c9370000004c3e".. 103 | "056405c938000000091d".. 104 | "056405c939000000e1df".. 105 | "056405c93a000000a0d5".. 106 | "056405c93b0000004817".. 107 | "056405c93c00000022c1".. 108 | "056405c93d000000ca03".. 109 | "056405c93e0000008b09".. 110 | "056405c93f00000063cb".. 111 | "056405c940000000584a".. 112 | "056405c941000000b088".. 113 | "056405c942000000f182".. 114 | "056405c9430000001940".. 115 | "056405c9440000007396".. 116 | "056405c9450000009b54".. 117 | "056405c946000000da5e".. 118 | "056405c947000000329c".. 119 | "056405c94800000077bf".. 120 | "056405c9490000009f7d".. 121 | "056405c94a000000de77".. 122 | "056405c94b00000036b5".. 123 | "056405c94c0000005c63".. 124 | "056405c94d000000b4a1".. 125 | "056405c94e000000f5ab".. 126 | "056405c94f0000001d69".. 127 | "056405c9500000007fed".. 128 | "056405c951000000972f".. 129 | "056405c952000000d625".. 130 | "056405c9530000003ee7".. 131 | "056405c9540000005431".. 132 | "056405c955000000bcf3".. 133 | "056405c956000000fdf9".. 134 | "056405c957000000153b".. 135 | "056405c9580000005018".. 136 | "056405c959000000b8da".. 137 | "056405c95a000000f9d0".. 138 | "056405c95b0000001112".. 139 | "056405c95c0000007bc4".. 140 | "056405c95d0000009306".. 141 | "056405c95e000000d20c".. 142 | "056405c95f0000003ace".. 143 | "056405c9600000006f49".. 144 | "056405c961000000878b".. 145 | "056405c962000000c681".. 146 | "056405c9630000002e43".. 147 | "056405c9640000004495") 148 | local status, respone = comm.exchange(host, port, reqlinkstat, settimeout) 149 | if ( status and (#respone > 9) ) then 150 | local d, protocol_id1 = bin.unpack("C",respone,1) 151 | local d, protocol_id2 = bin.unpack("C",respone,2) 152 | if ( protocol_id1 == 0x05 ) then 153 | if ( protocol_id2 == 0x64 ) then 154 | local d, dstcode = bin.unpack("C", respone, 7) 155 | output["Source address"] = dstcode 156 | local d, srccode = bin.unpack("C", respone, 5) 157 | output["Destination address"] = srccode 158 | local d, ctrcode = bin.unpack("C", respone, 4) 159 | output["Control code"] = ctrcode 160 | set_nmap(host, port) 161 | return output 162 | end 163 | end 164 | else 165 | return nil 166 | end 167 | end -------------------------------------------------------------------------------- /melsecq-discover-udp.nse: -------------------------------------------------------------------------------- 1 | -- Nmap Scripting Engine 2 | -- required packages for this script 3 | -- 4 | -- ICS Security Workspace(plcscan.org) 5 | -- ICS Discovery Tools Releases 6 | --- 7 | 8 | local bin = require "bin" 9 | local nmap = require "nmap" 10 | local shortport = require "shortport" 11 | local stdnse = require "stdnse" 12 | local string = require "string" 13 | local table = require "table" 14 | 15 | --Usage: 16 | --Identify MELSEC-Q Series PLC CPUINFO 17 | --nmap -script melsecq-discover-udp.nse -sU -p 5006 18 | 19 | --Output Example: 20 | --PORT STATE SERVICE REASON 21 | --5006/udp open Mitsubishi/Melsoft udp syn-ack 22 | --| melsecq-discover: 23 | --|_ CPUINFO: Q03UDECPU 24 | 25 | 26 | description = [[ 27 | discovery Mitsubishi Electric Q Series PLC 28 | GET CPUINFO 29 | ]] 30 | 31 | 32 | author = "ICS Security Workspace(plcscan.org)" 33 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 34 | categories = {"discovery","intrusive"} 35 | 36 | function set_nmap(host, port) 37 | port.state = "open" 38 | port.version.name = "MelsoftUdp" 39 | port.version.product = "Mitsubishi Q PLC" 40 | nmap.set_port_version(host, port) 41 | nmap.set_port_state(host, port, "open") 42 | 43 | end 44 | 45 | function send_receive(socket, query) 46 | local sendstatus, senderr = socket:send(query) 47 | if(sendstatus == false) then 48 | return "Error Sending getcpuinfopack" 49 | end 50 | local rcvstatus,response = socket:receive() 51 | if(rcvstatus == false) then 52 | return "Error Reading getcpuinfopack" 53 | end 54 | return response 55 | end 56 | 57 | portrule = shortport.port_or_service(5006, "MelsoftUdp", "udp") 58 | action = function(host,port) 59 | local getcpuinfopack = bin.pack("H","57000000001111070000ffff030000fe03000014001c080a080000000000000004" .. "0101" .. "010000000001") 60 | local response 61 | local output = stdnse.output_table() 62 | local sock = nmap.new_socket() 63 | local constatus,conerr = sock:connect(host,port) 64 | if not constatus then 65 | stdnse.print_debug(1, 66 | 'Error establishing connection for %s - %s', host,conerr 67 | ) 68 | return nil 69 | end 70 | response = send_receive(sock, getcpuinfopack) 71 | local mel, pack_head = bin.unpack("C", response, 1) 72 | -- local mel, space_id = bin.unpack("C", response, 55) 73 | local offset = 0 74 | if ( pack_head == 0xd7) then 75 | -- if ( space_id == 0x20) then 76 | local mel 77 | local mel, cpuinfo = bin.unpack("z", response, 42 + offset) 78 | output["CPUINFO"] = string.sub(cpuinfo, 1, 16) 79 | set_nmap(host, port) 80 | sock:close() 81 | return output 82 | -- end 83 | else 84 | sock:close() 85 | return nil 86 | 87 | end 88 | 89 | 90 | end -------------------------------------------------------------------------------- /melsecq-discover.nse: -------------------------------------------------------------------------------- 1 | -- Nmap Scripting Engine 2 | -- required packages for this script 3 | -- 4 | -- ICS Security Workspace(plcscan.org) 5 | -- ICS Discovery Tools Releases 6 | --- 7 | 8 | local bin = require "bin" 9 | local nmap = require "nmap" 10 | local shortport = require "shortport" 11 | local stdnse = require "stdnse" 12 | local string = require "string" 13 | local table = require "table" 14 | 15 | --Usage: 16 | --Identify MELSEC-Q Series PLC CPUINFO 17 | --nmap -script melsecq-discover -sT -p 5007 18 | 19 | 20 | --Output Example: 21 | --PORT STATE SERVICE REASON 22 | --5007/tcp open MelsoftTCP syn-ack 23 | --| melsecq-discover: 24 | --|_ CPUINFO: Q03UDECPU 25 | 26 | 27 | description = [[ 28 | discovery Mitsubishi Electric Q Series PLC 29 | GET CPUINFO 30 | ]] 31 | 32 | author = "ICS Security Workspace(plcscan.org)" 33 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 34 | categories = {"discovery","intrusive"} 35 | 36 | function set_nmap(host, port) 37 | port.state = "open" 38 | port.version.name = "MelsoftTCP" 39 | port.version.product = "Mitsubishi Q PLC" 40 | nmap.set_port_version(host, port) 41 | nmap.set_port_state(host, port, "open") 42 | 43 | end 44 | 45 | function send_receive(socket, query) 46 | local sendstatus, senderr = socket:send(query) 47 | if(sendstatus == false) then 48 | return "Error Sending getcpuinfopack" 49 | end 50 | local rcvstatus,response = socket:receive() 51 | if(rcvstatus == false) then 52 | return "Error Reading getcpuinfopack" 53 | end 54 | return response 55 | end 56 | 57 | portrule = shortport.port_or_service(5007, "MelsoftTCP", "tcp") 58 | action = function(host,port) 59 | local getcpuinfopack = bin.pack("H","57000000001111070000ffff030000fe03000014001c080a080000000000000004" .. "0101" .. "010000000001") 60 | local response 61 | local output = stdnse.output_table() 62 | local sock = nmap.new_socket() 63 | local constatus,conerr = sock:connect(host,port) 64 | if not constatus then 65 | stdnse.print_debug(1, 66 | 'Error establishing connection for %s - %s', host,conerr 67 | ) 68 | return nil 69 | end 70 | response = send_receive(sock, getcpuinfopack) 71 | local mel, pack_head = bin.unpack("C", response, 1) 72 | -- local mel, space_id = bin.unpack("C", response, 55) 73 | local offset = 0 74 | if ( pack_head == 0xd7) then 75 | -- if ( space_id == 0x20) then 76 | local mel 77 | local mel, cpuinfo = bin.unpack("z", response, 42 + offset) 78 | output["CPUINFO"] = string.sub(cpuinfo, 1, 16) 79 | set_nmap(host, port) 80 | sock:close() 81 | return output 82 | -- end 83 | else 84 | sock:close() 85 | return nil 86 | 87 | end 88 | 89 | 90 | end -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /s71200-enumerate-old.nse: -------------------------------------------------------------------------------- 1 | -- Nmap Scripting Engine 2 | -- required packages for this script 3 | -- 4 | -- ICS Discovery Tools Releases 5 | -- ICS Security Workspace(plcscan.org) 6 | --- 7 | 8 | local bin = require "bin" 9 | local nmap = require "nmap" 10 | local shortport = require "shortport" 11 | local stdnse = require "stdnse" 12 | local string = require "string" 13 | local table = require "table" 14 | 15 | 16 | description = [[ 17 | discover Siemens SIMATIC S7 1200 PLC. 18 | Based on TIA Portal software. 19 | 20 | ]] 21 | 22 | author = "ICS Security Workspace(plcscan.org)" 23 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 24 | categories = {"discovery","intrusive"} 25 | 26 | -- 27 | -- @usage 28 | -- nmap -sP --script s71200-enumerate-old.nse -p 102 29 | -- 30 | -- @output 31 | --102/tcp open Siemens S7 1200 Ethernet Controller 32 | --| s71200-enumerate-old.nse: 33 | --| Basic Hardware: 6ES7 212-1HE31-0XB0 34 | --|_ Firmware Version: V3.0 35 | -- 36 | 37 | function set_nmap(host, port) 38 | port.state = "open" 39 | port.version.name = "iso-tsap" 40 | port.version.product = "Siemens S7 1200 Ethernet Controller" 41 | nmap.set_port_version(host, port) 42 | nmap.set_port_state(host, port, "open") 43 | 44 | end 45 | 46 | function send_receive(socket, query) 47 | local sendstatus, senderr = socket:send(query) 48 | if(sendstatus == false) then 49 | return "Error Sending pack" 50 | end 51 | local rcvstatus,response = socket:receive() 52 | if(rcvstatus == false) then 53 | return "Error Reading pack" 54 | end 55 | return response 56 | end 57 | 58 | portrule = shortport.port_or_service(102, "iso-tsap", "tcp") 59 | action = function(host,port) 60 | -- soft handshake Methods one 61 | local connectpack = bin.pack("H","030000231ee00000000600c1020600c20f53494d415449432d524f4f542d4553c0010a") 62 | -- soft handshake Methods two 63 | -- local connectpack = bin.pack("H","0300001611e00000000800c1020600c2020600c0010a") 64 | -- send local connection packet(IE NIC and session) 65 | local gethwinfo = bin.pack("H","030000e502f080720100d631000004ca00000001".. 66 | "00000120360000011d00040000000000a1000000".. 67 | "d3821f0000a38169001515536572766572536573".. 68 | "73696f6e5f31433943333932a3822100152c313a".. 69 | "3a3a362e303a3a5443502f4950202d3e2042726f".. 70 | "6164636f6d204e65744c696e6b2028544d29202e".. 71 | "2e2ea38228001500a38229001500a3822a001516".. 72 | "4846504654385246375052474837595f34313831".. 73 | "3731a3822b000401a3822c001201c9c392a3822d".. 74 | "001500a1000000d3817f0000a381690015155375".. 75 | "62736372697074696f6e436f6e7461696e6572a2".. 76 | "a20000000072010000") 77 | local response 78 | local output = stdnse.output_table() 79 | local sock = nmap.new_socket() 80 | local constatus,conerr = sock:connect(host,port) 81 | if not constatus then 82 | stdnse.print_debug(1, 83 | 'Error establishing connection for %s - %s', host,conerr 84 | ) 85 | return nil 86 | end 87 | response = send_receive(sock, connectpack) 88 | local s7, length_hex = bin.unpack("C", response, 4) 89 | if ( length_hex == 0x23 ) then 90 | s7, output["Connectstatus"] = "S7 Connect ok" 91 | response = send_receive(sock, gethwinfo) 92 | local s7, protocol_id = bin.unpack("C", response, 1) 93 | if ( #response > 80 and protocol_id == 0x03 ) then 94 | local s, e = string.find(response, "6ES7") 95 | local o, d = string.find(response, "V") 96 | local offset = 15 97 | output["Basic Hardware"] = string.sub(response, s, e + offset) 98 | output["Firmware Version"] = string.sub(response, o, d + 3) 99 | set_nmap(host, port) 100 | sock:close() 101 | return output 102 | end 103 | sock:close() 104 | end 105 | end --------------------------------------------------------------------------------