这个漏洞属于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句柄是否有效,其实只要构造一个名为”SessionsBaseNamedObjectsOleDfRoot%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,如图:
引用