/*
 * cracker for ssh's identity file
 * v1.0 - 12 jun 1999 by stran9er
 * v1.1 - 30 jun 1999 (added SecureCRT keys support)
 */

#include "includes.h"
#include "ssh-crack.h"
#include "cipher.h"
#include "getput.h"
#include "crc32.h"

#define IDENTITY_MAX 32000
#define AUTHFILE_ID_STRING "SSH PRIVATE KEY FILE FORMAT 1.1\n"
#define SECURECRT_ID_STRING "VANDYKE SSH PRIVATE KEY FILE FORMAT 1.1\n"
#define SECURECRT_ID "VANDYKE1"
#define DECRYPTED_SUFFIX ".nopass"
#define LINESIZE 256

int counter = 0;
time_t start_time;
int guesses = 0;
FILE *pd = NULL;

#define ESC_LINEUP "\033\[1A"
#define ESC_CLEARL "\033\[0K"

void show_counter(int need_show)
{
 static time_t last_show = 0;
 time_t now = time(NULL);
 static int last_counter = 0;
 time_t delta = now - start_time;

 if (((now - last_show) > 60) || need_show) { /* at least minute */
 if (!delta || (now == last_show)) return;
 if (!last_show) last_show = start_time;
  printf(
   "%sattempts:%d  time: %d:%02d:%02d:%02d  c/s: %d  c/s last %dsec: %d %s\n",
          ESC_LINEUP,
          counter,
          (delta / (60 * 60 * 24)),
          (delta / (60 * 60)) % 60,
          (delta / 60) % 60,
          delta % 60,
          counter / delta,
          now - last_show,
          (counter - last_counter) / (now - last_show),
          ESC_CLEARL);
  last_show = now;
  last_counter = counter;
 }
}

void nextpass(char *pass)
{
 static int noasked = 1;
 char *ptr;

 while (!feof(pd ? pd : stdin)) {
  if (noasked && !pd && isatty(0)) {
   printf("Enter passwords:\n");
   noasked = 0;
  }
  fgets (pass, LINESIZE, pd ? pd : stdin);
  if ((ptr = strpbrk(pass, "\n\r"))) *ptr = 0;
  if (*pass) return;
 }
 show_counter(1);
 fatal("Done. %d passwords tried\n", counter);
}

main (int argc, char **argv)
{
 FILE *fd;
 int fsize, rsize;
 unsigned char *f, *p, *dest, *d, *dtop;
 char pass[LINESIZE];
 char *ifile, *ofile;
 int i, j, match, bits;
 int cipher_type;
 CipherContext cipher;
 int buffer_len;
 int stdout_isatty;
 int securecrt = 0;
 unsigned long sc_crc32;

 if (argc < 2) {
   printf("ssh-crack v"VERSION" - ssh identity file brute forcer by stran9er\n");
   fatal("ssh-crack <identity-file> [-pipe cmd ...]\n");
 }

 ifile = argv[1];

 /* options */
 for (i = 2; i < argc; i++) {
  if (strcmp("-pipe", argv[i]) == 0) {
   char *popencmd;
   int cmdlen;

   for (cmdlen = 0, j = i + 1; j < argc; j++) cmdlen += strlen(argv[j]) + 1;
   if (!cmdlen)
    fatal("specify command for -popen\n");
   if (!(popencmd = malloc ((size_t)cmdlen)))
    fatal("can't malloc %d bytes\n", cmdlen);
   for (popencmd[0] = 0, j = i + 1; j < argc; j++) {
    if (popencmd[0]) strcat (popencmd, " ");
    strcat (popencmd, argv[j]);
   }
   printf("popen: %s\n", popencmd);
   if ((pd = popen (popencmd, "r")) == NULL)
    fatal("can't create pipe to: %s: %s\n", popencmd, strerror(errno));
   break;
  }
 }

 if ((fd = fopen (ifile, "rb")) == NULL)
   fatal("can't open identity file: %s: %s\n", ifile, strerror(errno));

 if ((fseek (fd, 0L, SEEK_END)))
    fatal("can't fseek on %s: %s\n", ifile, strerror(errno));
 fsize = ftell (fd);
 if (fsize > IDENTITY_MAX)
   fatal("illegal size %d > %d of identity file: %s\n",
            fsize, IDENTITY_MAX, ifile);

 if (fsize < strlen(AUTHFILE_ID_STRING) + 1)
   fatal("illegal size %d < %d of identity file: %s\n",
            fsize, strlen(AUTHFILE_ID_STRING) + 1, ifile);

 if ((fseek (fd, 0L, SEEK_SET)))
    fatal("can't fseek on %s: %s\n", ifile, strerror(errno));

 if ((f = calloc (1, fsize + 7)) == NULL)
   fatal("can't malloc %d bytes\n", fsize);

 if (fsize != (rsize = fread (f, 1, (size_t)fsize, fd)))
   fatal("can't read %d/%d bytes identity file: %s\n", fsize, rsize, ifile);

 fclose (fd); /* don't care.. */

 /* check is it SecureCRT keyfile first */
 if ( (fsize > sizeof(SECURECRT_ID_STRING)) &&
      (!strncmp(SECURECRT_ID_STRING, f, sizeof(SECURECRT_ID_STRING) - 1)) ) {
  securecrt++;
  i = sizeof(SECURECRT_ID_STRING) - 1;
 } else {
  /* check if identity file contain printable characters until zero */
  for (i = 0; i < fsize; i++) {
   if (isprint(f[i]) || isspace(f[i])) continue;
   if (!f[i]) break;
   fatal("illegal id string for identity file: %s\n", ifile);
  }
  if (i == fsize)
    fatal("no id string in identity file: %s\n", ifile);

  if (strcmp (AUTHFILE_ID_STRING, f))
    fatal ("unsupported identify file type: %s\n", f);
 }

 /* skip ssh id string */
 /* securecrt id string not null terminated and has some garbage(?) byte
    after it, skip it too.. */
 p = &f[i + 1];

 /* check if we have next 1(cipher) + 4(reserved) + 4(bits) + 2(n) bytes */
 if ((p + 1 + 4 + 4 + 2) > (f + fsize))
   fatal("%s: illegal identity file format\n", ifile);

 cipher_type = *p; /* guess securecrt used same cypher numbers... */
 *p++ = SSH_CIPHER_NONE;
 /* Check that it is a supported cipher. */
 if (securecrt) {
  switch (cipher_type) {
   case 1:
    cipher_type = SSH_CIPHER_3DES; /* 1 mean 3des there.. */
   case 0:
    break;
   default:
    fatal("%s: Unknown cipher (%d) used for SecureCRT\n", ifile, cipher_type);
  }
 } else {
  if (cipher_type != SSH_CIPHER_NONE &&
     (cipher_mask() & (1 << cipher_type)) == 0)
    fatal("%s: Unsupported cipher (%d) %s used\n",
           ifile, cipher_type, cipher_name(cipher_type));
 }
 printf ("%s: Cypher type: %s\n", ifile, cipher_name(cipher_type));

 if (!securecrt) {
  i = GET_32BIT(p); p += 4;
  if (i) printf ("%s: Reserved data: 0x%08X\n", ifile, i);

  bits = GET_32BIT(p); p += 4;
  printf ("%s: Public key %d bits, ", ifile, bits);
 }

 i = GET_16BIT(p); p += 2;
 if (securecrt) bits = i;
 printf ("prv->n %d bits, ", i);
 p += (i + 7) / 8; /* skip prv->n */

 /* check if we have next 2(e) bytes */
 if ((p + 2) > (f + fsize))
   fatal("%s: illegal identity file format\n", ifile);

 i = GET_16BIT(p); p += 2;
 printf ("prv->e %d bits\n", i);
 p += (i + 7) / 8; /* skip prv->e */

 /* check if we have next 4(comment length) bytes */
 if ((p + 4) > (f + fsize))
   fatal("%s: illegal identity file format\n", ifile);

 i = GET_32BIT(p); p += 4;
 if ((i > 256*1024) || ((p + i) > (f + fsize)))
  fatal("bad comment string length %d", i);
 else if (i) {
  printf ("%s: Comment: ", ifile);
  fwrite (p, 1, (size_t)i, stdout);
  if ((i >= 2) && (p[i - 2] != '\n')) puts ("");
 }
 p += i;

 /* how much bytes rest */
 buffer_len = fsize - (p - f);
 if ( (p + 8) > (f + fsize) ||
      (securecrt && ((buffer_len - 4) & 7)) ||
      (!securecrt && (buffer_len & 7)) )
   fatal("%s: illegal identity file format\n", ifile);

 if (cipher_type == SSH_CIPHER_NONE)
   fatal("%s: passwordless identity file!", ifile);

 printf ("%s: Encrypted part length: %d\n", ifile, buffer_len);
 if ((dest = malloc ((size_t)buffer_len + 8)) == NULL)
   fatal("can't malloc %d bytes\n", buffer_len);
 dtop = dest + buffer_len;

 /* make output filename */
 ofile = malloc ((size_t)(strlen(ifile) + sizeof(DECRYPTED_SUFFIX)));
 if (ofile == NULL)
   fatal("can't allocate memory\n");
 strcpy (ofile, ifile);
 strcat (ofile, DECRYPTED_SUFFIX);

 if ((stdout_isatty = isatty(1))) puts(""); /* empty line for stats */
 start_time = time(NULL);

 /*** cracking loop ***/

 for (pass[0] = 0; ; nextpass(pass)) {

  if (stdout_isatty && !(counter & 0x7ff)) show_counter(0);
  counter++;

  cipher_set_key_string(&cipher, cipher_type, (unsigned char *)pass, 0);
  cipher_decrypt(&cipher, d = dest, p, 8);

  if (securecrt) {

   if (SECURECRT_ID[0] != d[0]) continue;
   if (memcmp (SECURECRT_ID, d, sizeof(SECURECRT_ID) - 1)) continue;
   /* now we have 128 bit guarantee validity of the key.. */
   d += 8;

   /* decrypt rest */
   cipher_decrypt(&cipher, d, p + 8, buffer_len - 8 - 4);

  } else { /* normal ssh */

   i = GET_16BIT(d); d += 2;
   j = GET_16BIT(d);
   if (i != j) continue; /* don't match */
   d += 2;

   /* ok, possible match, check for more.. */

   i = GET_16BIT(d); /* prv->d */
   if (i > bits) continue;

   /* decrypt rest */
   cipher_decrypt(&cipher, d + 4, p + 8, buffer_len - 8);

   if ((d += 2 + ((i + 7) / 8)) > dtop) continue;

   i = GET_16BIT(d); /* prv->u */
   if ((i > bits) || ((d += 2 + ((i + 7) / 8)) > dtop)) continue;

   i = GET_16BIT(d); /* prv->p */
   if ((i > bits) || ((d += 2 + ((i + 7) / 8)) > dtop)) continue;

   i = GET_16BIT(d); /* prv->q */
   if ((i > bits) || ((d += 2 + ((i + 7) / 8)) > dtop)) continue;
  }

  /* !@#$! looks like that pasword ok */
  printf ("%s: (%s) MATCH! ", ifile, pass);

  /* write decrypted file to disk */
  if ((fd = fopen (ofile, "wb")) == NULL)
   error("can't write decrypted file: %s\n", ofile);
  else {
   /* copy decrypted part to their place */
   memcpy (f + fsize - buffer_len, dest, buffer_len);
   fwrite (f, 1, securecrt ? fsize - 4 : fsize , fd);
   if (securecrt) { /* SecureCRT need crc32 for some reason.. */
    sc_crc32 = crc32(f, fsize - 4);
    PUT_32BIT(f, sc_crc32);
    fwrite (f, 1, 4, fd);
   }
   fclose (fd);
   printf ("saved to: %s", ofile);
  }
  if (stdout_isatty) puts(""); /* empty line for stats */
  break;
 }
 exit(0);
}
/**/
