root/fwknop/tags/fwknop-1.9.3/fwknop

Revision 1046, 56.3 kB (checked in by mbr, 8 months ago)

version 1.9.3

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id Revision
Line 
1 #!/usr/bin/perl -w
2 #
3 #############################################################################
4 #
5 # File: fwknop
6 #
7 # Purpose: fwknop implements an authorization scheme known as Single Packet
8 #          Authorization (SPA) that requires only a single encrypted packet
9 #          to communicate various pieces of information including desired
10 #          access through an iptables policy and/or specific commands to
11 #          execute on the target system.  The main application of this
12 #          program is to protect services such as SSH with an additional
13 #          layer of security in order to make the exploitation of
14 #          vulnerabilities (both 0-day and unpatched code) much more
15 #          difficult.  For more information, see the fwknop(8) man page.
16 #
17 # Author: Michael Rash (mbr@cipherdyne.org)
18 #
19 # Version: 1.9.3
20 #
21 # Copyright (C) 2004-2007 Michael Rash (mbr@cipherdyne.org)
22 #
23 # License (GNU Public License):
24 #
25 #    This program is distributed in the hope that it will be useful,
26 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
27 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28 #    GNU General Public License for more details.
29 #
30 #    You should have received a copy of the GNU General Public License
31 #    along with this program; if not, write to the Free Software
32 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
33 #    USA
34 #
35 #############################################################################
36 #
37 # $Id$
38 #
39
40 use lib '/usr/lib/fwknop';
41 use Crypt::CBC;
42 use Net::IPv4Addr qw(ipv4_in_network);
43 use Net::Ping::External qw(ping);
44 use IO::Socket;
45 use IO::Handle;
46 use MIME::Base64;
47 use Data::Dumper;
48 use POSIX;
49 use Term::ReadKey;
50 use Getopt::Long;
51 use strict;
52
53 my $version = '1.9.3';
54 my $revision_svn = '$Revision$';
55 my $rev_num = '1';
56 ($rev_num) = $revision_svn =~ m|\$Rev.*:\s+(\S+)|;
57
58 my $print_version = 0;
59 my $print_help    = 0;
60 my $run_last_args = 0;
61 my $debug         = 0;
62 my $quiet         = 0;
63 my $verbose       = 0;
64 my $test_mode     = 0;
65 my $cmdl_homedir  = '';
66 my $knock_sleep   = 1;  ### default to 1 second difference between port knocks
67 my $knock_dst     = '';
68 my $homedir       = '';
69 my $spoof_src     = '';
70 my $server_mode   = 'pcap';
71 my $user_rc_file  = '';
72 my $server_proto  = '';
73 my $run_last_host = '';
74 my $total_digest  = '';
75 my $show_last_host_cmd = '';
76 my $show_last_cmd = 0;
77 my $use_md5       = 0;
78 my $use_sha1      = 0;
79 my $use_sha256    = 0;
80 my $gpg_home_dir  = '';
81 my $gpg_recipient = '';
82 my $use_gpg_agent = 0;
83 my $max_msg_len   = 1500;
84 my $gpg_verbose   = 0;
85 my $gpg_agent_info = '';
86 my $include_salted = 0;
87 my $gpg_default_key = 0;
88 my $err_wait_timer  = 30;  ### seconds
89 my $resolve_ip_url  = 'http://www.whatismyip.org/';
90 my $gpg_signing_key = '';
91 my $save_packet_mode = 0;
92 my $save_packet_file = '';
93 my $cmdline_pcap_cmd   = '';
94 my $no_save_last_args  = 0;
95 my $save_destination   = 0;
96 my $server_auth_method = '';
97 my $spa_established_tcp  = 0;
98 my $resolve_external_ip  = 0;
99 my $server_auth_crypt_pw = '';
100 my $pcap_sleep_interval  = 1;  ### seconds
101 my $knock_dst_pre_resolve = '';
102
103 ### User agent for contacting http://www.whatismyip.org/, (can
104 ### override with --User-agent)
105 my $ext_resolve_user_agent = "Fwknop/$version";
106 $ext_resolve_user_agent =~ s|-pre\d+||;
107
108 ### ACCESS message:
109 ###     random data : user : client_timestamp : client_version : \
110 ###     type (1) : access_request : message digest
111 my $SPA_ACCESS_MODE  = 1;  ### default
112
113 ### COMMAND message:
114 ###     random data : user : client_timestamp : client_version : \
115 ###     type (0) : command : message digest
116 my $SPA_COMMAND_MODE = 0;
117
118 ### FORWARD ACCESS message:
119 ###     random data : user : client_timestamp : client_version : \
120 ###     type (2) : access_request : NAT_info : message digest
121 my $SPA_FORWARD_ACCESS_MODE = 2;
122
123 ### ACCESS message with client-defined firewall timeout:
124 ###     random data : user : client_timestamp : client_version : \
125 ###     type (3) : access_request : timeout : message digest
126 my $SPA_CLIENT_TIMEOUT_ACCESS_MODE = 3;
127
128 ### FORWARD ACCESS message with client-defined firewall timeout:
129 ###     random data : user : client_timestamp : client_version : \
130 ###     type (4) : access_request : NAT_info : timeout : message digest
131 my $SPA_CLIENT_TIMEOUT_FORWARD_ACCESS_MODE = 4;
132
133 ### default time values
134 my $knock_interval  = 60;
135 my $cmdl_fw_timeout = 0;
136
137 ### Digest types and command argument flags
138 my $MD5_DIGEST    = 1;
139 my $SHA1_DIGEST   = 2;
140 my $SHA256_DIGEST = 3;
141 my $digest_type = $SHA256_DIGEST; ### default
142 my $cmdl_digest_alg = '';
143
144 ### default to root (client must run as root in this mode)
145 my $spoof_username = '';
146 my $spoof_proto    = 'udp'### default to udp
147
148 ### encrypted port knock vars (these are only used in the legacy
149 ### port knocking mode).
150 my $cmdline_offset  = 0;
151 my $enc_port_offset = 61000;  ### default offset
152 my $enc_key         = '';
153 my $enc_alg         = 'Rijndael';
154 my $enc_blocksize   = 32;
155
156 ### there is a constant "RIJNDAEL_KEYSIZE" in the Crypt::Rijndael sources, but
157 ### it is not used; a 16 byte key size is fine.
158 my $enc_keysize = 16;
159
160 my $enc_shared_secret = '';
161 my $enc_allow_ip      = '';
162 my $enc_source_ip     = '';
163 my $enc_rotate_proto  = 0;
164 my $get_key_file      = ''### get key from file
165 my $enc_pcap_port     = 62201;  ### default pcap port
166 my $access_str        = '';
167 my $forward_access_str = ''### for access through the iptables FORWARD chain
168
169 ### packet counters
170 my $tcp_ctr  = 0;
171 my $udp_ctr  = 0;
172 my $icmp_ctr = 0;
173
174 ### tcp option types
175 my $tcp_nop_type       = 1;
176 my $tcp_mss_type       = 2;
177 my $tcp_win_scale_type = 3;
178 my $tcp_sack_type      = 4;
179 my $tcp_timestamp_type = 8;
180
181 my %tcp_p0f_opt_types = (
182     'N' => $tcp_nop_type,
183     'M' => $tcp_mss_type,
184     'W' => $tcp_win_scale_type,
185     'S' => $tcp_sack_type,
186     'T' => $tcp_timestamp_type
187 );
188
189 my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|;
190
191 my @args_cp = @ARGV;
192
193 ### run GetOpt() to get comand line args
194 &handle_command_line();
195
196 &usage(0) if $print_help;
197
198 if ($print_version) {
199     print "[+] fwknop v$version (file revision: $rev_num)\n",
200         "      by Michael Rash <mbr\@cipherdyne.org>\n";
201     exit 0;
202 }
203
204 die "[*] Cannot run in both --quiet and --verbose modes simultaneously"
205     if $quiet and $verbose;
206
207 die "[*] Must also specify a GnuPG signing key with --gpg-signing-key or\n",
208     "    use --gpg-default-key to use a default key (specified in\n",
209     "    ~/.gnupg/options with the default-key variable).\n"
210     if ($gpg_recipient and (not $gpg_default_key and not $gpg_signing_key));
211
212 die "[*] Must specify a GnuPG recipient key (on the fwknopd side) with\n",
213     "    --gpg-recipient"
214     if (($gpg_default_key or $gpg_signing_key) and not $gpg_recipient);
215
216 die "[*] Cannot spoof source address for a real TCP socket."
217     if ($spoof_src and $spa_established_tcp);
218
219 die "[*] Server auth method not supported in FORWARD access mode.\n"
220     if $server_auth_method and $forward_access_str;
221
222 &set_digest_type() if $cmdl_digest_alg;
223 $digest_type = $MD5_DIGEST    if $use_md5;
224 $digest_type = $SHA1_DIGEST   if $use_sha1;
225 $digest_type = $SHA256_DIGEST if $use_sha256;
226
227 ### this is only necessary for older versions of perl (newer versions
228 ### call srand() automatically at the first usage of rand() if srand()
229 ### was not already called).
230 srand();
231
232 &get_homedir();
233
234 ### save a copy
235 $knock_dst_pre_resolve = $knock_dst;
236
237 if ($run_last_args or $show_last_cmd) {
238
239     ### run fwknop with same command line args as the previous
240     ### execution
241     &run_last_cmdline();
242
243 } elsif ($run_last_host or $show_last_host_cmd) {
244
245     $run_last_host = $show_last_host_cmd if $show_last_host_cmd;
246
247     ### run fwknop with the last args for this particular knock destination
248     &run_last_host_cmdline();
249 }
250
251 die "[*] Must specify a destination server with -D <IP|Host>"
252     unless $knock_dst;
253
254 if ($cmdl_fw_timeout ne '0') {
255     die "[*] Must specify a firewall timeout > 0"
256         unless $cmdl_fw_timeout > 0;
257 }
258
259 my $print_mode = '';
260 if (lc($server_mode) eq 'pcap') {
261     $print_mode = 'SPA';
262 } elsif (lc($server_mode) eq 'knock') {
263     $print_mode = 'encrypted port knocking';
264 } elsif (lc($server_mode) eq 'shared') {
265     $print_mode = 'shared sequence port knocking';
266 } else {
267     die "[*] Unknown server mode: $server_mode ",
268         qq|(must be "pcap", "knock", or "shared").|;
269 }
270
271 if ($debug) {
272     print "[+] ***DEBUG*** Starting fwknop client ($print_mode mode)...\n";
273 } else {
274     print "[+] Starting fwknop client ($print_mode mode)...\n"
275         unless $quiet;
276 }
277
278 if ($verbose) {
279     print "[+] Command line: @args_cp\n";
280 }
281
282 unless ($knock_dst =~ /$ip_re/) {
283     print "[+] Resolving hostname: $knock_dst\n" unless $quiet;
284     ### resolve to an IP
285     my $iaddr = inet_aton($knock_dst)
286         or die "[*] Could not resolve $knock_dst to an IP.";
287     my $addr = inet_ntoa($iaddr)
288         or die "[*] Could not resolve $knock_dst to an IP.";
289     $knock_dst = $addr;
290 }
291
292 &validate_access_str() if $access_str;
293 &validate_forward_access_str() if $forward_access_str;
294
295 if (lc($server_mode) eq 'pcap' or lc($server_mode) eq 'knock') {
296     die "[*] Must also specify: -D <knock destination>\n"
297         unless $knock_dst;
298
299     unless ($enc_allow_ip
300             or $enc_source_ip
301             or $resolve_external_ip) {
302         die "[*] Must either specify: --allow-IP <IP>, ",
303             "--source-IP, or --Resolve-external-IP\n";
304     }
305
306     ### make fwknop server see "0.0.0.0" in the encrypted sequence.
307     ### This will instruct the server to open the port for whatever
308     ### source IP the sequence comes from.  This is useful for
309     ### clients that are behind a NAT device.
310     $enc_allow_ip = '0.0.0.0' if $enc_source_ip;
311
312     ### resolve the extenal IP via http://www.whatismyip.org
313     $enc_allow_ip = &resolve_external_ip() if $resolve_external_ip;
314
315     if ($spoof_src) {
316         $< == 0 && $> == 0 or
317             die '[*] You must be root (or equivalent ',
318                 "UID 0 account) to spoof the source address.\n";
319     }
320     unless ($enc_allow_ip =~ /$ip_re/) {
321         ### resolve to an IP
322         my $iaddr = inet_aton($enc_allow_ip)
323             or die "[*] Could not resolve $enc_allow_ip to IP.";
324         my $addr = inet_ntoa($iaddr)
325             or die "[*] Could not resolve $enc_allow_ip to IP.";
326         $enc_allow_ip = $addr;
327     }
328     if ($cmdline_offset) {
329         if (lc($server_mode) eq 'pcap') {
330             die "[*] Port offset is meaningless in pcap mode ",
331                 "(only a single packet is sent).";
332         }
333         unless ($cmdline_offset < 65280 and $cmdline_offset > 0) {
334             die "[*] Port offset must be 0 < port < 65280";
335         }
336         $enc_port_offset = $cmdline_offset;
337     }
338     if (lc($server_mode) eq 'pcap') {
339         unless ($enc_pcap_port < 65535 and $enc_pcap_port > 0) {
340             die "[*] Port offset must be 0 < port < 65535";
341         }
342     }
343 } else {
344     if ($enc_rotate_proto) {
345         die '[*] Can only specify --rotate-proto with ',
346             'encrypted sequences.';
347     }
348 }
349
350 if ($save_packet_mode) {
351     ### save of copy of the packet
352     unless ($save_packet_file) {
353         $save_packet_file = "$homedir/fwknop_save_packet.$$";
354     }
355     unlink $save_packet_file if -e $save_packet_file;
356 }
357
358 ### save our command line args (so -l can be used next time)
359 unless ($run_last_args or $run_last_host or $no_save_last_args
360         or $show_last_cmd or $show_last_host_cmd) {
361     &save_args();
362 }
363
364 if (lc($server_mode) eq 'pcap' or lc($server_mode) eq 'knock') {
365
366     ### get the encryption key from the --get-key file
367     ### or from STDIN if it's not in the file.
368     &get_key();
369
370     &handle_server_auth_method() if $server_auth_method;
371
372     if (lc($server_mode) eq 'pcap') {
373
374         ### construct and send the encrypted message to the server
375         ### (sends a single packet).
376         &pcap_send_encrypted_msg(&pcap_build_enc_msg());
377
378     } else {
379         ### we are running in port knocking mode, so get the
380         ### encrypted port sequence (16 ports)
381         &knock_ports(&encrypt_sequence());
382     }
383 } else {
384     ### we are running in non-encrypted port knocking mode, so get
385     ### the port sequence
386     &knock_ports(&import_shared_sequence());
387 }
388 exit 0;
389 #============================ end main ==============================
390
391 sub pcap_build_enc_msg() {
392
393     ### message format (all fields are separated by ":" characters
394     #
395     #  random number (16 bytes)
396     #  username
397     #  timestamp
398     #  software version
399     #  message type and content:
400     #    0 => command mode / command to execute
401     #    1 => access mode / IP,proto,port
402     #    2 => forward access mode / IP,proto,port / internalIP,externalNATPort
403     #  (optional) server_auth (post 0.9.2 release)
404     #  message digest (SHA256 / SHA1 / MD5 )
405
406     my $msg = '';
407
408     unless ($quiet) {
409         print "\n[+] Building encrypted Single Packet Authorization (SPA) ",
410             "message...\n";
411         print "[+] Packet fields:\n\n";
412     }
413
414     ### start the SPA message with 16 bytes of random data
415     $msg = &SPA_random_number();
416
417     ### append the username
418     $msg .= &SPA_user();
419
420     ### append the timestamp
421     $msg .= &SPA_timestamp();
422
423     ### append the fwknop client version
424     $msg .= &SPA_version();
425
426     ### append the message type (integer)
427     $msg .= &SPA_message_type();
428
429     ### append the SPA message (this is usually just a request for
430     ### access to a port/protocol combination)
431     $msg .= &SPA_message();
432
433     ### append FORWARD access requirement (optional)
434     $msg .= &SPA_forward_access();
435
436     ### append server authentication method (optional)
437     $msg .= &SPA_server_auth();
438
439     ### append any client defined fw timeout (optional)
440     $msg .= &SPA_client_timeout();
441
442     ### append Message Digest
443     $msg =~ s/\n//g;
444     $msg .= &SPA_digest($msg);
445
446     if ($debug) {
447         print "\n[+] Clear text message (fields base64 encoded): $msg\n",
448             "    Digest: $total_digest\n";
449     }
450
451     if ($gpg_signing_key or $gpg_default_key) {
452         return &pcap_GPG_encrypt_msg($msg);
453     }
454     return &pcap_Rijndael_encrypt_msg($msg);
455 }
456
457 sub SPA_random_number() {
458     my $random_num = int(rand(100000000000000));
459     $random_num .= int(rand(10)) while (length($random_num) < 16);
460
461     print "        Random data:    $random_num\n" unless $quiet;
462
463     return $random_num;
464 }
465
466 sub SPA_user() {
467     my $user = 'root';
468     if ($spoof_src) {
469         if ($spoof_username) {
470             $user = $spoof_username;
471         }
472     } else {
473         ### getlogin() is better than using ENV{'USER'}, which is
474         ### easily manipulated, so only use as a last resort.
475         if ($spoof_username) {
476             $user = $spoof_username;
477         } else {
478             $user = getlogin() || getpwuid($<) ||
479                 die "[*] Could not determine user; try using the ",
480                     "--Spoof-user option";
481         }
482     }
483     print  "        Username:       $user\n" unless $quiet;
484     return ':' . encode_base64($user, '');
485 }
486
487 sub SPA_timestamp() {
488     my $timestamp = time();
489     print "        Timestamp:      $timestamp\n" unless $quiet;
490     return ':' . $timestamp;
491 }
492
493 sub SPA_version() {
494     print "        Version:        $version\n" unless $quiet;
495     return ':' . $version;
496 }
497
498 sub SPA_message_type() {
499     if ($cmdline_pcap_cmd) {
500         print "        Type:           $SPA_COMMAND_MODE (command mode)\n"
501             unless $quiet;
502         return ':' . $SPA_COMMAND_MODE;
503     } elsif ($forward_access_str) {
504         if ($cmdl_fw_timeout > 0) {
505             print "        Type:           " .
506                 "$SPA_CLIENT_TIMEOUT_FORWARD_ACCESS_MODE ",
507                 "(FORWARD client-timeout access mode)\n" unless $quiet;
508             return ':' . $SPA_CLIENT_TIMEOUT_FORWARD_ACCESS_MODE;
509         } else {
510             print "        Type:           $SPA_FORWARD_ACCESS_MODE ",
511                 "(FORWARD access mode)\n" unless $quiet;
512             return ':' . $SPA_FORWARD_ACCESS_MODE;
513         }
514     }
515     if ($cmdl_fw_timeout > 0) {
516         print "        Type:           " .
517             "$SPA_CLIENT_TIMEOUT_ACCESS_MODE (access ",
518             "client-timeout mode)\n" unless $quiet;
519         return ':' . $SPA_CLIENT_TIMEOUT_ACCESS_MODE;
520     } else {
521         print "        Type:           $SPA_ACCESS_MODE (access mode)\n"
522             unless $quiet;
523         return ':' . $SPA_ACCESS_MODE;
524     }
525 }
526
527 sub SPA_message() {
528     ### a specific command will be executed on the server.  Note we
529     ### prepend the command string with the $enc_allow_ip so that the
530     ### fwknopd server can apply the REQUIRE_SOURCE_ADDRESS criteria.
531     if ($cmdline_pcap_cmd) {
532         print "        Cmd:            $cmdline_pcap_cmd\n" unless $quiet;
533         return ':' . encode_base64("$enc_allow_ip,$cmdline_pcap_cmd", '');
534
535     }
536     unless ($access_str) {
537         $access_str = 'none,0';
538     }
539     ### access to port(s)/protocol(s) will be granted on the
540     ### server
541     print "        Access:         $enc_allow_ip,$access_str\n"
542         unless $quiet;
543     return ':' . encode_base64("$enc_allow_ip,$access_str", '');
544 }
545
546 sub SPA_client_timeout() {
547     return '' unless $cmdl_fw_timeout;
548     return ':' . $cmdl_fw_timeout;
549 }
550
551 sub SPA_server_auth() {
552     if (lc($server_auth_method) eq 'crypt') {
553         unless ($quiet) {
554             print "        Server auth:   $server_auth_method,";
555             for (my $i=0; $i<length($server_auth_crypt_pw); $i++) {
556                 print '*';
557             }
558             print "\n";
559         }
560         return ':' . encode_base64("crypt,$server_auth_crypt_pw", '');
561     }
562     return '';
563 }
564
565 sub SPA_forward_access() {
566     if ($forward_access_str) {
567         print "        Forward access: $forward_access_str\n"
568             unless $quiet;
569         return ':' . encode_base64($forward_access_str, '');
570     }
571     return '';
572 }
573
574 sub SPA_digest() {
575     my $msg = shift;
576
577     my $digest = '';
578
579     if ($digest_type == $MD5_DIGEST) {
580         require Digest::MD5;
581         Digest::MD5->import(qw(md5_base64));
582         $digest = md5_base64($msg);
583         print "        MD5 digest:     $digest\n" unless $quiet;
584         if ($debug) {
585             $total_digest = md5_base64("$msg:$digest");
586         }
587     } elsif ($digest_type == $SHA1_DIGEST) {
588         require Digest::SHA;
589         Digest::SHA->import(qw(sha1_base64));
590         $digest = sha1_base64($msg);
591         print "        SHA1 digest:    $digest\n" unless $quiet;
592         if ($debug) {
593             $total_digest = sha1_base64("$msg:$digest");
594         }
595     } elsif ($digest_type == $SHA256_DIGEST) {
596         require Digest::SHA;
597         Digest::SHA->import(qw(sha256_base64));
598         $digest = sha256_base64($msg);
599         print "        SHA256 digest:  $digest\n" unless $quiet;
600         if ($debug) {
601             $total_digest = sha256_base64("$msg:$digest");
602         }
603     } else {
604         die "[*] Improper digest algorithm, use --help";
605     }
606     return ':' . $digest;
607 }
608
609 sub pcap_GPG_encrypt_msg() {
610     my $msg = shift;
611
612     my $gnupg = GnuPG::Interface->new();
613
614     $gpg_home_dir = "$homedir/.gnupg" unless $gpg_home_dir;
615
616     if ($gpg_verbose) {
617         $gnupg->options->hash_init(
618             'homedir' => $gpg_home_dir);
619     } else {
620         $gnupg->options->hash_init(
621             'batch' => 1,
622             'homedir' => $gpg_home_dir);
623     }
624
625     ### if --gpg-default-key is given, then we trust that the user has
626     ### set the default key with the default-key variable in ~/.gnupg/options
627     $gnupg->options->default_key($gpg_signing_key) unless $gpg_default_key;
628
629     $gnupg->options->push_recipients($gpg_recipient);
630
631     my ($input, $output, $error, $pw, $status) =
632         (IO::Handle->new(),
633         IO::Handle->new(),
634         IO::Handle->new(),
635         IO::Handle->new(),
636         IO::Handle->new());
637
638     my $handles = GnuPG::Handles->new(
639         stdin  => $input,
640         stdout => $output,
641         stderr => $error,
642         passphrase => $pw,
643         status => $status
644     );
645
646     my $pid;
647
648     if ($use_gpg_agent or $gpg_agent_info) {
649         if ($gpg_agent_info) {
650             $ENV{'GPG_AGENT_INFO'} = $gpg_agent_info;
651         }
652         $pid = $gnupg->sign_and_encrypt('handles' => $handles,
653             'command_args' => [ qw( --use-agent ) ]);
654     } else {
655         $pid = $gnupg->sign_and_encrypt('handles' => $handles);
656     }
657
658     print $pw $enc_key;
659     close $pw;
660
661     print $input $msg;
662     close $input;
663
664     my @ciphertext = <$output>;
665     close $output;
666
667     my @errors = <$error>;
668     close $error;
669
670     waitpid $pid, 0;
671
672     my $ctext = '';
673     if (@ciphertext) {
674         $ctext = join '', @ciphertext;
675     }
676
677     unless ($ctext) {
678         print "[*] GnuPG encrypt failed.\n";
679         unless ($gpg_verbose) {
680             print "    GnuPG errors:\n";
681             print for @errors;
682         }
683         exit 1;
684     }
685
686     if ($verbose) {
687         print "[+] Encrypted msg hex dump (" .
688             length($ctext) . " bytes):\n";
689         &hex_dump($ctext);
690     }
691
692     my $encoded_msg = encode_base64($ctext, '');
693
694     print "[+] Encrypted message: $encoded_msg\n" if $debug;
695     return $encoded_msg;
696 }
697
698 sub pcap_Rijndael_encrypt_msg() {
699     my $msg = shift;
700
701     my $cipher = Crypt::CBC->new(
702         {
703             'key'    => $enc_key,
704             'cipher' => $enc_alg,
705         }
706     );
707
708     my $encrypted_msg = $cipher->encrypt($msg);
709
710     if ($verbose) {
711         print "\n[+] Encrypted msg hex dump before base64 encoding (" .
712             length($encrypted_msg) . " bytes):\n";
713         &hex_dump($encrypted_msg);
714     }
715
716     my $encoded_msg = encode_base64($encrypted_msg, '');
717
718     ### Crypt::CBC adds the string "Salted__" to the beginning of the
719     ### encrypted text (at least for how we create the cipher object
720     ### above), so delete the encoded version of this string ("U2FsdGVkX1")
721     ### before sending on the wire.  The fwknopd server will add this
722     ### string back in before decrypting.  This makes it harder to write
723     ### an IDS signature that looks for fwknop traffic (e.g. look for the
724     ### string "U2FsdGVkX1" over UDP port 62201).
725     unless ($include_salted) {
726         print "[+] Stripping encoded Salted__ prefix (U2FsdGVkX1) from ",
727             "outgoing encoded SPA packet.\n" if $debug;
728         $encoded_msg =~ s/^U2FsdGVkX1//;  ### encoded "Salted__" string
729     }
730
731     print "[+] Encrypted message: $encoded_msg\n" if $debug;
732     return $encoded_msg;
733 }
734
735 sub pcap_send_encrypted_msg() {
736     my $msg = shift;
737
738     my $msg_len = length($msg);
739
740     if ($msg_len > $max_msg_len) {
741         die "[*] Message length is too long ($msg_len bytes), ",
742             "must be less than $max_msg_len bytes";
743     }
744
745     if ($verbose) {
746         print "\n[+] Packet data:\n\n", $msg, "\n\n" unless $quiet;
747     }
748
749     if ($save_packet_mode) {
750         print "    Saving packet data to: $save_packet_file\n" unless $quiet;
751         open F, "> $save_packet_file" or die "[*] Could not open ",
752             "$save_packet_file: $!";
753         print F $msg;
754         close F;
755     }
756
757     if ($spoof_src) {
758         unless ($spoof_src =~ /$ip_re/) {
759             ### resolve to an IP
760             my $iaddr = inet_aton($spoof_src)
761                 or die "[*] Could not resolve $spoof_src to IP.";
762             my $addr = inet_ntoa($iaddr)
763                 or die "[*] Could not resolve $spoof_src to IP.";
764             $spoof_src = $addr;
765         }
766
767         print
768 "\n[+] Sending $msg_len byte message to $knock_dst over $spoof_proto",
769     "/$enc_pcap_port\n    (spoofed src ip: $spoof_src).\n" unless $quiet;
770         my $rand_src_port = int(rand(65535));
771         $rand_src_port = 65001 if $rand_src_port > 65535;
772         $rand_src_port += 1024 if $rand_src_port < 1024;
773
774         ### use Net::RawIP to spoof the packets
775         require Net::RawIP;
776
777         if ($spoof_proto eq 'udp') {
778             my $rawpkt = new Net::RawIP({
779                 ip => {
780                     saddr => $spoof_src,
781                     daddr => $knock_dst
782                 },
783                 udp =>{}});
784             $rawpkt->set({ ip => {
785                     saddr => $spoof_src,
786                     daddr => $knock_dst
787                 },
788                 udp => {
789                     source => $rand_src_port,
790                     dest   => $enc_pcap_port,
791                     data   => $msg,
792                 }
793             });
794             $rawpkt->send();
795         } elsif ($spoof_proto eq 'icmp') {
796             my $rawpkt = new Net::RawIP({
797                 ip => {
798                     saddr => $spoof_src,
799                     daddr => $knock_dst
800                 },
801                 icmp =>{}});
802             $rawpkt->set({ ip => {
803                     saddr => $spoof_src,
804                     daddr => $knock_dst
805                 },
806                 icmp => {
807                     type => 0,
808                     code => 0,
809                     sequence => 0,
810                     data => $msg
811                 }
812             });
813             $rawpkt->send();
814         } elsif ($spoof_proto eq 'tcp') {
815             my $rawpkt = new Net::RawIP({
816                 ip => {
817                     saddr => $spoof_src,
818                     daddr => $knock_dst
819                 },
820                 tcp =>{}});
821             $rawpkt->set({ ip => {
822                     saddr => $spoof_src,
823                     daddr  => $knock_dst
824                 },
825                 tcp => {
826                     ack => 1,
827                     source => $rand_src_port,
828                     dest   => $enc_pcap_port,
829                     data => $msg
830                 }
831             });
832             if ($test_mode) {
833   &nbs