root/fwknop/tags/fwknop-1.9.2/fwknop

Revision 1028, 55.0 kB (checked in by mbr, 9 months ago)

bumped version to 1.9.2

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to 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.2
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: fwknop 586 2006-11-04 20:45:49Z mbr $
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.2';
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     my $encoded_msg = encode_base64($ctext, '');
687
688     print "[+] Encrypted message: $encoded_msg\n" if $debug;
689     return $encoded_msg;
690 }
691
692 sub pcap_Rijndael_encrypt_msg() {
693     my $msg = shift;
694
695     my $cipher = Crypt::CBC->new(
696         {
697             'key'    => $enc_key,
698             'cipher' => $enc_alg
699         }
700     );
701     my $encoded_msg = encode_base64($cipher->encrypt($msg), '');
702
703     ### Crypt::CBC adds the string "Salted__" to the beginning of the
704     ### encrypted text (at least for how we create the cipher object
705     ### above), so delete the encoded version of this string ("U2FsdGVkX1")
706     ### before sending on the wire.  The fwknopd server will add this
707     ### string back in before decrypting.  This makes it harder to write
708     ### an IDS signature that looks for fwknop traffic (e.g. look for the
709     ### string "U2FsdGVkX1" over UDP port 62201).
710     unless ($include_salted) {
711         print "[+] Stripping encoded Salted__ prefix (U2FsdGVkX1) from ",
712             "outgoing encoded SPA packet.\n" if $debug;
713         $encoded_msg =~ s/^U2FsdGVkX1//;  ### encoded "Salted__" string
714     }
715
716     print "[+] Encrypted message: $encoded_msg\n" if $debug;
717     return $encoded_msg;
718 }
719
720 sub pcap_send_encrypted_msg() {
721     my $msg = shift;
722
723     my $msg_len = length($msg);
724
725     if ($msg_len > $max_msg_len) {
726         die "[*] Message length is too long ($msg_len bytes), ",
727             "must be less than $max_msg_len bytes";
728     }
729
730     if ($verbose) {
731         print "\n[+] Packet data:\n\n", $msg, "\n\n" unless $quiet;
732     }
733
734     if ($save_packet_mode) {
735         print "    Saving packet data to: $save_packet_file\n" unless $quiet;
736         open F, "> $save_packet_file" or die "[*] Could not open ",
737             "$save_packet_file: $!";
738         print F $msg;
739         close F;
740     }
741
742     if ($spoof_src) {
743         unless ($spoof_src =~ /$ip_re/) {
744             ### resolve to an IP
745             my $iaddr = inet_aton($spoof_src)
746                 or die "[*] Could not resolve $spoof_src to IP.";
747             my $addr = inet_ntoa($iaddr)
748                 or die "[*] Could not resolve $spoof_src to IP.";
749             $spoof_src = $addr;
750         }
751
752         print
753 "\n[+] Sending $msg_len byte message to $knock_dst over $spoof_proto",
754     "/$enc_pcap_port\n    (spoofed src ip: $spoof_src).\n" unless $quiet;
755         my $rand_src_port = int(rand(65535));
756         $rand_src_port = 65001 if $rand_src_port > 65535;
757         $rand_src_port += 1024 if $rand_src_port < 1024;
758
759         ### use Net::RawIP to spoof the packets
760         require Net::RawIP;
761
762         if ($spoof_proto eq 'udp') {
763             my $rawpkt = new Net::RawIP({
764                 ip => {
765                     saddr => $spoof_src,
766                     daddr => $knock_dst
767                 },
768                 udp =>{}});
769             $rawpkt->set({ ip => {
770                     saddr => $spoof_src,
771                     daddr => $knock_dst
772                 },
773                 udp => {
774                     source => $rand_src_port,
775                     dest   => $enc_pcap_port,
776                     data   => $msg,
777                 }
778             });
779             $rawpkt->send();
780         } elsif ($spoof_proto eq 'icmp') {
781             my $rawpkt = new Net::RawIP({
782                 ip => {
783                     saddr => $spoof_src,
784                     daddr => $knock_dst
785                 },
786                 icmp =>{}});
787             $rawpkt->set({ ip => {
788                     saddr => $spoof_src,
789                     daddr => $knock_dst
790                 },
791                 icmp => {
792                     type => 0,
793                     code => 0,
794                     sequence => 0,
795                     data => $msg
796                 }
797             });
798             $rawpkt->send();
799         } elsif ($spoof_proto eq 'tcp') {
800             my $rawpkt = new Net::RawIP({
801                 ip => {
802                     saddr => $spoof_src,
803                     daddr => $knock_dst
804                 },
805                 tcp =>{}});
806             $rawpkt->set({ ip => {
807                     saddr => $spoof_src,
808                     daddr  => $knock_dst
809                 },
810                 tcp => {
811                     ack => 1,
812                     source => $rand_src_port,
813                     dest   => $enc_pcap_port,
814                     data => $msg
815                 }
816             });
817             if ($test_mode) {
818                 print "    --Test-mode enabled, SPA packet not sent.\n";
819             } else {
820                 $rawpkt->send();
821             }
822         }
823     } else {
824
825         if ($spa_established_tcp) {  ### useful for Tor
826             print "\n[+] Sending $msg_len byte message to $knock_dst ",
827                 "over established tcp/$enc_pcap_port socket...\n"
828                 unless $quiet;
829
830