root/fwknop/tags/fwknop-1.8.4-pre2/fwknop

Revision 824, 48.6 kB (checked in by mbr, 1 year ago)

fwknop-1.8.4-pre2

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