[翻译]揭秘Safari密码存储的秘密

作者:lu4nx 发布时间:September 25, 2011 分类:默认分类

文:Nagareshwar Talekar 译:乱雪

前言

Safari浏览器是目前所出浏览器中排在前五名的,它有着新颖的外观和最好的体验感。并且提供了最好的网页浏览方式,而且最大程度地支持HTML5,并还可以体验一些其他的使更好地浏览网页的新功能。

同其他浏览器一样,Safari也带有自带的密码管理功能,它可以安全地存储和管理用户在网页中保存的登录密码。

本文将首次公开Safari密码的存储方式、加密算法以及提供破解存储密码的代码。

Safari存储密码的位置

Safari浏览器拥有良好的密码管理、安全模型和加密算法,以尽可能地保证安全。不像其他浏览器,比如FirefoxChromeSafari中你是无法直接查看保存的密码的。

你可以在“设置——自动填充——用户名和密码”选项中启用或者禁用Safari的密码管理(如下图所示)。一旦启用,Safari将会提示保存用户登录的每个网站的密码。一经确认,用户名和密码将随着网址一同保存在保密的存储密码的文件中。
safari-browser-password-manager-settings.jpg

Safari将所有这些网站的登录密码存储在以下位置的'keychain.plist'文件中(根据所用的平台):

[Windows XP]
C:\Documents and Settings\<username>\Application Data\Apple Computer\Preferences


[Windows Vista & Windows 7]
C:\Users\<username>\AppData\Roaming\Apple Computer\Preferences


Safari存储数据的keychain.plist文件采用的是“Binary Property List”文件格式——它是苹果公司用于存储二进制数据的一种“Property List”文件格式的变种。下图是“keychain”文件的样子:
safari-password-keychain-file.jpg


解开“Keychain”文件的秘密

看着上图中“keychain”文件的内容,几乎没有什么可让人理解的数据。不过能暗示你的是,该文件是用“bplist”作为文件开头的关键字。

经过长时间的对“bplist”关键字的搜索,我终于想出了办法将这些内容为转换为纯XML文件。利用苹果提供的“plutil.exe”工具可以将它们转换为“Property List”文件。你可以在以下位置找到基于控制台的工具:

[Windows x86]
C:\Program Files\Common Files\Apple\Apple Application Support

[Windows x64]
C:\Program Files (x86)\Common Files\Apple\Apple Application Support


这条命令便可以将神秘的“keychain.plist”文件转换为易读的“keychain.xml”文件:

plutil.exe -convert xml1 -s -o c:\keychain.xml "c:\users\administrator\appdata\roaming\apple computer\preferences\keychain.plist"

这看起像解码后的XML文件:
safari-keychain-decoded-xml.jpg

Safari加密算法的内幕

生成的XML文件中包含了加密后的密码、网址以及用户名信息。存储的密码采用的是BASE64加密算法加密而成的。

不过注意,存储在“keychain.plist”文件中的原密码并没有采用BASE64加密。当我们用工具把它转换成XML文件时,它才会被加密成BASE64格式。

一旦将BASE64解码,就可以看到原来加密后的数据。Safari使用标准的“Windows Data Protection”机制加密用户层的密码。Windows DPAPI提供了类似于CryptProtectData/CryptUnprotectData这样的函数来轻易加密或者解密数据,比如密码。

Safari使用了CryptProtectData函数与一个静态的Salt结合,对所有网站中保存的登录密码进行加密。最后再将用户的这些登录信息保存在“keychain.plist”文件中。

解码并解密Safari中保存的密码

如上所述,完成整个的解密过程只需要以下两个步骤:

1.将存储在XML文件中的密码数据进行解码。

2.Windows DPAPI解密数据。

首先你需要使用BASE64解码工具将XML文件中的密码字节解码。

之后我们对这个加密后的数据进行解密。为了解密这个数据,我们必须找到在CryptUnprotectData使用的Salt。这里是我在逆向工程时找到的Salt

safari-encryption-salt-data.jpg



整个Salt生成算法和解密的函数,在苹果提供的共享库“CFNetwork.dll”中,文件位于以下位置:

[Windows x86]
C:\Program Files\Common Files\Apple\Apple Application Support


[Windows x64]

C:\Program Files (x86)\Common Files\Apple\Apple Application Support


以下是用IDA Pro反汇编CFNetwork.dllSalt生成和解密函数的调用:


\safari-password-disassembly-cfnetwork-dll.jpg

最开始,Salt看起来似乎动态的生成的,但是我在不同的系统中逆向后,我怀疑它只是静态的。Salt是一串144字节的数据和使用“com.apple.Safari作为结束的尾部。之前的截图中所示。

一旦我们得到了Salt数据,就可以轻易地使用CryptUnprotectData函数解密了,完整的代码如下:

BYTE salt[] = { 

0x1D, 0xAC, 0xA8, 0xF8, 0xD3, 0xB8, 0x48, 0x3E, 0x48, 0x7D, 0x3E, 0x0A, 0x62, 0x07, 0xDD, 0x26,

0xE6, 0x67, 0x81, 0x03, 0xE7, 0xB2, 0x13, 0xA5, 0xB0, 0x79, 0xEE, 0x4F, 0x0F, 0x41, 0x15, 0xED,

0x7B, 0x14, 0x8C, 0xE5, 0x4B, 0x46, 0x0D, 0xC1, 0x8E, 0xFE, 0xD6, 0xE7, 0x27, 0x75, 0x06, 0x8B,

0x49, 0x00, 0xDC, 0x0F, 0x30, 0xA0, 0x9E, 0xFD, 0x09, 0x85, 0xF1, 0xC8, 0xAA, 0x75, 0xC1, 0x08,

0x05, 0x79, 0x01, 0xE2, 0x97, 0xD8, 0xAF, 0x80, 0x38, 0x60, 0x0B, 0x71, 0x0E, 0x68, 0x53, 0x77,

0x2F, 0x0F, 0x61, 0xF6, 0x1D, 0x8E, 0x8F, 0x5C, 0xB2, 0x3D, 0x21, 0x74, 0x40, 0x4B, 0xB5, 0x06,

0x6E, 0xAB, 0x7A, 0xBD, 0x8B, 0xA9, 0x7E, 0x32, 0x8F, 0x6E, 0x06, 0x24, 0xD9, 0x29, 0xA4, 0xA5,

0xBE, 0x26, 0x23, 0xFD, 0xEE, 0xF1, 0x4C, 0x0F, 0x74, 0x5E, 0x58, 0xFB, 0x91, 0x74, 0xEF, 0x91,

0x63, 0x6F, 0x6D, 0x2E, 0x61, 0x70, 0x70, 0x6C, 0x65, 0x2E, 0x53, 0x61, 0x66, 0x61, 0x72, 0x69 

};

//now decrypt the data

DATA_BLOB DataIn;

DATA_BLOB DataOut;

DATA_BLOB OptionalEntropy;

DataIn.pbData = byteEncBuffer; //encrypted password data

DataIn.cbData = dwEncBufferSize; //encrypted password data size

OptionalEntropy.pbData = (unsigned char*)&salt;

OptionalEntropy.cbData = 144;

if(CryptUnprotectData(&DataIn, 0, &OptionalEntropy, NULL, NULL,0, &DataOut) == FALSE )

{

printf("CryptUnprotectData failed = 0x%.8x", GetLastError());

return FALSE;

}

//Decrypted data is in following format

//Password Length [4 bytes] + Pass Data []

BYTE *byteData = (BYTE *) DataOut.pbData;

DWORD dwPassLen = byteData[0];

memcpy(strPassword, &byteData[4], dwPassLen);

strPassword[dwPassLen] = 0;

printf("Decrypted Password %d - %s", dwPassLen, strPassword);

上面的程序初始化了Salt,然后通过CryptUnprotectData进行解码,从而得到解码后的数据。解密后的前4个字节的数据包含了ASCII密码的长度,之后才是明文。

使用SafariPasswordDecryptor获得Safari密码

SafariPasswordDecryptor是一款自动获得存储在Safari浏览器中的所保存的网站中的登录密码,它可以自动地解码并解密keychain文件中所存储的信息。

safaripassworddecryptor_article.jpg

并且它同时提供了用户界面和命令行界面,后者更适用于渗透测试人员。它除了可以恢复失去的密码,也可以用于调查取证。

SafariPasswordDecryptor可运行在Windows平台,从Windows XP到最新的Windows7








矩阵验证的后台

作者:lu4nx 发布时间:September 25, 2011 分类:默认分类,安全架构

在给网站后台做强化设计,根据这么多年的经验,光验证用户名和密码真的让我太没安全感了。头脑风暴了一下,从农行的动态密码卡那里得到的灵感。登陆后台除了验证用户名以外,还得有“动态密码卡”。“动态密码卡”就是一个矩阵,里面的每行每列都有一个对应的值,登陆页面会随机显示两组行列号,需要输入行列号对应的值才行,这个雏形我只随机了一组行列号,慢慢来。当然弱点就是管理员需要保存这个“动态密码卡”上的数组且不被人得到:)

测试代码,这里的矩阵如下:
e07d14caa83df770bf09e6c7.png
044ded8ab6b8adbcfc1f10a9.png

854c2c17fa964a2521a4e9b8.png

界面,test.php:

 

<?php

 include_once("c.php");

 //随机生成一个行列号

 $test = rnd();

 $num1 = $test[0];

 $num2 = $test[1];

 

?>

<p>行:<?=$num1?>列:<?=$num2?></p>

<form id="form1" name="form1" method="POST" action="c.php">

  <label>

  <input type="text" name="num" id="textfield" />

  <input name="rnd1" type="hidden" value="<?=$num1?>" />

  <input name="rnd2" type="hidden" value="<?=$num2?>" />

  </label>

  <label>

  <input type="submit" value="提交" />

  </label>

</form>

 

核心代码,c.php:

 

<?php

 /*

 生成一个5行5列的矩阵,为了不头脑糊涂,故让数组从1下标开始。

 */

 $pass = Array();  

 $pass[0] = 0;

 $pass[1] = Array(0,1,2,3,4,5);

 $pass[2] = Array(0,6,7,8,9,10);

 $pass[3] = Array(0,11,12,13,14,15);

 $pass[4] = Array(0,16,17,18,19,20);

 $pass[5] = Array(0,21,22,23,24,25);

 

 /*

 函数说明:随机行列号

 */

 function rnd()

 {

  //产生1到5之间的随机数

  $rand1 = rand(1,5);

  $rand2 = rand(1,5);

  

  //返回一个数组,第一个元素包含行号,第二个元素包含列号

  return Array($rand1,$rand2);  

 }

 

 //接收行号

 $rnd1 = $_POST['rnd1'];

 //接受列号

 $rnd2 = $_POST['rnd2'];

 //接受对应的值

 $num = $_POST['num'];

 

 $check = $pass[$rnd1][$rnd2];

 

 if ( $num >0 )

 {

  if ( $num == $check )

   echo '正确,你输入的是:'.$num;

  else

   echo ':-(';

 }

?>

 

 这只是一个原型罢了,还需要很多强化。