root/fwknop/tags/fwknop-1.8/fwknop

Revision 685, 44.2 kB (checked in by mbr, 2 years ago)

minor Netfilter -> iptables wording update

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