CreateRemoteThread, Vista and separate sessions

Recently I’ve hit a wall during development. I had written a nice workaround for a problem, based on code injection. In fact the code wasn’t injected by loading a DLL but instead by loading relocatable (32bit) code of less than 250 byte size. However, once I started testing it on Vista, the topstack method to retrieve the address of kernel32.dll inside the target process didn’t work anymore, so I had to resort to the PEB method. No big deal.

However, once I got that sorted out, the whole thing worked when run from the same (terminal) session, but failed as soon as the program (in simulation of the later real-world conditions) was started as SYSTEM by the task scheduler (i.e. from the session in which services run). Obviously the task scheduler isn’t all too talkative about the reasons of failure of a scheduled program, so my assumption was, that it must have to do with the stricter session separation on Vista and the documentation of CreateRemoteThread() confirms this:

Terminal Services isolates each terminal session by design. Therefore, CreateRemoteThread fails if the target process is in a different session than the calling process.

Only later I found out, that the task scheduler has its own window station and desktop to separate programs, but the problem was in fact based on the restrictions in CreateRemoteThread() – i.e. the fact it didn’t work with task scheduler was a false alarm wrt. to my problem. After asking Nico, a friend and fellow system-level developer, it turned out he had already hit the same wall. He pointed me to this thread in the madshi.net forum (if you’re a Delphi/BCB programmer, try out madshi’s excellent components!). In this thread Nico describes how to overcome the limitation by using an APC. A quite clever method indeed. Here’s how it works in a nutshell. The APC which is being queued for the thread will get executed once the thread gets signaled. Normally a thread gets signaled when it exits, so setting the thread start address to kernel32!ExitThread as suggested by Nico will achieve exactly this. To create the suspended thread using ntdll!RtlCreateUserThread at kernel32!ExitThread is only the “framework” for what’s actually going on. Whatever code needs to be executed will be defined by the “APC routine” and the parameter is the one to the call of ntdll!NtQueueApcThread. It has to be tested whether ntdll!NtQueueApcThread can be replaced by a call to kernel32!QueueUserAPC.

Searching the web will also reveal that some people claim that NtCreateThreadEx() can be used to overcome the limitation in a similar way. Perhaps it can, given that RtlCreateUserThread() calls NtCreateThreadEx() almost directly – the only change is a “cast” of the third parameter (a BOOLEAN) to a 32bit type.

But that still doesn’t even touch the actual issue. Windows uses LPCs in order to register threads as belonging to the Win32 subsystem (csrss.exe is the server process). People have reportedly destroyed hardware by sending the wrong LPC message to the wrong port on older Windows versions. The point is, there is no easy way to “emulate” this behavior and those messages as well as their structure differ by OS version. So to make a long story short: there is no easy way to register a thread as being a Win32-thread yourself without risking some major damage.

Meet native threads. The mystery that was left, was why LoadLibrary() would seemingly work with the above APC-method, yet a call of CreateProcess() from within my relocatable code would fail at all times. The reason is simple: the thread not being a Win32-thread is limited in what it can do, as there is no proper “connection” to the subsystem. Consequently those calls that rely on respective features will fail, while others may succeed. Sad, but that is how it is.

CreateProcessAsUser() came to the rescue on those systems, although – of course – creating the new process in the right desktop inside the target session is not possible by any documented means, thanks to the inability to retrieve the information which is “right” …

// Oliver

This entry was posted in /dev/null, IT Security, Programming, Reversing. Bookmark the permalink.

24 Responses to CreateRemoteThread, Vista and separate sessions

  1. carlos says:

    “creating the new process in the right desktop inside the target session is not possible by any documented means” so you cant get the session id form a process/thread running in the right desktop?
    I mean gettokeninformation(targetthread… sessionid…) then settokeninformation (newtoken, sessionid) CreateProcessAsUser(newtoken) if you have problems with drawing in the desktop you can always add the logonsid to newtoken

  2. carlos says:

    Also have you tried elicz libs?

  3. Oliver says:

    Carlos, the problem is, to find out in which exact desktop the other process is in the target session. If it is the default desktop, everything will be fine, otherwise there really doesn’t seem to be any documented way.

    To elaborate on the scenario: I wanted to start a GUI program under the session of which I knew that it ran some other program already. So injection was a natural choice.

    And no, haven’t tried libs from EliCZ.

  4. Oliver says:

    GetTokenInformation tells you only the session (and there are other functions to retrieve this as well), however, getting the desktop/winsta, requires to run code in the remote process, so you’re back at step one.

  5. carlos says:

    Ahh i see i missunderstod you, but are you sure that getting the desktop/winsta, requires to run code in the remote process?

  6. Oliver says:

    Ahh i see i missunderstod you, but are you sure that getting the desktop/winsta, requires to run code in the remote process?

    Frankly, I didn’t find a documented way. Obviously the flaw must be in me or the documentation then 😉

    If you found a method, go ahead and tell. Although the problem has been solved, it would be nice to refine it in this point.

  7. carlos says:

    well there is no documented way but i don’t think it requires to run code in the remote process, maybe you can get the winsta\desktop name from peb->ProcessParameters->DesktopInfo

  8. Oliver says:

    Wasn’t that at a random address now in Vista? So I’d still have to check the fs:0 address within the context of the remote thread/process?!

  9. carlos says:

    I was thinking NtQueryInformationProcess(…ProcessBasicInformation…).

  10. Oliver says:

    Aaah, indeed. That might work.

  11. Chris says:

    If this process resets its winsta or desktop, you have no chance to find it without creating a process and let it look in every available winstas and desktops for the window you are looking for.

  12. movax says:

    but madshi uses RtlCreateUserThread in his madRemote. He claims that work on other sessions

  13. Oliver says:

    The problem is not to create “a thread”, the problem is to create “a Win32 thread” and initialize it enough to be able to run any Win32 API function. If you look at the various implementations (including the Vista one) you’ll see that a lot more is necessary to connect the thread to the Win32 subsystem. On now, sadly it does not work to have a half-initialized thread and use the Win32 function CreateThread or similar to get a fully iniitalized one. I tried all that.

    Also the point was not to “make it work by all means”, but to “make it work properly”. I am not talking about some weird hack that is going to be used on one machine, the whole thing was going to be used on literally thousands of different machines. And it was supposed to work despite of differences of SP level or OS version.

    Maybe madshi has managed to connect the thread to the Win32 subsystem properly, even when using RtlCreateUserThread. However, there is no point in discussing this unless he managed to do so in a manner that does not require sending LPC messages (which is not only documented, but highly dangerous – as I pointed out before).

    // Oliver

  14. movax says:

    I am testing it in vista, i haven’t seen a call to CsrClientCallServer, CsrCreateRemoteThread or any lpc related function.

  15. Oliver says:

    Let us know of the result 😉

  16. movax says:

    CreateProcess works with madshi’s CreateRemoteThread.

  17. Oliver says:

    CreateProcess works with madshi’s CreateRemoteThread.

    … initiated from a service in Vista that runs in its own WinSta?

    “It works” is somewhat irrelevant for a researcher. For me the relevance after having a working code is how and why it works. So how and why does it work?

    // Oliver

  18. movax says:

    tries to set a sd
    if CurrentSessionID == ProcessIdToSessionId(HandleToPID(handle))
    kernel32!CreateRemoteThread
    else
    set up some relocatable code
    injects relocatable code
    ntdll!RtlCreateUserThread(handle, sd, relocatable code, params)
    and that is it.

    I don’t know why CreateProcess doesn’t work for you ❓ .

  19. Oliver says:

    I was using relocatable code and once I started touching more complex CSRSS-related APIs, things went foobar.

    Creating a thread doesn’t help you either, because the created thread will be native as well, i.e. it will not be able to call CreateProcess etc either …

    // Oliver

  20. movax says:

    I was describing how CreateRemoteThreadEx works.
    I have not made a service to test it, but in the task scheduler works.

  21. Oliver says:

    The task scheduler was my first lazy solution as well and it didn’t work. Could be we’re using a different version of Vista or some other differences apply. All machines up to Vista did it successfully, but I wanted a uniform solution that works on all platforms …

  22. movax says:

    Can you post the code?

  23. Oliver says:

    I’m afraid no. I am not allowed to (not my copyright). But I solved it actually with an approximate solution.

    // Oliver

  24. movax says:

    Oh well i guess we’ll never know why your code didn’t work.
    I’m bored about this, do you know where I could get info about lpc?
    I already read this
    http://www.windowsitlibrary.com/Content/356/08/toc.html
    http://www.zezula.net/en/prog/lpc.html

Leave a Reply

Your email address will not be published. Required fields are marked *