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); }