close
Windows事件跟蹤(ETW)機制允許記錄內核或應用程序定義的事件以進行調試。開發人員能夠啟動和停止事件跟蹤會話,檢測應用程序以提供跟蹤事件,並通過調用 ETW 用戶模式 Windows API 集來使用跟蹤事件。最終的請求部分都是在內核(ntoskrnl.exe)模塊中完成。由於該功能存在於ntoskrnl.exe,部分功能可以在沙箱中訪問到,尤其受攻擊者關注。
在EtwpUpdatePeriodicCaptureState函數中,存在UAF漏洞,攻擊者能夠可控地分配一個0x30字節的緩衝區,釋放它並隨後重用該內存來執行任意代碼。
我們可以通過NtTraceControl系統調用來觸發 EtwpUpdatePeriodicCaptureState函數,輸入的buffer結構如下:

typedef struct _ETW_UPDATE_PERIODIC_CAPTURE_STATE

{

ULONGLoggerId;

ULONGDueTime;//system time units (100-nanosecond intervals)

ULONGNumOfGuids;

GUIDGuids[ANYSIZE_ARRAY];

} ETW_UPDATE_PERIODIC_CAPTURE_STATE, * PETW_UPDATE_PERIODIC_CAPTURE_STATE;

觸發UAF需要3個步驟,下面我們具體來分析:
第一個請求中,ntoskrnl執行了這些操作,首先通過用戶傳入的id來獲取相應的EtwpLoggerContext對象,同時回去驗證用戶傳入的Guid數組是否具有0x80訪問權限。隨後調用ExAllocateTimer函數創建一個定時器,定時器超時的回調函數是PeriodicCaptureStateTimerCallback,回調函數的參數類型是_WORK_QUEUE_ITEM,你可以在msdn中找到具體定義,隨後定時器被激活,等待超時時間到達後觸發回調函數。

__int64 __fastcall EtwpUpdatePeriodicCaptureState(unsigned int LoggerId, unsigned int DueTime, unsigned __int16 NumOfGuids, GUID *Guids)

{

...

//如果沒有定時器存在則直接創建定時器

if ( !LoggerContext_->ExTimerObject )

{

TimerContextInfo = (CONTEXTINFO *)ExAllocatePoolWithTag(NonPagedPoolNx, 0x30ui64, 'UwtE'); //創建回調函數參數,類型為_WORK_QUEUE_ITEM

TimerContextInfo_ = TimerContextInfo;

if ( !TimerContextInfo )

goto RETURN_ERROR_C0000017;

TimerContextInfo->LoggerId = LoggerId;//InBuff1.LoggerId

TimerContextInfo->Unknown = v22;

TimerContextInfo->WorkItem.WorkerRoutine = SendCaptureStateNotificationsWorker;// worker的回調函數

TimerContextInfo->WorkItem.Parameter = TimerContextInfo;// worker回調函數的參數,也就是SendCaptureStateNotificationsWorker的參數

TimerContextInfo->WorkItem.List.Flink = 0i64;

LoggerContext_->ExTimerObject = ExAllocateTimer((PEXT_CALLBACK)PeriodicCaptureStateTimerCallback, TimerContextInfo, 8u); //保存定時器

}

ExTimerObject = (PEX_TIMER)LoggerContext_->ExTimerObject;

LoggerContext_->DueTime = 0xFFFFFFFFFF676980ui64 * DueTime;

ExSetTimer((ULONG_PTR)ExTimerObject);//超時時間以秒計算,為負數

LODWORD(LoggerContext_->ExTimerState) = 1;

goto RETURN_1;

...

RETURN_1:

if ( (_InterlockedExchangeAdd64(pLock, 0xFFFFFFFFFFFFFFFFui64) & 6) == 2 )

ExfTryToWakePushLock(pLock);

KeAbPostRelease((ULONG_PTR)pLock);

RETURN_2:

EtwpReleaseLoggerContext(LoggerContext_, 0i64);

return (unsigned int)res_EtwpCheckNotificationAccess;

}

最終在PeriodicCaptureStateTimerCallback函數中會去調用ExQueueWorkItem函數將_WORK_QUEUE_ITEM放入系統隊列中,然後最後調用SendCaptureStateNotificationsWorker函數。

在第二個請求中,我們可以傳入一些當前用戶沒有權限訪問的Guid,這樣就會觸發如下流程:

__int64 __fastcall EtwpUpdatePeriodicCaptureState(unsigned int LoggerId, unsigned int DueTime, unsigned __int16 NumOfGuids, GUID *Guids)

{

...

{

...

FREE_POOLS_AND_RESET:

GuidsPool = (void *)LoggerContext_->GuidsPool;

if ( GuidsPool )

{

ExFreePoolWithTag(GuidsPool, 0);

LoggerContext->GuidsPool = 0i64;

LOWORD(LoggerContext->NumOfGuids) = 0;

}

...

}

if ( (_DWORD)NumOfGuids_ )

{

while ( 1 )//循環判斷用戶傳入的guid是否具有訪問權限,如果有一個不滿足就進入FREE_POOLS_AND_RESET分支

{

res_EtwpCheckNotificationAccess = EtwpCheckNotificationAccess(

&Guids[v4].Data1,

(__int64)&LoggerContext_->field_0[0x124]);

if ( res_EtwpCheckNotificationAccess < 0 )

break;

if ( ++v4 >= (int)NumOfGuids_ )

goto ALL_GUIDS_HAVE_NOTIFICATION_ACCESS_OK;

}

res_EtwpCheckNotificationAccess = 0xC0000022;

v8 = 0;

goto FREE_POOLS_AND_RESET;

}

ALL_GUIDS_HAVE_NOTIFICATION_ACCESS_OK:

...

}

void __fastcall SendCaptureStateNotificationsWorker(CONTEXTINFO *TimerContextInfo)

{

...

if ( TimerContextInfo )

{

LoggerContext = (LOGGERCONTEXT *)EtwpAcquireLoggerContextByLoggerId(

TimerContextInfo->Unknown,

LOWORD(TimerContextInfo->LoggerId),

0);

if ( LoggerContext )

{

pLock = &LoggerContext->Lock;

ExAcquirePushLockExclusiveEx((ULONG_PTR)&LoggerContext->Lock, 0i64);

LODWORD(LoggerContext__->ExTimerState) = 0;

if ( *(_DWORD *)&LoggerContext__->field_0[336] )

{

//在第二個請求中我們已經將這個數量設置為空,所以後面會進入LABEL_31分支

NumOfGuids = LOWORD(LoggerContext__->NumOfGuids);

if ( (_WORD)NumOfGuids )

{

...

if ( (int)EtwpBuildNotificationPacket(v10, v23, v15, &v19) >= 0 )

{

EtwpSendDataBlock(v12, v19);

EtwpUnreferenceDataBlock(v19);

}

...

if ( LOWORD(LoggerContext__->NumOfGuids) && !LODWORD(LoggerContext__->ExTimerState) )

{

ExSetTimer(LoggerContext__->ExTimer);

LODWORD(LoggerContext__->ExTimerState) = 1;

v2 = 1;

}

...

...

}

}

if ( (_InterlockedExchangeAdd64(pLock, 0xFFFFFFFFFFFFFFFFui64) & 6) == 2 )

ExfTryToWakePushLock(pLock);

KeAbPostRelease((ULONG_PTR)pLock);

EtwpReleaseLoggerContext(LoggerContext__, 0i64);

if ( v2 )//這裡沒有進入上面分支所以不會設置為0

return;

goto LABEL_31;//

}

}

//最終釋放了之前的WORK_QUEUE_ITEM參數

LABEL_31:

ExFreePoolWithTag(TimerContextInfo, 0);

}

在我們第一步的syscall創建的定時器超時前,在第二個syscall調用操作中我們將LoggerContext->NumOfGuids設置為0,導致回調函數超時的時候釋放了WORK_QUEUE_ITEM。
在第三步的操作中,我們還需要再次重複第一步的操作,由於定時器已經存在,在判斷LoggerContext_->ExTimerObject不為空後,不會再創建WORK_QUEUE_ITEM對象了,所以之前第一步創建的參數仍然還會被再次使用,而這個時候其實回調函數的參數已經是一個被釋放的內存了,不應該被再次引用,後面的一系列操作就會導致UAF的發生。

漏洞修復

這個漏洞可以認為是設計不當而導致的,一開始以為是定時器使用不當導致的,但是看過分析後發現沒有那麼簡單,大概率是開發人員對定時器的操作編程理解不夠透徹,在傳入定時器超時的參數中不應該使用一個生命周期不明確的對象,必須對此類對象的使用格外的小心,猜測修復的邏輯中也要對這裡的邏輯進行重新設計,後來看了修復的代碼也差不多能驗證自己的猜想,看看修復後的代碼,我們可以看到傳入定時器回調函數的參數都發生了變化,變成了LoggerContext,沒有傳遞CallbackContext,這樣這個對象就不會存在釋放後重用了,同時也沒有再對LoggerContext->NumOfGuids進行操作:

__int64 __fastcall EtwpUpdatePeriodicCaptureState(unsigned int LoggerId, unsigned int DueTime, unsigned __int16 NumOfGuids, GUID *Guids)

{

...

ExAcquirePushLockExclusiveEx(EtwpLoggerContext1 + 688, 0i64);

CallbackContext1 = *(_QWORD *)(EtwpLoggerContext1 + 1080);//取出之前創建的Context

if ( CallbackContext1 )//如果存在了CallbackContext則進入

goto LABEL_31;

if ( !(_WORD)v4 )

{

LABEL_24:

if ( (_InterlockedExchangeAdd64((volatile signed __int64 *)(EtwpLoggerContext1 + 688), 0xFFFFFFFFFFFFFFFFui64) & 6) == 2)

ExfTryToWakePushLock(EtwpLoggerContext1 + 688);

KeAbPostRelease(EtwpLoggerContext1 + 688);

goto LABEL_27;

}

CallbackContext = ExAllocatePool2(64i64, 72i64, 0x55777445i64);

//設置LoggerContext+1080位置為創建的CallbackContext

*(_QWORD *)(EtwpLoggerContext1 + 1080) = CallbackContext;

CallbackContext1 = CallbackContext;

...

Pool2 = (void *)ExAllocatePool2(256i64, 16 * v4, 0x55777445i64);

*(_QWORD *)(CallbackContext1 + 24) = Pool2;

if ( Pool2 )

{

*(_WORD *)(CallbackContext1 + 16) = v4;

memmove(Pool2, (const void *)InputBuffer_kernel_C, 16 * v4);

if ( !*(_QWORD *)(CallbackContext1 + 8) )

{

//回調函數的參數變成了LoggerContext

Timer = ExAllocateTimer((__int64)PeriodicCaptureStateTimerCallback, EtwpLoggerContext1, 8u);

*(_QWORD *)(CallbackContext1 + 8) = Timer;

if ( !Timer )

{

ExFreePoolWithTag(*(PVOID *)(CallbackContext1 + 24), 0);

*(_QWORD *)(CallbackContext1 + 24) = 0i64;

*(_WORD *)(CallbackContext1 + 16) = 0;

goto LABEL_11;

}

*(_QWORD *)(CallbackContext1 + 56) = EtwpLoggerContext1;

*(_QWORD *)(CallbackContext1 + 48) = SendCaptureStateNotificationsWorker;

*(_QWORD *)(CallbackContext1 + 32) = 0i64;// WORK_QUEUE_ITEM的開始位置

}

*((_QWORD *)&v21 + 1) = 0xFFFFFFFFFFFFFFFFui64;

v17 = *(_QWORD *)(CallbackContext1 + 8);

v18 = 0xFFFFFFFFFF676980ui64 * a2;

*(_QWORD *)CallbackContext1 = v18;

ExSetTimer(v17, v18, 0i64, (__int64)&v21);

*(_DWORD *)(CallbackContext1 + 64) = 1;

goto LABEL_24;

}

...

}

void __fastcall PeriodicCaptureStateTimerCallback(__int64 Timer, __int64 EtwpLoggerContext)

{

if ( ExAcquireRundownProtectionCacheAwareEx(

*(PEX_RUNDOWN_REF_CACHE_AWARE *)(*(_QWORD *)(*(_QWORD *)(EtwpLoggerContext + 1096) + 448i64)

+ 8i64 * *(unsigned int *)EtwpLoggerContext),

1u) )

{

// EtwpLoggerContext + 1080就是CallbackContext,+32位置就是WORK_QUEUE_ITEM的開始位置

ExQueueWorkItem((PWORK_QUEUE_ITEM)(*(_QWORD *)(EtwpLoggerContext + 1080) + 32i64), NormalWorkQueue);

}

}

參考鏈接

https://www.pixiepointsecurity.com/blog/advisory-cve-2021-34486.html


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()