Recently I had been looking at PCAP traces of a PLC5 communicating with RsLinx. Wireshark just saw it as a blob of data on top of the TCP header. Well this just would not do. Wireshark provides a nice interface for using LUA to write your own dissector. This is what I ended up doing for the CSPv4 data (which is actually CSPv4 Header + LSAP + PCCC or PC cubed). An added bonus of writing a dissector for an unknown protocol is that the protocol filter will also register the bytes you define, so you can easily filter a packet stream with your newly defined byte fields.
A big thanks to these two articles from Lynn’s Iatips, specifically:
- http://iatips.wikispaces.com/Rockwell-CSP-Header
- http://iatips.wikispaces.com/Rockwell-CSP-Local-Remote-Header
As well as the Rockwell document that provided valuable PCCC format information (Chapter 6,7):
Wireshark Dissector for PLC5 – CSPv4 + LSAP + PCCC
Without further ado – the LUA code for the Wireshark dissector. Following this code include is a screenshot and instructions of how to include this parser within Wireshark.
You may download it here – cspv4.lua
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 | -- CSPv4 Parser -------------------------------- -- -- Date: July 25, 2012 -- Author: Erik Schweigert -- E-mail: [email protected] -- -- Purpose: To decode the CSPv4 Packet -- CSPv4 + LSAP + PCCC ------------------------------------------------ p_cspv4 = Proto("cspv4","CSPv4") p_lsap = Proto("lsap","LSAP") p_pccc = Proto("pccc","PCCC") -- ----------------- CSPv4 Header ------------ local f_mode = ProtoField.uint8("cspv4.mode", "Mode", base.HEX) local f_submode = ProtoField.uint8("cspv4.submode", "Submode", base.HEX) local f_data_length = ProtoField.uint16("cspv4.data_length", "Data Length", base.HEX) local f_conn_id = ProtoField.uint32("cspv4.conn_id", "Connection ID [slave/server]", base.HEX) local f_status = ProtoField.uint32("cspv4.status", "Status", base.HEX) local f_context = ProtoField.bytes("cspv4.context", "Context", base.HEX) -- ---------------- End CSPv4 Header ----------- -- ------------------ LSAP --------------------- -- Local local f_dest = ProtoField.uint8("cspv4.dst", "Destination Byte", base.HEX) local f_res5 = ProtoField.uint8("cspv4.res5", "Control Byte", base.HEX) local f_src = ProtoField.uint8("cspv4.src", "Source Byte [Master Address]", base.HEX) local f_lsap = ProtoField.uint8("cspv4.lsap", "LSAP", base.HEX) -- Remote local f_resX = ProtoField.uint8("cspv4.resX", "Mystery Byte", base.HEX) local f_dst_link = ProtoField.uint16("cspv4.dst_link","Destination Link Address", base.HEX) local f_dst_station = ProtoField.uint16("cspv4.dst_station", "Destination Station Address", base.HEX) local f_resY = ProtoField.uint8("cspv4.resY", "Mystery Byte 2", base.HEX) local f_src_link = ProtoField.uint16("cspv4.src_link", "Source Link Address", base.HEX) local f_src_station = ProtoField.uint16("cspv4.src_station", "Source Station Address", base.HEX) local f_resZ = ProtoField.uint8("cspv4.resZ", "Mystery Byte 3", base.HEX) -- ------------------ End LSAP ------------------ -- ------------------ PCCC ---------------------- local f_pccc_command = ProtoField.uint8("cspv4.pccc_command", "Command Code", base.HEX) local f_pccc_sts = ProtoField.uint8("cspv4.pccc_sts", "Status Code", base.HEX) local f_pccc_tns = ProtoField.uint16("cspv4.pccc_tns", "Transaction Number", base.HEX) local f_pccc_fnc = ProtoField.uint8("cspv4.pccc_fnc", "Function Code", base.HEX) local f_pccc_addr = ProtoField.uint16("cspv4.pccc_addr", "Address of Memory Location", base.HEX) local f_pccc_size = ProtoField.uint8("cspv4.pccc_size", "Size", base.HEX) local f_pccc_data = ProtoField.bytes("cspv4.pccc_data", "Data", base.HEX) -- ------------------ End PCCC ------------------- -- CSPv4 Fields p_cspv4.fields = {f_mode} p_cspv4.fields = {f_submode} p_cspv4.fields = {f_data_length} p_cspv4.fields = {f_conn_id} p_cspv4.fields = {f_status} p_cspv4.fields = {f_context} p_cspv4.fields = {f_dest} p_cspv4.fields = {f_res5} p_cspv4.fields = {f_src} p_cspv4.fields = {f_lsap} -- Remote LSAP Fields p_cspv4.fields = {f_resX} p_cspv4.fields = {f_dst_link} p_cspv4.fields = {f_dst_station} p_cspv4.fields = {f_resY} p_cspv4.fields = {f_src_link} p_cspv4.fields = {f_src_station} p_cspv4.fields = {f_resZ} -- PCCC Fields p_cspv4.fields = {f_pccc_command} p_cspv4.fields = {f_pccc_sts} p_cspv4.fields = {f_pccc_tns} p_cspv4.fields = {f_pccc_fnc} p_cspv4.fields = {f_pccc_addr} p_cspv4.fields = {f_pccc_size} p_cspv4.fields = {f_pccc_data} function build_cspv4_header(buf) build_request(buf) build_submode(buf) subtree:add(f_data_length, buf(2,2)) subtree:add(f_conn_id, buf(4,4)) subtree:add(f_status, buf(8,4)) subtree:add(f_context, buf(12,16)) end function build_request(buf) if buf(0,1):uint() == 1 then subtree:add(f_mode, buf(0,1)):append_text(" (Request)") elseif buf(0,1):uint() == 2 then subtree:add(f_mode, buf(0,1)):append_text(" (Response)") else subtree:add(f_mode, buf(0,1)) end end function build_submode(buf) if buf(1,1):uint() == 1 then subtree:add(f_submode, buf(1,1)):append_text(" (Connection)") elseif buf(1,1):uint() == 7 then subtree:add(f_submode, buf(1,1)):append_text(" (PCCC)") else subtree:add(f_submode, buf(1,1)) end end function build_lsap(buf, root) lsap_tree = root:add(p_lsap, buf(28)) lsap_tree:add(f_dest, buf(28,1)) lsap_tree:add(f_res5, buf(29,1)) lsap_tree:add(f_src, buf(30,1)) if buf(31,1):uint() == 0 then lsap_tree:add(f_lsap, buf(31,1)):append_text(" (Local Form)") elseif buf(31,1):uint() == 1 then lsap_tree:add(f_lsap, buf(31,1)):append_text(" (Remote Form)") build_lsap_remote(buf, lsap_tree) else lsap_tree:add(f_lsap, buf(31,1)) end end function build_lsap_remote(buf, lsap_tree) lsap_tree:add(f_resX, buf(32,1)) lsap_tree:add(f_dst_link, buf(33,2)) lsap_tree:add(f_dst_station, buf(35,2)) lsap_tree:add(f_resY, buf(37,1)) lsap_tree:add(f_src_link, buf(38,2)) lsap_tree:add(f_src_station, buf(40,2)) lsap_tree:add(f_resZ, buf(42,1)) end function build_pccc(buf, root) pccc_tree = root:add(p_pccc, buf(32)) -- Ensure its PCCCC if buf(1,1):uint() ~= 7 then end if buf(31,1):uint() == 1 then offset = 11 else offset = 0 end pccc_tree:add(f_pccc_command, buf(32 + offset, 1)) pccc_tree:add(f_pccc_sts, buf(33 + offset, 1)) pccc_tree:add(f_pccc_tns, buf(34 + offset, 2)) pccc_tree:add(f_pccc_fnc, buf(36 + offset, 1)) pccc_tree:add(f_pccc_addr, buf(37 + offset, 2)) pccc_tree:add(f_pccc_size, buf(39 + offset, 1)) pccc_tree:add(f_pccc_data, buf(40 + offset, buf:len() - (40 + offset))) end -- cspv4 dissector function function p_cspv4.dissector (buf, pkt, root) -- validate packet length is adequate, otherwise quit if buf:len() == 0 then return end pkt.cols.protocol = p_cspv4.name -- create subtree for cspv4 subtree = root:add(p_cspv4, buf(0)) -- add protocol fields to subtree build_cspv4_header(buf) build_lsap(buf, root) build_pccc(buf, root) -- description of payload subtree:set_text("CSPv4, CSPv4 Header Information") -- add debug info if debug field is not nil if f_debug then -- write debug values subtree:add(f_debug, buf:len()) end end -- Initialization routine function p_cspv4.init() end -- register a chained dissector for port 2222 local tcp_dissector_table = DissectorTable.get("tcp.port") dissector = tcp_dissector_table:get_dissector(2222) -- you can call dissector from function p_cspv4.dissector above -- so that the previous dissector gets called tcp_dissector_table:add(2222, p_cspv4) |
As you can see there is nothing ground breaking in this parser, and the code itself is quite rudimentary. A great enhancement would be to add the textual value of what the PCCC command vs function code actually equates to (read bit, write bit, etc).
The results of installing the LUA code above to decipher the PLC5 – CSPv4 data is:
Installing LUA Dissector to Wireshark
- Save the lua script above to any folder and call the file cspv4.lua
- Open init.lua in the Wireshark installation directory for editing. In Linux it can be found in /etc/wireshark/init.lua. You will need Admin privileges on Windows Vista and 7.
- Comment out the following line in init.lua (single line comments begin with
--):1
disable_lua = true; do return end;
- Add the following lines to init.lua (at the very end):
1
dofile("/path/to/the/file/cspv4.lua")
- Run Wireshark
- Load a capture file that has the packets of your custom protocol or start a live capture.
Now you have enhanced Wireshark to properly dissect your PLC5 packets – at least if they are CSPv4 with PCCC.




Latest Comments