summaryrefslogtreecommitdiffstats
path: root/scripts/omron-info.nse
blob: 54561c4f9110aea0ca5a5ee8439651bc1d088d52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"

description = [[
This NSE script is used to send a FINS packet to a remote device. The script
will send a Controller Data Read Command and once a response is received, it
validates that it was a proper response to the command that was sent, and then
will parse out the data.
]]
---
-- @usage
-- nmap --script omron-info -sU -p 9600 <host>
--
-- @output
-- 9600/tcp open  OMRON FINS
-- | omron-info:
-- |   Controller Model: CJ2M-CPU32          02.01
-- |   Controller Version: 02.01
-- |   For System Use:
-- |   Program Area Size: 20
-- |   IOM size: 23
-- |   No. DM Words: 32768
-- |   Timer/Counter: 8
-- |   Expansion DM Size: 1
-- |   No. of steps/transitions: 0
-- |   Kind of Memory Card: 0
-- |_  Memory Card Size: 0

-- @xmloutput
-- <elem key="Controller Model">CS1G_CPU44H         03.00</elem>
-- <elem key="Controller Version">03.00</elem>
-- <elem key="For System Use"></elem>
-- <elem key="Program Area Size">20</elem>
-- <elem key="IOM size">23</elem>
-- <elem key="No. DM Words">32768</elem>
-- <elem key="Timer/Counter">8</elem>
-- <elem key="Expansion DM Size">1</elem>
-- <elem key="No. of steps/transitions">0</elem>
-- <elem key="Kind of Memory Card">0</elem>
-- <elem key="Memory Card Size">0</elem>


author = "Stephen Hilt (Digital Bond)"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "version"}

--
-- Function to define the portrule as per nmap standards
--
--
portrule = shortport.version_port_or_service(9600, "fins", {"tcp", "udp"})

---
--  Function to set the nmap output for the host, if a valid OMRON FINS packet
--  is received then the output will show that the port is open instead of
--  <code>open|filtered</code>
--
-- @param host Host that was passed in via nmap
-- @param port port that FINS is running on (Default UDP/9600)
function set_nmap(host, port)

  --set port Open
  port.state = "open"
  -- set version name to OMRON FINS
  port.version.name = "fins"
  nmap.set_port_version(host, port)
  nmap.set_port_state(host, port, "open")

end

local memcard = {
  [0] = "No Memory Card",
  [1] = "SPRAM",
  [2] = "EPROM",
  [3] = "EEPROM"
}

function memory_card(value)
  local mem_card = memcard[value] or "Unknown Memory Card Type"
  return mem_card
end
---
--  send_udp is a function that is used to run send the appropriate traffic to
--  the omron devices via UDP
--
-- @param socket Socket that is passed in from Action
function send_udp(socket)
  local controller_data_read = stdnse.fromhex( "800002000000006300ef050100")
  -- send Request Information Packet
  socket:send(controller_data_read)
  local rcvstatus, response = socket:receive()
  return response
end
---
--  send_tcp is a function that is used to run send the appropriate traffic to
--  the omron devices via TCP
--
-- @param socket Socket that is passed in from Action
function send_tcp(socket)
  -- this is the request address command
  local req_addr = stdnse.fromhex( "46494e530000000c000000000000000000000000")
  -- TCP requires a network address that is revived from the first request,
  -- The read controller data these two strings will be joined with the address
  local controller_data_read = stdnse.fromhex("46494e5300000015000000020000000080000200")
  local controller_data_read2 = stdnse.fromhex("000000ef050501")

  -- send Request Information Packet
  socket:send(req_addr)
  local rcvstatus, response = socket:receive()
  local header = string.byte(response, 1)
  if(header == 0x46) then
    local address = string.byte(response, 24)
    local controller_data = ("%s%c%s%c"):format(controller_data_read, address, controller_data_read2, 0x00)
    -- send the read controller data request
    socket:send(controller_data)
    local rcvstatus, response = socket:receive()
    return response
  end
  return "ERROR"
end

---
--  Action Function that is used to run the NSE. This function will send the initial query to the
--  host and port that were passed in via nmap. The initial response is parsed to determine if host
--  is a FINS supported device.
--
-- @param host Host that was scanned via nmap
-- @param port port that was scanned via nmap
action = function(host,port)

  -- create table for output
  local output = stdnse.output_table()
  -- create new socket
  local socket = nmap.new_socket()
  local catch = function()
    socket:close()
  end
  -- create new try
  local try = nmap.new_try(catch)
  -- connect to port on host
  try(socket:connect(host, port))
  -- init response var
  local response = ""
  -- set offset to 0, this will mean its UDP
  local offset = 0
  -- check to see if the protocol is TCP, if it is set offset to 16
  -- and perform the tcp_send function
  if (port.protocol == "tcp")then
    offset = 16
    response = send_tcp(socket)
    -- else its udp and call the send_udp function
  else
    response = send_udp(socket)
  end
  -- unpack the first byte for checking that it was a valid response
  local header = string.unpack("B", response, 1)
  if(header == 0xc0 or header == 0xc1 or header == 0x46) then
    set_nmap(host, port)
    local response_code = string.unpack("<I2", response, 13 + offset)
    -- test for a few of the error codes I saw when testing the script
    if(response_code == 2081) then
      output["Response Code"] = "Data cannot be changed (0x2108)"
    elseif(response_code == 290) then
      output["Response Code"] = "The mode is wrong (executing) (0x2201)"
      -- if a successful response code then
    elseif(response_code == 0) then
      -- parse information from response
      output["Response Code"] = "Normal completion (0x0000)"
      output["Controller Model"] = string.unpack("z", response,15 + offset)
      output["Controller Version"] = string.unpack("z", response, 35 + offset)
      output["For System Use"] = string.unpack("z", response, 55 + offset)
      local pos
      output["Program Area Size"], pos = string.unpack(">I2", response, 95 + offset)
      output["IOM size"], pos = string.unpack("B", response, pos)
      output["No. DM Words"], pos = string.unpack(">I2", response, pos)
      output["Timer/Counter"], pos = string.unpack("B", response, pos)
      output["Expansion DM Size"], pos = string.unpack("B", response, pos)
      output["No. of steps/transitions"], pos = string.unpack(">I2", response, pos)
      local mem_card_type
      mem_card_type, pos = string.unpack("B", response, pos)
      output["Kind of Memory Card"] = memory_card(mem_card_type)
      output["Memory Card Size"], pos = string.unpack(">I2", response, pos)

    else
      output["Response Code"] = "Unknown Response Code"
    end
    socket:close()
    return output

  else
    socket:close()
    return nil
  end

end