DNP3 Recon
Most protocols (particularly SCADA protocols) and many field devices have a “magic packet” that allows them to say HERE I AM! that are great candidates for discovery algorithms in vulnerability scanners such as Nessus.
I discussed this phenomena in an ISA Talk back in 2003. See the slide on “discovery protocols.” The best way to find these is to capture traffic when you unbox your field device and fire up the (typically) Windows configuration utility, which will probe devices on the broadcast (or even multicast) network for some weird UDP port.
Howver, DNP3 (if unsolicited messages are turned off) tends to follow the “if you don’t have anything nice to say, don’t say anything at all” philosophy. This is much different from the behavior of ICCP/COTP which provide an “address unknown” message if you get the TSAP wrong (some vendors even give some juicy bits like a “(c) vendorname” for good measure, thanks guys!) or send “protocol error” back if you send an invalid message.
The DNP3 REQUEST_LINK_STATUS message (FC 9) is useful for this, if you get the destination address right it sends back an 0×0B as you can see below. Notice how the 5th byte (immediately after the control field, 0xC9) increments, ignoring all the requests to messages except for the valid one (in this case, 4)
mdfranz@franz-d610:~/dev/pydnp$ ./dnptest.py 192.168.169.11 Src: 0 Dst: 0 Sending 056405c900000000364c Closed by 192.168.169.11 Src: 0 Dst: 1 Sending 056405c901000000de8e Closed by 192.168.169.11 Src: 0 Dst: 2 Sending 056405c9020000009f84 Closed by 192.168.169.11 Src: 0 Dst: 3 Sending 056405c9030000007746 Closed by 192.168.169.11 Src: 0 Dst: 4 Sending 056405c9040000001d90 Received:0564050b000004003e6c Src: 0 Dst: 5 Sending 056405c905000000f552 Closed by 192.168.169.11 [(4, 0)]
Not only can we identify a DNP3 slave based on the 5th frame, but we now know its link address and can start moving up the stack.
1. 192.168.169.61 -> 192.168.169.11 TCP 46364 > 20000 [SYN] Seq=0 Len=0 MSS=1460 TSV=83393561 TSER=0 WS=2
2. 192.168.169.11 -> 192.168.169.61 TCP 20000 > 46364 [SYN, ACK] Seq=0 Ack=1 Win=17520 Len=0 MSS=1460 WS=0 TSV=0 TSER=0
3. 192.168.169.61 -> 192.168.169.11 TCP 46364 > 20000 [ACK] Seq=1 Ack=1 Win=5840 Len=0 TSV=83393561 TSER=0
4. 192.168.169.61 -> 192.168.169.11 DNP 3.0 len=5, from 0 to 4, Request Link Status
5. 192.168.169.11 -> 192.168.169.61 DNP 3.0 len=5, from 4 to 0, Status of Link
6. 192.168.169.61 -> 192.168.169.11 TCP 46364 > 20000 [ACK] Seq=11 Ack=11 Win=5840 Len=0 TSV=83393571 TSER=1742209
7. 192.168.169.61 -> 192.168.169.11 TCP 46364 > 20000 [FIN, ACK] Seq=11 Ack=11 Win=5840 Len=0 TSV=83393574 TSER=1742209
8. 192.168.169.11 -> 192.168.169.61 TCP 20000 > 46364 [ACK] Seq=11 Ack=12 Win=17510 Len=0 TSV=1742209 TSER=83393574
9. 192.168.169.11 -> 192.168.169.61 TCP 20000 > 46364 [FIN, ACK] Seq=11 Ack=12 Win=17510 Len=0 TSV=1742218 TSER=83393574
10. 192.168.169.61 -> 192.168.169.11 TCP 46364 > 20000 [ACK] Seq=12 Ack=12 Win=5840 Len=0 TSV=83394392 TSER=1742218
11. 192.168.169.61 -> 192.168.169.11 TCP 46365 > 20000 [SYN] Seq=0 Len=0 MSS=1460 TSV=83396574 TSER=0 WS=2
12. 192.168.169.11 -> 192.168.169.61 TCP 20000 > 46365 [SYN, ACK] Seq=0 Ack=1 Win=17520 Len=0 MSS=1460 WS=0 TSV=0 TSER=0
13. 192.168.169.61 -> 192.168.169.11 TCP 46365 > 20000 [ACK] Seq=1 Ack=1 Win=5840 Len=0 TSV=83396574 TSER=0
14. 192.168.169.61 -> 192.168.169.11 DNP 3.0 len=5, from 0 to 5, Request Link Status
15. 192.168.169.11 -> 192.168.169.61 TCP 20000 > 46365 [ACK] Seq=1 Ack=11 Win=17510 Len=0 TSV=1742241 TSER=83396575
16. 192.168.169.61 -> 192.168.169.11 TCP 46365 > 20000 [FIN, ACK] Seq=11 Ack=1 Win=5840 Len=0 TSV=83397576 TSER=1742241
17. 192.168.169.11 -> 192.168.169.61 TCP 20000 > 46365 [ACK] Seq=1 Ack=12 Win=17510 Len=0 TSV=1742249 TSER=83397576
18. 192.168.169.11 -> 192.168.169.61 TCP 20000 > 46365 [FIN, ACK] Seq=1 Ack=12 Win=17510 Len=0 TSV=1742260 TSER=83397576
19. 192.168.169.61 -> 192.168.169.11 TCP 46365 > 20000 [ACK] Seq=12 Ack=2 Win=5840 Len=0 TSV=83398589 TSER=1742260
Although this is the output of a simple Python tool (but not as simple as I would have liked since DNP3 goes absolutely crazy with CRC16’s both in the link layer and after every 16 bytes of data in the application layer) these are the sort of checks we are writing for Nessus in NASL3.
Author: Matt Franz
Posted: October 18th, 2006 under DNP3, Nessus SCADA Plugins.
Comments: 3
Comments
Comment from Roy
Time: October 19, 2006, 12:08 pm
Interesting that a DNP protocol analyzer doesn’t find anything of substance in that exchange. What happens if you interrogate address 65535? (that’s the DNP broadcast address) And is your code available? I have some DNP gear I could throw it against.
Comment from Jake Brodsky
Time: October 19, 2006, 1:04 pm
Regarding the “absolutely crazy” scheme for CRC checks, Matt, people often forget that DNP3 came from the less reliable serial communications world.
Let me tell you a war story: About six or seven years ago we got a call from our control center that they were getting poor communications with RTUs on two different radio channels. We gathered our gear and trudged to the master site. Lo and behold it was picking up some weird interference. Mind you, we were operating on licensed radio frequencies, so we were very curious as to what this could be.
We identified the interference as some form of very powerful, but relatively slow chip, frequency hopping spread spectrum. At that time, few knew what spread spectrum was. However, I did happen to know what it was because I used to be a member of a ham radio club which experimented with spread spectrum in the early 1980s.
With the signal identified, we began a Direction Finding operation. We only managed to grab one bearing on this interference source before it disappeared, and we haven’t seen it since. Not surprisingly, this interference source seemed to be coming from the Downtown Washington DC/Pentagon area.
In any case, while the interference was going on I noticed some very rude things. Most of the time when you get interference on an asynchronous line, it trashes enough of the data that the UART drops the whole character off the face of the earth. When that happens, it’s easy to catch and discard a packet. Even a timeout from an incomplete packet can be used to invalidate the transmission.
However, in the case of our spread spectrum interference, it would smash a bit here, a bit there, but somehow leave the rest of the character intact! The UART didn’t dump the character, the packets still had the correct number of characters. So how did our protocol do? Not so nice, as it turns out. I watched in horror as the software had no choice but to validate traffic that I clearly could see should have fallen on the floor. It would happen in roughly one out of every 50 to 100 packets. This wasn’t very good.
The bottom line to this sordid war story is that when it came time for me to select a protocol to replace the old system with, I chose one with the most robust error checking I could find; and that’s how I ended up using DNP3.
Remember DNP3’s roots, Matt. It came from the serial world. There were no other CRCs to protect the traffic. Most of what the DNP folks did when it came time to build a TCP/IP version was to insert the whole frame as it would have been sent on a serial line. Customers still like that sort of error checking. As it turns out, if you use a TCP connection to send these packets, the chances of a bit error to affect the packet without being noticed are exceedingly small. And that’s where we like it…
Comment from Matt Franz
Time: October 23, 2006, 4:56 pm
Jake,
The “absolutely crazy” was meant to be quite tongue in cheek, but I (and probably many readers) appreciate the background info.
Roy,
The Triangle Microworks Protocol Test Harness didn’t really seem to respond to broadcasts, not sure why.
Right now the code (and other small tools) aren’t available but might be on our new site sometime in the futre.
- mdf
Write a comment