root/fwknop/tags/fwknop-1.8.2-pre7/fwknop

Revision 739, 45.6 kB (checked in by mbr, 1 year ago)

1.8.2-pre7

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