It rather involved being on the other side of this airtight hatchway: Code injection via QueueUserAPC

A security vulnerability report arrived that took the following form:

The Queue­User­APC function can be used to effect an elevation of privilege, as follows:

  1. Identify a process you wish to attack.
  2. Obtain access to a thread with THREAD_SET_­CONTEXT access.

  3. Make some educated guesses as to what DLLs are loaded in that process. Start with kernel32.dll, since you're going to need it in step 5.

  4. From the attacking process, scan the memory of those DLLs looking for a backslash, followed by something that can pass for a path and file name. Such strings are relatively abundant because there are a lot of registry paths hard-coded into those binaries. Suppose you found the string \Windows NT\Current­Version\App­Compat­Flags. Even though ASLR randomizes DLL placement, the placement is consistent among all processes, so an address calculated in one process is highliy likely to be valid in all processes.

  5. Create a DLL called C:\Windows NT\Current­Version\App­Compat­Flags.dll. Put your payload in this DLL.

  6. From the attacking thread, call Queue­User­APC with the address of Load­LibraryW as the function pointer, the victim thread as the thread handle, and a pointer to the fixed string identified in part 4 as the last parameter.

  7. The next time the victim thread processes APCs, it will pass \Windows NT\Current­Version\App­Compat­Flags to the Load­LibraryW function, which will load the payload DLL, thereby effecting code injection and consequent elevation of privilege.

Note that this attack fails if the victim thread never waits alertably, which is true of most threads.

If you have been paying attention, the alarm bells probably went off at step 2. If you have THREAD_SET_­CONTEXT access to a thread, then you pwn that thread. There's no need to use Queue­User­APC to make the thread do your bidding. You already have enough to make the thread dance to your music. In other words, you are already on the other side of the airtight hatchway.

Here's how: Look for a code sequence that goes

    push someregister
    call LoadLibraryW

Use the ­Set­Thread­Context function to set the pushed register equal to the address of the string you found in step 4, and set the instruction pointer to the code fragment. The thread will then resume execution at the specified instruction pointer: It pushes the address of the string, and then it calls Load­LibraryW. Bingo, your DLL loads, and you didn't even have to wait for the thread to wait alertably.

On non-x86 platforms, this is even easier: Since all other platforms use register-based calling conventions, you merely have to load the address of the string into the "first parameter" register (rcx for x64) and set the instruction pointer to the beginning of Load­LibaryW.

By default, THREAD_SET_­CONTEXT access is granted only to the user, and never to lower integrity levels. In other words, a low IL process cannot get THREAD_SET_­CONTEXT access to a medium or high integrity thread, and a medium IL process cannot get access to a high integrity thread. This means that, by default, you can only get THREAD_SET_­CONTEXT access to threads that have equivalent permissions to what you already have, so there is no elevation.

Comments (11)
  1. skSdnW says:

    And how often do you have THREAD_SET_­CONTEXT but not PROCESS_VM_WRITE+PROCESS_VM_OPERATION so you cannot inject the "normal" way?

    If someone were to put this in a reusable library you might start running into the "what if another application also did this" problem unless it picks a random string. You also litter the users drive with strange folders which are sure to ring some alarm bells…

  2. anonymouscommenter says:

    Sounds like somebody tried to create threads running as different users in the same process. While you can do this (and I know why you might want to), the threads are not secured from each other, so don't run code from a different user in such a thread.

  3. anonymouscommenter says:

    I fail to see how step 3 is relevant.

  4. anonymouscommenter says:

    Doesn't step 5 also require Administrator privileges to write into a subdirectory of C:Windows NT?  Or are there compatibility shims that allow that to work without being an Administrator?

  5. Medinoc says:

    SetThreadContext() also allows you to directly set the Instruction Pointer register for this thread, so you can directly make it call LoadLibrary()… Oh, and you control the Stack Pointer, too.

  6. Brian_EE says:

    >Doesn't step 5 also require Administrator privileges to write into a subdirectory of C:Windows NT?

    Since "C:Windows NT" doesn't exist (at least not on Win7 Enterprise), unless there is a group policy in place preventing users from creating folders in the root folder, anyone can create a new folder named "Windows NT" and sub-folders thereof.

  7. anonymouscommenter says:

    Oh right, duh.  Things that are not the same: {C:Windows, C:Windows NT}.

  8. anonymouscommenter says:

    The set of {PROCESS_CREATE_THREAD, PROCESS_DUP_HANDLE, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, THREAD_SET_­CONTEXT} should just have been a single access right. The way it is now just makes people think that you can grant one of them without giving full access to your process – in reality every one of them implies full access except for PROCESS_VM_OPERATION – but why would you change the protection of pages in the process without doing anything else to it anyways?

  9. anonymouscommenter says:

    It feels like "It rather involved being on the other side of this airtight hatchway" should be a tag by now.

  10. Pyjong says:

    I love how Raymond elaborates the presented attempt and then says "nope, doesn't work" in the last sentence :D

  11. anonymouscommenter says:

    yet another way to steal your own money

Comments are closed.

Skip to main content