www.pudn.com > chntpw-source-000607.zip > chntpw.c


/*
 * chntpw.c - Offline Password Edit Utility for NT 3.51 & 4.0 SAM database.
 * 1999-feb: Now able to browse registry hives. (write support to come)
 * 2000-jan: Attempt to detect and disable syskey
 * 
 * Copyright (c) 1997-2000 Petter Nordahl-Hagen.
 * Freely distributable in source or binary for noncommercial purposes.
 * 
 * Uses (links with) DES SSL Library by Eric Young (eay@mincom.oz.au)
 * and MD4 routines by RSA.
 * 
 * Part of some routines, information and ideas taken from
 * pwdump by Jeremy Allison.
 *
 * Some stuff (like proto.h) from NTCrack by Jonathan Wilkins.
 * 
 * Please see the COPYING file for more details on
 * copyrights & credits.
 *  
 * THIS SOFTWARE IS PROVIDED BY PETTER NORDAHL-HAGEN `AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */ 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
/* #include  */

/* These are from the DESlib & MD packages, see Makefile & README */
#include 
#include 
#include 

#include "proto.h"

#include "ntreg.h"

const char chntpw_version[] = "chntpw version 0.98.1 000607, (c) Petter N Hagen";

extern char *val_types[REG_MAX+1];

#define MAX_HIVES 10
#define H_SAM 0
#define H_SYS 1
#define H_SEC 2


/* Array of loaded hives */
struct hive *hive[MAX_HIVES+1];
int no_hives = 0;

int syskeyreset = 0;
int dirty = 0;

/*
 * of user with RID 500, because silly MS decided
 * to localize the bloody admin-username!! AAAGHH!
 */
char admuser[129]="StupidMS";

/* ============================================================== */

/* Crypto-stuff & support for what we'll do in the V-value */

/* Zero out string for lanman passwd, then uppercase
 * the supplied password and put it in here */

void make_lanmpw(char *p, char *lm, int len)
{
   int i;
   
   for (i=0; i < 15; i++) lm[i] = 0;
   for (i=0; i < len; i++) lm[i] = toupper(p[i]);
}

/*
 * Convert a 7 byte array into an 8 byte des key with odd parity.
 */

void str_to_key(unsigned char *str,unsigned char *key)
{
	void des_set_odd_parity(des_cblock *);
	int i;

	key[0] = str[0]>>1;
	key[1] = ((str[0]&0x01)<<6) | (str[1]>>2);
	key[2] = ((str[1]&0x03)<<5) | (str[2]>>3);
	key[3] = ((str[2]&0x07)<<4) | (str[3]>>4);
	key[4] = ((str[3]&0x0F)<<3) | (str[4]>>5);
	key[5] = ((str[4]&0x1F)<<2) | (str[5]>>6);
	key[6] = ((str[5]&0x3F)<<1) | (str[6]>>7);
	key[7] = str[6]&0x7F;
	for (i=0;i<8;i++) {
		key[i] = (key[i]<<1);
	}
	des_set_odd_parity((des_cblock *)key);
}

/*
 * Function to convert the RID to the first decrypt key.
 */

void sid_to_key1(unsigned long sid,unsigned char deskey[8])
{
	unsigned char s[7];

	s[0] = (unsigned char)(sid & 0xFF);
	s[1] = (unsigned char)((sid>>8) & 0xFF);
	s[2] = (unsigned char)((sid>>16) & 0xFF);
	s[3] = (unsigned char)((sid>>24) & 0xFF);
	s[4] = s[0];
	s[5] = s[1];
	s[6] = s[2];

	str_to_key(s,deskey);
}

/*
 * Function to convert the RID to the second decrypt key.
 */

void sid_to_key2(unsigned long sid,unsigned char deskey[8])
{
	unsigned char s[7];
	
	s[0] = (unsigned char)((sid>>24) & 0xFF);
	s[1] = (unsigned char)(sid & 0xFF);
	s[2] = (unsigned char)((sid>>8) & 0xFF);
	s[3] = (unsigned char)((sid>>16) & 0xFF);
	s[4] = s[0];
	s[5] = s[1];
	s[6] = s[2];

	str_to_key(s,deskey);
}

/* DES encrypt, for LANMAN */

void E1(uchar *k, uchar *d, uchar *out)
{
  des_key_schedule ks;
  des_cblock deskey;

  str_to_key(k,(uchar *)deskey);
#ifdef __FreeBSD__
  des_set_key(&deskey,ks);
#else /* __FreeBsd__ */
  des_set_key((des_cblock *)deskey,ks);
#endif /* __FreeBsd__ */
  des_ecb_encrypt((des_cblock *)d,(des_cblock *)out, ks, DES_ENCRYPT);
}


/* Decode the V-struct, and change the password
 * vofs - offset into SAM buffer, start of V struct
 * rid - the users RID, required for the DES decrypt stage
 *       If rid==0 it will try to extract the rid out of the V struct
 *       else the supplied one will be used
 *
 * Some of this is ripped & modified from pwdump by Jeremy Allison
 * 
 */
char *change_pw(char *buf, int rid, int vlen, int stat)
{
   
   uchar x1[] = {0x4B,0x47,0x53,0x21,0x40,0x23,0x24,0x25};
   char magic[] = {0x24, 0, 0x44 };
   int pl,ridofs,vend;
   char *vp;
   static char username[128],fullname[128];
   char comment[128],homedir[128],md4[32],lanman[32];
   char newunipw[34], newp[20], despw[20], newlanpw[16], newlandes[20];
   char yn[4];
   int username_offset,username_len;
   int fullname_offset,fullname_len;
   int comment_offset,comment_len;
   int homedir_offset,homedir_len;
   int ntpw_len,lmpw_len,ntpw_offs,lmpw_offs,i;
   des_key_schedule ks1, ks2;
   des_cblock deskey1, deskey2;

   MD4_CTX context;
   unsigned char digest[16];


   vp = buf;
   username_offset = get_int(vp + 0xC);
   username_len = get_int(vp + 0x10); 
   fullname_offset = get_int(vp + 0x18);
   fullname_len = get_int(vp + 0x1c);
   comment_offset = get_int(vp + 0x24);
   comment_len = get_int(vp + 0x28);
   homedir_offset = get_int(vp + 0x48);
   homedir_len = get_int(vp + 0x4c);
   lmpw_offs = get_int(vp + 0x9c);
   lmpw_len = get_int(vp + 0xa0);
   ntpw_offs = get_int(vp + 0xa8);
   ntpw_len = get_int(vp + 0xac);

#if 0
   printf("lmpw_offs: 0x%x, lmpw_len: %d (0x%x)\n",lmpw_offs,lmpw_len,lmpw_len);
   printf("ntpw_offs: 0x%x, ntpw_len: %d (0x%x)\n",ntpw_offs,ntpw_len,ntpw_len);
#endif

   *username = 0;
   *fullname = 0;
   *comment = 0;
   *homedir = 0;
   
   if(username_len <= 0 || username_len > vlen ||
      username_offset <= 0 || username_offset >= vlen ||
      comment_len < 0 || comment_len > vlen   ||
      fullname_len < 0 || fullname_len > vlen ||
      homedir_offset < 0 || homedir_offset >= vlen ||
      comment_offset < 0 || comment_offset >= vlen ||
      lmpw_offs < 0 || lmpw_offs >= vlen)
     {
	if (stat != 1) printf("Not a legal struct? (negative lengths)\n");
	return(NULL);
     }

   /* Offsets in top of struct is relative to end of pointers, adjust */
   username_offset += 0xCC;
   fullname_offset += 0xCC;
   comment_offset += 0xCC;
   homedir_offset += 0xCC;
   ntpw_offs += 0xCC;
   lmpw_offs += 0xCC;
   
   cheap_uni2ascii(vp + username_offset,username,username_len);
   cheap_uni2ascii(vp + fullname_offset,fullname,fullname_len);
   cheap_uni2ascii(vp + comment_offset,comment,comment_len);
   cheap_uni2ascii(vp + homedir_offset,homedir,homedir_len);
   
#if 0
   if (!rid) {
      printf("No RID supplied, doing some magic to find it.\n");
      ridofs = find_in_buf(buf, magic,3, 0);
      if (!ridofs || ridofs > vlen) {
	 printf("No RID found in struct..\n");
	 rid = 0;
      } else {
	 rid = get_int(buf+ridofs+0x1e);
      }
   }
#endif

#if 0
   /* Reset hash-lengths to 16 if syskey has been reset */
   if (syskeyreset && ntpw_len > 16 && !stat) {
     ntpw_len = 16;
     lmpw_len = 16;
     ntpw_offs -= 4;
     (unsigned int)*(vp+0xa8) = ntpw_offs - 0xcc;
     *(vp + 0xa0) = 16;
     *(vp + 0xac) = 16;
   }
#endif
   if (stat) {
      printf("RID: %04x, Username: <%s>%s\n",
	     rid, username,  ( !rid ? ", account locked?" : (ntpw_len < 16) ? ", *BLANK password*" : "")  );
      return(username);
   }

   printf("RID     : %04d [%04x]\n",rid,rid);
   printf("Username: %s\n",username);
   printf("fullname: %s\n",fullname);
   printf("comment : %s\n",comment);
   printf("homedir : %s\n\n",homedir);
   
   if (ntpw_len < 16) {
      printf("** This user _probably_ has a BLANK password! (Unable to change it, let NT do it instead :)\n\n");
      return(0);
   }
   
   if (!rid) {
     printf("No RID given. Unable to change passwords..\n");
     return(0);
   }
   
   hexprnt("Crypted NT pw: ",(vp+ntpw_offs),16);
   hexprnt("Crypted LM pw: ",(vp+lmpw_offs),16);

   /* Get the two decrpt keys. */
   sid_to_key1(rid,(unsigned char *)deskey1);
   des_set_key((des_cblock *)deskey1,ks1);
   sid_to_key2(rid,(unsigned char *)deskey2);
   des_set_key((des_cblock *)deskey2,ks2);
   
   /* Decrypt the NT md4 password hash as two 8 byte blocks. */
   des_ecb_encrypt((des_cblock *)(vp+ntpw_offs ),
		   (des_cblock *)md4, ks1, DES_DECRYPT);
   des_ecb_encrypt((des_cblock *)(vp+ntpw_offs + 8),
		   (des_cblock *)&md4[8], ks2, DES_DECRYPT);

   /* Decrypt the lanman password hash as two 8 byte blocks. */
   des_ecb_encrypt((des_cblock *)(vp+lmpw_offs),
		   (des_cblock *)lanman, ks1, DES_DECRYPT);
   des_ecb_encrypt((des_cblock *)(vp+lmpw_offs + 8),
		   (des_cblock *)&lanman[8], ks2, DES_DECRYPT);
   
   
   hexprnt("MD4 hash     : ",md4,16);
   hexprnt("LANMAN hash  : ",lanman,16);
   
   pl = fmyinput("\nPlease enter new password\nor nothing to leave unchanged: ",newp,16);
   
   /*   printf("password: [%s] have length %d\n",newp,pl); */

   if (!pl) { printf("Nothing changed.\n"); return(0); }
   
   cheap_ascii2uni(newp,newunipw,pl);
   
   make_lanmpw(newp,newlanpw,pl);

   /*   printf("Ucase Lanman: %s\n",newlanpw); */
   
   MD4Init (&context);
   MD4Update (&context, newunipw, pl<<1);
   MD4Final (digest, &context);
   
   hexprnt("\nNEW MD4 hash    : ",digest,16);
   
   E1(newlanpw,   x1, lanman);
   E1(newlanpw+7, x1, lanman+8);
   
   hexprnt("NEW LANMAN hash : ",lanman,16);
   
   /* Encrypt the NT md4 password hash as two 8 byte blocks. */
   des_ecb_encrypt((des_cblock *)digest,
		   (des_cblock *)despw, ks1, DES_ENCRYPT);
   des_ecb_encrypt((des_cblock *)(digest+8),
		   (des_cblock *)&despw[8], ks2, DES_ENCRYPT);

   des_ecb_encrypt((des_cblock *)lanman,
		   (des_cblock *)newlandes, ks1, DES_ENCRYPT);
   des_ecb_encrypt((des_cblock *)(lanman+8),
		   (des_cblock *)&newlandes[8], ks2, DES_ENCRYPT);

      
   hexprnt("NEW DES crypt   : ",despw,16);
   hexprnt("NEW LANMAN crypt: ",newlandes,16);
  
   fmyinput("\nDo you really wish to change it? (y/n) [n] ",yn,2);

   /* Reset hash length to 16 if syskey enabled, this will cause
    * a conversion to syskey-hashes upon next boot */
   if (syskeyreset && ntpw_len > 16) {
     ntpw_len = 16;
     lmpw_len = 16;
     ntpw_offs -= 4;
     (unsigned int)*(vp+0xa8) = ntpw_offs - 0xcc;
     *(vp + 0xa0) = 16;
     *(vp + 0xac) = 16;
   }

   if (*yn == 'y') {
      for (i = 0; i < 16; i++) {
	 (unsigned char)*(vp+ntpw_offs+i) = despw[i];
	 (unsigned char)*(vp+lmpw_offs+i) = newlandes[i];
      }
#if 0
      hexprnt("Pw in buffer: ",(vp+ntpw_offs),16);
      hexprnt("Lm in buffer: ",(vp+lmpw_offs),16);
#endif
      dirty = 1;
      printf("\n");
   } else {
      printf("Nothing changed.\n");
   }
   
   printf("\n");
   return(username);
}


/* Here we put our knowledge to use, basic routines to
 * decode and display registry contents almost like a filesystem
 */

/* display (cat) the value,
 * vofs = offset to 'nk' node, paths relative to this (or 0 for root)
 * path = path string to value
 * Does not handle all types yet (does a hexdump instead)
 * MULTI_SZ (multi unicode-string) - only displays first string,
 * but also does a hexdump.
 */
void cat_vk(struct hive *hdesc, int nkofs, char *path)
{     
  void *data;
  int len,i,type;
  char string[SZ_MAX+1];

  type = get_val_type(hdesc, nkofs, path);
  if (type == -1) {
    printf("cat_vk: No such value <%s>\n",path);
    return;
  }

  len = get_val_len(hdesc, nkofs, path);
  if (!len) {
    printf("cat_vk: Value <%s> has zero length\n",path);
    return;
  }

  data = (void *)get_val_data(hdesc, nkofs, path, 0);
  if (!data) {
    printf("cat_vk: Value <%s> references NULL-pointer (bad boy!)\n",path);
    abort();
    return;
  }

  printf("Value <%s> of type %s, data length %d [0x%x]\n", path,
	 (type < REG_MAX ? val_types[type] : "(unknown)"), len, len);

  switch (type) {
  case REG_SZ:
  case REG_EXPAND_SZ:
  case REG_MULTI_SZ:
    cheap_uni2ascii(data,string,len);
    for (i = 0; i < (len>>1)-1; i++) {
      if (string[i] == 0) string[i] = '\n';
      if (type == REG_SZ) break;
    }
    puts(string);
    break;
  case REG_DWORD:
    printf("0x%08x",*(unsigned short *)data);
    break;
  default:
    printf("Don't know how to handle type yet!\n");
  case REG_BINARY:
    hexdump((char *)data, 0, len, 1);
  }
  putchar('\n');
}

/* =================================================================== */

/* Registry editor frontend */

struct cmds {
  char cmd_str[12];
  int  cmd_num;
};

#define MCMD_CD 1
#define MCMD_LS 2
#define MCMD_QUIT 3
#define MCMD_CAT  4
#define MCMD_STRUCT 5
#define MCMD_DEBUG 6
#define MCMD_HELP 7
#define MCMD_PASSWD 8
#define MCMD_HIVE 9
#define MCMD_EDIT 10

struct cmds maincmds[] = {
  "cd" , MCMD_CD,
  "ls" , MCMD_LS,
  "dir", MCMD_LS,
  "q"  , MCMD_QUIT,
  "cat", MCMD_CAT,
  "type",MCMD_CAT,
  "st" , MCMD_STRUCT,
  "pw" , MCMD_PASSWD,
  "passwd", MCMD_PASSWD,
  "debug", MCMD_DEBUG,
  "hive", MCMD_HIVE,
  "ed", MCMD_EDIT,
  "?", MCMD_HELP,
  "", 0
};

/* Edit value: Invoke whatever is needed to edit it
 * based on its type
 */

void edit_val(struct hive *h, int nkofs, char *path)
{
  struct keyval *kv;
  int type,len,n,i,in,go, d = 0;
  char inbuf[SZ_MAX+4];
  char origstring[SZ_MAX+4];
  char newstring[SZ_MAX+4];
  char *dbuf;

  type = get_val_type(h, nkofs, path);
  if (type == -1) {
    printf("Value <%s> not found!\n",path);
    return;
  }

  kv = get_val2buf(h, NULL, nkofs, path, type);
  if (!kv) {
    printf("Unable to get data of value <%s>\n",path);
    return;
  }
  len = kv->len;

  printf("EDIT: <%s> of type %s with length %d [0x%x]\n", path,
	 (type < REG_MAX ? val_types[type] : "(unknown)"),
	 len, len);

  switch(type) {
  case REG_DWORD:
    printf("DWORD: Old value %d [0x%x], ", kv->data, kv->data);
    fmyinput("enter new value (prepend 0x if hex, empty to keep old value)\n-> ",
	     inbuf, 12);
    if (*inbuf) {
      sscanf(inbuf,"%i",&kv->data);
      d = 1;
    }
    printf("DWORD: New value %d [0x%x], ", kv->data, kv->data);
    break;
  case REG_SZ:
  case REG_EXPAND_SZ:
  case REG_MULTI_SZ:
    dbuf = (char *)&kv->data;
    cheap_uni2ascii(dbuf,origstring,len);
    n = 0; i = 0;
    while (i < (len>>1)-1) {
      printf("[%2d]: %s\n",n,origstring+i);
      i += strlen(origstring+i) + 1;
      n++;
    }

    printf("\nNow enter new strings, one by one.\n");
    printf("Enter nothing to keep old,\n");
    if (type == REG_MULTI_SZ) {
      printf("'--n' to quit (filling rest of value with NULLs)\n");
      printf("'--q' to quit (leaving remaining strings as is)\n");
      printf("'--' for empty string in this position\n");
    }
    n = 0; i = 0; in = 0; go = 0;
    memset(newstring, 0, (len>>1));

    while (i < (len>>1)-1) {
      printf("%d bytes left\n",len - (in<<1));
      printf("[%2d]: %s\n",n,origstring+i);
      if (!go) fmyinput("-> ",inbuf, 500);
      else *inbuf = 0;
      if (*inbuf && strcmp("--q", inbuf)) {
	if (!strcmp("--n", inbuf)) {
	  in = (len>>1) +1; i = in;
	} else {
	  strncpy(newstring+in, inbuf, 499);
	  in += strlen(inbuf)+1;
	}
      } else {
	strncpy(newstring+in, origstring+i, 499);
	in += strlen(origstring+i)+1;
	if (!strcmp("--q", inbuf)) go = 1;
      }
      i += strlen(origstring+i) + 1;
      n++;
      if (type != REG_MULTI_SZ) i = (len<<1);
    }
    cheap_ascii2uni(newstring, dbuf, len>>1);
    /* Force NULL termination */
    *(dbuf+len-1) = 0;
    *(dbuf+len-2) = 0;
    *(dbuf+len-3) = 0;
    d = 1;
    break;
    /*    debugit(newstring, len>>1); */
  default:
    printf("Type not handeled (yet), invoking hex editor on data!\n");
  case REG_BINARY:
    d = debugit((char *)&kv->data, kv->len);
    break;
  }

  if (d) {
    if (!(put_buf2val(h, kv, nkofs, path, type))) {
      printf("Failed to set value!?\n");
    }
  }
  FREE(kv);
}

/* look up command in array
 */
int parsecmd(char **s, struct cmds *cmd)
{
  char temp[10];
  int i,l = 0;

  while ((*s)[l] && ((*s)[l] != ' ')) {
    l++;
  }
  while (cmd->cmd_num) {
    if (!strncmp(*s, cmd->cmd_str, l)) {
      *s += l;
      return(cmd->cmd_num);
    }
    cmd++;
  }
  return(0);
}

/* Simple interactive command-parser
 * Main loop for manually looking through the registry
 */

void mainloop(void)
{
  struct hive *hdesc;
  int cdofs, newofs;
  struct nk_key *cdkey;
  char inbuf[100],whatbuf[100],*bp;
  char path[1000];
  int l, vkofs, nh;
  int usehive = 0;

  hdesc = hive[usehive];
  cdofs = hdesc->rootofs;

  printf("Simple registry editor. ? for help.\n");

  while (1) {
    cdkey = (struct nk_key *)(hdesc->buffer + cdofs);

    *path = 0;
    get_abs_path(hdesc,cdofs+4, path, 50);

    printf("\n[%0x] %s> ",cdofs,path);
    l = fmyinput("",inbuf,90);
    bp = inbuf;
    skipspace(&bp);
      
    if (l > 0 && *bp) {
      switch(parsecmd(&bp,maincmds)) {
      case MCMD_HELP:
	printf("Simple registry editor:\n");
	printf("hive [] - list loaded hives or switch to hive numer n'\n");
	printf("cd  - change key\nls | dir [] - show subkeys & values,\n");
        printf("cat | type  - show key value\nst [] - show struct info\n");
  /* printf("pw | passwd [] - try the password routine on struct at \n"); */
	printf("ed  - edit existing value (only same datalength allowed for now)\n");
	printf("debug - enter buffer hexeditor\nq - quit\n");
        break;

      case MCMD_LS :
	bp++;
	skipspace(&bp);
        nk_ls(hdesc, bp, cdofs+4, 0);
	break;
      case MCMD_EDIT :
	bp++;
	skipspace(&bp);
        edit_val(hdesc, cdofs+4, bp);
	break;
      case MCMD_HIVE :
	bp++;
	skipspace(&bp);
	if (*bp) {
	  nh = gethex(&bp);
	  if (nh >= 0 && nh < no_hives) {
	    usehive = nh;
	    printf("Switching to hive #%d, named <%s>, size %d [0x%x]\n",
		   usehive, hive[usehive]->filename,
		   hive[usehive]->size,
		   hive[usehive]->size);
	    hdesc = hive[usehive];
	    cdofs = hdesc->rootofs;
	  }
	} else {
	  for (nh = 0; nh < no_hives; nh++) {
	    printf("%c %c %2d %9d 0x%08x <%s>\n", (nh == usehive) ? '*' : ' ',
		   (hive[nh]->state & HMODE_DIRTY) ? 'D' : ' ',
		   nh, 
		   hive[nh]->size,
		   hive[nh]->size, hive[nh]->filename);
	  }
	}
        break;
      case MCMD_CD :
	bp++;
	skipspace(&bp);
	newofs = trav_path(hdesc, cdofs+4,bp,0);
        if (newofs) cdofs = newofs;
	else printf("Key %s not found!\n",bp);
	break;
      case MCMD_CAT:
	bp++;
	skipspace(&bp);
	cat_vk(hdesc,cdofs+4,bp);
	break;
      case MCMD_STRUCT:
	bp++;
	skipspace(&bp);
	vkofs = cdofs;
	if (*bp) {
	  vkofs = gethex(&bp);
	}
	parse_block(hdesc,vkofs,1);
	break;
#if 0
      case MCMD_PASSWD:
	bp++;
	skipspace(&bp);
	vkofs = cdofs;
	if (*bp) {
	  vkofs = gethex(&bp);
	}
	seek_n_destroy(vkofs,0,-get_int(buf+vkofs)+4,0);
	break;
#endif
      case MCMD_DEBUG:
	if (debugit(hdesc->buffer,hdesc->size)) hdesc->state |= HMODE_DIRTY;
	break;
      case MCMD_QUIT:
        return;
        break;
      default:
	printf("Unknown command: %s\n",bp);
	break;
      }
    }
  }
}

/* List users in SAM file
 * pageit - hmm.. forgot this one for this release..
 */

int list_users(int pageit)
{
  char s[200];
  struct keyval *v;
  int nkofs,vkofs;
  int rid;
  int count = 0, countri = 0;
  struct ex_data ex;

  nkofs = trav_path(hive[H_SAM], 0,"\\SAM\\Domains\\Account\\Users\\Names\\",0);
  if (!nkofs) {
    printf("Cannot find usernames in registry! (is this a SAM-hive?)\n");
    return(1);
  }

  while ((ex_next_n(hive[H_SAM], nkofs+4, &count, &countri, &ex) > 0)) {

    /* Extract the value out of the username-key, value is RID  */
    snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",ex.name);
    rid = get_dword(hive[H_SAM], 0, s);
    if (rid == 500) strncpy(admuser,ex.name,128); /* Copy out admin-name */

    /*    printf("name: %s, rid: %d (0x%0x)\n", ex.name, rid, rid); */

    /* Now that we have the RID, build the path to, and get the V-value */
    snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid);
    v = get_val2buf(hive[H_SAM], NULL, 0, s, REG_BINARY);
    if (!v) {
      printf("Cannot find value <%s>\n",s);
      return(1);
    }
    
    if (v->len < 0xcc) {
      printf("Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n",
	     s, v->len);
    } else {
      change_pw( (char *)&v->data , rid, v->len, 1);
    }
    FREE(v);
    FREE(ex.name);
  }
  return(0);
}


/* Find a username in the SAM registry, then get it's V-value,
 * and feed it to the password changer.
 */

void find_n_change(char *username)
{
  char s[200];
  struct vk_key *vkkey;
  int vkofs;
  int rid;
  struct keyval *v;

  /* Extract the unnamed value out of the username-key, value is RID  */
  snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",username);
  rid = get_dword(hive[H_SAM],0,s);
  if (rid == -1) {
    printf("Cannot find value <%s>\n",s);
    return;
  }
  /*  printf("Username: %s, RID = %d (0x%0x)\n",username,rid,rid); */

  /* Now that we have the RID, build the path to, and get the V-value */
  snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid);
  v = get_val2buf(hive[H_SAM], NULL, 0, s, REG_BINARY);
  if (!v) {
    printf("Cannot find value <%s>\n",s);
    return;
  }

  if (v->len < 0xcc) {
    printf("Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n",
	   s, vkkey->len_data);
  } else {
    change_pw( (char *)&v->data , rid, v->len, 0);
    if (dirty) {
      if (!(put_buf2val(hive[H_SAM], v, 0, s, REG_BINARY))) {
	printf("Failed to write updated <%s> to registry! Password change not completed!\n",s);
      }
    }
  }
  FREE(v);
}

/* Check for presence of syskey and possibly disable it if
 * user wants it.
 * This is tricky, and extremely undocumented!
 * See docs for more info on what's going on when syskey is installed
 */

#undef LSADATA

void handle_syskey(void)
{

  /* This is \SAM\Domains\Account\F */
  struct samkeyf {
    char unknown[0x50];       /* 0x0000 - Unknown. May be machine SID */
    char unknown2[0x14];
    char syskeymode;          /* 0x0064 - Type/mode of syskey in use     */
    char syskeyflags1[0xb];   /* 0x0065 - More flags/settings            */
    char syskeyobf[0x30];     /* 0x0070 - This may very well be the obfuscated syskey */
  };    /* There may be more, usually 8 null-bytes? */

  /* Security\Policy\SecretEncryptionKey\@, only on NT5 */
  /* Probably contains some keyinfo for syskey. Second DWORD seems to be syskeymode */
  struct secpoldata {
    int  unknown1;             /* Some kind of flag? usually 1 */
    int  syskeymode;           /* Is this what we're looking for? */
    int  unknown2;             /* Usually 0? */
    char keydata[0x40];        /* Some kind of scrambled keydata? */
  };

#ifdef LSADATA
  /* SYSTEM\CurrentControlSet\Control\Lsa\Data, only on NT5?? */
  /* Probably contains some keyinfo for syskey. Byte 0x34 seems to be mode */
  struct lsadata {
    char keydata[0x34];        /* Key information */
    int  syskeymode;           /* Is this what we're looking for? */
  };
#endif

  void *fdata;
  struct samkeyf *ff = NULL;
  struct secpoldata *sf = NULL;
  struct lsadata *ld = NULL;
  int len,i,secboot, samfmode, secmode, ldmode;
  struct keyval *samf, *secpol, *lsad;
  char *syskeytypes[4] = { "off", "key-in-registry", "enter-passphrase", "key-on-floppy" }; 
  char yn[5];

  printf("** Checking for syskey!\n");


  samf = get_val2buf(hive[H_SAM], NULL, 0, "\\SAM\\Domains\\Account\\F", REG_BINARY);

  if (samf && samf->len > 0x70 ) {
    ff = (struct samkeyf *)&samf->data;
    samfmode = ff->syskeymode;
  } else {
    samfmode = -1;
  }

  secboot = -1;
  if (no_hives >= 2) {
    secboot = get_dword(hive[H_SYS], 0, "\\ControlSet001\\Control\\Lsa\\SecureBoot");
  }

  secmode = -1;
  if (no_hives >= 3) {
    secpol = get_val2buf(hive[H_SEC], NULL, 0, "\\Policy\\PolSecretEncryptionKey\\@", REG_NONE);
    if (secpol) {     /* Will not be found in NT 4, take care of that */
      sf = (struct secpoldata *)&secpol->data;
      secmode = sf->syskeymode;
    }
  }

#ifdef LSADATA
  lsad = get_val2buf(hive[H_SYS], NULL, 0, "\\ControlSet001\\Control\\Lsa\\Data\\Pattern", REG_BINARY);

  if (lsad && lsad->len >= 0x38) {
    ld = (struct lsadata *)&lsad->data;
    ldmode = ld->syskeymode;
  } else {
    ldmode = -1;
  }
#endif

  printf("SYSTEM   SecureBoot            : %d -> %s\n", secboot,
	 (secboot < 0 || secboot > 3) ? "Not Set (not installed, good!)" : syskeytypes[secboot]);
  printf("SAM      Account\\F             : %d -> %s\n", samfmode,
	 (samfmode < 0 || samfmode > 3) ? "Not Set" : syskeytypes[samfmode]);
  printf("SECURITY PolSecretEncryptionKey: %d -> %s\n", secmode,
	 (secmode < 0 || secmode > 3) ? "Not Set (OK if this is NT4)" : syskeytypes[secmode]);

#ifdef LSADATA
  printf("SYSTEM   LsaData               : %d -> %s\n\n", ldmode,
	 (ldmode < 0 || ldmode > 3) ? "Not Set (strange?)" : syskeytypes[ldmode]);
#endif

  if (secboot != samfmode && secboot != -1) {
    printf("WARNING: Mismatch in syskey settings in SAM and SYSTEM!\n");
    printf("WARNING: It may be dangerous to continue (however, resetting syskey\n");
    printf("         may very well fix the problem)\n");
  }

  if (secboot > 0 || samfmode > 0) {
    printf("\n***************** SYSKEY ENABLED! **************\n");
    printf("This installation very likely has the syskey passwordhash-obfuscator installed\n");
    printf("It's currently in mode = %d, %s-mode\n",secboot,
	   (secboot < 0 || secboot > 3) ? "Unknown" : syskeytypes[secboot]);

    if (no_hives < 2) {
      printf("\nSYSTEM (and possibly SECURITY) hives not loaded, unable to disable syskey!\n");
      printf("Please start the program with at least SAM & SYSTEM-hive filenames as arguments!\n\n");
      return;
    }
    printf("SYSKEY is on! However, you do not have to disable it just to\n");
    printf("change a password, only if you have lost the key (floppy/passphrase)\n");
    printf("you must turn it off. NOTE: On WINDOWS 2000 it will not be possible\n");
    printf("to turn it on again! (and other problems may also show..)\n\n");
    printf("NOTE: Disabling syskey will invalidate ALL\n");
    printf("passwords, requiring them to be reset. You should at least reset the\n");
    printf("administrator password using this program, then the rest ought to be\n");
    printf("done from NT.\n");

    fmyinput("\nDo you really wish to disable SYSKEY? (y/n) [n] ",yn,2);
    if (*yn == 'y') {
      /* Reset SAM syskey infostruct, fill with zeroes */
      if (ff) { 
	ff->syskeymode = 0;

	for (i = 0; i < 0x3b; i++) {
	  ff->syskeyflags1[i] = 0;
	}

	put_buf2val(hive[H_SAM], samf, 0, "\\SAM\\Domains\\Account\\F", REG_BINARY);

      }
      /* Reset SECURITY infostruct (if any) */
      if (sf) { 
	memset(sf, 0, secpol->len);
	sf->syskeymode = 0;

	put_buf2val(hive[H_SEC], secpol, 0, "\\Policy\\PolSecretEncryptionKey\\@", REG_BINARY);

      }

#if LSADATA
      if (ld) { 

	ld->syskeymode = 0;

	put_buf2val(hive[H_SYS], lsad, 0, "\\ControlSet001\\Control\\Lsa\\Data\\Pattern", REG_BINARY);

      }
#endif

      /* And SYSTEM SecureBoot parameter */

      put_dword(hive[H_SYS], 0, "\\ControlSet001\\Control\\Lsa\\SecureBoot", 0);

      dirty = 1;
      syskeyreset = 1;
      printf("Updating passwordhash-lengths..\n");
      list_users(1);
      printf("* SYSKEY RESET!\nNow please set new administrator password!\n");
    } else {

      syskeyreset = 1;
    }
  } else {
    printf("Syskey not installed!\n");
    return;
  }

}

void usage(void) {
   printf("chntpw: change password of a user in a NT SAM file, or invoke registry editor.\n"
	  "chntpw [OPTIONS]  [systemfile] [securityfile] [otherreghive] [...]\n"
	  " -h          This message\n"
	  " -u    Username to change, Administrator is default\n"
	  " -l          list all users in SAM file\n"
	  " -i          Interactive. List users (as -l) then ask for username to change\n"
	  " -e          Registry editor (currently only in-place writesupport)\n"
	  " -d          Enter buffer debugger instead (hex editor), \n"
          " -t          Trace. Show hexdump of structs/segments. (deprecated debug function)\n"
          "See readme file on how to extract/read/write the NT's SAM file\n"
	  "if it's on an NTFS partition!\n"
          "Source/binary freely distributable. See README/COPYING for details\n"
	  "(Contains DESlib code (c) Eric Young)\n"
          "NOTE: This program is somewhat hackish! You are on your own!\n"
	  );
}

int main(int argc, char **argv)
{
   
   int vofs;
   int dodebug = 0, list = 1, inter = 0,edit = 0,il,d = 0, dd = 0;
   extern int opterr,optind;
   extern char* optarg;
   char *filename,c;
   char *who = "Administrator";
   char iwho[20];
   
   char *options = "idehltu:";
   
   printf("%s\n",chntpw_version);
   while((c=getopt(argc,argv,options)) > 0) {
      switch(c) {
       case 'd': dodebug = 1; break;
       case 'e': edit = 1; break;
       case 'l': list = 1; who = 0; break;
       case 't': list = 2; who = 0; break;
       case 'i': list = 1; who = 0; inter = 1; break;
       case 'u': who = optarg; list = 1; break;
       case 'h': usage(); exit(0); break;
       default: usage(); exit(1); break;
      }
   }
   filename=argv[optind];
   if (!filename || !*filename) {
      usage(); exit(1);
   }
   do {
     if (!(hive[no_hives] = openHive(filename,
				     HMODE_RW|(list == 2 ? HMODE_VERBOSE : 0)))) {
       printf("Unable to open/read a hive, exiting..\n");
       exit(1);
     }
     no_hives++;
     filename = argv[optind+no_hives];
   } while (filename && *filename && no_hives < MAX_HIVES);
      
   if (dodebug) debugit(hive[0]->buffer,hive[0]->size);
   else {

     if (list && !edit) {
       if ( list_users(1) ) edit = 1;
     }
     if (edit) mainloop();
     else if (who) { handle_syskey(); find_n_change(who); }

     if (inter) handle_syskey();
     while (inter) {
       printf("\nUsername to change (! to quit, . to list users): [%s] ",admuser);
       il = fmyinput("",iwho,15);
       if (il == 1 && *iwho == '.') { list_users(1); continue; }
       if (il == 1 && *iwho == '!') { inter = 0; break; }
       if (il == 0) strcpy(iwho,admuser);
       find_n_change(iwho);
     }
   }
   
   printf("Hives that have changed:\n #  Name\n");
   for (il = 0; il < no_hives; il++) {
     if (hive[il]->state & HMODE_DIRTY) {
       printf("%2d  <%s>\n",il,hive[il]->filename);
       d = 1;
     }
   }
   if (d) {
     fmyinput("Write hive files? (y/n) [n] : ",iwho,3);
     if (*iwho == 'y') {
       for (il = 0; il < no_hives; il++) {
	 if (hive[il]->state & HMODE_DIRTY) {
	   printf("%2d  <%s> - ",il,hive[il]->filename);
	   if (!writeHive(hive[il])) {
	     printf("OK\n"); dd = 2;
	   }
	 }
       }
     } else {
       printf("Not written!\n");
     }
   } else {
     printf("None!\n");
   }
   return(dd);
}