screenshots
introduction
In summary, this application allows you to enable alpha-blending (transparency) on any top-level window on a Windows 2000 or XP system (it also allows you to make a window topmost, though this is a simple and unrelated convenience). The technique that makes this possible is generally not advised and can potentially crash the targetted applications. This technique is inherently risky and this application is intended only as a demonstration. Run this application at your own risk! For more details and background on this program, read on:
Alpha-blending, available in Windows 2000 and XP, is a fun but rarely utilized feature. The simplest way to use alpha-blending is to simply set the WS_EX_LAYERED (see CreateWindowEx) extended style using SetWindowLong, then call SetLayeredWindowAttributes with your desired opacity or transparency settings (see Layered Windows in the MSDN for an overview). However, it's not possible to call these functions successfully on a window which isn't owned by the calling process.
Once I realized this fairly obvious fact, this project was put on the back burner. One day, while browsing the Win32 Process and Thread Functions Reference, I noticed the odd CreateRemoteThread function. This seemed like exactly what I needed!
Of course, it only took a few minutes for my enthusiasm to fade as I noticed that the MSDN says, "The function must exist in the remote process." Huh, well that's just great if you're calling a thread function the other application's developer just happened to leave lying around! The project went on the back burner again.
Some time later, when I had some free time, I returned to the project. There had to be some way of getting a function into another process' address space, even if MSDN wasn't being up-front about it. So I Googled it to death. Interestingly, an overwhelming number of results were in Chinese, and a significant number contained 'virus'. Eeek! Well, this must be fun then!
Finally, I turned up some obscure examples of how one can launch a thread within the address space of another process. In fact, as it turns out, this is a possible way to 'inject' a DLL (ie force it to be loaded) within another process (see the May 1994 issue of Microsoft System Journal, "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB"), and is therefore not entirely unheard of. However, with more convenient registry-based methods, it seems everyone shies away from this technique.
technique
The basic steps to launching a remote thread are:
- Having found the window handle of the target window, call GetWindowThreadProcessId to get the process identifier.
- Call OpenProcess on this process identifier.
- Enable debug privileges on our own process. (not certain if/when this is necessary) Refer to the Jeffrey Richter column mentioned above.
- Call VirtualAllocEx to allocate memory within the opened process. Allocate enough memory for the thread itself (guesstimate) as well as any data that must be passed into the thread.
- Set up a local copy of the data to be passed into the remote thread.
- Call WriteProcessMemory once each on the data to be passed, and on the function to be executed.
- Call CreateRemoteThread.
- Clean up.
The basic steps within the remote thread are:
- Using the passed-in pointer to LoadLibrary and the passed-in library name, load User32.DLL.
- Using the passed-in pointer to GetProcAddress and known function ordinals obtained with DUMPBIN.EXE, obtain function-pointers to GetWindowLong and SetWindowLong.
- If we're going to be setting the WS_EX_LAYERED bit and it's already set, unset it so that our new settings will take effect properly.
- Set or clear the WS_EX_LAYERED bit according to the passed-in argument.
- Obtain a pointer to the SetLayeredWindowAttributes function, again using the function ordinal to avoid having to use a string.
- Finally, call SetLayeredWindowAttributes using the passed-in arguments.
potential pitfalls
Among the numerous mistakes that can kill the host process, calling functions in DLLs which aren't loaded -- or have been loaded at a different location -- is perhaps the easiest. Before calling any function, verify in the MSDN that the function is within a library that's been loaded. Always use LoadLibrary and GetProcAddress, then make your function call using the returned pointer.
The only library which is can be reasonably expected to be loaded by virtually all processes is Kernel32.DLL. Even so, there are processes which don't load even this library! However, this application only deals with windows, all of which must have Kernel32.DLL loaded. Therefore, one can depend on Kernel32.DLL's functions being at the same address in all targetted processes. Kernel32.DLL allows additional libraries to be loaded and their functions to be called using LoadLibrary and GetProcAddress.
The simplest problem to occur is to allocate too little space. In this application, I've completely guessed at the length of my thread procedure and made it extra-large for Debug builds. I believe it would be possible to do a partial compilation and find the actual length of the function, but since it currently works for my purposes I'm freezing the code as-is.
It's very important to remember that string literals do not exist "in the code" (ie they are not embedded with the code where they are declare) but rather are stuffed together with all the other string literals in the program. Therefore, it's crucial to _never_ use a string literal in your remote thread function! If your remote thread needs to use a string (such as when calling LoadLibrary), you must create a character array within the thread's data structure and copy the string into that memory. I allowed this obvious detail to slip my mind and, when someone pointed out why my remote thread kept crashing, I slapped my forehead so hard it was red for days...
Remember that you can very easily crash other processes if your thread function is not very carefully written!