Wednesday, May 30, 2012

CreateRemoteThread. Bypass Windows 7 Session Separation

Internet is full of programmers' forums and those forums are full with questions about CreateRemoteThread Windows API function not working on Windows 7 (when trying to inject a DLL). Those posts made by lucky people, somehow, redirect you to the MSDN page dedicated to this API, which says: "Terminal Services isolates each terminal session by design. Therefore, CreateRemoteThread fails if the target process is in a different session than the calling process." and, basically, means - start the process from your injector as suspended, inject your DLL and then resume the process' main thread. This works... Most of the time... But sometimes you really need to inject your code into a running process. Isn't there a way to do that? Well, there is. As a matter of fact, it is so easy, that I decided not to attach my source code to this article (mainly, because I am too lazy to make it look readable :) ). It appears to be that I am not the only one lazy here :), so I have uploaded the source code.

Let me start as usual, with a note for nerds in order to avoid meaningless comments and stupid discussions. 
The code provided within the article is for example purposes only. Error checks have been omitted on purpose. Yes, there may be another, probably even better, way of doing this. No, manual DLL mapping is not better unless you have plenty of time and nothing to do with it.

All others, let's get to business :)


Opening the Victim Process

This is the easiest part. At this stage you will see whether you are able to inject your code or not (in case of a system process, for example). Nothing unusual here - you simply invoke the good old OpenProcess API

HANDLE WINAPI OpenProcess(
       DWORD dwDesiredAccess, /* in our case PROCESS_ALL_ACCESS */
       BOOL  bInheritHandle, /* no need, so FALSE */
       DWORD dwProcessId /* self explanatory enough */
);

which opens the process specified by dwProcessId and returns a handle to that process, unless, you have no sufficient rights to access that process.


Reading the Shellcode

What you usually see in the examples of shellcode over the internet, is an unsigned char array of hexadecimal values somewhere in the C code. Helps to keep the amount of files smaller, but is not really comfortable to deal with. I decided to store the shellcode in a separate binary file, produced with FASM (Flat Assembler):

use32
   ; offset of the LoadLibraryA address within the shellcode
   dd    func
   ; save all registers
   push  eax ebx ecx edx ebp edi esi
   ; get your EIP
   call  next
next:
   pop   eax
   mov   ebx, eax
   ; get the address of the DLL name
   mov   eax, string - next
   ; do this to avoid possible negative values (due to sign extend)
   movzx eax, al
   add   eax, ebx
   ; pass it to the LoadLibraryA API
   push  eax
   ; get the address of the LoadLibraryA function
   mov   eax, func - next
   movzx eax, al
   add   eax, ebx
   mov   eax, [eax]
   ; call LoadLibraryA
   call  eax
   ; restore registers
   pop   esi edi ebp edx ecx ebx eax
   ; return
   ret
func     dd 0x12345678 ; placeholder for the address
string:

Compiling this code with FASM.EXE will produce a raw binary file, where all offsets are 0 - based. There are some parts in the code above, that may require some additional explanation (for example, why does it not end with ExitThread()). I am aware of this and I will provide you with the explanation a little bit later.

For now, allocate an unsigned char buffer for your shellcode. Make this buffer large enough to contain the shellcode and the name of the DLL (my assumption is, that you passed that name as a command line parameter to your injector). with it's terminating zero.

Once you have read the shellcode into that buffer - append the name of the DLL (which may be a full path to the DLL) to the end of the shellcode with, for example, memcpy() function. Half done with it. Now we still have to "tell" the shellcode where the LoadLibraryA API function is located in memory. Fortunately, the load address randomization in Windows is far from being perfect (addresses  of loaded modules may vary between subsequent reboots, but are the same for all processes). This means that, just as in usual DLL injection, we obtain the address of this API in our process by calling good old GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA") and save it to the "func" variable of the shellcode. Due to the fact that our shellcode may vary in size from time to time (that depends on the needs), we saved the offset to that variable in the first four bytes of the shellcode, which eliminates the need to hardcode the offset. Simply do the following:

*(unsigned int*)(shellcode_ptr + *(int*)(shellcode_ptr)) = (unsigned int)LoadLibraryA_address;

Our shellcode is ready now.


"Create remote thread" without CreateRemoteThread()

As the title of this paragraph suggests - we are not going to use the CreateRemoteThread(). In fact, we are not going to create any thread in the victim process (well, the injected DLL may, but the shellcode won't).


Code Injection

Surely, we need to move our shellcode into the victim process' address space in order to load or library. We are doing it in the same manner, as we would copy the name of the DLL in regular DLL injection procedure:
  1. Allocate memory in the remote process with
    LPVOID WINAPI VirtualAllocEx(
       HANDLE hProcess, /* the handle we obtained with OpenProcess */
       LPVOID lpAddress, /* preferred address; may be NULL */
       SIZE_T dwSize, /* size of the allocation in bytes */
       DWORD  flAllocationType, /* MEM_COMMIT */
       DWORD  flProtect /* PAGE_EXECUTE_READWRITE */
    );
    This function returns the address of the allocation in the address space of the victim process or NULL if it fails.
  2. Copy the shellcode into the buffer we've just allocated in the address space of the victim process:
    BOOL WINAPI WriteProcessMemory(
       HANDLE   hProcess, /* same handle as above */
       LPVOID   lpBaseAddress, /* address of the allocation */
       LPCVOID  lpBuffer, /* address of the local buffer with the shellcode */
       SIZE_T   nSize, /* size of the shellcode together with the appended                                 NULL-terminated string */
  3.    SIZE_T   *lpNumberOfBytesWritten /* if this is zero - check your code */
    );
    If the return value of this function is non zero - we have successfully copied our shellcode into the victim process' address space. It may also be a good idea to check the value returned in the lpNumberOfBytesWritten.

Make It Run
So, we have copied our shell code. The only thing left, is to make it run, but we cannot use the CreateRemoteThread() API... Solution is a bit more complicated.

First of all, we have to suspend all threads of the victim process. In general, suspending only one thread is enough, but, as we cannot know for sure what is going on there, we should suspend them all. There is no specific API that would provide us with the list of threads for a specified process, instead, we have to create a snapshot with CreateToolhelp32Snapshot, which provides us with the list of all currently running threads of all processes running in the system:

HANDLE WINAPI CreateToolhelp32Snapshot(
   DWORD dwFlags, /* TH32CS_SNAPTHREAD = 0x00000004 */
   DWORD th32ProcessID /* in this case may be 0 */
);

This function returns the handle to the snapshot, which contains information on all present threads. Once we have this, we "iterate through the list" with Thread32First and Thread32Next API functions:

BOOL WINAPI Thread32First(
   HANDLE hSnapshot, /* the handle to the snapshot */
   LPTHREADENTRY32 lpte /* pointer to the THREADENTRY32 structure */
);

The Thread32Next has the same prototype as Thread32First.

typedef struct tagTHREADENTRY32{
   DWORD dwSize; /* size of this struct; you have to initialize this field before use */
   DWORD cntUsage; 
   DWORD th32ThreadID; /* use this value to open thread for suspension */
   DWORD th32OwnerProcessID; /* compare this value against the PID of the victim 
                              to filter out threads of other processes */
   LONG  tpBasePri;
   LONG  tpDeltaPri;
   DWORD dwFlags;
} THREADENTRY32, *PTHREADENTRY32;

For each THREADENTRY32 with matching th32OwnerProcessID, open it with OpenThread() and suspend with SuspendThread:

HANDLE WINAPI OpenThread(
   DWORD dwDesiredAccess, /* THREAD_ALL_ACCESS */
   BOOL  bInheritHandle, /* FALSE */
   DWORD dwThreadId /* th32ThreadID field of THREADENTRY32 structure */
);

and

DWORD WINAPI SuspendThread(
   HANDLE hThread, /* Obtained by OpenThread() */
);

Don't forget to CloseHandle(openedThread) :)

Take the first thread, once it is opened (actually, you can do that with any thread that belongs to the victim process) and suspended, and get its CONTEXT (see "Community Additions" here) using the GetThreadContext API:

BOOL WINAPI GetThreadContext(
   HANDLE    hThread, /* handle to the thread */
   LPCONTEXT lpContext /* pointer to the CONTEXT structure */
);

Now, when all the threads of the victim process are suspended, we are may do our job. The idea is to redirect the execution flow of this thread to our shellcode, but make it in such a way, that the shellcode would return to where the suspended thread currently is. This is not a problem at all, as we have the CONTEXT of the thread. The following code does that just fine:

/* "push" current EIP of the thread onto its stack, so that the ret instruction in the shellcode returns the execution flow to this address (which is somewhere in WaitForSingleObject for suspended threads) */
ctx.Esp -= sizeof(unsigned int);
WriteProcessMemory(victimProcessHandle, 
                   (LPVOID)ctx.Esp, 
                   (LPCVOID)&ctx.Eip,
                   sizeof(unsigned int),
                   &bytesWritten);
/* Set the EIP to our injected shellcode; do not forget to skip the first four bytes */
ctx.Eip = remoteAddress + sizeof(unsigned int);

Almost there. All we have to do now, is resume the previously suspended threads in the same manner (iterating with Thread32First and Thread32Next with the same snapshot handle).

Don't forget to close the victim process' handle with CloseHandle() ;)


Shellcode

After all this, the execution flow in the selected thread of the victim process reaches our shellcode, which source code should be pretty clear now. It simply calls the LoadLibraryA() API function with the name/path of the DLL we want to inject.

One important note - it is a bad practice to do anything "serious" inside the DllMain() function. My suggestion is - create a new thread in DllMain() and do all the job there, so that it may return safely.

Hope this article was helpful.

Have fun injecting and see you at the next.




18 comments:

  1. Have you just think about RtlCreateUserThread ?

    I use it personnaly with : SetLastError(RtlCreateUserThread(handleProcess, NULL, 0, 0, 0, 0, pThreadStart, remoteVm, &hRemoteThread, NULL));

    Help here : http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Executable%20Images/RtlCreateUserThread.html

    It bypass very well session isolation with Windows NT 6.

    ReplyDelete
    Replies
    1. Oh! Thanks for this info! It appears to be that Windows 7 has more holes than I initially thought :)

      Delete
  2. So you've got a way to create a thread in another session's process. To what extend is it possible to OpenProcess() another process?
    My guess is that you can't open another process that has a session as admin (privilege escalation would occur otherwise.)

    ReplyDelete
    Replies
    1. Your guess is correct - this has nothing to do with privilege escalation.

      Delete
  3. Is the point of this to run a thread in the process of the same user, but different session? PROCESS_ALL_ACCESS implies SeDebugPrivilege if your accessing the process of another user. With default user rights assignment, that would mean you have admin rights.

    If you have admin rights you could launch a process in the target session which uses CreateRemoteThread.

    Surprisingly, CRT doesn't seem to work across sessions even running as SYSTEM.

    ReplyDelete
  4. i tried to run the program in windows 7 32 system, it works only with cmd.exe where it is executed on other processes like explorer.exe and some other cmd.exe , it doesnt work.I am running as Administrator.

    ReplyDelete
    Replies
    1. Is there any particular reason for you to inject your code into explorer.exe? I mean, come on people, this is not a "how to screw up one's system" tutorial.

      Delete
    2. Probably because a user's explorer.exe process runs at their permissions level until they log out. This is common in penetration testing.

      Delete
  5. The only way that I can see for this to work is if the process performing the injection is running with rights above SYSTEM. Yes, you read that right. The only thing above SYSTEM is KERNEL. So it would have to run from kernel space (Ring 0 privileges for those who are familiar with it.) in supervisor mode. From there, you can do whatever you want. AKA device driver. The problem with programming at that level is that many of the nice macro type functions that do things for you do not exist, so you have to do it yourself. It's also very easy to Blue Screen a system too since a page fault in kernel mode is a fatal error.

    ReplyDelete
  6. Esp & Eip are both 0xCCCCCCCC for me when reading it out.. Hence WPM fails..
    Wrote my own code first then tried yours with same result.

    Running Windows 7 x64 (injecting to 32bit process, same user) with UAC disabled completely.

    Also tried setting debug privilege first, did not help.

    ReplyDelete
    Replies
    1. I have not tried it on a 64 bit version. Will check it later

      Delete
  7. Seems great will try and let you know. But i am facing another problem. I create my own process which calls loadlibrary on a dll. The dll hooks the keyboard messages of the processes and maps it on MyHookProc. The problem is that when i press a key with focus on my process it runs MyHookProc but on subsequent keypress MyHookProc is never invoked. I have to hook the keyboard messages again to do that. I just am unable to get through this problem.

    ReplyDelete
  8. Does this work??? How can the process write at the address pointed by ctx.Esp ????

    ReplyDelete
  9. bypass process explorer? thanks

    ReplyDelete
  10. This comment has been removed by a blog administrator.

    ReplyDelete

Note: Only a member of this blog may post a comment.