<% dim ModuleName,InfoID,ChannelShortName,CorrelativeArticle,InstallDir,ChannelDir,Keyword,PageTitle,ArticleIntro,Articlecontent Keyword=stripHTML("微软,MS10-046") PageTitle=stripHTML("微软MS10-046细节分析") ArticleIntro=stripHTML("早在往年1月初,研讨员Michael Heerklotz发现了一个Windows操作零碎的0day。我们把这个破绽命名为 ZDI-15-086,本文中的技术细节是基于他的研讨而总结的") Articlecontent=stripHTML("使用的图标是在shell32.dll文件中的CControlPanelFolder::GetUiObjectOf()函数定义的。通过使用DarunGrim工具我…") ModuleName = stripHTML("exploits") InfoID = stripHTML("194865") ChannelShortName=stripHTML("漏洞") InstallDir=stripHTML("http://www.77169.com/") ChannelDir=stripHTML("exploits") %> 微软MS10-046细节分析 - 华盟网 - http://www.77169.com
您现在的位置: 华盟网 >> 漏洞 >> 最新漏洞 >> win 漏洞 >> 正文

[组图]微软MS10-046细节分析

2015/3/16 作者:彩儿 来源: 360安全播报
导读 <% if len(ArticleIntro)<3 then Response.Write Articlecontent 'Response.Write "Articlecontent" else Response.Write ArticleIntro 'Response.Write "ArticleIntro" end if %>

使用的图标是在shell32.dll文件中的CControlPanelFolder::GetUiObjectOf()函数定义的。通过使用DarunGrim工具我们能看到在RTM版本的Shell32.dll与最新存在漏洞的版本的不同之处。

t011c482da797b8102a.png

图1 比较函数

我们能看到在发布后该函数仅仅有两个代码区(图中红色高亮)有所改变。

第一个区域块改变如下:

t01a0f52d72fdd57f9c.png

图2 函数中第一个区域块改变(点击图片会在新窗口打开)

在自定义图标的事件声明中,当请求一个ID为0的图标时,会检查注册表。我们把该片段反编译成C++后,就得到了下面的结果:

  …
       if ( (iconID == 0) &&
            !this->_IsRegisteredCPLApplet(&wszModuleFullPath) )
       {
           iconID = -1;
       }
       …


如果dll文件不在白名单之列。你就不能使得图标ID为0,也就不能加载自定义的图标了。这样问题就解决了?很明显,还不是!我们再来看看另一个改变后的代码片段。看看他能否给我们什么线索。

http://www.77169.com/exploits/UploadFiles_7195/201503/20150316002842535.png

图3 函数中第二个改变区域块

令我们兴奋的是,如果模块路径包含逗号,会出现无效的参数回显错误提示。

这似乎跟防御震网病毒无关吧?看起来很是奇怪!我们再来看看跟该区域块相关代码。

在该代码块之前,有一个未改变的代码块需要用户提供数据并用逗号来格式化数据:

t01e6e998a8015f2697.png

图4 未改变的代码

我们把这个代码块反编译为C++语言:

StringCchPrintfW(wszWorkingBuffer, 554u, L"%s,%d,%s", &wszModuleFullPath,
                      iconID, &wszDisplayName);

第二个改变地方似乎就是修复震网病毒的部分,我们尝试把图标ID变为除0之外的其他值,或者一个逗号分隔的字符串。从报错中,我们发现修复的只是防止了一个假的图标ID被嵌入到路径中,这也说明了构造字符串会被以ID的格式来解析。

很明显,上面的尝试是行不通的,我们不能通过格式化的字符串来插入到图标ID中去。

格式化并检查好的字符串会被传递到ControlExtractIcon_CreateInstance()

函数中,这个函数会创建一个CCtrlExtIconBase 对象并把它作为一个组合字符串进行传递。其构成函数如下:

t0171465c44bf2fd341.png

图5 在构成函数中跟踪传递的字符串

我们来看看使用缺省参数的情况,函数的结束部分(编译为C++):

StringCchCopyW(this->wszIconString, 260u, pwzIconString);
+

当我们放入554个宽字节的字符的时候,这些字符会被截断并放入到容量为260个宽字节的缓冲区。此外,这些字符串也包含了在图标加载时的两个信息:dll文件的路径以及图标ID。

这些信息是从CControlPanelFolder::GetModuleMapped()函数中获取到的:

t019ba69f72bbfa8663.png

图 6 调用CControlPanelFolder::GetModuleMapped()函数

反编译为C++后:


retVal = CControlPanelFolder::GetModuleMapped(pControl, false,
                                              &wszModuleFullPath, 260,
                                              &iconID,
                                              &wszModuleDisplayName, 260);


在这个函数中有两个部分是我们值得注意的。调用的地方固定了缓冲区的数据容量。这个接收字符串的缓冲区只能容纳260个宽字节的字符。这个缓冲区便是可控了,我们可以使用更长的字符串来填充该缓冲区。现在我们知道了这里就存在一个截断漏洞。

第二个问题实际上是出在CControlPanelFolder::GetModuleMapped()函数内部,在函数中,如果指定的路径不存在,则会从系统路径目录查找:

if ( !PathFileExistsW(pwzModuleFullPath) )
   {
     if ( fDoNotUseWoW || !CControlPanelFolder::_IsWowCPL(pControl) )
       GetSystemDirectoryW(&wzSystemDir, 260u);
     else
       GetSystemWow64DirectoryW(&wzSystemDir, 260u);
     if ( PathCombineW(&wzBuffer, &wzSystemDir, pwzModuleFileName) )
       retVal = StringCchCopyW(pwzModuleFullPath, cwchModuleFullPath,
                               &wzBuffer);
     else
       retVal = E_FAIL;
   }

这似乎不存在什么问题(其实我们只是需要加载我们注入的dll模块并执行代码),但随后我们能看到开发中不足之处,我们先来看看我们构造和被截断的字符串。

当漏洞被触发时,能看到堆栈调用的情况:

t019d5063254a5ae428.png

图 7 图标 dll加载时堆栈调用

构造的字符串在CCtrlExtIconBase类中是以一个成员变量存储的,接下来会调用_GetIconLocationW()函数。

t016005442f6caa086c.png

图8 构造字符串在CCtrlExtIconBase ::_GetIconLocationW的解析方式

函数执行时会搜索逗号分隔符(缓冲区就是我们要复制数据的源头)。我们尝试来把它留空,并通过StrToInt函数获取图标ID。最后发现补丁是将我们修改的图标ID变为了-1。并把截断字符串复制到260个宽字节的缓冲区。截断之后是一个空位,以及259个宽字符(其中一个就是逗号),剩下的257个字符路径,也就是我们能够可控解析的路径了。

因为此时 StrToIntW(L”-“) 就是0。

我们通过用负数来绕过了ID为0的检查,(实际上,我们也能通过在开始的时候赋给一个负数图标ID来跳过检查。)

过长的路径会导致模块加载失败。调用的LoadLibrary函数定义在CPL_LoadCPLModule()中,加载失败是因为CPL_LoadCPLModule()函数会检查头文件。实际问题并不是检查头文件,而是查找文件的方式:

t01bbc9f44c73790671.png

图 9 构造头文件路径

以上反编译为c++语言:


if ( StringCchPrintfW(&wzManifestPath, 260u, L"%s.manifest",
                      pwzModuleFullPath) < 0 )
{
    return NULL;
}


当我们的路径包含有头文件扩展后缀(260个字符之内)时,并不会加载dll。如果路径字符达257个时,我们就能把图标ID变为0并进入到CPL_LoadCPLModule()函数。

为了找到更多的问题,需要我们进行堆栈跟踪,看看我们能否通过使用其他路径名字来绕过CPL_LoadCPLModule()模块。此时你会发现在这个函数模块中,字符串会被提取出来。

CPL解析控制使用了CPL_ParseToSeparator() 函数将组件元素提取出来,跟到函数里面去会发现有分隔符隔开的两个选项:

t01670ff9cc209e81a8.png

图 10 分析CPL_ParseToSeparator函数

当我们查看CPL_ParseToSeparator() 的第一次调用的时候,发现空格也能作为分隔符。

t01e4510c6d7bfab6bf.png

图 11  CPL_ParseSeparator函数默认调用方式

到现在,我们已经利用漏洞来进行攻击了。构造一个路径长度为257个字符的恶意链接文件,并使用嵌入空格键作为分隔符来触发CPL_ParseToSeparator()函数,达到截断效果,使得我们的短路径链接头文件能在CPL_LoadCPLModule()函数中运行起来。

 因为CControlPanelFolder::GetModuleMapped()函数会检测模块路径(包括嵌入的空格)是否存在。所以我们需要两个文件,一个嵌入有空格键(绕过检测文件存在性),以及一个是没有空格键的头文件。

不像内存中断,这类攻击方式对所有windows系统版本都是有效的。在早起的系统版本早就存在在点击图标时会加载可执行文件,而且加载的模块会驻留在内存中。所以,这种攻击方式在所有的windows版本还是能很稳定利用起来。微软一直在努力来阻止系统内存中断,这也就是防御的劣势所在----防御的一方必须面面聚到才能有效的防御各类攻击

本文由 360安全播报 翻译,转载请注明“转自360安全播报”,并附上链接。
原文链接:http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Full-details-on-CVE-2015-0096-and-the-failed-MS10-046-Stuxnet/ba-p/6718459