root/fwknop/tags/fwknop-1.8.4-pre1/fwknop

Revision 817, 47.3 kB (checked in by mbr, 1 year ago)

fwknop-1.8.4-pre1

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