How to Help Your Business Become an AI Early Adopter
1.B B B B B Introduction
1.1.B B B B B B B The task
1.2.B B B B B B B Who is this article for?
2.B B B B B About the Windows Clipboard
2.1.B B B B B B B Clipboard overview
2.2.B B B B B B B How does it all work?
3.B B B B B Solution description
3.1.B B B B B B B Possible task solutions
3.2.B B B B B B B Project implementation
3.2.1.B B B B B B B B B How to find an unexported function?
3.2.2.B B B B B B B B B Finding the signatures
3.2.3.B B B B B B B B B Function search implementation
3.2.4.B B B B B B B B B Setting hooks
3.2.5.B B B B B B B B B Hooks implementation
3.3.B B B B B B B Building the sample
3.4.B B B B B B B Supported Windows versions
4.B B B B B Conclusions
5.B B B B B Bibliography
This article is based on the real task from the project I participated in, the task was to create a software system for protecting the Windows Clipboard against unauthorized data exchange. Though the Clipboard is one of the fundamental parts of the Windows operating system, there is little information about how it works, especially in the low level. In this article, I'm going to tell you something about the Clipboard internals by showing how you can forbid access to it. I simplified the task as much as it was possible to give you a possibility to focus on the most important parts.
Create a program to forbid copying data via the Clipboard from one specified application (Wordpad) to another (Notepad). The names of the applications will be hardcoded. There will be no additional control possibilities. Also, no GUI is intended.
The article is written for C/C++ developers, who are familiar with Windows API and kernel-mode driver development and who are interested in how to work with the Clipboard on the low level.
The reader is supposed to have some knowledge in C programming language, Windows API (both user- and kernel-mode ones) and the OS architecture. The reader doesn't have to be an expert in driver development but he/she has to know what drivers are and how to write the simplest one.
As you know, the Clipboard is Windows component which enables applications to transfer data. An application places data into the clipboard for cut and copy operations and retrieves data from the clipboard for paste operations. Data in the clipboard can be in various formats. Each format is identified by an unsigned integer value.
An application places data into the clipboard in as many clipboard formats as possible, ordered from the most descriptive clipboard format to the least descriptive. For each format, the application calls the
SetClipboardData function, specifying the format identifier and a global memory handle.
HANDLE SetClipboardData( UINT uFormat, HANDLE hMem );
To retrieve data from the clipboard, an application first determines the clipboard format to retrieve. Typically, an application enumerates the available clipboard formats by using the
EnumClipboardFormats function and uses the first format it recognizes. Alternatively, an application can use the
GetPriorityClipboardFormat function. This function identifies the best available clipboard format according to the specified priority. An application can determine whether a format is available by using the
After determining the clipboard format to use, an application calls the
GetClipboardData function. This function returns the handle to a global memory object containing data in the specified format.
HANDLE GetClipboardData( UINT uFormat );
Before doing some operations with the Clipboard, an application must open it with
OpenClipboard function. After finishing all operations with the Clipboard it must be closed with
The Clipboard is implemented as a part of the Windows subsystem. API functions of this subsystem, including Clipboard API, are in user32.dll dynamic library. But the main work is preformed in win32k.sys module, which is the kernel mode part of the Windows subsystem. Win32k.sys is responsible for many things like managing windows and window messages, drawing graphics, working with the clipboard, etc. Win32k.sys has more than 600 functions which are undocumented and not exported.
Before placing data into the Clipboard application allocates memory using
GlobalAlloc function and copies the data to that memory. Eventually, application calls
SetClipboard with a format code and a handle returned by
GlobalAlloc as arguments. SetClipboardData in its turn makes call to the kernel-mode function
NtUserSetClipboardData, which performes actual work.
When some other application wants to get data from the clipboard it calls
GetClipboardData, which also calls the kernel-mode function inside itself,
NtUserGetClipboardData. If retrieving the data is succeeded, it returns the handle of the data.
Now, let's get back to the task. We want to forbid copying data from one specific process to another. Copying some data via the Clipboard is actually separated on two independent operations: copy (or cut), which is represented by
SetClipboardData and paste, represented by
GetClipboardData. So, on copying we have to save the process name from which data is copied and on pasting we can choose, to allow or to forbid the copying.
There are two approaches. The first approach is to hook
GetClipboardData in the user mode. Both functions are well documented and, that is much more important, they are exported. This is the main advantage. But this approach requires setting hooks for all processes in the system. Also we need to track creation of the new processes to set hooks in their address spaces too.
The second approach, which is described in this article, is to hook kernel-mode functions
NtUserGetClipboardData. In this case we need to set hooks only once. But both functions are not exported from win32k.sys, so it is quite a problem to find them.
Hooking a couple of functions is not a big problem. But before that we need to find them. How to find a function which is not exported? One way is to hardcode the function address in the program. The address can be easily found manually, using WinDbg, for instance. Unfortunately, it is necessary to keep a base of these addresses for each operating system (and, probably, for each service pack) which you're going to support.
Other way is to keep not an address but some sequence of bytes from which that address can be obtained. This sequence of bytes is called unique signature. It can be a piece of the function's code or a piece of code from where the function is called. Though this way is not very portable too, it much less depends on differences between the various operating system versions.
First of all, we need to find the unique signatures for two functions:
NtUserGetClipboardData. Both of them are in the win32k.sys. To do it, we will use WinDbg. Let's start with the signature for
As I mentioned the function we're looking for is in the win32k.sys and this module is mapped to a session address space, it means that it is available only in the context of some interactive process, like explorer.exe. To switch to explorer.exe, you can use
!process 0 0 explorer.exe to get the address of its
EPROCESS and then use
kd> !process 0 0 explorer.exe PROCESS 81946990 SessionId: 0 Cid: 063c Peb: 7ffd4000 ParentCid: 060c DirBase: 09b85000 ObjectTable: e19cafb8 HandleCount: 260. Image: explorer.exe kd> .process /p 81946990 Implicit process is now 81946990 .cache forcedecodeuser done
Let's take a look at the
NtUserGetClipboardData. We can see its disassembly using
u debugger command
kd> u win32k!NtUserGetClipboardData win32k!NtUserGetClipboardData+93 win32k!NtUserGetClipboardData: bf8e9569 6a20 push 20h bf8e956b 6888b198bf push offset win32k!`string'+0x268 (bf98b188) bf8e9570 e84376f1ff call win32k!_SEH_prolog (bf800bb8) ...
Now, look at the piece in the middle of the function code, where
xxxGetClipboardData is called. The first function argument is copied to a local variable, then the values of the three registers are placed into the stack and finally the
xxxGetClipboardData is called. That piece of code seems to be suitable for using as the unique signature. On the one hand, it can be unique (actually, it is) and on the other hand, we can hope that it can be used not only for this version of Windows.
The last byte of the second instruction is an offset of a local variable which can be easily changed in another build. So, it won't be included in the signature, as well as the last byte of the third instruction.
After checking some other versions of Windows we decide to use the described sequence of bytes as the unique signature for the
NtUserGetClipboardData function. Unique signature for the
NtUserSetClipboardData can be found in the same way.
Signatures of the functions are pieces of binary code inside them. Therefore, the process of finding a function address consists of two steps: search forward for the signature and search back for the start bytes of the function.