-
Notifications
You must be signed in to change notification settings - Fork 1
/
DNP3.cc
420 lines (349 loc) · 13.1 KB
/
DNP3.cc
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
//
// DNP3 was initially used over serial links; it defined its own application
// layer, transport layer, and data link layer. This hierarchy cannot be
// mapped to the TCP/IP stack directly. As a result, all three DNP3 layers
// are packed together as a single application layer payload over the TCP
// layer. Each DNP3 packet in the application layer may look like this DNP3
// Packet:
//
// DNP3 Link Layer | DNP3 Transport Layer | DNP3 Application Layer
//
// (This hierarchy can be viewed in the Wireshark visually.)
//
// === Background on DNP3
//
// 1. Basic structure of DNP3 Protocol over serial links. This information
// can be found in detail in
//
// DNP3 Specification Volume 2, Part 1 Basic, Application Layer
// DNP3 Specification Volume 4, Data Link Layer
//
// Traditionally, the DNP3 Application Layer in serial links contains a
// "DNP3 Application Layer Fragment". The data that is parsed by the end
// device and then executed. As the "DNP3 Application Layer Fragment" can
// be long (>255 bytes), it may be trunkcated and carried in different
// DNP3 Application Layer of more than one DNP3 packets.
//
// So we may find a long DNP3 Application Layer Fragment to be transmitted in the following format
//
// DNP3 Packet #1 : DNP3 Link Layer | DNP3 Transport Layer | DNP3 Application Layer #1
// DNP3 Packet #2 : DNP3 Link Layer | DNP3 Transport Layer | DNP3 Application Layer #2
// ....
// DNP3 Packet #n : DNP3 Link Layer | DNP3 Transport Layer | DNP3 Application Layer #n
//
// So to get the whole DNP3 application layer fragment, we concatenate
// each DNP3 Application Layer Data into a logic DNP3 Application Layer
// Fragment:
//
// DNP3 Application Layer #1 + DNP3 Application Layer #2 + ... + DNP3 Application Layer #n
//
// 2. Packing DNP3 Network Packet into TCP/IP stack
//
// We will call the original DNP3 Link Layer, Transport Layer and Application
// Layer used in serial link as Pseudo Link Layer, Pseudo Transport Layer and
// Pseudo Application Layer.
//
// For a long DNP3 application layer fragment, we may find it tramistted
// over IP network in the following format:
//
// Network Packet #1 : TCP Header | DNP3 Pseudo Link Layer | DNP3 Pseudo Transport Layer | DNP3 Pseudo Application Layer #1
// Network Packet #2 : TCP Header | DNP3 Pseudo Link Layer | DNP3 Pseudo Transport Layer | DNP3 Pseudo Application Layer #2
// ....
// Network Packet #n : TCP Header | DNP3 Pseudo Link Layer | DNP3 Pseudo Transport Layer | DNP3 Pseudo Application Layer #n
//
// === Challenges of Writing DNP3 Analyzer on Binpac ===
//
// The detailed structure of the DNP3 Link Layer is:
//
// 0x05 0x64 Len Ctrl Dest_LSB Dest_MSB Src_LSB Src_MSB CRC_LSB CRC_MSB
//
// Each field is a byte; LSB: least significant byte; MSB: most significatn byte.
//
// "Len" indicates the length of the byte stream right after this field
// (excluding CRC fields) in the current DNP3 packet.
//
// Since "Len" is of size one byte, the largest length it can represent is
// 255 bytes. The larget DNP3 Application Layer size is "255 - 5 + size of
// all CRC fields". "minus 5" is coming from the 5 bytes after "Len" field in
// the DNP3 Link Layer, i.e. Ctrl Dest_LSB Dest_MSB Src_LSB Src_MSB Hence,
// the largest size of a DNP3 Packet (DNP3 Data Link Layer : DNP3 Transport
// Layer : DNP3 Application Layer) can only be 292 bytes.
//
// The "Len" field indicates the length of of a single chunk of DNP3 Psuedo
// Application Layer data instead of the whole DNP3 Application Layer
// Fragment. However, we can not know the whole length of the DNP3
// Application Layer Fragment (which Binpac would normally need) until all
// chunks of Pseudo Application Layer Data are received.
//
// We hence exploit the internal flow_buffer class used in Binpac to buffer
// the application layer data until all chunk are received, which does
// require a bit of internal knowledge of the generated code.
//
// The binpac analyzer parses the DNP3 Application Layer Fragment. However,
// we manually add the original Pseudo Link Layer data as an additional
// header before the DNP3 Application Fragment. This helps to know how many
// bytes are in the current chunk of DNP3 application layer data (not the
// whole Application Layer Fragment).
//
// Graphically, the procedure is:
//
// DNP3 Packet : DNP3 Pseudo Data Link Layer : DNP3 Pseudo Transport Layer : DNP3 Pseudo Application Layer
// || ||
// || (length field) || (original paylad byte stream)
// \/ \/
// DNP3 Additional Header : Reassembled DNP3 Pseudo Application Layer Data
// ||
// \/
// Binpac DNP3 Analyzer
#include "DNP3.h"
#include <algorithm>
// #include "events.bif.h"
using namespace analyzer::dnp3;
using std::min;
const unsigned int PSEUDO_LENGTH_INDEX = 2; // index of len field of DNP3 Pseudo Link Layer
const unsigned int PSEUDO_CONTROL_FIELD_INDEX = 3; // index of ctrl field of DNP3 Pseudo Link Layer
const unsigned int PSEUDO_TRANSPORT_INDEX = 10; // index of DNP3 Pseudo Transport Layer
const unsigned int PSEUDO_APP_LAYER_INDEX = 11; // index of first DNP3 app-layer byte.
const unsigned int PSEUDO_TRANSPORT_LEN = 1; // length of DNP3 Transport Layer
const unsigned int PSEUDO_LINK_LAYER_LEN = 8; // length of DNP3 Pseudo Link Layer
bool DNP3_Base::crc_table_initialized = false;
unsigned int DNP3_Base::crc_table[256];
DNP3_Base::DNP3_Base()
{
// analyzer = arg_analyzer;
// interp = new binpac::DNP3::DNP3_Conn(analyzer);
ClearEndpointState(true);
ClearEndpointState(false);
// if ( ! crc_table_initialized )
// PrecomputeCRCTable();
}
DNP3_Base::~DNP3_Base()
{
// delete interp;
}
bool DNP3_Base::ProcessData(int len, const u_char* data, bool orig)
{
Endpoint* endp = orig ? &orig_state : &resp_state;
while ( len )
{
if ( endp->in_hdr )
{
// We're parsing the DNP3 header and link layer, get that in full.
if ( ! AddToBuffer(endp, PSEUDO_APP_LAYER_INDEX, &data, &len) )
return true;
// The first two bytes must always be 0x0564.
if( endp->buffer[0] != 0x05 || endp->buffer[1] != 0x64 )
{
// analyzer->Weird("dnp3_header_lacks_magic");
return false;
}
// Make sure header checksum is correct.
if ( ! CheckCRC(PSEUDO_LINK_LAYER_LEN, endp->buffer, endp->buffer + PSEUDO_LINK_LAYER_LEN, "header") )
{
// analyzer->ProtocolViolation("broken_checksum");
return false;
}
// If the checksum works out, we're pretty certainly DNP3.
// analyzer->ProtocolConfirmation();
// DNP3 packets without transport and application
// layers can happen, we ignore them.
if ( (endp->buffer[PSEUDO_LENGTH_INDEX] + 3) == (char)PSEUDO_LINK_LAYER_LEN )
{
ClearEndpointState(orig);
return true;
}
// Double check the direction in case the first
// received packet is a response.
u_char ctrl = endp->buffer[PSEUDO_CONTROL_FIELD_INDEX];
if ( orig != (bool)(ctrl & 0x80) )
// analyzer->Weird("dnp3_unexpected_flow_direction");
// Update state.
endp->pkt_length = endp->buffer[PSEUDO_LENGTH_INDEX];
endp->tpflags = endp->buffer[PSEUDO_TRANSPORT_INDEX];
endp->in_hdr = false; // Now parsing application layer.
// For the first packet, we submit the header to
// BinPAC.
// if ( ++endp->pkt_cnt == 1 )
// interp->NewData(orig, endp->buffer, endp->buffer + PSEUDO_LINK_LAYER_LEN);
}
if ( ! endp->in_hdr )
{
assert(endp->pkt_length);
// We're parsing the DNP3 application layer, get that
// in full now as well. We calculate the number of
// raw bytes the application layer consists of from
// the packet length by determining how much 16-byte
// chunks fit in there, and then add 2 bytes CRC for
// each.
int n = PSEUDO_APP_LAYER_INDEX + (endp->pkt_length - 5) + ((endp->pkt_length - 5) / 16) * 2
+ 2 * ( ((endp->pkt_length - 5) % 16 == 0) ? 0 : 1) - 1 ;
if ( ! AddToBuffer(endp, n, &data, &len) )
return true;
// Parse the the application layer data.
if ( ! ParseAppLayer(endp) )
return false;
// Done with this packet, prepare for next.
endp->buffer_len = 0;
endp->in_hdr = true;
}
}
return true;
}
bool DNP3_Base::AddToBuffer(Endpoint* endp, int target_len, const u_char** data, int* len)
{
if ( ! target_len )
return true;
int to_copy = min(*len, target_len - endp->buffer_len);
memcpy(endp->buffer + endp->buffer_len, *data, to_copy);
*data += to_copy;
*len -= to_copy;
endp->buffer_len += to_copy;
return endp->buffer_len == target_len;
}
bool DNP3_Base::ParseAppLayer(Endpoint* endp)
{
bool orig = (endp == &orig_state);
// binpac::DNP3::DNP3_Flow* flow = orig ? interp->upflow() : interp->downflow();
u_char* data = endp->buffer + PSEUDO_TRANSPORT_INDEX; // The transport layer byte counts as app-layer it seems.
int len = endp->pkt_length - 5;
// DNP3 Packet : DNP3 Pseudo Link Layer | DNP3 Pseudo Transport Layer | DNP3 Pseudo Application Layer
// DNP3 Serial Transport Layer data is always 1 byte.
// Get FIN FIR seq field in transport header.
// FIR indicate whether the following DNP3 Serial Application Layer is first chunk of bytes or not.
// FIN indicate whether the following DNP3 Serial Application Layer is last chunk of bytes or not.
int is_first = (endp->tpflags & 0x40) >> 6; // Initial chunk of data in this packet.
int is_last = (endp->tpflags & 0x80) >> 7; // Last chunk of data in this packet.
int transport = PSEUDO_TRANSPORT_LEN;
// int i = 0;
while ( len > 0 )
{
int n = min(len, 16);
// Make sure chunk has a correct checksum.
if ( ! CheckCRC(n, data, data + n, "app_chunk") )
return false;
// Pass on to BinPAC.
assert(data + n < endp->buffer + endp->buffer_len);
// flow->flow_buffer()->BufferData(data + transport, data + n);
transport = 0;
data += n + 2;
len -= n;
}
if ( is_first )
endp->encountered_first_chunk = true;
if ( ! is_first && ! endp->encountered_first_chunk )
{
// We lost the first chunk.
// analyzer->Weird("dnp3_first_application_layer_chunk_missing");
return false;
}
if ( is_last )
{
// flow->flow_buffer()->FinishBuffer();
// flow->FlowEOF();
ClearEndpointState(orig);
}
return true;
}
void DNP3_Base::ClearEndpointState(bool orig)
{
Endpoint* endp = orig ? &orig_state : &resp_state;
// binpac::DNP3::DNP3_Flow* flow = orig ? interp->upflow() : interp->downflow();
endp->in_hdr = true;
endp->encountered_first_chunk = false;
endp->buffer_len = 0;
endp->pkt_length = 0;
endp->tpflags = 0;
endp->pkt_cnt = 0;
}
bool DNP3_Base::CheckCRC(int len, const u_char* data, const u_char* crc16, const char* where)
{
return true;
// unsigned int crc = CalcCRC(len, data);
// if ( crc16[0] == (crc & 0xff) && crc16[1] == (crc & 0xff00) >> 8 )
// return true;
// analyzer->Weird(fmt("dnp3_corrupt_%s_checksum", where));
// return false;
}
void DNP3_Base::PrecomputeCRCTable()
{
for( unsigned int i = 0; i < 256; i++)
{
unsigned int crc = i;
for ( unsigned int j = 0; j < 8; ++j )
{
if ( crc & 0x0001 )
crc = (crc >> 1) ^ 0xA6BC; // Generating polynomial.
else
crc >>= 1;
}
crc_table[i] = crc;
}
}
unsigned int DNP3_Base::CalcCRC(int len, const u_char* data)
{
unsigned int crc = 0x0000;
for ( int i = 0; i < len; i++ )
{
unsigned int index = (crc ^ data[i]) & 0xFF;
crc = crc_table[index] ^ (crc >> 8);
}
return ~crc & 0xFFFF;
}
// DNP3_TCP_Analyzer::DNP3_TCP_Analyzer(Connection* c)
// : DNP3_Base(this), TCP_ApplicationAnalyzer("DNP3_TCP", c)
// {
// }
// DNP3_TCP_Analyzer::~DNP3_TCP_Analyzer()
// {
// }
// void DNP3_TCP_Analyzer::Done()
// {
// TCP_ApplicationAnalyzer::Done();
// Interpreter()->FlowEOF(true);
// Interpreter()->FlowEOF(false);
// }
// void DNP3_TCP_Analyzer::DeliverStream(int len, const u_char* data, bool orig)
// {
// TCP_ApplicationAnalyzer::DeliverStream(len, data, orig);
// try
// {
// if ( ! ProcessData(len, data, orig) )
// SetSkip(1);
// }
// catch ( const binpac::Exception& e )
// {
// SetSkip(1);
// throw;
// }
// }
// void DNP3_TCP_Analyzer::Undelivered(uint64 seq, int len, bool orig)
// {
// TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);
// Interpreter()->NewGap(orig, len);
// }
// void DNP3_TCP_Analyzer::EndpointEOF(bool is_orig)
// {
// TCP_ApplicationAnalyzer::EndpointEOF(is_orig);
// Interpreter()->FlowEOF(is_orig);
// }
// DNP3_UDP_Analyzer::DNP3_UDP_Analyzer(Connection* c)
// : DNP3_Base(this), Analyzer("DNP3_UDP", c)
// {
// }
// DNP3_UDP_Analyzer::~DNP3_UDP_Analyzer()
// {
// }
// void DNP3_UDP_Analyzer::DeliverPacket(int len, const u_char* data, bool orig, uint64 seq, const IP_Hdr* ip, int caplen)
// {
// Analyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
// try
// {
// if ( ! ProcessData(len, data, orig) )
// SetSkip(1);
// }
// catch ( const binpac::Exception& e )
// {
// SetSkip(1);
// throw;
// }
// }