My schedule has been a mess in recent months, and continues to be so for the next few months. However, I am opening registration today for the Windows Internals training with some date changes from my initial plan.
Here are the dates and times (all based on London time) – 5 days total:
July 6: 4pm to 12am (full day)
July 7: 4pm to 8pm
July 11: 4pm to 12am (full day)
July 12, 13, 14, 18, 19: 4pm to 8pm
Training cost is 800 USD, if paid by an individual, or 1500 USD if paid by a company. Participants from Ukraine (please provide some proof) are welcome with a 90% discount (paying 80 USD, individual payments only).
If you’d like to register, please send me an email to zodiacon@live.com with “Windows Internals training” in the title, provide your full name, company (if any), preferred contact email, and your time zone. The basic syllabus can be found here. if you’ve sent me an email before when I posted about my upcoming classes, you don’t have to do that again – I will send full details soon.
The sessions will be recorded, so can watch any part you may be missing, or that may be somewhat overwhelming in “real time”.
The relationship between processes and threads is fairly well known – a process contains one or more threads, running within the process, using process wide resources, like handles and address space.
Where do windows (those usually-rectangular elements, not the OS) come into the picture?
The relationship between a process, its threads, and windows, along with desktop and Window Stations, can be summarized in the following figure:
A process, threads, windows, desktops and a Window Station
A window is created by a thread, and that thread is designated as the owner of the window. The creating thread becomes a UI thread (if it’s the first User/GDI call it makes), getting a message queue (provided internally by Win32k.sys in the kernel). Every occurrence in the created window causes a message to be placed in the message queue of the owner thread. This means that if a thread creates 100 windows, it’s responsible for all these windows.
“Responsible” here means that the thread must perform something known as “message pumping” – pulling messages from its message queue and processing them. Failure to do that processing would lead to the infamous “Not Responding” scenario, where all the windows created by that thread become non-responsive. Normally, a thread can read its own message queue only – other threads can’t do it for that thread – this is a single threaded UI scheme used by the Windows OS by default. The simplest message pump would look something like this:
The call to GetMessagedoes not return until there is a message in the queue (filling up the MSG structure), or the WM_QUIT message has been retrieved, causing GetMessage to return FALSE and the loop exited. WM_QUIT is the message typically used to signal an application to close. Here is the MSG structure definition:
Once a message has been retrieved, the important call is to DispatchMessage, that looks at the window handle in the message (hwnd) and looks up the window class that this window instance is a member of, and there, finally, the window procedure – the callback to invoke for messages destined to these kinds of windows – is invoked, handling the message as appropriate or passing it along to default processing by calling DefWindowProc. (I may expand on that if there is interest in a separate post).
You can enumerate the windows owned by a thread by calling EnumThreadWindows. Let’s see an example that uses the Toolhelp API to enumerate all processes and threads in the system, and lists the windows created (owned) by each thread (if any).
We’ll start by creating a snapshot of all processes and threads (error handling mostly omitted for clarity):
#include <Windows.h>
#include <TlHelp32.h>
#include <unordered_map>
#include <string>
int main() {
auto hSnapshot = ::CreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
We’ll build a map of process IDs to names for easy lookup later on when we show details of a process:
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
// we can skip the idle process
::Process32First(hSnapshot, &pe);
std::unordered_map<DWORD, std::wstring> processes;
processes.reserve(500);
while (::Process32Next(hSnapshot, &pe)) {
processes.insert({ pe.th32ProcessID, pe.szExeFile });
}
Now we loop through all the threads, calling EnumThreadWindows and showing the results:
THREADENTRY32 te;
te.dwSize = sizeof(te);
::Thread32First(hSnapshot, &te);
static int count = 0;
struct Data {
DWORD Tid;
DWORD Pid;
PCWSTR Name;
};
do {
if (te.th32OwnerProcessID <= 4) {
// idle and system processes have no windows
continue;
}
Data d{
te.th32ThreadID, te.th32OwnerProcessID,
processes.at(te.th32OwnerProcessID).c_str()
};
::EnumThreadWindows(te.th32ThreadID, [](auto hWnd, auto param) {
count++;
WCHAR text[128], className[32];
auto data = reinterpret_cast<Data*>(param);
int textLen = ::GetWindowText(hWnd, text, _countof(text));
int classLen = ::GetClassName(hWnd, className, _countof(className));
printf("TID: %6u PID: %6u (%ws) HWND: 0x%p [%ws] %ws\n",
data->Tid, data->Pid, data->Name, hWnd,
classLen == 0 ? L"" : className,
textLen == 0 ? L"" : text);
return TRUE; // bring in the next window
}, reinterpret_cast<LPARAM>(&d));
} while (::Thread32Next(hSnapshot, &te));
printf("Total windows: %d\n", count);
EnumThreadWindows requires the thread ID to look at, and a callback that receives a window handle and whatever was passed in as the last argument. Returning TRUE causes the callback to be invoked with the next window until the window list for that thread is complete. It’s possible to return FALSE to terminate the enumeration early.
I’m using a lambda function here, but only a non-capturing lambda can be converted to a C function pointer, so the param value is used to provide context for the lambda, with a Data instance having the thread ID, its process ID, and the process name.
The call to GetClassNamereturns the name of the window class, or type of window, if you will. This is similar to the relationship between objects and classes in object orientation. For example, all buttons have the class name “button”. Of course, the windows enumerated are only top level windows (have no parent) – child windows are not included. It is possible to enumerate child windows by calling EnumChildWindows.
Going the other way around – from a window handle to its owner thread and *its* owner process is possible with GetWindowThreadProcessId.
Desktops
A window is placed on a desktop, and is bound to it. More precisely, a thread is bound to a desktop once it creates its first window (or has any hook installed with SetWindowsHookEx). Before that happens, a thread can attach itself to a desktop by calling SetThreadDesktop.
Normally, a thread will use the “default” desktop, which is where a logged in user sees his or her windows appearing. It is possible to create additional desktops using the CreateDesktopAPI. A classic example is the desktopsSysinternals tool. See my post on desktops and windows stations for more information.
It’s possible to redirect a new process to use a different desktop (and optionally a window station) as part of the CreateProcess call. The STARTUPINFO structure has a member named lpDesktop that can be set to a string with the format “WindowStationName\DesktopName” or simply “DesktopName” to keep the parent’s window station. This could look something like this:
STARTUPINFO si = { sizeof(si) };
// WinSta0 is the interactive window station
WCHAR desktop[] = L"Winsta0\\MyOtherDesktop";
si.lpDesktop = desktop;
PROCESS_INFORMATION pi;
::CreateProcess(..., &si, &pi);
“WinSta0” is always the name of the interactive Window Station (one of its desktops can be visible to the user).
The following example creates 3 desktops and launches notepad in the default desktop and the other desktops:
If yoy run the above function, you’ll see one Notepad window on your desktop. If you dig deeper, you’ll find 4 notepad.exe processes in Task Manager. The other three have created their windows in different desktops. Task Manager gives no indication of that, nor does the process list in Process Explorer. However, we see a hint in the list of handles of these “other” notepad processes. Here is one:
Handle named \Desktop1
Curiously enough, desktop objects are not stored as part of the kernel’s Object Manager namespace. If you double-click the handle, copy its address, and look it up in the kernel debugger, this is the result:
Notice the directory object address is zero – it’s not part of any directory. It has meaning in the context of its containing Window Station.
You might think that we can use the EnumThreadWindows as demonstrated at the beginning of this post to locate the other notepad windows. Unfortunately, access is restricted and those notepad windows are not listed (more on that possibly in a future post).
We can try a different approach by using yet another enumration function – EnumDesktopWindows. Given a powerful enough desktop handle, we can enumerate all top level windows in that desktop:
The lambda body is very similar to the one we’ve seen earlier. The difference is that we start with a window handle with no thread ID. But it’s fairly easy to get the owner thread and process with GetWindowThreadProcessId. Here is one way to invoke this function:
auto hDesktop = ::OpenDesktop(L"Desktop1", 0, FALSE, DESKTOP_ENUMERATE);
ListDesktopWindows(hDesktop);
A Notepad window is clearly there. We can repeat the same exercise with the other two desktops, which I have named “Desktop2” and “Desktop3”.
Can we view and interact with these “other” notepads? The SwitchDesktopAPI exists for that purpose. Given a desktop handle with the DESKTOP_SWITCHDESKTOP access, SwitchDesktop changes the input desktop to the requested desktop, so that input devices redirect to that desktop. This is exactly how the Sysinternals Desktops tool works. I’ll leave the interested reader to try this out.
Window Stations
The last piece of the puzzle is a Window Station. I assume you’ve read my earlier post and understand the basics of Window Stations.
A Thread can be associated with a desktop. A process is associated with a window station, with the constraint being that any desktop used by a thread must be within the process’ window station. In other words, a process cannot use one window station and any one of its threads using a desktop that belongs to a different window station.
A process is associated with a window station upon creation – the one specified in the STARTUPINFO structure or the creator’s window station if not specified. Still, a process can associate itself with a different window station by calling SetProcessWindowStation. The constraint is that the provided window station must be part of the current session. There is one window station per session named “WinSta0”, called the interactive window station. One of its desktop can be the input desktop and have user interaction. All other window stations are non-interactive by definition, which is perfectly fine for processes that don’t require any user interface. In return, they get better isolation, because a window station contains its own set of desktops, its own clipboard and its own atom table. Windows handles, by the way, have Window Station scope.
Just like desktops, window stations can be created by calling CreateWindowStation. Window stations can be enumerated as well (in the current session) with EnumerateWindowStations.
Summary
This discussion of threads, processes, desktops, and window stations is not complete, but hopefully gives you a good idea of how things work. I may elaborate on some advanced aspects of Windows UI system in future posts.
I am announcing the next Windows Internals remote training to be held in July 2021 on the 12, 14, 15, 19, 21. Times: 11am to 7pm, London time.
The syllabus can be found here (slight changes are possible if new important topics come up).
Cost and Registration
I’m keeping the cost of these training classes relatively low. This is to make these classes accessible to more people, especially in these unusual and challenging times.
Cost: 800 USD if paid by an individual, 1500 USD if paid by a company. Multiple participants from the same company are entitled to a discount (email me for the details). Previous students of my classes are entitled to a 10% discount.
To register, send an email to zodiacon@live.com and specify “Windows Internals Training” in the title. The email should include your name, contact email, and company name (if any).
Later this year I plan a Windows Kernel Programming class. Stay tuned!
I am also announcing a new training, requested by quite a few people, Windows System Programming. The dates are in February 2021: 8, 9, 11, 15, 17. Times: 12pm to 8pm, London time.
I’m keeping the cost of these training classes relatively low. This is to make these classes accessible to more people, especially in these challenging times. If you register for both classes, you get 10% off the second class. Previous students of my classes get 10% off as well.
Cost: 750 USD if paid by an individual, 1500 USD if paid by a company. Multiple participants from the same company are entitled to a discount (email me for the details).
To register, send an email to zodiacon@live.com and specify “Training” in the title. The email should include the training(s) you’re interested in, your name, contact email, company name (if any) and preferred time zone. The training times have already been selected, but it’s still good to know which time zone you live in.
Many of you are probably familiar with Process Explorer‘s ability to close a handle in any process. How can this be accomplished programmatically?
The standard CloseHandle function can close a handle in the current process only, and most of the time that’s a good thing. But what if you need, for whatever reason, to close a handle in another process?
There are two routes than can be taken here. The first one is using a kernel driver. If this is a viable option, then nothing can prevent you from doing the deed. Process Explorer uses that option, since it has a kernel driver (if launched with admin priveleges at least once). In this post, I will focus on user mode options, some of which are applicable to kernel mode as well.
The first issue to consider is how to locate the handle in question, since its value is unknown in advance. There must be some criteria for which you know how to identify the handle once you stumble upon it. The easiest (and probably most common) case is a handle to a named object.
Let take a concrete example, which I believe is now a classic, Windows Media Player. Regardless of what opnions you may have regarding WMP, it still works. One of it quirks, is that it only allows a single instance of itself to run. This is accomplished by the classic technique of creating a named mutex when WMP comes up, and if it turns out the named mutex already exists (presumabley created by an already existing instance of itself), send a message to its other instance and then exits.
The following screenshot shows the handle in question in a running WMP instance.
This provides an opportunity to close that mutex’ handle “behind WMP’s back” and then being able to launch another instance. You can try this by manually closing the handle with Process Explorer and then launch another WMP instance successfully.
If we want to achieve this programmatically, we have to locate the handle first. Unfortunately, the documented Windows API does not provide a way to enumerate handles, not even in the current process. We have to go with the (officially undocumented) Native API if we want to enumerate handles. There two routes we can use:
Enumerate all handles in the system with NtQuerySystemInformation, search for the handle in the PID of WMP.
Enumerate all handles in the WMP process only, searching for the handle yet again.
Inject code into the WMP process to query handles one by one, until found.
Option 3 requires code injection, which can be done by using the CreateRemoteThreadEx function, but requires a DLL that we inject. This technique is very well-known, so I won’t repeat it here. It has the advantage of not requring some of the native APIs we’ll be using shortly.
Options 1 and 2 look very similar, and for our purposes, they are. Option 1 retrieves too much information, so it’s probably better to go with option 2.
Let’s start at the beginning: we need to locate the WMP process. Here is a function to do that, using the Toolhelp API for process enumeration:
#include <windows.h>
#include <TlHelp32.h>
#include <stdio.h>
DWORD FindMediaPlayer() {
HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return 0;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
// skip the idle process
::Process32First(hSnapshot, &pe);
DWORD pid = 0;
while (::Process32Next(hSnapshot, &pe)) {
if (::_wcsicmp(pe.szExeFile, L"wmplayer.exe") == 0) {
// found it!
pid = pe.th32ProcessID;
break;
}
}
::CloseHandle(hSnapshot);
return pid;
}
int main() {
DWORD pid = FindMediaPlayer();
if (pid == 0) {
printf("Failed to locate media player\n");
return 1;
}
printf("Located media player: PID=%u\n", pid);
return 0;
}
Now that we have located WMP, let’s get all handles in that process. The first step is opening a handle to the process with PROCESS_QUERY_INFORMATION and PROCESS_DUP_HANDLE (we’ll see why that’s needed in a little bit):
HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE,
FALSE, pid);
if (!hProcess) {
printf("Failed to open WMP process handle (error=%u)\n",
::GetLastError());
return 1;
}
If we can’t open a proper handle, then something is terribly wrong. Maybe WMP closed in the meantime?
Now we need to work with the native API to query the handles in the WMP process. We’ll have to bring in some definitions, which you can find in the excellent phnt project on Github (I added extern "C" declaration because we use a C++ file).
The #include <memory> is for using unique_ptr<> as we’ll do soon enough. The #parma links the NTDLL import library so that we don’t get an “unresolved external” when calling NtQueryInformationProcess. Some people prefer getting the functions address with GetProcAddress so that linking with the import library is not necessary. I think using GetProcAddress is important when using a function that may not exist on the system it’s running on, otherwise the process will crash at startup, when the loader (code inside NTDLL.dll) tries to locate a function. It does not care if we check dynamically whether to use the function or not – it will crash. Using GetProcAddress will just fail and the code can handle it. In our case, NtQueryInformationProcess existed since the first Windows NT version, so I chose to go with the simplest route.
Our next step is to enumerate the handles with the process information class I plucked from the full list in the phnt project (ntpsapi.h file):
ULONG size = 1 << 10;
std::unique_ptr<BYTE[]> buffer;
for (;;) {
buffer = std::make_unique<BYTE[]>(size);
auto status = ::NtQueryInformationProcess(hProcess, ProcessHandleInformation,
buffer.get(), size, &size);
if (NT_SUCCESS(status))
break;
if (status == STATUS_INFO_LENGTH_MISMATCH) {
size += 1 << 10;
continue;
}
printf("Error enumerating handles\n");
return 1;
}
The Query* style functions in the native API request a buffer and return STATUS_INFO_LENGTH_MISMATCH if it’s not large enough or not of the correct size. The code allocates a buffer with make_unique<BYTE[]> and tries its luck. If the buffer is not large enough, it receives back the required size and then reallocates the buffer before making another call.
Now we need to step through the handles, looking for our mutex. The information returned from each handle does not include the object’s name, which means we have to make yet another native API call, this time to NtQyeryObject along with some extra required definitions:
NtQueryObject has several information classes, but we only need the name. But what handle do we provide NtQueryObject? If we were going with option 3 above and inject code into WMP’s process, we could loop with handle values starting from 4 (the first legal handle) and incrementing the loop handle by four.
Here we are in an external process, so handing out the handles provided by NtQueryInformationProcess does not make sense. What we have to do is duplicate each handle into our own process, and then make the call. First, we set up a loop for all handles and duplicate each one:
auto info = reinterpret_cast<PROCESS_HANDLE_SNAPSHOT_INFORMATION*>(buffer.get());
for (ULONG i = 0; i < info->NumberOfHandles; i++) {
HANDLE h = info->Handles[i].HandleValue;
HANDLE hTarget;
if (!::DuplicateHandle(hProcess, h, ::GetCurrentProcess(), &hTarget,
0, FALSE, DUPLICATE_SAME_ACCESS))
continue; // move to next handle
}
We duplicate the handle from WMP’s process (hProcess) to our own process. This function requires the handle to the process opened with PROCESS_DUP_HANDLE.
Now for the name: we need to call NtQueryObject with our duplicated handle and buffer that should be filled with UNICODE_STRING and whatever characters make up the name.
BYTE nameBuffer[1 << 10];
auto status = ::NtQueryObject(hTarget, ObjectNameInformation,
nameBuffer, sizeof(nameBuffer), nullptr);
::CloseHandle(hTarget);
if (!NT_SUCCESS(status))
continue;
Once we query for the name, the handle is not needed and can be closed, so we don’t leak handles in our own process. Next, we need to locate the name and compare it with our target name. But what is the target name? We see in Process Explorer how the name looks. It contains the prefix used by any process (except UWP processes): “\Sessions\<session>\BasedNameObjects\<thename>”. We need the session ID and the “real” name to build our target name:
WCHAR targetName[256];
DWORD sessionId;
::ProcessIdToSessionId(pid, &sessionId);
::swprintf_s(targetName,
L"\\Sessions\\%u\\BaseNamedObjects\\Microsoft_WMP_70_CheckForOtherInstanceMutex",
sessionId);
auto len = ::wcslen(targetName);
This code should come before the loop begins, as we only need to build it once.
Not for the real comparison of names:
auto name = reinterpret_cast<UNICODE_STRING*>(nameBuffer);
if (name->Buffer &&
::_wcsnicmp(name->Buffer, targetName, len) == 0) {
// found it!
}
The name buffer is cast to a UNICODE_STRING, which is the standard string type in the native API (and the kernel). It has a Length member which is in bytes (not characters) and does not have to be NULL-terminated. This is why the function used is _wcsnicmp, which can be limited in its search for a match.
Assuming we find our handle, what do we do with it? Fortunately, there is a trick we can use that allows closing a handle in another process: call DuplicateHandle again, but add the DUPLICATE_CLOSE_SOURCE to close the source handle. Then close our own copy, and that’s it! The mutex is gone. Let’s do it:
// found it!
::DuplicateHandle(hProcess, h, ::GetCurrentProcess(), &hTarget,
0, FALSE, DUPLICATE_CLOSE_SOURCE);
::CloseHandle(hTarget);
printf("Found it! and closed it!\n");
return 0;
This is it. If we get out of the loop, it means we failed to locate the handle with that name. The general technique of duplicating a handle and closing the source is applicable to kernel mode as well. It does require a process handle with PROCESS_DUP_HANDLE to make it work, which is not always possible to get from user mode. For example, protected and PPL (protected processes light) processes cannot be opened with this access mask, even by administrators. In kernel mode, on the other hand, any process can be opened with full access.
It’s been a while since I gave the Windows Internals training, so it’s time for another class of my favorite topics!
This time I decided to make it more afordable, to allow more people to participate. The cost is based on whether paid by an individual vs. a company. The training includes lab exercises – some involve working with tools, while others involve coding in C/C++.
Public 5-day remote class
Dates: April 20, 22, 23, 27, 30
Time: 8 hours / day. Exact hours TBD
Price: 750 USD (payed by individual) / 1500 USD (payed by company)
Register by emailing zodiacon@live.com and specifying “Windows Internals Training” in the title
Provide names of participants (discount available for multiple participants from the same company), company name (if any) and preferred time zone.
You’ll receive instructions for payment and other details
Virtual space is limited!
The training time zone will be finalized closer to the start date.
Objectives:
Understand the Windows system architectureExplore the internal workings of process, threads, jobs, virtual memory, the I/O system and other mechanisms fundamental to the way Windows works
Write a simple software device driver to access/modify information not available from user mode
Target Audience:
Experienced windows programmers in user mode or kernel mode, interested in writing better programs, by getting a deeper understanding of the internal mechanisms of the windows operating system.Security researchers interested in gaining a deeper understanding of Windows mechanisms (security or otherwise), allowing for more productive research
Pre-Requisites:
Basic knowledge of OS concepts and architecture.Power user level working with Windows
Practical experience developing windows applications is an advantage
C/C++ knowledge is an advantage
Module 1: System Architecture
Brief Windows NT History
Windows Versions
Tools: Windows, Sysinternals, Debugging Tools for Windows
Processes and Threads
Virtual Memory
User mode vs. Kernel mode
Architecture Overview
Key Components
User/kernel transitions
APIs: Win32, Native, .NET, COM, WinRT
Objects and Handles
Sessions
Introduction to WinDbg
Lab: Task manager, Process Explorer, WinDbg
Module 2: Processes & Jobs
Process basics
Creating and terminating processes
Process Internals & Data Structures
The Loader
DLL explicit and implicit linking
Process and thread attributes
Protected processes and PPL
UWP Processes
Minimal and Pico processes
Jobs
Nested jobs
Introduction to Silos
Server Silos and Docker
Lab: viewing process and job information; creating processes; setting job limits
Module 3: Threads
Thread basics
Thread Internals & Data Structures
Creating and terminating threads
Thread Stacks
Thread Priorities
Thread Scheduling
CPU Sets
Direct Switch
Deep Freeze
Thread Synchronization
Lab: creating threads; thread synchronization; viewing thread information; CPU sets
Module 4: Kernel Mechanisms
Trap Dispatching
Interrupts
Interrupt Request Level (IRQL)
Deferred Procedure Calls (DPCs)
Exceptions
System Crash
Object Management
Objects and Handles
Sharing Objects
Thread Synchronization
Synchronization Primitives (Mutex, Semaphore, Events, and more)
Signaled vs. Non-Signaled
High IRQL Synchronization
Windows Global Flags
Kernel Event Tracing
Wow64
Lab: Viewing Handles, Interrupts; creating maximum handles; Thread synchronization
Module 5: Memory Management
Overview
Small, large and huge pages
Page states
Memory Counters
Address Space Layout
Address Translation Mechanisms
Heaps
APIs in User mode and Kernel mode
Page Faults
Page Files
Commit Size and Commit Limit
Workings Sets
Memory Mapped Files (Sections)
Page Frame Database
Other memory management features
Lab: committing & reserving memory; using shared memory; viewing memory related information
Module 6: Management Mechanisms
The Registry
Services
Starting and controlling services
Windows Management Instrumentation
Lab: Viewing and configuring services; Process Monitor
Module 7: I/O System
I/O System overview
Device Drivers
Plug & Play
The Windows Driver Model (WDM)
The Windows Driver Framework (WDF)
WDF: KMDF and UMDF
Device and Driver Objects
I/O Processing and Data Flow
IRPs
Power Management
Driver Verifier
Writing a Software Driver
Labs: viewing driver and device information; writing a software driver
One of the new Windows 10 features visible to users is the support for additional “Desktops”. It’s now possible to create additional surfaces on which windows can be used. This idea is not new – it has been around in the Linux world for many years (e.g. KDE, Gnome), where users have 4 virtual desktops they can use. The idea is that to prevent clutter, one desktop can be used for web browsing, for example, and another desktop can be used for all dev work, and yet a third desktop could be used for all social / work apps (outlook, WhatsApp, Facebook, whatever).
To create an additional virtual desktop on Windows 10, click on the Task View button on the task bar, and then click the “New Desktop” button marked with a plus sign.
Now you can switch between desktops by clicking the appropriate desktop button and then launch apps as usual. It’s even possible (by clicking Task View again) to move windows from desktop to desktop, or to request that a window be visible on all desktops.
The Sysinternals tools had a tool called “Desktops” for many years now. It too allows for creation of up to 4 desktops where applications can be launched. The question is – is this Desktops tool the same as the Windows 10 virtual desktops feature? Not quite.
First, some background information. In the kernel object hierarchy under a session object, there are window stations, desktops and other objects. Here’s a diagram summarizing this tree-like relationship:
As can be seen in the diagram, a session contains a set of Window Stations. One window station can be interactive, meaning it can receive user input, and is always called winsta0. If there are other window stations, they are non-interactive.
Each window station contains a set of desktops. Each of these desktops can hold windows. So at any given moment, an interactive user can interact with a single desktop under winsta0. Upon logging in, a desktop called “Default” is created and this is where all the normal windows appear. If you click Ctrl+Alt+Del for example, you’ll be transferred to another desktop, called “Winlogon”, that was created by the winlogon process. That’s why your normal windows “disappear” – you have been switched to another desktop where different windows may exist. This switching is done by a documented function – SwitchDesktop.
And here lies the difference between the Windows 10 virtual desktops and the Sysinternals desktops tool. The desktops tool actually creates desktop objects using the CreateDesktop API. In that desktop, it launches Explorer.exe so that a taskbar is created on that desktop – initially the desktop has nothing on it. How can desktops launch a process that by default creates windows in a different desktop? This is possible to do with the normal CreateProcess function by specifying the desktop name in the STARTUPINFO structure’s lpDesktop member. The format is “windowstation\desktop”. So in the desktops tool case, that’s something like “winsta0\Sysinternals Desktop 1”. How do I know the name of the Sysinternals desktop objects? Desktops can be enumerated with the EnumDesktops API. I’ve written a small tool, that enumerates window stations and desktops in the current session. Here’s a sample output when one additional desktop has been created with “desktops”:
In the Windows 10 virtual desktops feature, no new desktops are ever created. Win32k.sys just manipulates the visibility of windows and that’s it. Can you guess why? Why doesn’t Window 10 use the CreateDesktop/SwitchDesktop APIs for its virtual desktop feature?
The reason has to do with some limitations that exist on desktop objects. For one, a window (technically a thread) that is bound to a desktop cannot be switched to another; in other words, there is no way to transfer a windows from one desktop to another. This is intentional, because desktops provide some protection. For example, hooks set with SetWindowsHookEx can only be set on the current desktop, so cannot affect other windows in other desktops. The Winlogon desktop, as another example, has a strict security descriptor that prevents non system-level users from accessing that desktop. Otherwise, that desktop could have been tampered with.
The virtual desktops in Windows 10 is not intended for security purposes, but for flexibility and convenience (security always “contradicts” convenience). That’s why it’s possible to move windows between desktops, because there is no real “moving” going on at all. From the kernel’s perspective, everything is still on the same “Default” desktop.
The next public remote Windows kernel Programming class I will be delivering is scheduled for April 15 to 18. It’s going to be very similar to the first one I did at the end of January (with some slight modifications and additions).
Cost: 1950 USD. Early bird (register before March 30th): 1650 USD
I have not yet finalized the time zone the class will be “targeting”. I will update in a few weeks on that.
If you’re interested in registering, please email zodiacon@live.com with the subject “Windows Kernel Programming class” and specify your name, company (if any) and time zone. I’ll reply by providing more information.
Feel free to contact me for questions using the email or through twitter (@zodiacon).
The complete syllabus is outlined below:
Duration:
4 Days
Target Audience:
Experienced windows developers, interested in developing kernel mode drivers
Objectives:
· Understand the Windows kernel driver programming model
· Write drivers for monitoring processes, threads, registry and some types of objects
· Use documented kernel hooking mechanisms
· Write basic file system mini-filter drivers
Pre Requisites:
· At least 2 years of experience working with the Windows API
· Basic understanding of Windows OS concepts such as processes, threads, virtual memory and DLLs
Software requirements:
· Windows 10 Pro 64 bit (latest stable version)
· Visual Studio 2017 + latest update
· Windows 10 SDK (latest)
· Windows 10 WDK (latest)
· Virtual Machine for testing and debugging
Instructor: Pavel Yosifovich
Abstract
The cyber security industry has grown considerably in recent years, with more sophisticated attacks and consequently more defenders. To have a fighting chance against these kinds of attacks, kernel mode drivers must be employed, where nothing (at least nothing from user mode) can escape their eyes.
The course provides the foundations for the most common software device drivers that are useful not just in cyber security, but also other scenarios, where monitoring and sometimes prevention of operations is required. Participants will write real device drivers with useful features they can then modify and adapt to their particular needs.
Syllabus
Module 1: Windows Internals quick overview
Processes
Virtual memory
Threads
System architecture
User / kernel transitions
Introduction to WinDbg
Windows APIs
Objects and handles
Summary
Module 2: The I/O System
I/O System overview
Device Drivers
The Windows Driver Model (WDM)
The Kernel Mode Driver Framework (KMDF)
Other device driver models
Driver types
Software drivers
Driver and device objects
I/O Processing and Data Flow
Accessing devices
Asynchronous I/O
Summary
Module 3: Kernel programming basics
Setting up for Kernel Development
Basic Kernel types and conventions
C++ in a kernel driver
Creating a driver project
Building and deploying
The kernel API
Strings
Linked Lists
The DriverEntry function
The Unload routine
Installation
Testing
Debugging
Summary
Lab: deploy a driver
Module 4: Building a simple driver
Creating a device object
Exporting a device name
Building a driver client
Driver dispatch routines
Introduction to I/O Request Packets (IRPs)
Completing IRPs
Handling DeviceIoControl calls
Testing the driver
Debugging the driver
Using WinDbg with a virtual machine
Summary
Lab: open a process for any access; zero driver; debug a driver
Module 5: Kernel mechanisms
Interrupt Request Levels (IRQLs)
Deferred Procedure Calls (DPCs)
Dispatcher objects
Low IRQL Synchronization
Spin locks
Work items
Summary
Module 6: Process and thread monitoring
Motivation
Process creation/destruction callback
Specifying process creation status
Thread creation/destruction callback
Notifying user mode
Writing a user mode client
Preventing potentially malicious processes from executing
Summary
Lab: monitoring process/thread activity; prevent specific processes from running; protecting processes
Module 7: Object and registry notifications
Process/thread object notifications
Pre and post callbacks
Registry notifications
Performance considerations
Reporting results to user mode
Summary
Lab: protect specific process from termination; simple registry monitor
Module 8: File system mini filters
File system model
Filters vs. mini filters
The Filter Manager
Filter registration
Pre and Post callbacks
File name information
Contexts
File system operations
Filter to user mode communication
Debugging mini-filters
Summary
Labs: protect a directory from file deletion; backup file before deletion
AppContainers are the sanboxes typically used to run UWP processes (also known as metro, store, modern…). A process within an AppContainer runs with an Integrity Level of low, which effectively means it has no access to almost everything, as the default integrity level of objects (such as files) is Medium. This means code running inside an AppContainer can’t do any sigtnificant damage because of that lack of access. Furthermore, from an Object Manager perspective, named objects created by an AppContainer are stored under its own object manager directory, based on an identifier known as AppContainer SID. This means one AppContainer cannot interfere with another’s objects.
For example, if a process not in an AppContainer creates a mutex named “abc”, its full name is really “\Sessions\1\BaseNamedObjects\abc” (assuming the process runs in session 1). On the other hand, if AppContainer A creates a mutex named “abc”, its full name is something like “\Sessions\1\AppContainerNamedObjects\S-1-15-2-466767348-3739614953-2700836392-1801644223-4227750657-1087833535-2488631167\abc”, meaning it can nevr interfere with another AppContainer or any process running outside of an AppContainer.
Although AppContainers were created specifically for store apps, theye can also be used to execute “normal” applications, providing the same level of security and isolation. Let’s see how to do that.
First, we need to create the AppContainer and obtain an AppContainer SID. This SID is based on a hash of the container name. In the UWP world, this name is made up of the application package and the 13 digits of the signer’s hash. For normal applications, we can select any string; selecting the same string would yield the same SID – which means we can actually use it to “bundle” several processes into the same AppContainer.
The first step is to create an AppContainer profile (error handling ommitted):
The containerName argument is the important one. If the function fails, it probably means the container profile exists already. In that case, we need to extract the SID from the existing profile:
The next step is prepare for process creation. The absolute minimum is to initialize a process attribute list with a SECURITY_CAPABILITIES structure to indicate we want the process to be created inside an AppContainer. As part of this, we can specify capabilities this AppContainer should have, such as internet access, access to the documents library and any other capabilities as defined by the Windows Runtime:
We can try this with the usual first victim, Notepad. Notepad launches and everyhing seems OK. However, if we try to open almost any file by using Notepad’s File/Open menu item, we’ll see that notepad has no access to usual things, such as “my documents” or “my pictures”. This is because it’s runnign with Low Integrity Level and files are defaulted to Medium integrity level:
“AppContainer” in Process Explorer implies Low integirty level.
If we would want Notepad to have access to the user’s files, such as documents and pictures, we would have to set explict permissions on these objects allowing access to the AppContainer SID. Functions to use include SetNamedSecurityInfo (see the project on Github for the full code).
I’ve created a simple application to test these things. We can specify a container name, an executable path and click “Run” to execute in in AppContainer. We can add folders/files that would get full permissions:
Let’s now try a more interesting application – Windows Media Player (yes, I know, who uses the old Media Player these days? But it’s an interesting example). Windows Media Player has the (annoying?) feature where you can only run a single instance of it at any given time. The way this works is that WMP creates a mutex with a very specific name, “Microsoft_WMP_70_CheckForOtherInstanceMutex“, if it already exists, it sends a message to its buddy (a previous instance of WMP) and then terminates. A simple trick we can do with Process Explorer is to close that handle and then launching another WMP.
Let’s try something different: let’s run WMP in an AppContainer. Then let’s run another one in a different AppContainer. Will we get two instances?
Running WMP in this way popus up its helper, setup_wm.exe which asks for the initial settings for WMP. Clicking “Express Settings” closes the dialog but it then it comes up again! And again! You can’t get rid of it, unless you close the dialog and then WMP does not launch. Can you guess why is that?
If you guessed “permissions” – you are correct. Running Process Monitor when this dialog comes up and filtering for “ACCESS DENIED” shows something like this:
Clearly, some keys need access so the settings can be saved. The tool allows adding these keys and setting full permissions for them:
Now we can run WMP in two different containers (change the container name and re-run) and they both run just fine. That’s becuase each mutex now has a unique name prefixed with the AppContainer SID of the relevant AppContainer: