File Coverage

lib/SMB/DCERPC.pm
Criterion Covered Total %
statement 243 255 95.2
branch 51 86 59.3
condition 40 74 54.0
subroutine 21 22 95.4
pod 11 15 73.3
total 366 452 80.9


line stmt bran cond sub pod time code
1             # SMB-Perl library, Copyright (C) 2014-2018 Mikhael Goikhman, migo@cpan.org
2             #
3             # This program is free software: you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation, either version 3 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program. If not, see .
15              
16             package SMB::DCERPC;
17              
18 2     2   3825 use strict;
  2         5  
  2         59  
19 2     2   12 use warnings;
  2         4  
  2         60  
20              
21 2     2   343 use parent 'SMB';
  2         237  
  2         11  
22              
23 2     2   576 use bytes;
  2         14  
  2         23  
24              
25 2     2   380 use SMB::Parser;
  2         4  
  2         54  
26 2     2   327 use SMB::Packer;
  2         5  
  2         180  
27              
28             # DCERPC 5.0 protocol
29              
30             use constant {
31             # for bind contexts
32 2         6936 UUID_AS_SRVSVC => "\xc8\x4f\x32\x4b\x70\x16\xd3\x01\x12\x78\x5a\x47\xbf\x6e\xe1\x88",
33             UUID_TS_32BIT_NDR => "\x04\x5d\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60",
34             UUID_TS_64BIT_NDR => "\x33\x05\x71\x71\xba\xbe\x37\x49\x83\x19\xb5\xdb\xef\x9c\xcc\x36",
35             UUID_TS_BIND_TIME => "\x2c\x1c\xb7\x6c\x12\x98\x40\x45\x03\x00\x00\x00\x00\x00\x00\x00",
36             UUID_TS_NULL => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
37              
38             PACKET_TYPE_REQUEST => 0,
39             PACKET_TYPE_RESPONSE => 2,
40             PACKET_TYPE_BIND => 11,
41             PACKET_TYPE_BIND_ACK => 12,
42              
43             STATE_INITIAL => 0,
44             STATE_BIND => 1,
45             STATE_BIND_ACK => 2,
46             STATE_REQUEST => 3,
47             STATE_RESPONSE => 4,
48 2     2   15 };
  2         4  
49              
50             our %operation_codes = (
51             16 => 'NetShareGetInfo',
52             );
53              
54             our %operations = reverse %operation_codes;
55              
56             sub new ($%) {
57 2     2 1 441 my $class = shift;
58 2         6 my %options = @_;
59              
60             die "No name (service name) in constructor"
61 2 50       8 unless defined $options{name};
62              
63 2         11 return $class->SUPER::new(
64             state => STATE_INITIAL,
65             current_packet_type => undef,
66             current_context_id => undef,
67             current_call_id => 1,
68             requested_opnum => undef,
69             requested_opinfo => {},
70             contexts => [],
71             parser => SMB::Parser->new,
72             packer => SMB::Packer->new,
73             %options,
74             );
75             }
76              
77             sub parse_common ($$$) {
78 6     6 0 13 my $self = shift;
79 6         10 my $payload = shift;
80 6         8 my $packet_type = shift;
81              
82 6         24 my $parser = $self->parser;
83 6         24 $parser->set($payload);
84              
85 6   50     16 my $version_major = $parser->uint8 // '-';
86 6   50     12 my $version_minor = $parser->uint8 // '-';
87 6 50 33     22 return $self->err("Got unsupported DCERPC version ($version_major.$version_minor)")
88             unless $version_major eq 5 && $version_minor eq 0;
89              
90 6   50     10 my $given_packet_type = $parser->uint8 // '-';
91 6 50       12 return $self->err("Got DCERPC packet_type $given_packet_type (expected $packet_type)")
92             unless $packet_type eq $given_packet_type;
93 6         14 $self->current_packet_type($packet_type);
94              
95 6   50     10 my $packet_flags = $parser->uint8 // '-';
96 6 50       13 return $self->err("Got unsupported DCERPC packet_flags ($packet_flags)")
97             unless $packet_flags eq 3;
98              
99 6   50     14 my $data_representation = $parser->uint32 // '-';
100 6 50       14 return $self->err("Got unsupported DCERPC data_representation ($data_representation)")
101             unless $data_representation eq 0x10;
102              
103 6         13 my $len = $parser->uint16;
104 6   50     11 my $auth_len = $parser->uint16 // '-';
105 6 50       12 return $self->err("Got unsupported DCERPC auth_len ($auth_len)")
106             unless $auth_len eq 0;
107              
108 6   50     7 my $call_id = $parser->uint32 // '-';
109 6         15 $self->current_call_id($call_id);
110              
111 6 100 100     24 if ($packet_type == PACKET_TYPE_BIND || $packet_type == PACKET_TYPE_BIND_ACK) {
112 2         8 $parser->uint16; # max_xmit_frag
113 2         4 $parser->uint16; # max_recv_frag
114 2         3 $parser->uint32; # assoc_group
115 2         9 $self->current_context_id(undef);
116 2         6 $self->requested_opnum(undef);
117             } else {
118 4         8 $parser->uint32; # alloc hint
119 4         8 $self->current_context_id($parser->uint16);
120 4         6 $self->requested_opnum($parser->uint16);
121             }
122              
123 6         17 return 1;
124             }
125              
126             sub pack_common ($$) {
127 6     6 0 8 my $self = shift;
128 6         9 my $packet_type = shift;
129              
130 6         26 my $packer = $self->packer;
131 6         20 $packer->reset;
132              
133 6         15 $packer
134             ->mark('dcerpc-start')
135             ->uint8(5) # version_major
136             ->uint8(0) # version_minor
137             ->uint8($packet_type)
138             ->uint8(3) # packet_flags
139             ->uint32(0x10) # data_representation
140             ->stub('frag-length', 'uint16')
141             ->uint16(0) # auth_len
142             ->uint32($self->current_call_id)
143             ;
144              
145 6 100 100     27 if ($packet_type == PACKET_TYPE_BIND || $packet_type == PACKET_TYPE_BIND_ACK) {
146 2         5 $packer->uint16(4280); # max_xmit_frag
147 2         33 $packer->uint16(4280); # max_recv_frag
148 2         4 $packer->uint32(0x5011); # assoc_group
149             } else {
150 4         8 $packer->uint32(100); # alloc hint
151 4         9 $packer->uint16($self->current_context_id);
152 4         8 $packer->uint16($self->requested_opnum);
153             }
154              
155 6         21 $self->current_packet_type($packet_type);
156              
157 6         7 return 1;
158             }
159              
160             sub finalize_pack_common ($) {
161 6     6 0 7 my $self = shift;
162 6         6 my $packet_type = shift;
163              
164 6         10 my $packer = $self->packer;
165              
166 6         11 $packer->fill('frag-length', $packer->diff('dcerpc-start'));
167              
168 6         12 return ($packer->data, SMB::STATUS_SUCCESS);
169             }
170              
171             sub error ($$$) {
172 0     0 0 0 my $self = shift;
173 0         0 my $status = shift;
174 0         0 my $message = shift;
175              
176 0         0 $self->err($message);
177              
178 0 0       0 return (undef, $status)
179             if (caller(1))[3] =~ /::generate_/;
180 0         0 return $status;
181             }
182              
183             sub process_bind_request ($$) {
184 1     1 1 1032 my $self = shift;
185 1   50     4 my $payload = shift // '';
186              
187 1 50       4 return $self->error(SMB::STATUS_INVALID_SMB, "No STATE_INITIAL on bind_request")
188             unless $self->state == STATE_INITIAL;
189              
190 1 50       4 return $self->error(SMB::STATUS_INVALID_PARAMETER, "Skipping wrong bind_request packet")
191             unless $self->parse_common($payload, PACKET_TYPE_BIND);
192              
193 1         2 my $parser = $self->parser;
194              
195 1         5 $self->contexts([]);
196 1         2 my $num_contexts = $parser->uint32;
197              
198 1         3 for (0 .. $num_contexts - 1) {
199 3         4 my $context_id = $parser->uint16;
200 3 50       6 return $self->error(SMB::STATUS_INVALID_PARAMETER, "Got context id $context_id, expected $_")
201             unless $context_id eq $_;
202 3         4 my $num = $parser->uint16;
203 3 50       6 return $self->error(SMB::STATUS_INVALID_PARAMETER, "Got unexpected num_trans_items ($num)")
204             unless $num eq 1;
205              
206 3         4 my $as_uuid = $parser->bytes(16);
207 3         13 my $as_version = $parser->uint32;
208              
209 3         4 my $ts_uuid = $parser->bytes(16);
210 3         4 my $ts_version = $parser->uint32;
211              
212 3         2 $self->contexts([@{$self->contexts}, $ts_uuid]);
  3         6  
213             }
214              
215 1         2 $self->state(STATE_BIND);
216              
217 1         7 return SMB::STATUS_SUCCESS;
218             }
219              
220             sub generate_bind_request ($) {
221 1     1 1 9 my $self = shift;
222              
223 1 50       11 return $self->error(SMB::STATUS_INVALID_SMB, "No STATE_INITIAL on bind_response")
224             unless $self->state == STATE_INITIAL;
225              
226 1         3 $self->pack_common(PACKET_TYPE_BIND);
227              
228 1         2 my $packer = $self->packer;
229              
230 1         2 $packer->uint32(3); # num_contexts
231              
232 1         3 for (0 .. 2) {
233 3 100       12 $packer
    100          
    100          
234             ->uint16($_) # context id
235             ->uint16(1) # num_trans_items
236             ->bytes(UUID_AS_SRVSVC)
237             ->uint32(3) # as_version
238             ->bytes($_ == 0 ? UUID_TS_32BIT_NDR : $_ == 1 ? UUID_TS_64BIT_NDR : UUID_TS_BIND_TIME)
239             ->uint32($_ == 0 ? 2 : 1) # ts_version
240             ;
241             }
242              
243 1         3 $self->state(STATE_BIND);
244              
245 1         2 return $self->finalize_pack_common;
246             }
247              
248             sub process_bind_ack_response ($$) {
249 1     1 1 1010 my $self = shift;
250 1   50     5 my $payload = shift // '';
251              
252 1 50       5 return $self->error(SMB::STATUS_INVALID_SMB, "No STATE_BIND on bind_ack_request")
253             unless $self->state == STATE_BIND;
254              
255 1 50       4 return $self->error(SMB::STATUS_INVALID_PARAMETER, "Skipping wrong bind_request packet")
256             unless $self->parse_common($payload, PACKET_TYPE_BIND_ACK);
257              
258 1         3 my $parser = $self->parser;
259              
260 1         2 my $scndry_addr_len = $parser->uint16;
261 1         2 my $scndry_addr = $parser->bytes($scndry_addr_len);
262 1         3 $parser->skip(1);
263              
264 1         2 $self->contexts([]);
265 1         1 my $num_results = $parser->uint32;
266              
267 1         3 for (0 .. $num_results - 1) {
268 3         5 my $ack_result = $parser->uint16;
269 3         5 my $ack_reason = $parser->uint16;
270              
271 3         5 my $ts_uuid = $parser->bytes(16);
272 3         4 my $ts_version = $parser->uint32;
273              
274 3         3 $self->contexts([@{$self->contexts}, $ts_uuid]);
  3         5  
275             }
276              
277 1         2 $self->state(STATE_BIND_ACK);
278              
279 1         3 return SMB::STATUS_SUCCESS;
280             }
281              
282             sub generate_bind_ack_response ($$) {
283 1     1 1 496 my $self = shift;
284              
285 1 50       4 return $self->error(SMB::STATUS_INVALID_SMB, "No STATE_BIND on bind_ack_response")
286             unless $self->state == STATE_BIND;
287              
288 1         4 $self->pack_common(PACKET_TYPE_BIND_ACK);
289              
290 1         2 my $packer = $self->packer;
291              
292 1         8 my $scndry_addr = sprintf "\\PIPE\\%s\0", $self->name;
293 1         3 $packer->uint16(length($scndry_addr));
294 1         3 $packer->bytes($scndry_addr);
295 1         2 $packer->uint8(0);
296              
297 1         1 my $num_results = @{$self->contexts};
  1         2  
298 1         2 $packer->uint32($num_results);
299              
300 1         2 for (0 .. $num_results - 1) {
301 3 100       5 my ($ack_result, $ack_reason, $ts_uuid, $ts_version) =
    100          
302             $self->contexts->[$_] eq UUID_TS_64BIT_NDR
303             ? (0, 0, UUID_TS_64BIT_NDR, 1) :
304             $self->contexts->[$_] eq UUID_TS_BIND_TIME
305             ? (3, 3, UUID_TS_NULL, 0)
306             : (2, 2, UUID_TS_NULL, 0);
307              
308 3         6 $packer
309             ->uint16($ack_result)
310             ->uint16($ack_reason)
311             ->bytes($ts_uuid)
312             ->uint32($ts_version)
313             ;
314             }
315              
316 1         2 $self->state(STATE_BIND_ACK);
317              
318 1         3 return $self->finalize_pack_common;
319             }
320              
321             sub process_rpc_request ($$) {
322 2     2 1 1327 my $self = shift;
323 2   50     8 my $payload = shift // '';
324              
325 2 50 66     6 return $self->error(SMB::STATUS_INVALID_SMB, "No STATE_BIND_ACK or STATE_RESPONSE on rpc_request")
326             unless $self->state == STATE_BIND_ACK || $self->state == STATE_RESPONSE;
327              
328 2 50       7 return $self->error(SMB::STATUS_INVALID_PARAMETER, "Skipping wrong rpc_request packet")
329             unless $self->parse_common($payload, PACKET_TYPE_REQUEST);
330              
331 2         5 my $parser = $self->parser;
332              
333 2   50     6 my $opnum = $self->requested_opnum // '-';
334 2 50       6 if ($opnum == $operations{NetShareGetInfo}) {
335 2         5 my $referent_id = $parser->uint64;
336 2         5 my $max_count = $parser->uint64;
337 2         6 my $offset = $parser->uint64;
338 2         4 my $count = $parser->uint64;
339 2         6 my $server_unc = $parser->skip($offset)->str($count * 2); chop($server_unc);
  2         108  
340 2         7 $parser->align(0, 8);
341 2         4 $max_count = $parser->uint64;
342 2         5 $offset = $parser->uint64;
343 2         5 $count = $parser->uint64;
344 2         6 my $share_name = $parser->skip($offset)->str($count * 2); chop($share_name);
  2         90  
345 2         8 $parser->align(0, 4);
346 2         7 my $level = $parser->uint32;
347 2 50       7 return $self->error(SMB::STATUS_NOT_IMPLEMENTED, "Unsupported NetShareGetInfo level $level")
348             unless $level == 1;
349 2         11 $self->requested_opinfo({
350             referent_id => $referent_id,
351             share_name => $share_name,
352             });
353             }
354             else {
355 0         0 return $self->error(SMB::STATUS_NOT_IMPLEMENTED, "Unsupported rpc operation $opnum");
356             }
357              
358 2         6 $self->state(STATE_REQUEST);
359              
360 2         9 return SMB::STATUS_SUCCESS;
361             }
362              
363             sub generate_rpc_request ($$%) {
364 2     2 1 503 my $self = shift;
365 2   50     8 my $opname = shift // die "No operation name";
366 2         8 my %params = @_;
367              
368 2 50 66     7 return $self->error(SMB::STATUS_INVALID_SMB, "No STATE_BIND_ACK or STATE_RESPONSE on rpc_request")
369             unless $self->state == STATE_BIND_ACK || $self->state == STATE_RESPONSE;
370              
371 2         5 my $opnum = $operations{$opname};
372 2 50       11 return $self->error(SMB::STATUS_NOT_IMPLEMENTED, "Unsupported operation $opname on rpc_request")
373             unless defined $opnum;
374 2         6 $self->requested_opnum($opnum);
375 2   50     11 $self->current_context_id($params{context_id} // 0);
376              
377 2         6 $self->pack_common(PACKET_TYPE_REQUEST);
378              
379 2         4 my $packer = $self->packer;
380              
381 2 50       5 if ($opnum == $operations{NetShareGetInfo}) {
382 2   50     6 my $referent_id = $params{referent_id} // 0;
383 2   50     7 my $server_unc = ($params{server_unc} // '127.0.0.1') . "\0";
384 2   50     5 my $share_name = ($params{share_name} // '') . "\0";
385 2         2 my $len1 = length($server_unc);
386 2         2 my $len2 = length($share_name);
387 2         4 $packer
388             ->uint64($referent_id)
389             ->uint64($len1) # max_count
390             ->uint64(0) # offset
391             ->uint64($len1) # count
392             ->str($server_unc)
393             ->align(0, 8)
394             ->uint64($len2) # max_count
395             ->uint64(0) # offset
396             ->uint64($len2) # count
397             ->str($share_name)
398             ->align(0, 4)
399             ->uint32(1) # level
400             ;
401 2         23 $self->requested_opinfo({
402             referent_id => $referent_id,
403             share_name => $share_name,
404             });
405             }
406             else {
407 0         0 return $self->error(SMB::STATUS_NOT_IMPLEMENTED, "Unsupported rpc operation $opnum");
408             }
409              
410 2         6 $self->state(STATE_REQUEST);
411              
412 2         3 return $self->finalize_pack_common;
413             }
414              
415             sub process_rpc_response ($$$) {
416 2     2 1 1024 my $self = shift;
417 2   50     7 my $payload = shift // '';
418 2   50     8 my $retinfo = shift // die;
419              
420 2 50       19 return $self->error(SMB::STATUS_INVALID_SMB, "No STATE_REQUEST on rpc_response")
421             unless $self->state == STATE_REQUEST;
422              
423 2 50       8 return $self->error(SMB::STATUS_INVALID_PARAMETER, "Skipping wrong rpc_response packet")
424             unless $self->parse_common($payload, PACKET_TYPE_RESPONSE);
425              
426 2         5 my $parser = $self->parser;
427              
428 2   50     3 my $opnum = $self->requested_opnum // '-';
429 2 50       6 if ($opnum == $operations{NetShareGetInfo}) {
430 2         4 my $level = $parser->uint32();
431 2 50       5 return $self->error(SMB::STATUS_NOT_IMPLEMENTED, "Unsupported NetShareGetInfo level $level")
432             unless $level == 1;
433 2         3 $parser->skip(4);
434 2         3 my $referent_id = $parser->uint64;
435 2         5 $parser->skip(8); # share_name referent_id
436 2         2 my $stype = $parser->uint32;
437 2         4 $parser->skip(4);
438 2         3 $parser->skip(8); # comment referent_id
439 2         3 my $max_count = $parser->uint64;
440 2         4 my $offset = $parser->uint64;
441 2         4 my $count = $parser->uint64;
442 2         5 my $share_name = $parser->skip($offset)->str($count * 2); chop($share_name);
  2         78  
443 2         5 $parser->align(0, 8);
444 2         3 $max_count = $parser->uint64;
445 2         11 $offset = $parser->uint64;
446 2         4 $count = $parser->uint64;
447 2         5 my $comment = $parser->skip($offset)->str($count * 2); chop($comment);
  2         64  
448 2         3 $parser->align(0, 4);
449 2         46 my $winerror = $parser->uint32;
450 2         33 %$retinfo = (
451             referent_id => $referent_id,
452             share_name => $share_name,
453             comment => $comment,
454             );
455             }
456             else {
457 0         0 return $self->error(SMB::STATUS_NOT_IMPLEMENTED, "Unsupported rpc operation $opnum");
458             }
459              
460 2         7 $self->state(STATE_RESPONSE);
461              
462 2         7 return SMB::STATUS_SUCCESS;
463             }
464              
465             sub generate_rpc_response ($$%) {
466 2     2 1 4 my $self = shift;
467 2   33     11 my $opnum = shift // $self->requested_opnum;
468 2         5 my %params = @_;
469              
470 2 50       7 return $self->error(SMB::STATUS_INVALID_SMB, "No STATE_REQUEST on rpc_response")
471             unless $self->state == STATE_REQUEST;
472              
473 2         8 $self->pack_common(PACKET_TYPE_RESPONSE);
474              
475 2         5 my $packer = $self->packer;
476              
477 2 50       6 if ($opnum == $operations{NetShareGetInfo}) {
478 2   33     9 my $referent_id = $params{referent_id} // $self->requested_opinfo->{referent_id} // 0;
      50        
479 2   33     7 my $share_name = ($params{share_name} // $self->requested_opinfo->{share_name} // '') . "\0";
      50        
480 2   50     9 my $comment = ($params{comment} // '') . "\0";
481 2         3 my $len1 = length($share_name);
482 2         3 my $len2 = length($comment);
483 2         6 $packer
484             ->uint32(1) # level
485             ->skip(4)
486             ->uint64($referent_id)
487             ->uint64($referent_id) # share_name referent_id
488             ->uint32(0) # stype
489             ->skip(4)
490             ->uint64($referent_id) # comment referent_id
491             ->uint64($len1) # max_count
492             ->uint64(0) # offset
493             ->uint64($len1) # count
494             ->str($share_name)
495             ->align(0, 8)
496             ->uint64($len2) # max_count
497             ->uint64(0) # offset
498             ->uint64($len2) # count
499             ->str($comment)
500             ->align(0, 4)
501             ->uint32(0) # winerror
502             ;
503             }
504             else {
505 0         0 return $self->error(SMB::STATUS_NOT_IMPLEMENTED, "Unsupported rpc operation $opnum");
506             }
507              
508 2         9 $self->state(STATE_RESPONSE);
509              
510 2         5 return $self->finalize_pack_common;
511             }
512              
513             sub process_packet ($$@) {
514 2     2 1 1879 my $self = shift;
515 2         5 my $payload = shift;
516              
517 2         7 my $state = $self->state;
518              
519 2 50       8 return $self->process_bind_request($payload, @_)
520             if $state == STATE_INITIAL;
521 2 50       6 return $self->process_bind_ack_response($payload, @_)
522             if $state == STATE_BIND;
523 2 100 66     16 return $self->process_rpc_request($payload, @_)
524             if $state == STATE_BIND_ACK || $state == STATE_RESPONSE;
525 1 50       7 return $self->process_rpc_response($payload, @_)
526             if $state == STATE_REQUEST;
527              
528 0         0 return $self->error(SMB::STATUS_INVALID_SMB, "Invalid internal DCERPC state $state");
529             }
530              
531             sub generate_packet ($@) {
532 2     2 1 1074 my $self = shift;
533              
534 2         7 my $state = $self->state;
535              
536 2 50       7 return $self->generate_bind_request(@_)
537             if $state == STATE_INITIAL;
538 2 50       5 return $self->generate_bind_ack_response(@_)
539             if $state == STATE_BIND;
540 2 100 66     15 return $self->generate_rpc_request(@_)
541             if $state == STATE_BIND_ACK || $state == STATE_RESPONSE;
542 1 50       7 return $self->generate_rpc_response(@_)
543             if $state == STATE_REQUEST;
544              
545 0           return $self->error(SMB::STATUS_INVALID_SMB, "Invalid internal DCERPC state $state");
546             }
547              
548             1;
549              
550             __END__