Search This Blog

Monday, January 31, 2011

Remote Threads on Windows 7

While creating a remote thread was an easy task on XP, when Vista was released, it became a whole lot trickier.

CreateRemoteThread() still exists and can be used to create a thread in a different process than the one you are running. Usually, remote threads are used in order to load a DLL inside the target process and ultimately to do some API hooking.

The problem we encounter with CreateRemoteThread() and Windows 7 is that that API does not work across sessions. What this means is that is your program runs as a normal application, it will not be able to create a thread inside a Windows service because they do not run inside the same session.

Running in separate sessions is something that Microsoft has done in order to increase the security of the OS. It's not a bad thing in itself but it can be easily defeated. One way is to use some queued APC which can go across sessions but it's not trivial and I won't be discussing this here.

The solution that is accessible to the more casual programmer is the use of an undocumented API: CreateThreadEx().

Before discussing that function, we need to know about some structures that Windows uses.

typedef struct _CLIENT_ID {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;

typedef struct _PEB_LDR_DATA {
  ULONG                   Length;
  BOOL                    Initialized;
  PVOID                   SsHandle;
  LIST_ENTRY              InLoadOrderModuleList;
  LIST_ENTRY              InMemoryOrderModuleList;
  LIST_ENTRY              InInitializationOrderModuleList;

  ULONG                   MaximumLength;
  ULONG                   Length;
  ULONG                   Flags;
  ULONG                   DebugFlags;
  PVOID                   ConsoleHandle;
  ULONG                   ConsoleFlags;
  HANDLE                  StdInputHandle;
  HANDLE                  StdOutputHandle;
  HANDLE                  StdErrorHandle;
  _UNICODE_STRING          CurrentDirectoryPath;
  HANDLE                  CurrentDirectoryHandle;
  _UNICODE_STRING          DllPath;
  _UNICODE_STRING          ImagePathName;
  _UNICODE_STRING          CommandLine;
  PVOID                   Environment;
  ULONG                   StartingPositionLeft;
  ULONG                   StartingPositionTop;
  ULONG                   Width;
  ULONG                   Height;
  ULONG                   CharWidth;
  ULONG                   CharHeight;
  ULONG                   ConsoleTextAttributes;
  ULONG                   WindowFlags;
  ULONG                   ShowWindowFlags;
  _UNICODE_STRING          WindowTitle;
  _UNICODE_STRING          DesktopName;
  _UNICODE_STRING          ShellInfo;
  _UNICODE_STRING          RuntimeData;
  PVOID                   DLCurrentDirectory;

typedef struct _PEB {
  BOOL                    InheritedAddressSpace;
  BOOL                    ReadImageFileExecOptions;
  BOOL                    BeingDebugged;
  BOOL                    Spare;
  HANDLE                  Mutant;
  PVOID                   ImageBaseAddress;
  PPEB_LDR_DATA           LoaderData;
  PVOID                   SubSystemData;
  PVOID                   ProcessHeap;
  PVOID                   FastPebLock;
  PVOID                   FastPebLockRoutine;
  PVOID                   FastPebUnlockRoutine;
  ULONG                   EnvironmentUpdateCount;
  PVOID                   KernelCallbackTable;
  PVOID                   EventLogSection;
  PVOID                   EventLog;
  PVOID                   FreeList;
  ULONG                   TlsExpansionCounter;
  PVOID                   TlsBitmap;
  ULONG                   TlsBitmapBits[0x2];
  PVOID                   ReadOnlySharedMemoryBase;
  PVOID                   ReadOnlySharedMemoryHeap;
  PVOID                   ReadOnlyStaticServerData;
  PVOID                   AnsiCodePageData;
  PVOID                   OemCodePageData;
  PVOID                   UnicodeCaseTableData;
  ULONG                   NumberOfProcessors;
  ULONG                   NtGlobalFlag;
  BYTE                    Spare2[0x4];
  LARGE_INTEGER           CriticalSectionTimeout;
  ULONG                   HeapSegmentReserve;
  ULONG                   HeapSegmentCommit;
  ULONG                   HeapDeCommitTotalFreeThreshold;
  ULONG                   HeapDeCommitFreeBlockThreshold;
  ULONG                   NumberOfHeaps;
  ULONG                   MaximumNumberOfHeaps;
  PVOID                   *ProcessHeaps;
  PVOID                   GdiSharedHandleTable;
  PVOID                   ProcessStarterHelper;
  PVOID                   GdiDCAttributeList;
  PVOID                   LoaderLock;
  ULONG                   OSMajorVersion;
  ULONG                   OSMinorVersion;
  ULONG                   OSBuildNumber;
  ULONG                   OSPlatformId;
  ULONG                   ImageSubSystem;
  ULONG                   ImageSubSystemMajorVersion;
  ULONG                   ImageSubSystemMinorVersion;
  ULONG                   GdiHandleBuffer[0x22];
  ULONG                   PostProcessInitRoutine;
  ULONG                   TlsExpansionBitmap;
  BYTE                    TlsExpansionBitmapBits[0x80];
  ULONG                   SessionId;

     PVOID Previous;
     PVOID * ActivationContext;
     ULONG Flags;

     LIST_ENTRY FrameListCache;
     ULONG Flags;
     ULONG NextCookieSequenceNumber;
     ULONG StackId;

typedef struct _GDI_TEB_BATCH
     ULONG Offset;
     ULONG Buffer[310];

typedef struct _TEB
     NT_TIB NtTib;
     PVOID EnvironmentPointer;
     CLIENT_ID ClientId;
     PVOID ActiveRpcHandle;
     PVOID ThreadLocalStoragePointer;
     PPEB ProcessEnvironmentBlock;
     ULONG LastErrorValue;
     ULONG CountOfOwnedCriticalSections;
     PVOID CsrClientThread;
     PVOID Win32ThreadInfo;
     ULONG User32Reserved[26];
     ULONG UserReserved[5];
     PVOID WOW32Reserved;
     ULONG CurrentLocale;
     ULONG FpSoftwareStatusRegister;
     VOID * SystemReserved1[54];
     LONG ExceptionCode;
     PACTIVATION_CONTEXT_STACK ActivationContextStackPointer;
     UCHAR SpareBytes1[36];
     ULONG TxFsContext;
     GDI_TEB_BATCH GdiTebBatch;
     CLIENT_ID RealClientId;
     PVOID GdiCachedProcessHandle;
     ULONG GdiClientPID;
     ULONG GdiClientTID;
     PVOID GdiThreadLocalInfo;
     ULONG Win32ClientInfo[62];
     VOID * glDispatchTable[233];
     ULONG glReserved1[29];
     PVOID glReserved2;
     PVOID glSectionInfo;
     PVOID glSection;
     PVOID glTable;
     PVOID glCurrentRC;
     PVOID glContext;
     ULONG LastStatusValue;
     _UNICODE_STRING StaticUnicodeString;
     WCHAR StaticUnicodeBuffer[261];
     PVOID DeallocationStack;
     VOID * TlsSlots[64];
     LIST_ENTRY TlsLinks;
     PVOID Vdm;
     PVOID ReservedForNtRpc;
     VOID * DbgSsReserved[2];
     ULONG HardErrorMode;
     VOID * Instrumentation[9];
     GUID ActivityId;
     PVOID SubProcessTag;
     PVOID EtwLocalData;
     PVOID EtwTraceData;
     PVOID WinSockData;
     ULONG GdiBatchCount;
     UCHAR SpareBool0;
     UCHAR SpareBool1;
     UCHAR SpareBool2;
     UCHAR IdealProcessor;
     ULONG GuaranteedStackBytes;
     PVOID ReservedForPerf;
     PVOID ReservedForOle;
     ULONG WaitingOnLoaderLock;
     PVOID SavedPriorityState;
     ULONG SoftPatchPtr1;
     PVOID ThreadPoolData;
     VOID * * TlsExpansionSlots;
     ULONG ImpersonationLocale;
     ULONG IsImpersonating;
     PVOID NlsCache;
     PVOID pShimData;
     ULONG HeapVirtualAffinity;
     PVOID CurrentTransactionHandle;
     PVOID ActiveFrame;
     PVOID FlsData;
     PVOID PreferredLanguages;
     PVOID UserPrefLanguages;
     PVOID MergedPrefLanguages;
     ULONG MuiImpersonation;
     WORD CrossTebFlags;
     ULONG SpareCrossTebBits: 16;
     WORD SameTebFlags;
     ULONG DbgSafeThunkCall: 1;
     ULONG DbgInDebugPrint: 1;
     ULONG DbgHasFiberData: 1;
     ULONG DbgSkipThreadAttach: 1;
     ULONG DbgWerInShipAssertCode: 1;
     ULONG DbgRanProcessInit: 1;
     ULONG DbgClonedThread: 1;
     ULONG DbgSuppressDebugMsg: 1;
     ULONG SpareSameTebBits: 8;
     PVOID TxnScopeEnterCallback;
     PVOID TxnScopeExitCallback;
     PVOID TxnScopeContext;
     ULONG LockCount;
     ULONG ProcessRundown;
     UINT64 LastSwitchTime;
     UINT64 TotalSwitchOutTime;
     LARGE_INTEGER WaitReasonBitMap;

While you don't need to know all of those, they are useful if you want to get some information about the process you are inserting a thread into.

Now I will discuss the NtCreateThreadEx API.
The prototype is as follows:

typedef DWORD (WINAPI *LPFUNLPFUN_NtCreateThreadEx)
  OUT PHANDLE hThread,
  IN ACCESS_MASK DesiredAccess,
  IN LPVOID ObjectAttributes,
  IN HANDLE ProcessHandle,
  IN LPVOID lpParameter,
  IN BOOL CreateSuspended,
  IN ULONG StackZeroBits,
  IN ULONG SizeOfStackCommit,
  IN ULONG SizeOfStackReserve,
  OUT LPVOID lpBytesBuffer

As we can see, it looks more like a function you'd call inside a driver than in user mode. But, not to worry because you may call it from user mode.
The last parameter is an undocumented structure that looks like this:

typedef struct NtCreateThreadExBuffer
  ULONG Size;
  ULONG Unknown1;
  ULONG Unknown2;
  PCLIENT_ID ClientId;
  ULONG Unknown4;
  ULONG Unknown5;
  ULONG Unknown6;
  ULONG Unknown8;

This explains why I listed more structures at the begining.
The ClientId and TEB pointers will be filled upon return of the call. The TEB is obviously the more important but most will simply ignore that data.

Now that we know what the API looks like and what structures it uses we can start describing how to use it.

First, let's get the pointer to the function like this:

// Get the Windows version before trying to get the address of
// NtCreateThreadEx.
dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));

if (dwMajorVersion == 6 && dwMinorVersion >= 1)
    NtCreateThreadEx = (LPFUN_NtCreateThreadEx) GetProcAddress(modNtDll, "NtCreateThreadEx");
    if( !NtCreateThreadEx )
        // Failed to get funtion address from ntdll.dll
        return FALSE;
       printf("Located NtCreateThreadEx\r\n");

If everything works fine, by now we have the pointer we need.

Here is the actual code to create the thread we want:

//setup and initialize the buffer

memset (&ntbuffer,0,sizeof(NTCREATETHREADEX_STRUCT));
CLIENT_ID cid = {0};

ntbuffer.Size = sizeof(NTCREATETHREADEX_STRUCT);
ntbuffer.Unknown1 = 0x10003;
ntbuffer.Unknown2 = 0x8;
ntbuffer.ClientId = &cid;   // ClientId
ntbuffer.Unknown4 = 0;
ntbuffer.Unknown5 = 0x10004;
ntbuffer.Unknown6 = 4;
ntbuffer.TEB      = &pTEB; // PEB
ntbuffer.Unknown8 = 0;
HANDLE hRemoteThread = NULL;  
DWORD error = NtCreateThreadEx(&hRemoteThread,
                     (LPTHREAD_START_ROUTINE) pfnRoutine,

Here's a succinct description of the parameters used in the call:
  • hRemoteThread is a HANDLE that will be used for the newly created thread
  • 0x1FFFFF is the default access mask (should use proper values)
  • The object attributes can be NULL
  • hProc is the handle of the process you want to inject your thread into
  • pfnRoutine is the address of the thread
  • hRemoteMem is the memory allocated for the parameters of the thread (LPVOID)
  • everything until ntBuffer can be ignored
  • ntBuffer is the pointer to the NTCREATETHREADEX_STRUCT
Quite obviously, you will have enabled your own process with the SE_DEBUG_NAME privilege so that you can open the target process and use the API.

Foggy (and cold) HOD Day

Williams CA. Friday night, January 28th; 20:33.

Just got to the Motel 6 on a foggy evening. Considering that I left Milpitas at 17:00, it took me over 3 and a half hours to my luxurious pad in Colusa county. I must say that I was quite pooped after driving through the fog and the traffic jams but I made it in one piece.

As I'm savoring my most excellent Famous Star from Carl's in my room, I'm thinking about Saturday and how unlucky one can get. The weather has been really nice for the past couple of weeks and is supposed to remain nice for at least one more week. But like since last October, the week-end that I am to go to the track (Thunderhill) the weather turns to shite. Frustrating.

I enjoy driving in the rain but, because over the winter I added a few tasty bits to the car:

- APR Canards (here)
- G-Pan (here)
- 5 Element diffuser (here)
- 2 way, adjustable Nitron shocks (here)
- Quick Release hub (here)
- Personal F1 steering wheel (here)

Since October, I haven't been able to really try out those goodies because I've had to drive in torrential rains (OK, I'm exaggerating a little here but after all, that's my blog so I do what I want :)

Thunderhill Raceway, Willows CA. Saturday, January 29th; 06:57.

I just arrived to the track and I found a spot under one of the big canopies. Took my cooler (filled with ice and Gatorade) out and removed all the stuff from the boot of the car.

It's cold, there is some fog but it's not too bad and it appears to be low clouds rather than actual fog. No rain in sight, which is good. I go to the registration and attend the ensuing drivers' meeting.

HOD is a very nicely ran organization. They always host very nice track days and while they tend to be a lot more expensive than a bunch of other organizations, I like them a lot. The seat time is on par with most as we get 5 sessions of 20 minutes per day.

As usual my group (Group C, which is the advanced group) gets to go first and basically dry the track for the people that are next, so we have to be on track a little after 08:00.

Did I mentioned it was cold?
So, yes, it's cold (50F or so) and the track is wet. The paint on the kerbs is super duper slippery and I learned to avoid it in these conditions. I did get my wheels on a painted part in turn 14 and it was like driving on ice, uncool.

Thunderhill Raceway (Satellite view)

In the second lap of my session, some dude decided that it would be appropriate to go gardening on the outside of turn 6. Black flag (meaning that all cars have to go back to the hot pits). We waited for a few minutes and ended up losing the rest of the session because it took about 15mins to pull the guy out of the mud.

Very next session, for the B (intermediate level) group a guy in Shelby Cobra (or a replica, I don't know) went wide on the exit of 15, tried to correct and ended up doing a faceplant in the wall. Gnarly.
A few more people ended in the grass during the day but at least, there was no bad crash to report.

After lunch, the track finally dried up. It was still cold and the low clouds were still around but at least I was able to go a bit faster. There were a couple of Lotus Elise in my group, one of them belonging to Bruce. His' is anthracite, which I like. Pretty much stock, like mine but minus the aero bits that I have. I followed him during the last session of the day, which was fun, even though he was holding me back in most turns.
We talked about that session later on and I was able to give him a couple of pointers about things that I noticed.

I also met Eric from Suspension Performance. Very nice guy who also happens to be French (which gives him tons of bonus points of course). We chatted for a while as I reckon that between frog legs eaters, we had a lot of stories to tell one another. I decided to visit his shop sometimes this week or the next.

Overall this was a fairly decent track day. I didn't get to work on what I wanted to, specifically turn 1, 9 and 10, but still, I had a good time and that's what counts.

Note: You can see more pictures here if you want to.