-
-
[原创]CVE-2018-8550分析与复现
-
2019-5-1 21:34 6546
-
这个漏洞属于com组件Unmarshal类型本地权限提升漏洞
复现环境
- Windows 10 1709 32位操作系统
- 需要安装声卡或操作系统自带虚拟声卡
- 编译环境Visual Studio 2013
Poc 分析
原poc作者James Forshaw使用C#实现,我一直未复现成功,不过通过原poc的代码我大致明白了漏洞的成因和触发方法,原poc环境是win10 1803 X64系统.在cve2018-8550更新补丁出来不久,微软就取消了64位ole32.dll和coml2.dll调试符号提供,不过32的仍然可以正常提供.为了方便调试我用vc在32位系统上成功复现了poc.
poc采用的反射从低权限进程(poc进程)向高权限进程(bits服务)Unmarshal方式实现触发bits复制了一个poc进程中的一个句柄至高权限进程从而用来创建进程实现提权,Unmarshal采用的ClassID为CLSID_DfMarshal={ 0x0000030B, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, } ,Bits服务器调用了ole32(在win10中被代码迁移到coml2)的DfUnMarshalInterface方法来Unmarshal的数据流中SDfMarshalPacket被poc精心构造的数据包,由于poc使用的是StgCreateDocfile创建IStorage对象,所以最终DfUnMarshalInterface调用了CExposedDocFile::Unmarshal来Unmarshal,下面是逆向的代码:
int __stdcall CExposedDocFile::Unmarshal(IStream *pstm, void **ppv, unsigned int mshlflags) { int hrFinal; // esi int hrTempNext; // eax CPubDocFile *CPubDocFileObj; // eax CGlobalContext *CGlobalContextObj; // eax const void *v7; // eax CGlobalContext *CGlobalContextObjRef; // eax CPubDocFile *CBasedPubDocFileObjFirst; // eax unsigned int fUnmarshalOriginalConfig; // eax CPubDocFile *CBasedPubDocFileObjFirstRef; // ecx int fUnmarshalOriginalConfigRef; // ST10_4 int IsRootConfig; // eax void *ReservedForOleRef; // ebx unsigned int CMarshalListObj_baseObj; // ecx unsigned int pid; // eax CMarshalList *CMarshalListObjFirst; // ecx CMarshalList *CMarshalListObjFound; // eax CExposedDocFile *CExposedDocFile_MarshalList_Final; // esi CExposedDocFile *CExposedDocFileObjNext; // eax MAPDST CDFBasis *CBasedDFBasisObj; // esi CPubDocFile *CBasedPubDocFileObjNext; // edx CSmAllocator *CSmAllocator_A; // eax CSmAllocator *CSmAllocator_B; // eax unsigned int CBasedMarshalListObj; // eax CMarshalList *CBasedMarshalListObjNext; // ecx CPubDocFile *CPubDocFileObjNext; // eax CDFBasis *BasedDFBasisObj; // eax CPerContext *CPerContextObjRef; // ecx CSmAllocator *CSmAllocator_C; // eax CSmAllocator *CSmAllocator_D; // eax CPerContext pcSharedMemory; // [esp+Ch] [ebp-8Ch] unsigned int cbRead; // [esp+50h] [ebp-48h] SDfMarshalPacket SDfMarshalPacketCurrent; // [esp+54h] [ebp-44h] IStorage *pstorgeStd; // [esp+88h] [ebp-10h] CDfMutex mtx; // [esp+8Ch] [ebp-Ch] CPerContext *CPerContextObj; // [esp+94h] [ebp-4h] void *pvBaseOld; // [esp+A0h] [ebp+8h] // SDfMarshalPacketCurrent初始化里面的字段指针都是nullptr mtx._pGlobalPortion = 0; mtx._hLockEvent = 0; pstorgeStd = 0; SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr = 0; SDfMarshalPacketCurrent.CBasedPubStreamObj._SelftobjectPtr = 0; SDfMarshalPacketCurrent.CBasedSeekPointerObj._SelftobjectPtr = 0; SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr = 0; SDfMarshalPacketCurrent.CBasedDFBasisObj._SelftobjectPtr = 0; SDfMarshalPacketCurrent.CBasedGlobalContextObj._SelftobjectPtr = 0; SDfMarshalPacketCurrent.CBasedGlobalFileStreamObj._SelftobjectPtr = 0; SDfMarshalPacketCurrent.CBasedGlobalFileStreamDirty._SelftobjectPtr = 0; SDfMarshalPacketCurrent.CBasedGlobalFileStreamOriginal._SelftobjectPtr = 0; // 构造pcSharedMemory是一个cpercontext,先构造变量 CPerContext::CPerContext(&pcSharedMemory, 0); // 通用marshal方法先CoUnmarshalInterface CoUnmarshalInterface(pstm, &IID_IStorage, (IUnknown *)&pstorgeStd); if ( (mshlflags & 0x80000000) != 0 ) { // 这里都读流int就是largeint hrFinal = ((int (__stdcall *)(IStream *, signed int, _DWORD, signed int, _DWORD))pstm->_SelfStreamVtbl->Seek)( pstm, 96, 0, 1, 0); if ( hrFinal >= 0 ) hrFinal = -2147287039; goto EH_std_0; } // 把SDfMarshalPacketCurrent读出来 hrFinal = pstm->_SelfStreamVtbl->Read(pstm, &SDfMarshalPacketCurrent, 52u, &cbRead); if ( hrFinal >= 0 ) { if ( cbRead != 52 ) { hrFinal = -2147287010; goto EH_std_0; } // pcSharedMemory也是从packet中反序列化出来的,里面需要判断进程id UnmarshalSharedMemory(&SDfMarshalPacketCurrent, mshlflags, &pcSharedMemory); hrFinal = hrTempNext; if ( hrTempNext < 0 ) goto EH_std_0; // 为ReservedForOle赋值一个局部变量 pvBaseOld = *(void **)NtCurrentTeb()->ReservedForOle; // 反序列化出来全局baseDocFile,需要先验证这个是否正确 if ( SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr ) CPubDocFileObj = (CPubDocFile *)(SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CPubDocFileObj = 0; // 验证只是判断里面的sig hrFinal = CPubDocFile::Validate(CPubDocFileObj); if ( hrFinal < 0 ) { EH_Err_109: CPerContext::SetThreadAllocatorState(&pcSharedMemory, 0); CSmAllocator_A = GetTlsSmAllocator(); CSmAllocator::Uninit(CSmAllocator_A); CSmAllocator_B = GetTlsSmAllocator(); CSmAllocator::SetState(CSmAllocator_B, 0, 0, 0, 0, 0); goto EH_std_0; } if ( SDfMarshalPacketCurrent.CBasedGlobalContextObj._SelftobjectPtr ) CGlobalContextObj = (CGlobalContext *)(SDfMarshalPacketCurrent.CBasedGlobalContextObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CGlobalContextObj = 0; if ( !CGlobalContextObj || (!SDfMarshalPacketCurrent.CBasedGlobalContextObj._SelftobjectPtr ? (v7 = 0) : (v7 = (const void *)(SDfMarshalPacketCurrent.CBasedGlobalContextObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle)), !IsValidPtrIn(v7, 0x30u)) ) { hrFinal = -2147287031; goto EH_Err_109; } // 全局环境变量CGlobalContextObjRef也反序列出来 if ( SDfMarshalPacketCurrent.CBasedGlobalContextObj._SelftobjectPtr ) CGlobalContextObjRef = (CGlobalContext *)(SDfMarshalPacketCurrent.CBasedGlobalContextObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CGlobalContextObjRef = 0; // 里面是CGlobalContext的初始化它的锁createevent hrFinal = CDfMutex::Init(&mtx, CGlobalContextObjRef, 0); if ( hrFinal < 0 ) goto EH_Err_109; // 锁验证一下 hrFinal = CDfMutex::Take(&mtx, 0xFFFFFFFF); if ( hrFinal < 0 ) goto EH_Err_109; // 反序列化出来一个临时的PubDocFileObj和它的ref同类型局部变量不牵涉下下面具体分配 if ( SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr ) CBasedPubDocFileObjFirst = (CPubDocFile *)(SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CBasedPubDocFileObjFirst = 0; fUnmarshalOriginalConfig = CBasedPubDocFileObjFirst->PRevertableObj._UnmarshalOriginalConfig; if ( SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr ) CBasedPubDocFileObjFirstRef = (CPubDocFile *)(SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CBasedPubDocFileObjFirstRef = 0; // 2个配置做与运算双重判断 fUnmarshalOriginalConfigRef = fUnmarshalOriginalConfig & 4; // 这个配置判断是否是Rootl类型的PubDocFile IsRootConfig = CPubDocFile::IsRoot(CBasedPubDocFileObjFirstRef); // 这里只是根据读取到的2个配置反序列化出来一个环境变量CPerContextObj hrFinal = UnmarshalContext( &SDfMarshalPacketCurrent, &CPerContextObj, mshlflags, // 是否Root配置 IsRootConfig, // Unmarshal配置 fUnmarshalOriginalConfigRef); if ( hrFinal < 0 ) { EH_mtx_0: CDfMutex::Release(&mtx); goto EH_Err_109; } // 保存ReservedForOle局部临时变量 ReservedForOleRef = *(void **)NtCurrentTeb()->ReservedForOle; if ( gs_iSharedHeaps <= 256 ) { // 把CMarshalList反序列化出来先验证下 if ( SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr ) CMarshalListObj_baseObj = SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle; else CMarshalListObj_baseObj = 0; if ( CExposedDocFile::Validate((CExposedDocFile *)(CMarshalListObj_baseObj != 0 ? CMarshalListObj_baseObj - 72 : 0)) < 0 ) { // 不通过最近指针就是nullptr CExposedDocFile_MarshalList_Final = 0; } else { // 获取当前的ProcessId,做判断,执行第一种逻辑 pid = GetCurrentProcessId(); // CMarshalListObj也是从packet中反序列化出来的 if ( SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr ) CMarshalListObjFirst = (CMarshalList *)(SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CMarshalListObjFirst = 0; // 如果是当前进程先从CMarshalList找到最终的CExposedDocFile CMarshalListObjFound = CMarshalList::FindMarshal(CMarshalListObjFirst, pid, ReservedForOleRef); // 找到CMarshalListObjFound后只是它的指针偏移量减6作为最终结果 CExposedDocFile_MarshalList_Final = (CExposedDocFile *)(CMarshalListObjFound != 0 ? (unsigned int)&CMarshalListObjFound[-6] : 0); } if ( CExposedDocFile_MarshalList_Final ) { // 如果找到找到最终的CExposedDocFile的FBasis就从之前的CPerContextObj把里面字段赋值 if ( SDfMarshalPacketCurrent.CBasedDFBasisObj._SelftobjectPtr ) // BasedDFBasisObj也是反序列出来之后再执行赋值 BasedDFBasisObj = (CDFBasis *)(SDfMarshalPacketCurrent.CBasedDFBasisObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else BasedDFBasisObj = 0; CPerContextObjRef = CPerContextObj; // 赋值之后就完成unmarshal BasedDFBasisObj->_lockbytesBasePtr = CPerContextObj->_LockBytesBasePtr; BasedDFBasisObj->_CFileStreamDirtyPtr = CPerContextObjRef->_CFileStreamDirtyPtr; BasedDFBasisObj->_LockBytesOriginalPtr = CPerContextObjRef->_LockBytesOriginalPtr; CExposedDocFile_MarshalList_Final->baseIUnkownPtr._SelfMarshalVtbl->AddRef((IUnknown *)CExposedDocFile_MarshalList_Final); // 最终结果addref环境局部变量释放release CPerContext::Release(CPerContextObj); } else { // 如果从MarshalList未找到 CExposedDocFileObjNext = (CExposedDocFile *)CMallocBased::operator new(0x8Cu, CPerContextObj->_pMalloc); // 初始化最终的CExposedDocFileObjNext if ( CExposedDocFileObjNext ) { if ( SDfMarshalPacketCurrent.CBasedDFBasisObj._SelftobjectPtr ) // BasedDFBasisObj同样是反序列出来之后但是不赋值,可以为nullptr最为最终结果的构造函数参数传入 CBasedDFBasisObj = (CDFBasis *)(SDfMarshalPacketCurrent.CBasedDFBasisObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CBasedDFBasisObj = 0; if ( SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr ) // PubDocFileObj同样是反序列出来之后但是不赋值,可以为nullptr最为最终结果的构造函数参数传入 CBasedPubDocFileObjNext = (CPubDocFile *)(SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CBasedPubDocFileObjNext = 0; // !!!构造CExposedDocFileObjNext,它才是最终结果!!! CExposedDocFile::CExposedDocFile( CExposedDocFileObjNext, CBasedPubDocFileObjNext, CBasedDFBasisObj, CPerContextObj); // 最终结果就是刚才构造的CExposedDocFileObjNext CExposedDocFile_MarshalList_Final = CExposedDocFileObjNext; } else { CExposedDocFile_MarshalList_Final = 0; } // 如果构造出来的CExposedDocFile_MarshalList_Final 失败 if ( !CExposedDocFile_MarshalList_Final ) goto LABEL_54; if ( SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr ) CBasedMarshalListObj = SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle; else CBasedMarshalListObj = 0; if ( CBasedMarshalListObj ) { // 不是当前进程也需要反序列出来MarshalList把最终结果加进入 if ( SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr ) CBasedMarshalListObjNext = (CMarshalList *)(SDfMarshalPacketCurrent.CBasedMarshalListObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CBasedMarshalListObjNext = 0; // 通过最终结果的MarshalList字段加到全局也是反序列化出来的MarshalList CMarshalList::AddMarshal(CBasedMarshalListObjNext, &CExposedDocFile_MarshalList_Final->CMarshalListObj); // 最终结果是ExposedDoc } if ( SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr ) // PubDocFile也反序列化出来因为2个doc有关联所以需要addref下PubDocFile CPubDocFileObjNext = (CPubDocFile *)(SDfMarshalPacketCurrent.CBasedPubDocFileObj._SelftobjectPtr + *(_DWORD *)NtCurrentTeb()->ReservedForOle); else CPubDocFileObjNext = 0; InterlockedIncrement(&CPubDocFileObjNext->_count_References); } // 把CExposedDocFile_MarshalList_Final赋值给最终需要反序列化的远程对象指针ppv *ppv = CExposedDocFile_MarshalList_Final; CDfMutex::Release(&mtx); if ( pvBaseOld != ReservedForOleRef ) { CPerContext::SetThreadAllocatorState(&pcSharedMemory, 0); CSmAllocator_C = GetTlsSmAllocator(); CSmAllocator::Uninit(CSmAllocator_C); } CSmAllocator_D = GetTlsSmAllocator(); CSmAllocator::SetState(CSmAllocator_D, 0, 0, 0, 0, 0); if ( pstorgeStd ) pstorgeStd->_SelfStorageVtbl->Release(pstorgeStd); goto LABEL_80; } LABEL_54: hrFinal = -2147287032; CPerContext::Release(CPerContextObj); goto EH_mtx_0; } EH_std_0: if ( !pstorgeStd ) { CPerContext::~CPerContext(&pcSharedMemory); CDfMutex::~CDfMutex(&mtx); return hrFinal; } *ppv = pstorgeStd; LABEL_80: CPerContext::~CPerContext(&pcSharedMemory); CDfMutex::~CDfMutex(&mtx); return 0; }
DfUnMarshalInterface默认是采用MSHCTX=MSHCTX_INPROC也就是同进程内Unmrahsl方式读取的指针都是的同进程对象,不过它也支持UnmarshalSharedMemory方式,也就是从结构的hMem字段从audiodg.exe的共享内存初始化相应的共享对象
//SDfMarshalPacket结构 typedef struct _SDfMarshalPacket { unsigned int CBasedPubDocFileObj; void * CBasedPubStreamObj; void * CBasedSeekPointerObj; void * CBasedMarshalListObj; void * CBasedDFBasisObj; unsigned int CBasedGlobalContextObj; unsigned int CBasedGlobalFileStreamBaseObj; void * CBasedGlobalFileStreamDirty; void * CBasedGlobalFileStreamOriginal; unsigned int ulHeapName; unsigned int ProcessContextId; GUID cntxkey; CPerContext * CPerContextObj; //共享内存 void *hMem; } SDfMarshalPacket;
windows音频服务允许建立一个Initialize在audiodg.exe进程共享内存中AUDCLNT_SHAREMODE=AUDCLNT_SHAREMODE_SHARED的共享内存,并开辟这样一块共享内存给poc进程和bits进程使用.通过ntdll未公开的函数实现找到位于audiodg.exe进程共享内存的句柄,写入交换数据后,将共享内存的句柄值写入SDfMarshalPacket->hMem,关于ntdll未公开的函数具体实现poc中有代码请读者自行研究.
HRESULT STDMETHODCALLTYPE SharedMemoryMarshaller::StartAudioClient(){ const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); IMMDeviceEnumeratorPtr pEnumerator = nullptr; HRESULT hr = CoCreateInstance( CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator); IMMDevicePtr pDevice = nullptr; hr = pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eConsole, &pDevice); WAVEFORMATEX *pwfx = NULL; REFERENCE_TIME hnsRequestedDuration = 10000000; UINT32 nFrames = 0; IAudioClient *pAudioClient = NULL; // Get the audio client. (hr = pDevice->Activate( __uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&pAudioClient)); // Get the device format. hr = pAudioClient->GetMixFormat(&pwfx); // Open the stream and associate it with an audio session. hr = pAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, NULL, hnsRequestedDuration, 0, pwfx, NULL); return hr; }
在CSharedMemoryBlock::InitUnMarshal中CSharedMemoryBlockObj->_pbBase被赋值成了SDfMarshalPacket->hMem共享内存映射后的地址,之后在CSmAllocator::Init中 CPerContext->_pbBase = CSharedMemoryBlockObj->_pbBase + 8,之后在UnmarshalSharedMemory接着就调用CPerContext::SetThreadAllocatorState接着调用CSmAllocator::SetState将NtCurrentTeb()->ReservedForOle赋值成CPerContext->_pbBase.最终得出的结论是NtCurrentTeb()->ReservedForOle-8就是共享内存内存映射的相对地址.在poc中使用如下方式操作共享内存
在poc中映射的共享内存基址directoryMappedAddressLocal就是bits进程中NtCurrentTeb()->ReservedForOle-8即bits进程映射的共享内存基址.以下是poc操作共享内存的方法
CBasedMapSectionPtr pdf = (CBasedMapSectionPtr)(pck->CBasedPubDocFileObj + (ULONG)directoryMappedAddressLocal + 8);
pdf->_SelftobjectPtr = 0x46444250;
void __stdcall UnmarshalSharedMemory(SDfMarshalPacket *SDfMarshalPacketCurrent, unsigned int mshlflags, CPerContext *ppcOwner) { SDfMarshalPacket *pck; // esi unsigned int HeapName; // ebx CPerContext *CPerContextObjTemp; // eax HANDLE currentThreadHandle; // eax CPerContext *ppcOwnerRef; // ecx unsigned int ProcessContextIdRef; // [esp+Ch] [ebp-Ch] void *hToken; // [esp+10h] [ebp-8h] int sc; // [esp+14h] [ebp-4h] CSmAllocator *pMalloc; // [esp+20h] [ebp+8h] CSmAllocator *pMalloca; // [esp+20h] [ebp+8h] pck = SDfMarshalPacketCurrent; HeapName = SDfMarshalPacketCurrent->ulHeapName; // 获取当前进程pid ProcessContextIdRef = SDfMarshalPacketCurrent->ProcessContextId; // PerContextObj反序列出来如果是当前进程就取它的值 CPerContextObjTemp = SDfMarshalPacketCurrent->CPerContextObj; sc = 0; // pMalloc 就是CPerContextObjTemp的引用 pMalloc = (CSmAllocator *)CPerContextObjTemp; if ( GetCurrentProcessId() == ProcessContextIdRef ) { // CSmAllocatorObjRefThis就是去反序列化这和sharedmemeory没关系了,默认认为sharedmemeory已经存在并初始化 ppcOwnerRef = (CPerContext *)pMalloc; } else { pMalloca = GetTlsSmAllocator(); if ( pMalloca->_ulHeapName != HeapName ) { hToken = 0; // 获取当前线程Thread的tid currentThreadHandle = GetCurrentThread(); if ( OpenThreadToken(currentThreadHandle, 8u, 1, &hToken) ) { CloseHandle(hToken); sc = 0x80030005; JUMPOUT(&requireCreateInstance); } // 非当前进程认为sharedmemeory不存在并开始初始化 CSmAllocator::SetState(pMalloca, 0, 0, 0, 0, 0); // fUnmarshal=1,开始Unmarshal共享内存 sc = CSmAllocator::Init(pMalloca, pck->hMem, ProcessContextIdRef, HeapName, 1); sc = CSmAllocator::Sync(pMalloca); } // owner就是pcSharedMemory如果非当前进程,CPerContext的pbBase又赋值给CSmAllocator的pbBase CPerContext::GetThreadAllocatorState(ppcOwner); ppcOwnerRef = ppcOwner; } // 设置一下状态 CPerContext::SetThreadAllocatorState(ppcOwnerRef, 0); } void __thiscall CSmAllocator::SetState(CSmAllocator *this, CSharedMemoryBlock *psmb, char *pbBase, unsigned int ulHeapName, CPerContext **ppcPrev, CPerContext *ppcOwner) { unsigned int v6; // eax // that->CSharedMemoryBlockObj(+0x4) = shmBlock; this->CSharedMemoryBlockObj = psmb; // this->_pbBase(0x8偏移量) = pbBase; this->_pbBase = pbBase; if ( psmb ) v6 = psmb->_culCommitSize - 8; else v6 = 0; this->_cbSize = v6; this->_ulHeapName = ulHeapName; *(_DWORD *)NtCurrentTeb()->ReservedForOle = pbBase; if ( ppcPrev ) *ppcPrev = this->_ppcOwner; this->_ppcOwner = ppcOwner; } int __thiscall CSmAllocator::Init(CSmAllocator *this, void *hMem, unsigned int dwProcessId, unsigned int ulHeapName, int fUnmarshal) { CSharedMemoryBlock *shmBlock; // eax CSharedMemoryBlock *shmBlockRef; // edi .... shmBlock = (CSharedMemoryBlock *)operator new(0x18u); if ( shmBlock ) { shmBlock->_hMem = 0; shmBlock->_pbBase = 0; shmBlock->_culCommitSize = 0; shmBlock->_culInitCommitSize = 0; shmBlock->_fCreated = 1; shmBlock->_fReadWrite = 0; } else { shmBlock = 0; } shmBlockRef = shmBlock; // that->CSharedMemoryBlockObj(+0x4)偏移量 = shmBlock; that->CSharedMemoryBlockObj = shmBlock; if ( shmBlock ) { if ( fUnmarshal ) { hrTemp = CSharedMemoryBlock::InitUnMarshal(shmBlock, hMem, dwProcessId, 0x3FF8u); goto LABEL_13; } LABEL_20: hrTemp = CSharedMemoryBlock::Init(shmBlockRef, 0, 0x3FFFF8u, 0x3FF8u, 0, 0, 1); LABEL_13: sc = hrTemp; if ( hrTemp < 0 ) { if ( shmBlockRef != &g_smb && shmBlockRef ) CSharedMemoryBlock::`scalar deleting destructor'(shmBlockRef, 1u); that->CSharedMemoryBlockObj = 0; return sc; } that->_cbSize = shmBlockRef->_culCommitSize - 8; // pMalloca的pbBase就是NtCurrentTeb()->ReservedForOle->pvThreadBase(0x0偏移量)值相等,shmBlockRef->_pbBase也就是共享内存分配的基址,其中pMalloca=CPerContext that->_pbBase = shmBlockRef->_pbBase + 8; ..... } int __thiscall CSharedMemoryBlock::InitUnMarshal(CSharedMemoryBlock *this, void *hMem, unsigned int dwProcessId, unsigned int culCommitSize) { CSharedMemoryBlock *that; // esi int v5; // ebx signed int v6; // eax HANDLE v8; // eax int v9; // eax char *shareMemoryOffset; // eax int v11; // eax HANDLE hProcess; // [esp+18h] [ebp+Ch] that = this; v5 = 0; hProcess = OpenProcess(0x40u, 0, dwProcessId); if ( hProcess ) { v8 = GetCurrentProcess(); // &that->_hMem=0x偏移量 if ( DuplicateHandle(hProcess, hMem, v8, &that->_hMem, 0, 0, 2u) ) { shareMemoryOffset = (char *)MapViewOfFileEx(that->_hMem, 6u, 0, 0, 0, 0); // //pMalloca->_pbBase = shmBlockRef->_pbBase + 8; 其中的pMalloca->pbBase就是NtCurrentTeb()->ReservedForOle->pvThreadBase(0x0偏移量)值相等,shmBlockRef->_pbBase= that->_pbBase也就是共享内存分配的基址 that->_pbBase = shareMemoryOffset; ..... }
在windbg验证结论,共享内存CPerContext的地址为0617a350,可见它继承与CSmAllocator,它的CPerContext->CSharedMemoryBlockObj(+0x4)也就是CSharedMemoryBlock地址为060def40,CSharedMemoryBlock->_pbBase地址为03e40000,同时可以看到ReservedForOle指针指向的地址为03e40008,验证了结论.
0:018> dt ntdll!_TEB ReservedForOle @$teb +0xf80 ReservedForOle : 0x031a9e98 Void 0:018> dc 0x031a9e98 031a9e98 03e40008 0617a350 00000000 00001002 ....P........... 031a9ea8 0000000c 02f86f80 00000000 00000000 .....o.......... 031a9eb8 00000001 03112648 0506e850 00000000 ....H&..P....... 031a9ec8 00000000 00002f4c 03109f80 03109f80 ....L/.......... 031a9ed8 00000000 00000000 0000013c 00000000 ........<....... 031a9ee8 00000000 0313c658 00000000 00000000 ....X........... 031a9ef8 00000000 00000000 00000000 00000000 ................ 031a9f08 00000000 00000000 00000000 00000000 ................ 0:018> dps 0x031a9e98 //ReservedForOle指针指向的地址=CSharedMemoryBlock->_pbBase+8 031a9e98 03e40008 //CPerContext的地址 031a9e9c 0617a350 031a9ea0 00000000 031a9ea4 00001002 031a9ea8 0000000c 031a9eac 02f86f80 ... //CPerContext的地址为0617a350 0:018> dps 0617a350 0617a350 750c11a8 coml2!CSmAllocator::`vftable' //CPerContext->_psmb也就是CSharedMemoryBlock地址 0617a354 060def40 //NtCurrentTeb()->ReservedForOle赋值成CPerContext->_pbBase 0617a358 03e40008 .... //CSharedMemoryBlock地址 0:018> dps 060def40 060def40 000010b8 //CSharedMemoryBlock->_pbBase地址 060def44 03e40000 ...
设置好共享内存后会进入UnmarshalContext,其中先从SDfMarshalPacket->CBasedGlobalContextObj中通过CContextList::_Find找第一个CGlobalContext,其实CGlobalContext继承于CContextList结构,会根据CContextList->pctxNext找下一个CContextList直到CContextList->ctxid为要找的进程id为止.之后会验证下CPerContext句柄是否有效,其实只要构造一个名为"\Sessions\0\BaseNamedObjects\OleDfRoot%X%08lX"的内核Event通过NtCreateEvent,Bits服务的Sessions为0,详见poc代码.
SDfMarshalPacket *__userpurge UnmarshalContext@<eax>(CPerContext **a1@<edx>, SDfMarshalPacket *pckfrom@<ecx>, struct SDfMarshalPacket *pvkOrg, struct CPerContext **a4, unsigned int a5, int a6, int a7) { SDfMarshalPacket *pck; // edi CGlobalContext *pCGlobalContextOffset; // ecx CGlobalContext *pCGlobalContext; // esi unsigned int NowPid; // eax CPerContext *pCPerContextFound; // eax CContext *pContext; // ebx struct _GUID v13; // ST04_16 struct CPerContext **v14; // ecx CPerContext *v15; // eax CSmAllocator *v17; // eax unsigned int NowPidNext; // eax int CBasedGlobalFileStreamBaseObjOffset; // edx CGlobalFileStream *pCBasedGlobalFileStreamBase; // ecx int v21; // eax char *v22; // edx int v23; // esi CGlobalFileStream *fstmFromGlobal; // ecx SDfMarshalPacket *v25; // eax char *v26; // edx CGlobalFileStream *v27; // ecx SDfMarshalPacket *v28; // eax struct ILockBytes *v29; // edi struct CFileStream *v30; // ebx struct ILockBytes *v31; // esi unsigned int v32; // edx void **v33; // [esp+0h] [ebp-44h] void **v34; // [esp+0h] [ebp-44h] void **v35; // [esp+0h] [ebp-44h] struct IMalloc *v36; // [esp+4h] [ebp-40h] unsigned int v37; // [esp+4h] [ebp-40h] unsigned int *v38; // [esp+4h] [ebp-40h] CPerContext **v39; // [esp+10h] [ebp-34h] CPerContext *v40; // [esp+14h] [ebp-30h] int v41; // [esp+18h] [ebp-2Ch] unsigned int v42; // [esp+1Ch] [ebp-28h] BOOL pCPerContextFoundRef; // [esp+20h] [ebp-24h] CGlobalContext *pCGlobalContextRef; // [esp+24h] [ebp-20h] struct CFileStream *v45; // [esp+28h] [ebp-1Ch] struct ILockBytes *ppvRet; // [esp+2Ch] [ebp-18h] struct ILockBytes *v47; // [esp+30h] [ebp-14h] struct CFileStream *v48; // [esp+34h] [ebp-10h] struct ILockBytes *v49; // [esp+38h] [ebp-Ch] SDfMarshalPacket *pckRef; // [esp+3Ch] [ebp-8h] v39 = a1; v42 = 0; pck = pckfrom; v41 = 0; pckRef = pckfrom; ppvRet = 0; v48 = 0; pCGlobalContextOffset = pckfrom->CBasedGlobalContextObj; v45 = 0; v49 = 0; v47 = 0; v40 = 0; if ( pCGlobalContextOffset ) { pCGlobalContext = (pCGlobalContextOffset + *NtCurrentTeb()->ReservedForOle); v41 = ppvRet; v48 = v45; v49 = v47; } else { pCGlobalContext = 0; } pCGlobalContextRef = pCGlobalContext; NowPid = GetCurrentProcessId(); // 找第一个PerContext pCPerContextFound = CContextList::_Find(&pCGlobalContext->CContextListbase, NowPid); pContext = &pCPerContextFound->baseclass_CContext; pCPerContextFoundRef = !pCPerContextFound // 先验证下找到的PerContext必须成功 || (pContext = (CPerContext::IsHandleValid(pCPerContextFound) != 0 ? pCPerContextFound : 0)) == 0; // 如果不是当前进程检查CProcessSecret,不相符也没关系 if ( GetCurrentProcessId() != pck->ProcessContextId ) { *&v13.Data1 = *&pck->cntxkey.Data1; *v13.Data4 = *pck->cntxkey.Data4; if ( CProcessSecret::VerifyMatchingSecret(v13) < 0 && pContext ) // 不相符也没关系只是不设置状态 CPerContext::SetThreadAllocatorState(pContext, v14); pCGlobalContext = pCGlobalContextRef; pck = pckRef; } if ( !pCPerContextFoundRef ) { LABEL_21: v17 = GetTlsSmAllocator(); CSmAllocator::SetState(v17, pContext[5].pctxNext, pContext[6].ctxid, pContext[6].pctxNext, 0, pContext); NowPidNext = GetCurrentProcessId(); // 继续找下一个PerContext pContext = CContextList::_Find(&pCGlobalContext->CContextListbase, NowPidNext); if ( !pContext ) { pckRef = 0x800300FD; goto LABEL_23; } CBasedGlobalFileStreamBaseObjOffset = pck->CBasedGlobalFileStreamBaseObj; if ( CBasedGlobalFileStreamBaseObjOffset ) { pCBasedGlobalFileStreamBase = (CBasedGlobalFileStreamBaseObjOffset + *NtCurrentTeb()->ReservedForOle); v49 = v47; } else { pCBasedGlobalFileStreamBase = 0; } // 进入关键步骤 hr = CFileStream::Unmarshal(&ppvRet, pCBasedGlobalFileStreamBase, pCBasedGlobalFileStreamBase, v33, v36); .... } struct CContext *__thiscall CContextList::_Find(CContextList *this, unsigned int pid) { CContext *HeadCtx; // eax char *HeadCtxFoundRet; // edx CContext *v4; // ecx CContext *HeadCtxlookup; // ecx CContext *v6; // ecx CContext *HeadCtxFound; // ecx HeadCtx = this->_pctxHead; HeadCtxFoundRet = 0; while ( 1 ) { v4 = HeadCtx ? (HeadCtx + *NtCurrentTeb()->ReservedForOle) : 0; if ( !v4 ) break; HeadCtxlookup = HeadCtx ? (HeadCtx + *NtCurrentTeb()->ReservedForOle) : 0; if ( HeadCtxlookup->ctxid ) { v6 = HeadCtx ? (HeadCtx + *NtCurrentTeb()->ReservedForOle) : 0; if ( v6->ctxid == pid ) break; } if ( HeadCtx ) HeadCtxFound = (HeadCtx + *NtCurrentTeb()->ReservedForOle); else HeadCtxFound = 0; // 继续找下一个 HeadCtx = HeadCtxFound->pctxNext; } if ( HeadCtx ) HeadCtxFoundRet = HeadCtx + *NtCurrentTeb()->ReservedForOle; return HeadCtxFoundRet; } int __thiscall CPerContext::IsHandleValid(CPerContext *this) { CPerContext *that; // esi int result; // eax wchar_t Dest; // [esp+8h] [ebp-48h] that = this; StringCchPrintfW( &Dest, 0x20u, L"OleDfRoot%X%08lX", this->CGlobalContextPtr->_luidMutexName.HighPart, this->CGlobalContextPtr->_luidMutexName.LowPart); // 在poc中使用NtCreateEvent使它验证成功 result = CDfMutex::IsHandleValid(&that->_dmtx, &Dest); if ( !result ) // 失败就返回0 that->baseclass_CContext.ctxid = 0; return result; }
之后进入 CFileStream::Unmarshal,同样通过CContextList::_Find和方式通过设置和bits服务相同的pid从SDfMarshalPacket->CBasedGlobalFileStreamBaseObj找第一个CFileStream也就是poc中往共享内存0x7279 - 0x10写入的CFileStream,它的CFileStream->_hFile被赋值为0导致GetFileType返回-1导致验证失败(不影响程序运行),这个时候它的ctxid已经可以检测到为0,这时会进入poc中新建线程的第一个if语句断点.由于验证失败进入else,其中fstmOut->_CGlobalFileStreamPtr被赋值成SDfMarshalPacket->CBasedGlobalFileStreamBaseObj,fstmGlobal赋值成SDfMarshalPacket->CGlobalFileStreamPtr,之后它的fstmGlobal->_CGlobalFileStreamPtr->_pctxHead=0x7279被 CContextList::Add设置成fstmGlobal的相对地址,可以去判断读取SDfMarshalPacket->CBasedGlobalFileStreamBaseObj->_pctxHead已经不是0x7279,其实是fstmGlobal的相对地址.笔者发现一个小tips,fstmGlobal->_CGlobalFileStreamPtr也是可以在SDfMarshalPacket中读取到的,这样实际上求fstmGlobal->_CGlobalFileStreamPtr-SDfMarshalPacket->CBasedGlobalFileStreamBaseObj值就可以计算出bits进程的映射共享内存的映射基址也就是(NtCurrentTeb()->ReservedForOle->pvThreadBase-8)的值,从而实现读写bits进程的任意指定真实地址(非通过偏移内存计算)内存数据在共享内存区域,有兴趣的读者可以自行尝试后续研究.
int __userpurge CFileStream::Unmarshal@<eax>(unsigned int *ppvRet@<edx>, CGlobalFileStream *fstmFromGlobal@<ecx>, struct CGlobalFileStream *a3, void **a4, unsigned int a5) { CGlobalFileStream *fstmGlobal; // ebx unsigned int nowPid; // eax struct CContext *pCPerContextFound; // eax CFileStream *fstm; // esi CFileStream *fstmOut; // edi int hr; // eax int v11; // ebx CFileStream *CFileStreamAlloced; // eax unsigned int v14; // [esp+0h] [ebp-14h] struct IMalloc *v15; // [esp+4h] [ebp-10h] unsigned int *v16; // [esp+Ch] [ebp-8h] v16 = ppvRet; fstmGlobal = fstmFromGlobal; nowPid = GetCurrentProcessId(); pCPerContextFound = CContextList::_Find(&fstmGlobal->_pctxHead, nowPid); if ( pCPerContextFound ) // 就是poc的 CFileStream fstm=(CFileStream*)(0x7279 - 0x10 + (ULONG)directoryMappedAddressLocal + 8); fstm = &pCPerContextFound[-2]; else fstm = 0; // 验证句柄,这里在poc中新建线程中检测到that->baseclass_CContext.ctxid=0 if ( fstm && (fstmOut = (CFileStream::IsHandleValid(fstm) != 0 ? fstm : 0)) != 0 ) { // 这部在原作者的hajctvtable中实现 (fstmOut->_ILockBytesField.lpVtbl->AddRef)(fstmOut->_ILockBytesField.lpVtbl->AddRef, fstmOut); if ( !fstmGlobal->_awcPath[0] ) { LABEL_9: *v16 = fstmOut; return 0; } hr = CFileStream::InitWorker(fstmOut, 0, 1u, 0); } else { // 创建新的CFileStream CFileStreamAlloced = CMallocBased::operator new(v14, v15); if ( CFileStreamAlloced ) fstmOut = CFileStream::CFileStream(CFileStreamAlloced, fstmGlobal->_pMalloc); else fstmOut = 0; if ( !fstmOut ) return -2147287032; //其中fstmGlobal->_CGlobalFileStreamPt->_pctxHead=0x7279被poc设置,fstmGlobal=CGlobalFileStreamPtr,实际上就可以度bits进程的任意内存数据,因为可以根据CGlobalFileStreamPtr计算出映射共享内存的基址CGlobalFileStreamPtr-olebae-8 fstmOut->_CGlobalFileStreamPtr = fstmGlobal; ++fstmGlobal->_cReferences; // (CContextList)(fstmGlobal->_pctxHead(0x0))->_pctxHead(0x0)= &fstmOut->baseContext(0x10)-OleBase; // fstmOut=new_fs_offset = fsBase->baseclass_CContextList._pctxHead - 0x10; // 创建新的CFileStream链接到(CContextList)(fstmGlobal->_pctxHead(0x0))->_pctxHead(0x0)也就是原来的0x7279 CContextList::Add(&fstmOut->_CGlobalFileStreamPtr->_pctxHead, &fstmOut->baseContext); // 这里之前就要设置_awcPath[0]不为NULL if ( !fstmGlobal->_awcPath[0] ) goto LABEL_9; // 进入关键步骤 hr = CFileStream::InitWorker(fstmOut, 0, 1u, 0); } v11 = hr; if ( hr >= 0 ) goto LABEL_9; (fstmOut->_ILockBytesField.lpVtbl->Release)(fstmOut->_ILockBytesField.lpVtbl->Release, fstmOut); return v11; } int __thiscall CFileStream::IsHandleValid(CFileStream *this) { CFileStream *that; // edi signed int hr; // esi that = this; hr = 1; // 这里是hFile要预先设为0让他返回-1 if ( this->_hFile != -1 && GetFileType(this->_hFile) != 1 ) { hr = 0; // 这里在poc中新建线程中检测到that->baseclass_CContext.ctxid=0 that->baseContext.ctxid = 0; } return hr; }
之后进入CFileStream::InitWorker,that->_CGlobalFileStreamPtr->_pctxHead=0x7279,(CFileStream*)(0x7279 - 0x10)是第一个找到的fstmFoundNew,由于的我在poc中预先设置了
fstmFoundNew->baseContext.pctxNext = 0x7279这样pctxNext又链接到了它自己,这样可以构建 while ( fstmFoundNew )无限循环,产生一个时间差,让poc中的新建线程有时间读取最终复制的句柄.
int __thiscall CFileStream::InitWorker(CFileStream *this, const unsigned __int16 *a2, unsigned int a3, void *a4) { CFileStream *that; // esi CGlobalFileStream *fstmGlobal; // eax unsigned int v6; // ebx int hrTemp; // eax signed int v8; // edi unsigned int v9; // ecx CGlobalFileStream *v10; // eax const unsigned __int16 *v12; // ecx CGlobalFileStream *v13; // eax unsigned int v14; // [esp+0h] [ebp-43Ch] const unsigned __int16 *v15; // [esp+4h] [ebp-438h] LPWSTR FilePart; // [esp+10h] [ebp-42Ch] WCHAR Buffer; // [esp+14h] [ebp-428h] WCHAR FileName; // [esp+224h] [ebp-218h] // this=new 0x7279 that = this; FilePart = a4; fstmGlobal = this->_CGlobalFileStreamPtr; v6 = fstmGlobal->_df; // 这里之前就要设置hfile=-1,之后会被赋值 if ( this->_hFile != -1 ) return 0; // 这里之前就要设置_awcPath[0]不为NULL if ( fstmGlobal->_awcPath[0] ) { // 进入复制句柄方法 hrTemp = CFileStream::Init_DupFileHandle(this, 0); } .... } int __thiscall CFileStream::Init_DupFileHandle(CFileStream *this, unsigned int a2) { CFileStream *that; // eax HANDLE fakePid; // edi void *hPreDupedRef; // ecx int fstmFoundNewOffset; // ecx CFileStream *fstm; // esi CFileStream *fstmFoundNew; // esi void *hfileRef; // ebx HANDLE bitsPid; // eax CFileStream *_this; // [esp+8h] [ebp-4h] that = this; fakePid = 0; _this = this; hPreDupedRef = this->_hPreDuped; // hPreDuped之前也必需要是-1 if ( hPreDupedRef != -1 ) { that->_hPreDuped = -1; that->_hFile = hPreDupedRef; return 0; } // fstmFoundNewOffset是原CGlobalFileStreamPtr->_pctxHead=0x7279的CFileStream,不是是被替换后新创建的CFileStream,便于被查找next进入循环 fstmFoundNewOffset = that->_CGlobalFileStreamPtr->_pctxHead; if ( fstmFoundNewOffset ) fstm = (fstmFoundNewOffset + *NtCurrentTeb()->ReservedForOle); else fstm = 0; if ( fstm ) // fstmOut=new_fs_offset that->_CGlobalFileStreamPtr._pctxHead - 0x10; // 找到后再- 0x10 fstmFoundNew = (fstm - 0x10); else fstmFoundNew = 0; if ( !fstmFoundNew ) return -2147287034; do { hfileRef = fstmFoundNew->_hFile; if ( hfileRef == -1 ) { // 如果hFile还是-1就继续找下一个 if ( fstmFoundNew->_hPreDuped == -1 ) goto LABEL_17; hfileRef = fstmFoundNew->_hPreDuped; } // 根据ctxid打开poc进程 fakePid = OpenProcess(0x40u, 0, fstmFoundNew->baseContext.ctxid); if ( fakePid ) { bitsPid = GetCurrentProcess(); // 赋值poc进程的句柄至bits进程句柄写入CFileStream->_hFile if ( DuplicateHandle(fakePid, hfileRef, bitsPid, &_this->_hFile, 0, 0, 2u) ) break; GetLastError(); _this->_hFile = -1; CloseHandle(fakePid); fakePid = 0; } else { GetLastError(); } LABEL_17: // 找Next的方式就是new_fs_offset = CFileStream->baseContext.pctxNext - 0x10,这里Next预先链接为自己,这样可以构建时间差,但poc现成有时间读取最终复制的句柄 fstmFoundNew = CFileStream::GetNext(fstmFoundNew); } while ( fstmFoundNew ); if ( !fstmFoundNew ) return -2147287034; if ( fakePid ) CloseHandle(fakePid); return 0; }
最终bits服务调用DuplicateHandle函数复制了poc中pWorkFileStream->_hFile设置的句柄,使用这个句柄后作为创建新进程的父句柄,最后成功弹出了一个System权限NotePad,如图:
引用
作者来自ZheJiang Guoli Security Technology