
This article contains functions and features that are not documented by the original manufacturer. By following advice in this article, you're doing so at your own risk. The methods presented in this article may rely on internal implementation and may not work in the future.
Intro
This is a third blog post dedicated to the vulnerability that we discovered in the Windows 10 update service. After our initial research, Dennis A. Babkin wrote his own small software patch that he described in the second blog post on this subject. But, if you remember, besides just patching the aforementioned vulnerability our goal has also become to find a way to let users indefinitely delay forced restarts after installation of updates on all versions of Windows 10.
After the first patch, Rbmm proposed an alternative solution that was somewhat simpler. This third blog post will be dedicated to his approach.
Overview
If you remember, 
	the bug consisted of the DLL hijack vulnerability in the Windows update service (usosvc.dll) that was attempting to load
	a non-existent module ShellChromeAPI.dll and invoke the Shell_RequestShutdown function, that was exported from it, before attempting to reboot the system.
	So to notify the user and to delay a restart, Dennis A. Babkin in his interpretation simply 
	showed a message box in the intercepted Shell_RequestShutdown function.
Catching Manual Requests For A Restart
Another option though was to notice that when a user manually requested a restart from the UI (be it from the Settings window, or from the 
	toast notification) that the operating system sets the following DWORD Registry value to 1:
\REGISTRY\MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\StateVariables
RebootUXLaunched REG_DWORD 0 / 1
	Internally, this is done by calling the UxUsoShim::SetUxStateVariableBOOL function.
Thus, in our override of the Shell_RequestShutdown function we can use that Registry value to decide what to do. In other words, if we read it and it is not 0,
	this means that the user manually requested a reboot:
bool IsRebootUXLaunched = false;
static const WCHAR kRk[] = L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\WindowsUpdate\\UX\\StateVariables";
static const UNICODE_STRING sRk = { sizeof(kRk) - sizeof((kRk)[0]), sizeof(kRk), const_cast<PWSTR>(kRk) };
static OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, const_cast<PUNICODE_STRING>(&sRk), OBJ_CASE_INSENSITIVE };
HANDLE hKey;
if (0 <= ZwOpenKey(&hKey, KEY_QUERY_VALUE, &oa))
{
	static const WCHAR kRbt[] = L"RebootUXLaunched";
	static const UNICODE_STRING RebootUXLaunched = { sizeof(kRbt) - sizeof((kRbt)[0]), sizeof(kRbt), const_cast<PWSTR>(kRbt) };
	KEY_VALUE_PARTIAL_INFORMATION kvpi;
	IsRebootUXLaunched = 0 <= ZwQueryValueKey(hKey, const_cast<PUNICODE_STRING>(&RebootUXLaunched),
		KeyValuePartialInformation, &kvpi, sizeof(kvpi), &kvpi.TitleIndex) &&
		kvpi.DataLength == sizeof(ULONG) && kvpi.Type == REG_DWORD && *(ULONG*)kvpi.Data;
	NtClose(hKey);
}One caveat in this case though is this: if we determine that the user indeed wanted to restart the system to install updates, we need to reboot it ourselves.
	(We need this because of the 2-minute delay 
	that Microsoft has in their code after Shell_RequestShutdown returns.)
if (IsRebootUXLaunched)
{
	ULONG dwShutdownFlags = SHUTDOWN_ARSO | SHUTDOWN_RESTARTAPPS | SHUTDOWN_RESTART |
		SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_FORCE_SELF | SHUTDOWN_FORCE_OTHERS;
	if (GetShellWindow())
	{
		ExecUX(0, L"* ClearActiveNotifications", TRUE);
		dwShutdownFlags = wcsstr(GetCommandLineW(), L"RebootWithUXForceOthers")
			? SHUTDOWN_ARSO | SHUTDOWN_RESTARTAPPS | SHUTDOWN_RESTART | SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_FORCE_OTHERS
			: SHUTDOWN_ARSO | SHUTDOWN_RESTARTAPPS | SHUTDOWN_RESTART | SHUTDOWN_INSTALL_UPDATES;
	}
	InitiateShutdownW(0, 0, 0, dwShutdownFlags,
		SHTDN_REASON_FLAG_PLANNED |
		SHTDN_REASON_MAJOR_OPERATINGSYSTEM |
		SHTDN_REASON_MINOR_SERVICEPACK);
	return;
}In the code above, we also need to clear toast notifications before we call InitiateShutdownW.
	We can do so by invoking MusNotificationUx.exe with the ClearActiveNotifications command line parameter.
	But before we do that, we also need to make sure that we're running in an interactive desktop. We can achieve that by calling 
	GetShellWindow that will return NULL if we are not.
I use the following helper function to communicate with MusNotificationUx.exe - that is the UI process of the Windows 10 Update Service Orchestrator (USO).
void ExecUX(HANDLE hToken, PCWSTR CommandLine, BOOL bWait = FALSE)
{
	WCHAR ApplicationName[MAX_PATH];
	if (ExpandEnvironmentStringsW(L"%systemroot%\\system32\\MusNotificationUx.exe", ApplicationName, _countof(ApplicationName)))
	{
		PROCESS_INFORMATION pi;
		STARTUPINFO si = { sizeof(si) };
		if (CreateProcessAsUserW(hToken, ApplicationName, const_cast<PWSTR>(CommandLine), 0, 0, 0, 0, 0, 0, &si, &pi))
		{
			NtClose(pi.hThread);
			if (bWait) WaitForSingleObject(pi.hProcess, INFINITE);
			NtClose(pi.hProcess);
		}
	}
}The code above starts an instance of the MusNotificationUx.exe process with CommandLine parameters, in the user session specified in the user token we pass in hToken.
In actuality, the way USO performs a forced reboot is this:
- It sets a new Task Scheduler task, named:
with the command:\Microsoft\Windows\UpdateOrchestrator\Reboot_ACMusNotification.exe /RunOnAC Reboot
- And when the task fires, it runs:
MusNotificationUx.exe RebootWithUX
So in a sense, that is an example of how those two USO modules, MusNotification.exe and MusNotificationUx.exe, exchange commands between each other using the Task Scheduler.
Showing User Notification & Blocking Restart
So going back to our override of the Shell_RequestShutdown function, if we detect that the user did not manually request a restart,
	we need to display a UI that will prompt the user to do so.
But first we need to set the token of the thread that does not have the SE_SHUTDOWN_PRIVILEGE privilege. 
	(This is needed to make the caller's InitiateShutdownW function fail after 
	we return from the  Shell_RequestShutdown function
	in our override.)
HANDLE hToken, hNewToken = 0;
if (0 <= NtOpenProcessToken(NtCurrentProcess(), TOKEN_DUPLICATE, &hToken))
{
	BOOL b = DuplicateTokenEx(hToken, TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE,
		0, ::SecurityImpersonation, ::TokenImpersonation, &hNewToken);
	NtClose(hToken);
	if (b)
	{
		if (0 <= NtAdjustPrivilegesToken(hNewToken, FALSE, const_cast<PTOKEN_PRIVILEGES>(&tp_No_Shutdown), 0, 0, 0))
		{
			NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hNewToken, sizeof(hNewToken));
		}
		NtClose(hNewToken);
	}
}Unfortunately USO uses the process token to set theSE_SHUTDOWN_PRIVILEGE, which is somewhat risky for us, as there will be a whole 2-minute delay before the calling function invokesInitiateShutdownWafter we return from ourShell_RequestShutdownoverride, and some other service may reinstate theSE_SHUTDOWN_PRIVILEGEin the meantime. Remember, the USO service will be running in the sharedsvchost.exeprocess, among some other services.
After that we need to show a toast notification to the user and let them choose what to do:
static const WCHAR Toast_FairWarning[] = L"* Toast_FairWarningDesktop";
if (GetShellWindow())
{
	ExecUX(0, Toast_FairWarning);
	ExitProcess(0);
}Note that I also check for an interactive desktop with a call to GetShellWindow. And only if we're running in one, I display the toast notification
	with the following command, and kill self with a call to 
	ExitProcess:
MusNotificationUx.exe Toast_FairWarningDesktopWe need to kill the calling UI process to ensure that it doesn't invokeInitiateShutdownWif we return from ourShell_RequestShutdownoverride. Remember that Microsoft used the same code-base for theusosvc.dll, as well as for other USO UI processes. The reason we check if we're running in an interactive desktop is to differentiate it from when our override is called from the system service (orusosvc.dll) in which case we cannot kill the calling process.
This will display a message like so:
The good thing for us is that USO has a built-in mechanism for preventing multiple toast notifications from overlaying each other. In other words, if one such notification was already shown, it will be replaced with a new one. This way we should not be concerned with multiple notifications, as we would be in case we were just showing our own MessageBox.
One final condition to cover here is the situation when our Shell_RequestShutdown function was not called from an interactive desktop.
	This happens when the call to Shell_RequestShutdown function comes from the USO service itself (or usosvc.dll.)
ULONG dwSessionId = WTSGetActiveConsoleSessionId();
if (dwSessionId != MAXULONGLONG && WTSQueryUserToken(dwSessionId, &hToken))
{
	ExecUX(hToken, Toast_FairWarning);
	NtClose(hToken);
}In that case, we retrieve the currently active user session ID and acquire its user token with a call to 
	WTSQueryUserToken,
	and then use it to show the toast notification in that user session.
Additional Actions
After our toast notification is shown, the further actions depend on the user's choice. If the user clicks on our toast notification the USO calls:
MusNotificationUx.exe -EmbeddingWhich then invokes the internal function InteractiveToastActivationHandler::Activate through several 
	IPC calls
	(which is the implementation of 
	INotificationActivationCallback::Activate.)
	The Activate method for the Toast_FairWarningDesktop command will be invoked with the following parameters:
/ToastAction ImmRestartNow /MusUxStateString 79 /ToastLaunchTimestamp ...And then Activate will call SetUxStateVariableBOOL(RebootUXLaunched, true), that in turn will call InvokeRestartNow -> RebootToCompleteInstall from usosvc.
	And then internally RebootToCompleteInstall will invoke MusNotificationUx.exe for the third time, but this time with a different parameter:
	RebootWithUX, or RebootWithUXForceOthers (if there's more than one user logged on at that time).
Then it will call our Shell_RequestShutdown with the RebootUXLaunched set to 1. And in this case our override will call InitiateShutdownW to reboot the system.
Proof-of-Concept Code
The resulting code can be compiled from the provided Visual Studio solution:
void ExecUX(HANDLE hToken, PCWSTR CommandLine, BOOL bWait = FALSE)
{
	WCHAR ApplicationName[MAX_PATH];
	if (ExpandEnvironmentStringsW(L"%systemroot%\\system32\\MusNotificationUx.exe", ApplicationName, _countof(ApplicationName)))
	{
		PROCESS_INFORMATION pi;
		STARTUPINFO si = { sizeof(si) };
		if (CreateProcessAsUserW(hToken, ApplicationName, const_cast<PWSTR>(CommandLine), 0, 0, 0, 0, 0, 0, &si, &pi))
		{
			NtClose(pi.hThread);
			if (bWait) WaitForSingleObject(pi.hProcess, INFINITE);
			NtClose(pi.hProcess);
		}
	}
}
void Shell_RequestShutdown(int)
{
	bool IsRebootUXLaunched = false;
	static const WCHAR kRk[] = L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\WindowsUpdate\\UX\\StateVariables";
	static const UNICODE_STRING sRk = { sizeof(kRk) - sizeof((kRk)[0]), sizeof(kRk), const_cast<PWSTR>(kRk) };
	static OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, const_cast<PUNICODE_STRING>(&sRk), OBJ_CASE_INSENSITIVE };
	HANDLE hKey;
	if (0 <= ZwOpenKey(&hKey, KEY_QUERY_VALUE, &oa))
	{
		static const WCHAR kRbt[] = L"RebootUXLaunched";
		static const UNICODE_STRING RebootUXLaunched = { sizeof(kRbt) - sizeof((kRbt)[0]), sizeof(kRbt), const_cast<PWSTR>(kRbt) };
		KEY_VALUE_PARTIAL_INFORMATION kvpi;
		IsRebootUXLaunched = 0 <= ZwQueryValueKey(hKey, const_cast<PUNICODE_STRING>(&RebootUXLaunched),
			KeyValuePartialInformation, &kvpi, sizeof(kvpi), &kvpi.TitleIndex) &&
			kvpi.DataLength == sizeof(ULONG) && kvpi.Type == REG_DWORD && *(ULONG*)kvpi.Data;
		NtClose(hKey);
	}
	if (IsRebootUXLaunched)
	{
		ULONG dwShutdownFlags = SHUTDOWN_ARSO | SHUTDOWN_RESTARTAPPS | SHUTDOWN_RESTART |
			SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_FORCE_SELF | SHUTDOWN_FORCE_OTHERS;
		if (GetShellWindow())
		{
			ExecUX(0, L"* ClearActiveNotifications", TRUE);
			dwShutdownFlags = wcsstr(GetCommandLineW(), L"RebootWithUXForceOthers")
				? SHUTDOWN_ARSO | SHUTDOWN_RESTARTAPPS | SHUTDOWN_RESTART | SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_FORCE_OTHERS
				: SHUTDOWN_ARSO | SHUTDOWN_RESTARTAPPS | SHUTDOWN_RESTART | SHUTDOWN_INSTALL_UPDATES;
		}
		InitiateShutdownW(0, 0, 0, dwShutdownFlags,
			SHTDN_REASON_FLAG_PLANNED |
			SHTDN_REASON_MAJOR_OPERATINGSYSTEM |
			SHTDN_REASON_MINOR_SERVICEPACK);
		return;
	}
	static const TOKEN_PRIVILEGES tp_No_Shutdown = { 1, { { { SE_SHUTDOWN_PRIVILEGE } } } };
	HANDLE hToken, hNewToken = 0;
	if (0 <= NtOpenProcessToken(NtCurrentProcess(), TOKEN_DUPLICATE, &hToken))
	{
		BOOL b = DuplicateTokenEx(hToken, TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE,
			0, ::SecurityImpersonation, ::TokenImpersonation, &hNewToken);
		NtClose(hToken);
		if (b)
		{
			if (0 <= NtAdjustPrivilegesToken(hNewToken, FALSE, const_cast<PTOKEN_PRIVILEGES>(&tp_No_Shutdown), 0, 0, 0))
			{
				NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hNewToken, sizeof(hNewToken));
			}
			NtClose(hNewToken);
		}
	}
	static const WCHAR Toast_FairWarning[] = L"* Toast_FairWarningDesktop";
	if (GetShellWindow())
	{
		ExecUX(0, Toast_FairWarning);
		ExitProcess(0);
	}
	ULONG dwSessionId = WTSGetActiveConsoleSessionId();
	if (dwSessionId != MAXULONGLONG && WTSQueryUserToken(dwSessionId, &hToken))
	{
		ExecUX(hToken, Toast_FairWarning);
		NtClose(hToken);
	}
}	To install, make sure to compile a 32-bit or 64-bit Release configuration (depending on the bitness of the target operating system) and place the resulting 
	ShellChromeAPI.dll file info the system folder:
%WinDir%\System32To uninstall, simply remove the ShellChromeAPI.dll file from the system folder that I showed above.
Conclusion
Finally, if you are interested:
- You can download the full source code for the project described in this blog post as the Visual Studio 2019 solution.
 
		

