Blog Post

Things You Find While Reverse Engineering - AlertByThreadId

How to put a thread into a kernel wait state and how to wake it up by a thread ID.

Things You Find While Reverse Engineering - AlertByThreadId - How to put a thread into a kernel wait state and how to wake it up by a thread ID.
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

While working on my blog post about the internals of a syscall in Windows I stumbled upon an interesting way how Microsoft were placing a thread into a wait state and also how they were waking it up from that state for their internal implementation of a critical section, or for their slim reader/writer locks.

I am somewhat perplexed as to why Microsoft did not document it, as it seems like a very fast and efficient method versus the documented way.

With the help from Rbmm we were able to reverse engineer it. I'll share our findings with my readers below.

NtWaitForAlertByThreadId

This function is exported from the native ntdll.dll. It has been available since Windows 8.1 and it is declared as such:

C[Copy]
NTSTATUS NTAPI NtWaitForAlertByThreadId(       // Has the alias of ZwWaitForAlertByThreadId in user-mode
	_In_ HANDLE reserved,                      // Should be 0
	_In_opt_ PLARGE_INTEGER Timeout);          // Pointer to a timeout

Unlike its name, NtWaitForAlertByThreadId seems to ignore its first parameter and waits only for the thread that calls it. Thus I would pass NULL into it.

Upon further research Rbmm suggested that the first parameter is actually passed unchanged into the kernel (without any seeming checks) and is stored there in the KTHREAD::KWAIT_BLOCK.Object. That location is used to store the address of wait object(s) and is later accessed during wait operations on kernel dispatcher objects, for instance, explicitly when you call the KeWaitForMultipleObjects function, or implicitly during the KeWaitForSingleObject call, among others.

Further more, if one invokes the AcquireSRWLockExclusive user-mode API:

C++[Copy]
SRWLOCK lock {};
AcquireSRWLockExclusive(&lock);
AcquireSRWLockExclusive(&lock);     //Needed to put thread into the kernel wait state

It may pass the user-mode address of the SRWLOCK struct to the kernel via the NtWaitForAlertByThreadId call (if it needs to enter the wait state in the kernel) that will keep that user-mode address in the KTHREAD::KWAIT_BLOCK.Object for the thread.

The second parameter, Timeout could be a pointer to the LARGE_INTEGER struct that specifies the time-out for the wait. Its meaning is similar to the FILETIME structure in Win32: a negative value represents a time relative to now, measured in 100-nanosecond intervals. A positive number specifies an absolute date & time.

For instance, to set a time-out of 3 seconds, you'd set it to:
C[Copy]
LARGE_INTEGER li = {};
li.QuadPart = -10000LL * 1000 * 3;

Note that I'm using a negative number with the -10000LL constant. Also note the LL suffix to specify a 64-bit integer.

If you set the Timeout parameter to point to a 0 value, this will mean to return immediately. Or, if you set it to a NULL, this will mean to wait for as long as it takes:

C[Copy]
NtWaitForAlertByThreadId(NULL, NULL);          // Put the current thread into a waiting state with an infinite timeout

Return Value

The NtWaitForAlertByThreadId function returns an NTSTATUS with the result, which may be:

  • STATUS_ALERTED (or 0x101) if the alert was set. Or, success, in other words.
  • STATUS_TIMEOUT (or 0x102) if the wait timed out and the current thread was not alerted.

NtAlertThreadByThreadId

Then there's also a sister function to NtWaitForAlertByThreadId that is declared as such:

C[Copy]
NTSTATUS NTAPI NtAlertThreadByThreadId(        // Has the alias of ZwAlertThreadByThreadId in user-mode
		_In_ HANDLE ThreadId                   // Thread ID
	);

It takes a thread ID to alert. This function can be used to alert, or to "wake up" another thread that is currently in the kernel wait state, initiated by a call to the NtWaitForAlertByThreadId function.

You can use the NtAlertThreadByThreadId function to alert a thread from the same process only. Otherwise it will return STATUS_ACCESS_DENIED error.

Return Value

It returns an NTSTATUS with the result, most common one being a 0, or STATUS_SUCCESS.

What's The Big Deal?

So what's the thing about these functions?

What makes them handy is that they don't require a handle to put a thread into a waiting state. For instance, if you call the WaitForSingleObject function it will require a kernel object handle to wait on. In contrast, neither NtWaitForAlertByThreadId nor NtAlertThreadByThreadId require any handles. All they need is a thread ID.

There's one limitation though, both functions work only with threads in the same process where they are invoked from.

Keep in mind that both functions will make a trip to the kernel to initiate the wait, or to wake a thread up.

NtAlertResumeThread, a Bastard Child

If you search the ntdll.dll module for other exported functions that deal with alerts, you'll notice the NtAlertResumeThread function. Rbmm challenged me, so I quickly reverse engineered it.

It is declared as such:

C[Copy]
NTSTATUS NTAPI NtAlertResumeThread(            // Has the alias of ZwAlertResumeThread in user-mode
	_In_ HANDLE hThread,
	_Inout_opt_ DWORD* p_dwPrevSuspendCount
);

It makes a trip to the kernel and, as its name suggests, it calls two kernel APIs in this order:

Pseudo-code[Copy]
KeAlertThread(KTHREAD*);
KeResumeThread(KTHREAD*);

The first call alerts a thread and the second one resumes it (if it was suspended.)

Note that Microsoft does not give the world any justice by overusing the term "alert". The alert in this case has nothing to do with the alert that the NtWaitForAlertByThreadId function makes a thread wait for, or that NtAlertThreadByThreadId sends to a thread.

The alert that NtAlertResumeThread uses happens when a thread was in a wait state, say after entering it with a call to the ZwWaitForSingleObject function. In that case alerting such a thread with a call to KeAlertThread will result in it exiting the wait state with the STATUS_ALERTED return status.

Unlike the previous examples that I gave above, the first parameter to NtAlertResumeThread must be a thread handle.

The second optional parameter p_dwPrevSuspendCount may be set to an address of a DWORD that will receive thread's previous suspend count, or what would ResumeThread API may return.

So in a sense this API does two things in one call.

Note that we could not find any practical use for the NtAlertResumeThread function. It is not imported into any system DLL either.

Conclusion

I'm sure I don't need to tell you that the undocumented functions that I showed above can change at any time in the future. So if you decide to use them, please do so at your own risk.

This blog post is intended just as an informational resource to demonstrate an interesting mechanism that Microsoft are using internally for their synchronization locks.

Related Articles