/*
 * The IKE Scanner (ike-scan) is Copyright (C) 2003-2005 Roy Hills,
 * NTA Monitor Ltd.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * If this license is unacceptable to you, I may be willing to negotiate
 * alternative licenses (contact ike-scan@nta-monitor.com).
 *
 * You are encouraged to send comments, improvements or suggestions to
 * me at ike-scan@nta-monitor.com.
 *
 * $Id: psk-crack.c,v 1.1.1.1 2005/01/13 18:45:15 mcr Exp $
 *
 * psk-crack.c -- IKE Aggressive Mode Pre-Shared Key cracker for ike-scan
 *
 * Author: Roy Hills
 * Date: 8 July 2004
 *
 * Usage:
 *	psk-crack [options] <psk-parameters-file>
 *
 */
#include "ike-scan.h"
static const char rcsid[] = "$Id: psk-crack.c,v 1.1.1.1 2005/01/13 18:45:15 mcr Exp $";	/* RCS ID for ident(1) */

#define MAXLEN 4096
#define HASH_TYPE_MD5 1
#define HASH_TYPE_SHA1 2
#define MD5_HASH_LEN 16
#define SHA1_HASH_LEN 20

char *default_charset =
   "0123456789abcdefghijklmnopqrstuvwxyz"; /* default bruteforce charset */

int
main (int argc, char *argv[]) {
   const struct option long_options[] = {
      {"help", no_argument, 0, 'h'},
      {"verbose", no_argument, 0, 'v'},
      {"version", no_argument, 0, 'V'},
      {"bruteforce", required_argument, 0, 'B'},
      {"charset", required_argument, 0, 'c'},
      {"dictionary", required_argument, 0, 'd'},
      {0, 0, 0, 0}
   };
   const char *short_options = "hvVB:c:d:";
   int arg;
   int options_index=0;
   int verbose=0;
   int hash_type=0;	/* Hash type: MD5 or SHA1 */
   size_t hash_len=0;	/* Set to 0 to avoid uninitialised warning */
   char *hash_name=NULL; /* Hash name: MD5 or SHA1 */
   unsigned brute_len=0; /* Bruteforce len.  0=dictionary attack (default) */
   char *charset = NULL;
   char dict_file_name[MAXLINE];	/* Dictionary file name */
   FILE *dictionary_file=NULL;	/* Dictionary file, one word per line */
   FILE *data_file;	/* PSK parameters in colon separated format */
   IKE_UINT64 iterations=0;
   int found=0;
   struct timeval start_time;	/* Program start time */
   struct timeval end_time;	/* Program end time */
   struct timeval elapsed_time; /* Elapsed time as timeval */
   double elapsed_seconds;	/* Elapsed time in seconds */
   int n;

   char g_xr_hex[MAXLEN];
   char g_xi_hex[MAXLEN];
   char cky_r_hex[MAXLEN];
   char cky_i_hex[MAXLEN];
   char sai_b_hex[MAXLEN];
   char idir_b_hex[MAXLEN];
   char ni_b_hex[MAXLEN];
   char nr_b_hex[MAXLEN];
   char expected_hash_r_hex[MAXLEN];

   unsigned char *g_xr;
   unsigned char *g_xi;
   unsigned char *cky_r;
   unsigned char *cky_i;
   unsigned char *sai_b;
   unsigned char *idir_b;
   unsigned char *ni_b;
   unsigned char *nr_b;

   size_t g_xr_len;
   size_t g_xi_len;
   size_t cky_r_len;
   size_t cky_i_len;
   size_t sai_b_len;
   size_t idir_b_len;
   size_t ni_b_len;
   size_t nr_b_len;
   size_t expected_hash_r_len;

   unsigned char *skeyid;
   unsigned char *hash_r = NULL;	/* Initialised to avoid warning */
   unsigned char *expected_hash_r;

   unsigned char *skeyid_data;
   unsigned char *hash_r_data;

   size_t skeyid_data_len;
   size_t hash_r_data_len;

   unsigned char *cp;

   char line[MAXLINE];
   char psk_data[MAXLEN];

   dict_file_name[0] = '\0';	/* Initialise to empty string */

/*
 *      Process options and arguments.
 */
   while ((arg=getopt_long_only(argc, argv, short_options, long_options, &options_index)) != -1) {
      switch (arg) {
         case 'h':      /* --help */
            psk_crack_usage(EXIT_SUCCESS);
            break;
         case 'v':      /* --verbose */
            verbose++;
            break;
         case 'V':      /* --version */
            fprintf(stderr, "psk-crack (%s)\n\n", PACKAGE_STRING);
            fprintf(stderr, "Copyright (C) 2003-2005 Roy Hills, NTA Monitor Ltd.\n");
            fprintf(stderr, "ike-scan comes with NO WARRANTY to the extent permitted by law.\n");
            fprintf(stderr, "You may redistribute copies of ike-scan under the terms of the GNU\n");
            fprintf(stderr, "General Public License.\n");
            fprintf(stderr, "For more information about these matters, see the file named COPYING.\n");
            fprintf(stderr, "\n");
/* We use rcsid here to prevent it being optimised away */
            fprintf(stderr, "%s\n", rcsid);
            error_use_rcsid();
            utils_use_rcsid();
            wrappers_use_rcsid();
            exit(EXIT_SUCCESS);
            break;
         case 'B':      /* --bruteforce */
            brute_len=strtoul(optarg, (char **)NULL, 10);
            break;
         case 'c':      /* --charset */
            charset=make_message("%s", optarg);
            break;
         case 'd':      /* --dictionary */
            strncpy(dict_file_name, optarg, MAXLINE);
            brute_len = 0;
            break;
         default:       /* Unknown option */
            psk_crack_usage(EXIT_FAILURE);
            break;
      }
   } /* End While */

/*
 *	Check that we've got exactly one argument.
 */
   if ((argc - optind) != 1) {
      psk_crack_usage(EXIT_FAILURE);
   }

   if (!charset)
      charset = default_charset;
/*
 *	Open data file.
 */
   if ((data_file = fopen(argv[optind], "r")) == NULL)
      err_sys("error opening data file %s", argv[optind]);
/*
 *	Open dictionary file if required.
 */
   if (!brute_len) {	/* If not bruteforcing */
      char *fn;
#ifdef __CYGWIN__
      char fnbuf[MAXLINE];
      int fnbuf_siz;
      int i;
#endif

      if (dict_file_name[0] == '\0') {	/* If dictionary file not specified */
#ifdef __CYGWIN__
         if ((fnbuf_siz=GetModuleFileName(GetModuleHandle(0),
              fnbuf, MAXLINE)) == 0) {
            err_msg("ERROR: Call to GetModuleFileName failed");
         }
         for (i=fnbuf_siz-1; i>=0 && fnbuf[i] != '/' && fnbuf[i] != '\\'; i--)
            ;
         if (i >= 0) {
            fnbuf[i] = '\0';
         }
         fn = make_message("%s\\%s", fnbuf, DICT_FILE);
#else
         fn = make_message("%s/%s", IKEDATADIR, DICT_FILE);
#endif
      } else {
         fn = make_message("%s", dict_file_name);
      }
      if ((dictionary_file = fopen(fn, "r")) == NULL)
         err_sys("error opening dictionary file %s", fn);
      free(fn);
   }
/*
 *	Get program start time for statistics displayed on completion
 *	and print starting message.
 */
   Gettimeofday(&start_time);
   if (brute_len) {
      printf("Starting psk-crack in brute-force cracking mode\n");
   } else {
      printf("Starting psk-crack in dictionary cracking mode\n");
   }

   while ((fgets(psk_data, MAXLEN, data_file)) != NULL) {

      n=sscanf(psk_data,
               "%[^:]:%[^:]:%[^:]:%[^:]:%[^:]:%[^:]:%[^:]:%[^:]:%[^:\r\n]",
               g_xr_hex, g_xi_hex, cky_r_hex, cky_i_hex, sai_b_hex,
               idir_b_hex, ni_b_hex, nr_b_hex, expected_hash_r_hex);

      if (n != 9)
         err_msg("Error in data format.  Expected 9 fields, found %d", n);
/*
 *	Convert input fields from ASCII hex to binary.
 */
      g_xr = hex2data(g_xr_hex, &g_xr_len);
      g_xi = hex2data(g_xi_hex, &g_xi_len);
      cky_r = hex2data(cky_r_hex, &cky_r_len);
      cky_i = hex2data(cky_i_hex, &cky_i_len);
      sai_b = hex2data(sai_b_hex, &sai_b_len);
      idir_b = hex2data(idir_b_hex, &idir_b_len);
      ni_b = hex2data(ni_b_hex, &ni_b_len);
      nr_b = hex2data(nr_b_hex, &nr_b_len);
      expected_hash_r = hex2data(expected_hash_r_hex, &expected_hash_r_len);
/*
 *	Determine hash type.
 */
      if (expected_hash_r_len == MD5_HASH_LEN) {
         hash_type=HASH_TYPE_MD5;
         hash_len=MD5_HASH_LEN;
         hash_name="MD5";
      } else if (expected_hash_r_len == SHA1_HASH_LEN) {
         hash_type=HASH_TYPE_SHA1;
         hash_len=SHA1_HASH_LEN;
         hash_name="SHA1";
      } else {
         err_msg("Cannot determine hash type from %u byte HASH_R",
                 expected_hash_r_len);
      }

      skeyid_data_len = ni_b_len + nr_b_len;
      skeyid_data = Malloc(skeyid_data_len);
      cp = skeyid_data;
      memcpy(cp, ni_b, ni_b_len);
      cp += ni_b_len;
      memcpy(cp, nr_b, nr_b_len);
      skeyid = Malloc(hash_len);
      hash_r_data_len = g_xr_len + g_xi_len + cky_r_len + cky_i_len +
                        sai_b_len + idir_b_len;
      hash_r_data = Malloc(hash_r_data_len);
      cp = hash_r_data;
      memcpy(cp, g_xr, g_xr_len);
      cp += g_xr_len;
      memcpy(cp, g_xi, g_xi_len);
      cp += g_xi_len;
      memcpy(cp, cky_r, cky_r_len);
      cp += cky_r_len;
      memcpy(cp, cky_i, cky_i_len);
      cp += cky_i_len;
      memcpy(cp, sai_b, sai_b_len);
      cp += sai_b_len;
      memcpy(cp, idir_b, idir_b_len);
      hash_r = Malloc(hash_len);
/*
 *	Cracking loop.
 */
      if (brute_len) {	/* Brute force cracking */
         IKE_UINT64 max;
         unsigned base;
         unsigned i;
         IKE_UINT64 loop;
         IKE_UINT64 val;
         unsigned digit;
   
         base = strlen(charset);
         max = base;
         for (i=1; i<brute_len; i++)
            max *= base;	/* max = base^brute_len without using pow() */
         printf("Brute force with %u chars up to length %u will take up to "
                IKE_UINT64_FORMAT " iterations\n", base, brute_len, max);
   
         for (loop=0; loop<max; loop++) {
            char *line_p;

            val = loop;
            line_p = line;
            do {
               digit = val % base;
               val /= base;
               *line_p++ = charset[digit];
            } while (val);
            *line_p = '\0';
            if (verbose)
               printf("Trying key \"%s\"\n", line);
            if (hash_type == HASH_TYPE_MD5) {
               hmac_md5(skeyid_data, skeyid_data_len, (unsigned char *) line,
                        strlen(line), skeyid);
               hmac_md5(hash_r_data, hash_r_data_len, skeyid, hash_len, hash_r);
            } else if (hash_type == HASH_TYPE_SHA1) {
               hmac_sha1(skeyid_data, skeyid_data_len, (unsigned char *) line,
                         strlen(line), skeyid);
               hmac_sha1(hash_r_data, hash_r_data_len, skeyid, hash_len, hash_r);
            } else {
               err_msg("Unknown hash_type: %d\n", hash_type);
            }
            iterations++;
            if (!memcmp(hash_r, expected_hash_r, expected_hash_r_len)) {
               found=1;
               break;
            }
         }
      } else {	/* Dictionary cracking */
         rewind(dictionary_file);
         while (fgets(line, MAXLINE, dictionary_file)) {
            char *line_p;
            for (line_p = line; !isspace((unsigned char)*line_p) &&
                 *line_p != '\0'; line_p++)
               ;
            *line_p = '\0';
            if (verbose)
               printf("Trying key \"%s\"\n", line);
            if (hash_type == HASH_TYPE_MD5) {
               hmac_md5(skeyid_data, skeyid_data_len, (unsigned char *) line,
                        strlen(line), skeyid);
               hmac_md5(hash_r_data, hash_r_data_len, skeyid, hash_len, hash_r);
            } else if (hash_type == HASH_TYPE_SHA1) {
               hmac_sha1(skeyid_data, skeyid_data_len, (unsigned char *) line,
                         strlen(line), skeyid);
               hmac_sha1(hash_r_data, hash_r_data_len, skeyid, hash_len, hash_r);
            } else {
               err_msg("Unknown hash_type: %d\n", hash_type);
            }
            iterations++;
            if (!memcmp(hash_r, expected_hash_r, expected_hash_r_len)) {
               found=1;
               break;
            }
         }
      }
      if (found) {
         printf("key \"%s\" matches %s hash %s\n", line, hash_name,
                expected_hash_r_hex);
      } else {
         printf("no match found for %s hash %s\n", hash_name,
                expected_hash_r_hex);
      }
   }
/*
 *      Get program end time and calculate elapsed time.
 */
   Gettimeofday(&end_time);
   timeval_diff(&end_time, &start_time, &elapsed_time);
   elapsed_seconds = (elapsed_time.tv_sec*1000 +
                      elapsed_time.tv_usec/1000.0) / 1000.0;
   if (elapsed_seconds < 0.000001)
      elapsed_seconds = 0.000001;	/* Avoid div by zero */
   printf("Ending psk-crack: " IKE_UINT64_FORMAT
          " iterations in %.3f seconds (%.2f iterations/sec)\n",
          iterations, elapsed_seconds, iterations/elapsed_seconds);
   fclose(data_file);
   if (!brute_len)
      fclose(dictionary_file);

   return 0;
}

/*
 *	psk_crack_usage -- display usage message and exit
 *
 *      Inputs:
 *
 *      status	Status value to pass to exit()
 *
 *	Returns:
 *
 *	None (this function never returns).
 */
void
psk_crack_usage(int status) {
   fprintf(stderr, "Usage: psk-crack [options] <psk-parameters-file>\n");
   fprintf(stderr, "\n");
   fprintf(stderr, "<psk-parameters-file> is a file containing the parameters for the pre-shared\n");
   fprintf(stderr, "key cracking process in the format generated by ike-scan with the --pskcrack\n");
   fprintf(stderr, "(-P) option.\n");
   fprintf(stderr, "\n");
   fprintf(stderr, "By default, psk-crack will perform dictionary cracking using the default\n");
   fprintf(stderr, "dictionary.  The dictionary can be changed with the --dictionary (-d) option,\n");
   fprintf(stderr, "or brute-force cracking can be selected with the --bruteforce (-B) option.\n");
   fprintf(stderr, "\n");
   fprintf(stderr, "Options:\n");
   fprintf(stderr, "\n--help or -h\t\tDisplay this usage message and exit.\n");
   fprintf(stderr, "\n--version or -V\t\tDisplay program version and exit.\n");
   fprintf(stderr, "\n--verbose or -v\t\tDisplay verbose progress messages.\n");
   fprintf(stderr, "\n--dictionary=<f> or -d <f> Set dictionary file to <f>\n");
#ifdef __CYGWIN__
   fprintf(stderr, "\t\t\tdefault=%s in psk-crack.exe dir.\n", DICT_FILE);
#else
   fprintf(stderr, "\t\t\tdefault=%s/%s.\n", IKEDATADIR, DICT_FILE);
#endif
   fprintf(stderr, "\n--bruteforce=<n> or -B <n> Select bruteforce cracking up to <n> characters.\n");
   fprintf(stderr, "\n--charset=<s> or -c <s>\tSet bruteforce character set to <s>\n");
   fprintf(stderr, "\t\t\tDefault is \"%s\"\n", default_charset);
   fprintf(stderr, "\n");
   fprintf(stderr, "Report bugs or send suggestions to %s\n", PACKAGE_BUGREPORT);
   fprintf(stderr, "See the ike-scan homepage at http://www.nta-monitor.com/ike-scan/\n");
   exit(status);
}
