查看原文
其他

CVE-2017-8464分析

Aimeier 看雪学院 2019-05-25

0xFF 前言


以前大一听网络安全宣讲会的时候,听他们讲那个震网事件,说插一个U盘就可以在用户不知情的情况下执行任意代码,之前一直都没有搞懂到底是怎么做到的,所以现在就来尝试来分析分析。这里要分析的cve-2017-8464是一个LNK文件漏洞,控制面板快捷方式加载CPL的时候存在缺陷导致可以加载指定DLL,从而执行任意代码。



0x00 分析工具


  • IDA

  • WinHex

  • WINDBG

  • windows 7 sp1 x86 (no patch)



0x01 漏洞复现


1)、生成一个DLL用于测试


编写一个测试DLL,为了看到效果,就弹一个MessageBox吧。

#include<Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
   if(dwReason == DLL_PROCESS_ATTACH)
       MessageBox(0, TEXT("Called me!"), TEXT("Cap"), 0);
   return TRUE;
}


将生成的DLL名字改为a.dll,并将其放在漏洞机中的C盘根目录下。


2)、构造一个恶意的lnk二进制文件


使用winhex创建并编辑一个如下图数据的LNK文件(后缀为.lnk)。并将其放在任意的地方(我放的是桌面)。


3)、RUA!


ok,现在刷新一下桌面,wow!,会弹超多次的对话框。



0x02 POC细节


建议去看看微软官方资料:


https://msdn.microsoft.com/en-us/library/dd871305.aspx

 


红色: HeaderSize ,必须为0x4c

蓝色: LinkCLSID ,必须为00021401-0000-0000-C000

黄色: LinkFlags , 为1 表示HasLinkTargetIDList

 


橙色:ItemID[0]

蓝色:ItemID[1]

红色:ItemID[2]

绿色:TerminalID,A 16-bits unsigned integer,this value must be zero

黑色:IDListSize , 0x6E = sizeof(IDListSize)+sizeof(ItemID[0])+sizeof(Item[1])+sizeof(Item[2])

ps: ItemID[0]和ItemID[1]的具体含义可以去看看参考2那篇分析文章

 


红色:BlockSize

黄色:BlockSignature,这里是0xA0000005的话表面这块是SpecialFolderDataBlock

蓝色:SpecialFolderID,3 代表的是CSIDL_CONTROLS

绿色:Offset,Specifies the location of the ItemID of the first child segment of the IDList specified by SpecialFolderID. This value is the offset, in bytes, into the link target IDList(从IDListSize后面开始的偏移)

紫色:TERMINAL_BLOCK,This value must be less than 0x00000004



0x03 分析


1)、bp LoadLibraryW


大概率是explorer.exe解析的,所以用windbg双机调试,下条件断点。(为了便于调试,请先将那个a.dll中调用MessageBox那行注释掉,重新编译生成新的a.dll放于漏洞机的C盘根目录)。


  • 先使用!process 0 0找到 explorer.exe 的EPROCESS 假设为 8781f510


  • 断到指定进程环境下.process -i -r -p 8781f510


  • 使用条件断点:bp Kernel32!LoadLibraryW "$<C:\\windbgScript\\sp.txt"

sp.txt文件内容(绝对路径:C:\windbgScript\sp.txt)

as /mu ${/v:dllname} poi(esp+4)
.if ($sicmp( "${dllname}", "C:\a.dll")=0) {.echo ${dllname}} .else {gc}


如果下不了断点请先使用这个命令再加载下符号: !sym noisy;.reload /user *,再检查下绝对路径是否是使用的\\(双斜线),windbg符号路径是否已经设置。


  • 下好断点后,键入g命令运行,漏洞机中刷新桌面.windbg 断下:


显示如下内容:

kd> .if ($sicmp( "${dllname}", "C:\a.dll")=0) {.echo ${dllname}} .else {gc}
C:\a.dll
kernel32!LoadLibraryW:
001b:76653c01 8bff            mov     edi,edi


好,查看栈回溯。

kd> k
ChildEBP RetAddr
07edccc8 767b73ed kernel32!LoadLibraryW
07edcf24 769d259f SHELL32!CPL_LoadCPLModule+0x169
07edd5c8 769d26e6 SHELL32!CControlPanelFolder::_GetPidlFromAppletId+0x19c
07edd5f4 76767b0b SHELL32!CControlPanelFolder::ParseDisplayName+0x49
07edd678 7676f21f SHELL32!CRegFolder::ParseDisplayName+0x93
07edd6ec 7677083d SHELL32!ReparseRelativeIDList+0x137
07edd730 76770885 SHELL32!TranslateAliasWithEvent+0xa6
07edd748 7673e916 SHELL32!TranslateAlias+0x15
07edd774 7673e6ab SHELL32!CShellLink::_DecodeSpecialFolder+0xf9
07edea38 766fca50 SHELL32!CShellLink::_LoadFromStream+0x39f
07edec68 766fc9bf SHELL32!CShellLink::_LoadFromFile+0x90
07edec78 766fc914 SHELL32!CShellLink::Load+0x32
.....


看到SHELL32!CShellLink::_LoadFromFile,猜测是从这个函数开始解析lnk文件的。使用lm v m shell32


命令查看得到shell32.dll的在漏洞机中的路径:

kd> lm v m shell32
start    end        module name
....

1

Image path: C:\Windows\system32\SHELL32.dll

....


ok,找到shell32.dll 并把它拖进IDA pro中,开始我们的F5"逆向分析"


2)、Parse 恶意lnk文件的过程

CShellLink::_LoadFromStream    

 

在CShellLink::_LoadFromStream中,首先会读取恶意lnk文件头4个字节,判断是不是等于0x4C。接着读取恶意lnk文件头+0x4处(读取文件流会造成文件指针移动,刚刚已经读了4个了,所以下一次开始读取时文件指针偏移加4)继续读取恶意lnk文件ShellLinkHeader结构的剩下0x48个字节的数据并放到this->offset_0n244处(这个this就是CShellLink这个对象的实例),接着判断LinkCLSID(16 bytes)是否等于00021401-0000-0000-C000-000000000046。

 

之后取LinkFlags 检查有哪些结构在ShellLinkHeader后面存在,首先检查的是HasLinkTargetIDList ,在我们构造这个POC中,是成立的。(pCShellLink+244+16 就是LinkFlags,因为this->offset_0n244处存的是ShellLinkHeader结构剩下的的0x48字节,LinkCLSID后面紧跟着的就是LinkFlags字段,16是LinkCLSID的大小)。


下图中的v6会等于0,所以会调用CShellLink::_LoadIDList,通过逆向(F5)这个函数得知做的功能是分配一块内存加载lnk文件的LINKTARGET_IDLIST段(去掉这个段的前2个字节,即ItemID[0]+ItemID[1]+ItemID[2])this->0n188处。这个函数执行后,当前文件流指针就指向了LINKTARGET_IDLIST的后面,对于我们这个POC来说也就是EXTRA_DATA段了。

 

接着从文件流读取EXTRA_DATA到this->offset_0n228处。

 

下面那个是DWORD*的this+47,所以要乘以4即this->offset_0n188,即判断是不是存在LinkTargetIDList.IDList,我们这个POC是存在的,所以调用CShellLink::_DecodeSpecialFolder(this);

 

CShellLink::_DecodeSpecialFolder(CShellLink *this)

 

首先调用SHFindDataBlock查找this->offset_0n228处(EXTRA_DATA)是否有KnownFolderDataBlock(它的BlockSignature 是0xA000000B),显然我们的POC中没有构造这个Block。所以27行的v2会返回0,接着会执行第41行,调用SHFindDataBlock查找this->offset_0n228处(EXTRA_DATA)是否有SpecialFolderDataBlock(它的BlockSignature是0xA0000005),而我们的POC中的确存在这个Block,所以SHFindDataBlock返回指向SpecialFolderDataBlock的指针。

 

接着取pSpecialFolderDataBlock->SpecialFolderID(偏移为8)作为SHCloneSpecialIDList函数的第二个参数,根据MSDN上关于SHCloneSpecialIDList的介绍,它是用来获取一个KnownFolder的ITEMIDLIST结构,我们poc中构造的是3:

Retrieves a pointer to the ITEMIDLIST structure that specifies a special folder.

 

在vs中查看得知3对应的是Control Panel,所以这个函数返回的是一个指向Control Panel的ITEMIDList结构的指针(这个是个关键,详细的请看后面我单独会写)。

 

 

接着会调用TranslateAlias函数,传入的参数分别是:


  • this->offset_0n188,即ItemID[0],ItemID[1],ItemID[2]

  • v11 是指向一块存放 ItemID[0]和ItemID[1]的内存

  • 控制面板的ITEMIDLIST,用于之后绑定


TranslateAlias

 

TranslateAliasWithEvent


SHBindToObject函数MSDN上是有介绍的:

Retrieves and binds to a specified object by using the Shell namespace IShellFolder::BindToObject method.

所以要触发到后面调用CControlPanelFolder::的例程,那么一定要获取到ControlPanel的IDList。

 

ReparseRelativeIDList

 

58行的调用也是个关键,是调用这个将poc中IDList[2]中的"C:\a.dll" 加载到CControlPanelFolder::s_dsaTemporaryAppId里面的,之后LoadLibraryW的那个参数正是从这个CControlPanelFolder::_dsaTemp里面获取的。这个函数里面做了些什么我后面会单独出来讲。

 

CRegFolder::ParseDisplayName

 

CControlPanelFolder::ParseDisplayName

 

CControlPanelFolder::_GetPidlFromAppletId

 

首先调用的是35行那句,调用后从一个。

 

下面来看看CControlPanelFolder::_GetAppletPathForTemporaryAppId发生了啥:

 

CPL_LoadCPLModule

 

触发调用指定Dll

 


3)、关于那个3


不知道,你是否好奇为啥SpecailFolderID一定要指定为3?那个3到底是啥意思呢?我开始的时候理解的是就是指定读取TargetIDList的第3个IDList,即IDList[2]。其实不是。

 

ok,让我们看看当SpecialFolderBlock.SpecialFolderID为3的时候,SHCloneSpecialIDList的返回值是什么。

 

 

我在上面Parse过程中分析到这个地方的时候说过,SHCloneSpecialIDList根据传入的csidl,获取指定文件夹的IDList,为了验证这里获取到的的确是控制面板的IDList。我们可以打开控制面板,任意选择一个CPL创建一个快捷方式,我这里创建了一个管理工具的快捷方式。用winhex打开它,哇IDList[0]和IDList[1]和上图中返回的一模一样。

 

好,下面我们把我们构造的那个lnk文件的SpecialFolderID改为一个非3的数字,这里我们改成1,刷新桌面,并未触发调用a.dll。

 

还是在SHCloneSpecialIDList处下断,下面我们来试试将SHCloneSpecialIDList返回的IDList数据强行改成CSIDL_CONTROLS(3)的数据,看看会不会有奇迹出现。

 

嘿嘿,触发了。(为啥还要eq eax+20 0呢?因为我发现只改那0x20个字节的数据为CSIDL_CONTROLS的返回数据不会触发加载a.dll,还需要再多改8个字节才行)

 

注意,我上面不是改的SHCloneSpecialIDList的参数,样本里面指定的还是是1,那SHCloneSpecialIDList的第二个参数就是1,我改的是它返回的数据,改成控制面板里面的项的IDList[0]和IDList[1]就行了。所以间接证明了那个3的作用。


4)、关于读取指定dll路径的过程


加载dll路径到CControlPanelFolder::s_dsaTemporaryAppId那个里面是在ReparseRelativeIDList调用DisplayNameOfAsString函数做的。

 

流程如下:

CControlPanelFolder::GetModule

CControlPanelFolder::GetModuleMapped

CControlPanelFolder::GetExecName

CControlPanelFolder::_GetFullCPLName

CControlPanelFolder::_GetTemporaryAppIdForApplet

CControlPanelFolder::GetDetailsEx

GetStringProperty

CControlPanelFolder::GetDisplayNameOf

CRegFolder::GetDisplayNameOf

DisplayNameOfAsString //开始处


 

所以我们poc中的字符串必须要为宽字符字串,且必须要从偏移24字节出开始写。

 

下面再来看看是如何校验IDList[2]是否合法的。

 

但是其实上面那个函数只是CControlPanelFolder::_IsValid的子过程,真正校验包含dll路径的IDList[2]是否合法的函数其实是CControlPanelFolder::_IsValid。所以要构造恶意的IDList[2]的话,把这个函数调通就行了。下图是相关校验代码。



0x04 总结


那U盘怎么利用这个呢?U盘插入电脑盘符可能有多少种情况呢?26种,那就构造26个恶意lnk文件:

 

A:\a.dll B:\a.dll 以此类推。(a.dll放入U盘根目录)。



0x05 参考


  • https://msdn.microsoft.com/en-us/library/dd871305.aspx


  • https://paper.tuisec.win/detail/bb5e0d987cf23cc






- End -



看雪ID:Aimeier                          

https://bbs.pediy.com/user-798042.htm


本文由 Aimeier 原创

转载请注明来自看雪社区



热门图书推荐:

立即购买!


看雪学院官方QQ群

QQ:953174865

入群时请注明看雪id

欢迎入群交流!



热门技术文章:        



公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存