
How the clipboard works in Windows
- Transfer
Recently, I had the opportunity to debug the clipboard in Windows, and I decided that it would be nice to share the information that I could find out. The clipboard is that component of Windows that many of us use dozens (hundreds?) Of times a day, without really thinking about it. Before embarking on this business, I never even thought about how everything works. As it turned out, there are so many interesting things that you can’t even imagine. First, I will describe how applications store different types of data on the clipboard and how to extract them from there, and then how applications can “hook” to the clipboard and track changes in it. In both cases, you will see debugging entries that show how to access data from the debugger.
Let's start by discussing clipboard formats. Such formats are used to describe what types of data can be buffered. There are a number of predefined standard formats that an application can use, such as a bitmap, ANSI text, Unicode text, and TIFF. Windows also allows the application to set its own format. For example, a word processor may register a format including text, formatting, and pictures. Of course, this leads to a certain problem: what happens if you copy data from a text editor and paste it into Notepad, which does not understand all this formatting and does not display pictures?
The way out is to allow the simultaneous storage of data in the clipboard in several formats. When I thought about the clipboard before, I imagined that a single object was stored there (“my text” or “my picture”), but in fact my data is stored in the clipboard in different forms. A program that takes information from a buffer receives it in the format that it can use.
How does the data appear on the clipboard? Very simple, the application first announces ownership of the clipboard through the OpenClipboard function . After that, the program can clear the clipboard with the EmptyClipboard command and, finally, put its data there with the SetClipboardData command. SetClipboardData accepts two parameters. The first is the identifier of one of the clipboard formats that we mentioned above. The second is a segment descriptor in memory that contains data in this format. An application can repeatedly call the SetClipboardData command for each of the formats that it wants to buffer, from best to worst (because the application where the data will be inserted will select the first suitable format from the list). To make life easier for the developer, Windows automatically provides the conversion of some types of clipboard formats. At the end of the process, the program calls CloseClipboard .
When the user clicks the Paste button, the target application calls OpenClipboard and one of the following functions to determine the available data formats:IsClipboardFormatAvailable , GetPriorityClipboardFormat or EnumClipboardFormats . If it finds a suitable format, then it calls GetClipboardData with the identifier of the desired format as a parameter to get the data. At the end, the application uses the CloseClipboard command to close the buffer.
Now let's see how to use the debugger to determine what data is written to the clipboard. (Note that all my entries are made in the Win7 / 2008 R2 system - so on other versions of the OS they may look slightly different). Since the buffer is part of Win32k.sys, you will need a kernel debugger. I like to use as a control point
Here is the call to SetClipboardData from Notepad:
So, now we can view the contents of the RDI as tagCLIP and see what is written to the buffer:
Fmt is the format for the clipboard. 0Xd is the number 13, which corresponds to Unicode text. However, we cannot just run
aheList in gSharedInfo contains an array with descriptors, and the last two hData bytes multiplied by the size of the descriptor record show the address of our descriptor record:
If you look at phead with an offset of 14, then we get our data (this offset may vary on different platforms):
Imagine another scenario. I copied some text from Wordpad, and the SetClipboardData command worked a certain number of times to place data in different formats. A Unicode entry looks like this:
hData is zero! Why is that? It turns out that the clipboard allows the application to pass zero as the SetClipboardData parameter for a specific format. This means that the application is able to provide data in this format, but will do so later, if necessary. If I want to paste text into Notepad, for which there should be Unicode text in the buffer, Windows will send WM_RENDERFORMAT message to the WordPad window, and then WordPad will provide the data in a new format. Of course, if the application closes before it provides data in all formats, Windows will need all formats. In this case, Windows will send a WM_RENDERALLFORMATS message so that other applications can use the data from the clipboard after closing the parent application.
Now let's see how the application can track the clipboard for changes. This is important to know, because at this point Windows allows third-party applications to connect to the system. If you have obscure glitches with copy and paste, the cause may be the incorrect behavior of some of these programs. To begin with, we consider the mechanisms for connecting to the clipboard. Then, consider whether it is possible to identify applications that use such hooks using the debugger.
There are three ways to monitor the clipboard for changes: viewing the buffer, listening to buffer formats, and requesting the buffer sequence number. We will focus on the first two methods, because they provide for notifications when the contents of the buffer are updated. In the third method, the application itself must check each time whether the buffer has changed, and this method cannot be used in the polling cycle.
Clipboard Viewer functionality appeared in the Windows 2000 version, if not earlier. The principle of operation is quite simple: an application that is interested in receiving notifications about a change in the buffer calls SetClipboardViewer and passes a handle to its window. Windows stores this descriptor in the win32k structure, and each time the clipboard changes, Windows sends a WM_DRAWCLIPBOARD message to the registered window.
Of course, several windows can register to view the buffer - how will Windows deal with this? Well, if the application calls SetClipboardViewer, and another window has previously registered for viewing the clipboard, then Windows returns the handle of the previous window to the new window. And now a new window monitoring the buffer must call SendMessage every time it receives WM_DRAWCLIPBOARD, and notify the buffer about the next window in the chain of those who monitor the buffer. Each buffer monitoring window should also process WM_CHANGECBCHAIN messages. Such messages notify all other windows of the removal of one link in the chain and inform which link becomes the next in the queue. This allows you to save the chain.
The obvious problem with this architecture is the following: it expects that every application that monitors the buffer will behave correctly, will not end unexpectedly, and overall will be a good citizen in the system. If any of the applications starts to behave unfriendly, it will not send a notification about the clipboard change to the next application in the chain, as a result of which the entire chain will remain without notifications.
To cope with such problems, Windows Vista added a mechanism for listening to the clipboard format - Clipboard Format Listener. It works in much the same way as viewing the clipboard, except that Windows itself maintains a list of applications that listen on the clipboard, and does not rely on the integrity of the applications that should keep the chain.
If the application wants to listen to the buffer, it calls the AddClipboardFormatListener function and passes the handle to its window. After that, the window message handler will receive WM_CLIPBOARDUPDATE messages . When an application is about to shut down or no longer wants to receive notifications, it calls RemoveClipboardFormatListener .
We looked at how to register viewing / listening to the clipboard. Now let's see how to use the debugger to determine which programs are involved in these processes. First we need to identify the process in the session where we want to check the clipboard monitoring. It can be any win32 process in this session - we just need it to find a pointer to Window Station. In this case, I would use the Notepad window, as before:
If you do this while debugging the kernel, you will need to interactively change the context (using
Next, look at the address of Win32Process as win32k! TagPROCESSINFO and find out the value of rpwinsta:
This is our Window Station. Merge contents through dt:
Notice the spwndClipViewer, spwndClipboardListener, and spwndClipOwnerfields fields. Here spwndClipViewer is the last registered window in the chain of those who are viewing the clipboard. Also spwndClipboardListener is the last registered buffer listen window in the Clipboard Format Listener. The spwndClipOwner window is the window that placed the data on the clipboard.
If we know the window, then a few steps remain to find out which process it belongs to. We are interested in forspwndClipViewer, spwndClipboardListener and spwndClipOwner. First, run dt to find out the value of tagWND. For this demo, we use spwndClipViewer:
We are only interested in the value of head - so if the offset is 0, do dt for the same address on _THRDESKHEAD:
Now run dt for the address indicated in the pti field as tagTHREADINFO:
Now we are only interested in the value of the pEThread field, which we can pass to! Thread:
As you can see, the clipboard view was registered on behalf of the viewer02.exe process. Since browsing is in the chain, determining the next process in the chain will not be easy. But we can do this for those who are listening to the buffer. Take a look at our Window Station again:
If we start dt on spwndClipboardListener, then we will see the spwndClipboardListenerNext field with the following listening process:
When the last process in the list of listeners for the tagWND buffer is reached, the value of its spwndClipboardListenerNext field will be zero:
Using the address of the window, we can use the same method to get to the name of the process. As mentioned earlier, since tagWND is a kernel structure, the OS itself stores the spwndClipboardListener / spwndClipboardListenerNext pointers, so that they cannot lead to buffer tracking problems like lookup chains.
This concludes our review of the Windows clipboard. I hope it has become informative for you. Want to know more about clipboard monitoring? Here is a good MSDN article about this.
Let's start by discussing clipboard formats. Such formats are used to describe what types of data can be buffered. There are a number of predefined standard formats that an application can use, such as a bitmap, ANSI text, Unicode text, and TIFF. Windows also allows the application to set its own format. For example, a word processor may register a format including text, formatting, and pictures. Of course, this leads to a certain problem: what happens if you copy data from a text editor and paste it into Notepad, which does not understand all this formatting and does not display pictures?
The way out is to allow the simultaneous storage of data in the clipboard in several formats. When I thought about the clipboard before, I imagined that a single object was stored there (“my text” or “my picture”), but in fact my data is stored in the clipboard in different forms. A program that takes information from a buffer receives it in the format that it can use.
How does the data appear on the clipboard? Very simple, the application first announces ownership of the clipboard through the OpenClipboard function . After that, the program can clear the clipboard with the EmptyClipboard command and, finally, put its data there with the SetClipboardData command. SetClipboardData accepts two parameters. The first is the identifier of one of the clipboard formats that we mentioned above. The second is a segment descriptor in memory that contains data in this format. An application can repeatedly call the SetClipboardData command for each of the formats that it wants to buffer, from best to worst (because the application where the data will be inserted will select the first suitable format from the list). To make life easier for the developer, Windows automatically provides the conversion of some types of clipboard formats. At the end of the process, the program calls CloseClipboard .
When the user clicks the Paste button, the target application calls OpenClipboard and one of the following functions to determine the available data formats:IsClipboardFormatAvailable , GetPriorityClipboardFormat or EnumClipboardFormats . If it finds a suitable format, then it calls GetClipboardData with the identifier of the desired format as a parameter to get the data. At the end, the application uses the CloseClipboard command to close the buffer.
Now let's see how to use the debugger to determine what data is written to the clipboard. (Note that all my entries are made in the Win7 / 2008 R2 system - so on other versions of the OS they may look slightly different). Since the buffer is part of Win32k.sys, you will need a kernel debugger. I like to use as a control point
win32k!InternalSetClipboardData+0xe4
. The good thing about this offset is that it is behind the RDI register, filled with data from SetClipboardData in a structure known as tagCLIP.kd> u win32k! InternalSetClipboardData + 0xe4-c L5 win32k! InternalSetClipboardData + 0xd8: fffff960`0011e278 894360 mov dword ptr [rbx + 60h], eax fffff960`0011e27b 8937 mov dword ptr [rdi], esi fffff960`0011e27d 4c896708 mov qword ptr [rdi + 8], r12 fffff960`0011e281 896f10 mov dword ptr [rdi + 10h], ebp fffff960`0011e284 ff15667e1900 call qword ptr [win32k! _imp_PsGetCurrentProcessWin32Process (fffff960`002b60f0)]
kd> dt win32k! tagCLIP + 0x000 fmt: Uint4B + 0x008 hData: Ptr64 Void + 0x010fGlobalHandle: Int4B
Here is the call to SetClipboardData from Notepad:
kd> k Child-SP RetAddr Call Site fffff880`0513a940 fffff960`0011e14f win32k! InternalSetClipboardData + 0xe4 fffff880`0513ab90 fffff960`000e9312 win32k! SetClipboardData + 0x57 fffff880`0513abd0 fffff800`01482ed3 win32k! NtUserSetClipboardData + 0x9e fffff880`0513ac20 00000000`7792e30ant! KiSystemServiceCopyEnd + 0x13 00000000`001dfad8 00000000`7792e494 USER32! ZwUserSetClipboardData + 0xa 00000000`001dfae0 000007fe`fc5b892b USER32! SetClipboardData + 0xdf 00000000`001dfb20 000007fe`fc5ba625 COMCTL32! Edit_Copy + 0xdf 00000000`001dfb60 00000000`77929bd1 COMCTL32! Edit_WndProc + 0xec9 00000000`001dfc00 00000000`779298da USER32! UserCallWinProcCheckWow + 0x1ad 00000000`001dfcc0 00000000`ff5110bc USER32! DispatchMessageWorker + 0x3b5 00000000`001dfd40 00000000`ff51133c notepad! WinMain + 0x16f 00000000`001dfdc0 00000000`77a2652d notepad! DisplayNonGenuineDlgWorker + 0x2da 00000000`001dfe80 00000000`77b5c521 kernel32! BaseThreadInitThunk + 0xd 00000000`001dfeb0 00000000`00000000ntdll! RtlUserThreadStart + 0x1d
So, now we can view the contents of the RDI as tagCLIP and see what is written to the buffer:
kd> dt win32k! tagCLIP @rdi + 0x000 fmt: 0xd + 0x008 hData: 0x00000000`00270235 Void + 0x010fGlobalHandle: 0n1
Fmt is the format for the clipboard. 0Xd is the number 13, which corresponds to Unicode text. However, we cannot just run
du
by value hData
because it is a descriptor, not a direct pointer to data. So you need to look for it in the global win32k structure - gSharedInfo:kd>? win32k! gSharedInfo Evaluate expression: -7284261440224 = fffff960`002f3520 kd> dt win32k! tagSHAREDINFO fffff960`002f3520 + 0x000 psi: 0xfffff900`c0980a70 tagSERVERINFO + 0x008 aheList: 0xfffff900`c0800000 _HANDLEENTRY + 0x010 HeEntrySize: 0x18 + 0x018 pDispInfo: 0xfffff900`c0981e50 tagDISPLAYINFO + 0x020ulSharedDelta: 0 + 0x028 awmControl: [31] _WNDMSG + 0x218DefWindowMsgs: _WNDMSG + 0x228DefWindowSpecMsgs: _WNDMSG
aheList in gSharedInfo contains an array with descriptors, and the last two hData bytes multiplied by the size of the descriptor record show the address of our descriptor record:
kd>? 0x00000000`00270235 & FFFF Evaluate expression: 565 = 00000000`00000235 kd> ?? sizeof (win32k! _HANDLEENTRY) unsigned int64 0x18 kd>? 0xfffff900`c0800000 + (0x235 * 0x18) Evaluate expression: -7693351766792 = fffff900`c08034f8 kd> dt win32k! _HANDLEENTRY fffff900`c08034f8 + 0x000 phead: 0xfffff900`c0de0fb0 _HEAD + 0x008 pOwner: (null) + 0x010 bType: 0x6 '' + 0x011 bFlags: 0 '' + 0x012 wUniq: 0x27
If you look at phead with an offset of 14, then we get our data (this offset may vary on different platforms):
kd> du fffff900`c0de0fb0 + 0x14 fffff900`c0de0fc4 "Hi NTDebugging readers!"
Imagine another scenario. I copied some text from Wordpad, and the SetClipboardData command worked a certain number of times to place data in different formats. A Unicode entry looks like this:
Breakpoint 0 hit win32k! InternalSetClipboardData + 0xe4: fffff960`0011e284 ff15667e1900 call qword ptr [win32k! _imp_PsGetCurrentProcessWin32Process (fffff960`002b60f0)] kd> dt win32k! tagCLIP @rdi + 0x000 fmt: 0xd + 0x008 hData: (null) + 0x010fGlobalHandle: 0n0
hData is zero! Why is that? It turns out that the clipboard allows the application to pass zero as the SetClipboardData parameter for a specific format. This means that the application is able to provide data in this format, but will do so later, if necessary. If I want to paste text into Notepad, for which there should be Unicode text in the buffer, Windows will send WM_RENDERFORMAT message to the WordPad window, and then WordPad will provide the data in a new format. Of course, if the application closes before it provides data in all formats, Windows will need all formats. In this case, Windows will send a WM_RENDERALLFORMATS message so that other applications can use the data from the clipboard after closing the parent application.
Now let's see how the application can track the clipboard for changes. This is important to know, because at this point Windows allows third-party applications to connect to the system. If you have obscure glitches with copy and paste, the cause may be the incorrect behavior of some of these programs. To begin with, we consider the mechanisms for connecting to the clipboard. Then, consider whether it is possible to identify applications that use such hooks using the debugger.
There are three ways to monitor the clipboard for changes: viewing the buffer, listening to buffer formats, and requesting the buffer sequence number. We will focus on the first two methods, because they provide for notifications when the contents of the buffer are updated. In the third method, the application itself must check each time whether the buffer has changed, and this method cannot be used in the polling cycle.
Clipboard Viewer functionality appeared in the Windows 2000 version, if not earlier. The principle of operation is quite simple: an application that is interested in receiving notifications about a change in the buffer calls SetClipboardViewer and passes a handle to its window. Windows stores this descriptor in the win32k structure, and each time the clipboard changes, Windows sends a WM_DRAWCLIPBOARD message to the registered window.
Of course, several windows can register to view the buffer - how will Windows deal with this? Well, if the application calls SetClipboardViewer, and another window has previously registered for viewing the clipboard, then Windows returns the handle of the previous window to the new window. And now a new window monitoring the buffer must call SendMessage every time it receives WM_DRAWCLIPBOARD, and notify the buffer about the next window in the chain of those who monitor the buffer. Each buffer monitoring window should also process WM_CHANGECBCHAIN messages. Such messages notify all other windows of the removal of one link in the chain and inform which link becomes the next in the queue. This allows you to save the chain.
The obvious problem with this architecture is the following: it expects that every application that monitors the buffer will behave correctly, will not end unexpectedly, and overall will be a good citizen in the system. If any of the applications starts to behave unfriendly, it will not send a notification about the clipboard change to the next application in the chain, as a result of which the entire chain will remain without notifications.
To cope with such problems, Windows Vista added a mechanism for listening to the clipboard format - Clipboard Format Listener. It works in much the same way as viewing the clipboard, except that Windows itself maintains a list of applications that listen on the clipboard, and does not rely on the integrity of the applications that should keep the chain.
If the application wants to listen to the buffer, it calls the AddClipboardFormatListener function and passes the handle to its window. After that, the window message handler will receive WM_CLIPBOARDUPDATE messages . When an application is about to shut down or no longer wants to receive notifications, it calls RemoveClipboardFormatListener .
We looked at how to register viewing / listening to the clipboard. Now let's see how to use the debugger to determine which programs are involved in these processes. First we need to identify the process in the session where we want to check the clipboard monitoring. It can be any win32 process in this session - we just need it to find a pointer to Window Station. In this case, I would use the Notepad window, as before:
kd>! process 0 0 notepad.exe PROCESS fffff980366ecb30 SessionId: 1 Cid: 0374 Peb: 7fffffd8000 ParentCid: 0814 DirBase: 1867e000 ObjectTable: fffff9803d28ef90 HandleCount: 52. Image: notepad.exe
If you do this while debugging the kernel, you will need to interactively change the context (using
.process /I
, then click g
and wait for the debugger to break back). Now run DT
at the process address as _EPROCESS
, and look at the Win32Process field:kd> dt _EPROCESS fffff980366ecb30 Win32Process nt! _EPROCESS + 0x258 Win32Process: 0xfffff900`c18c0ce0 Void
Next, look at the address of Win32Process as win32k! TagPROCESSINFO and find out the value of rpwinsta:
kd> dt win32k! tagPROCESSINFO 0xfffff900`c18c0ce0 rpwinsta + 0x258 rpwinsta: 0xfffff980`0be2af60 tagWINDOWSTATION
This is our Window Station. Merge contents through dt:
kd> dt 0xfffff980`0be2af60 tagWINDOWSTATION win32k! tagWINDOWSTATION + 0x000 dwSessionId: 1 + 0x008 rpwinstaNext: (null) + 0x010 rpdeskList: 0xfffff980`0c5e2f20 tagDESKTOP + 0x018 pTerm: 0xfffff960`002f5560 tagTERMINAL + 0x020 dwWSF_Flags: 0 + 0x028 spklList: 0xfffff900`c192cf80 tagKL + 0x030 ptiClipLock: (null) + 0x038 ptiDrawingClipboard: (null) + 0x040 spwndClipOpen: (null) + 0x048 spwndClipViewer: 0xfffff900`c1a4ca70 tagWND + 0x050 spwndClipOwner: 0xfffff900`c1a3ef70 tagWND + 0x058 pClipBase: 0xfffff900`c5512fa0 tagCLIP + 0x060 cNumClipFormats: 4 + 0x064 iClipSerialNumber: 0x16 + 0x068 iClipSequenceNumber: 0xc1 + 0x070 spwndClipboardListener: 0xfffff900`c1a53440 tagWND + 0x078 pGlobalAtomTable: 0xfffff980`0bd56c70 Void + 0x080 luidEndSession: _LUID + 0x088 luidUser: _LUID + 0x090 psidUser: 0xfffff900`c402afe0 Void
Notice the spwndClipViewer, spwndClipboardListener, and spwndClipOwnerfields fields. Here spwndClipViewer is the last registered window in the chain of those who are viewing the clipboard. Also spwndClipboardListener is the last registered buffer listen window in the Clipboard Format Listener. The spwndClipOwner window is the window that placed the data on the clipboard.
If we know the window, then a few steps remain to find out which process it belongs to. We are interested in forspwndClipViewer, spwndClipboardListener and spwndClipOwner. First, run dt to find out the value of tagWND. For this demo, we use spwndClipViewer:
kd> dt 0xfffff900`c1a4ca70 tagWND win32k! tagWND + 0x000 head: _THRDESKHEAD + 0x028 state: 0x40020008 + 0x028 bHasMeun: 0y0 + 0x028 bHasVerticalScrollbar: 0y0 ...
We are only interested in the value of head - so if the offset is 0, do dt for the same address on _THRDESKHEAD:
kd> dt 0xfffff900`c1a4ca70 _THRDESKHEAD win32k! _THRDESKHEAD + 0x000 h: 0x00000000`000102ae Void + 0x008 cLockObj: 6 + 0x010 pti: 0xfffff900`c4f26c20tagTHREADINFO + 0x018 rpdesk: 0xfffff980`0c5e2f20 tagDESKTOP + 0x020 pSelf: 0xfffff900`c1a4ca70 "???"
Now run dt for the address indicated in the pti field as tagTHREADINFO:
kd> dt 0xfffff900`c4f26c20 tagTHREADINFO win32k! tagTHREADINFO + 0x000 pEThread: 0xfffff980`0ef6cb10 _ETHREAD + 0x008 RefCount: 1 + 0x010 ptlW32: (null) + 0x018 pgdiDcattr: 0x00000000`000f0d00 Void
Now we are only interested in the value of the pEThread field, which we can pass to! Thread:
kd>! thread 0xfffff980`0ef6cb10 e THREAD fffff9800ef6cb10 Cid 087c.07ec Teb: 000007fffffde000 Win32Thread: fffff900c4f26c20 WAIT: (WrUserRequest) UserModeNon-Alertable fffff9801c01efe0 SynchronizationEvent Not impersonating DeviceMap fffff980278a0fc0 Owning Process fffff98032e18b30 Image: viewer02.exe Attached Process N / A Image: N / A Wait Start TickCount 5435847 Ticks: 33 (0: 00: 00: 00.515) Context Switch Count 809 IdealProcessor: 0 LargeStack UserTime 00: 00: 00.000 KernelTime 00: 00: 00.062 Win32 Start Address 0x000000013f203044 Stack Init fffff880050acdb0 Current fffff880050ac6f0 Base fffff880050ad000 Limit fffff880050a3000 Call 0 Priority 11 BasePriority 8 UnusualBoost 0 ForegroundBoost 2IoPriority 2 PagePriority 5 Child-SP RetAddr Call Site fffff880`050ac730 fffff800`01488f32 nt! KiSwapContext + 0x7a fffff880`050ac870 fffff800`0148b74f nt! KiCommitThreadWait + 0x1d2 fffff880`050ac900 fffff960`000dc8e7 nt! KeWaitForSingleObject + 0x19f fffff880`050ac9a0 fffff960`000dc989 win32k! xxxRealSleepThread + 0x257 fffff880`050aca40 fffff960`000dafc0 win32k! xxxSleepThread + 0x59 fffff880`050aca70 fffff960`000db0c5 win32k! xxxRealInternalGetMessage + 0x7dc fffff880`050acb50 fffff960`000dcab5 win32k! xxxInternalGetMessage + 0x35 fffff880`050acb90 fffff800`01482ed3 win32k! NtUserGetMessage + 0x75 fffff880`050acc20 00000000`77929e6a nt! KiSystemServiceCopyEnd + 0x13 (TrapFrame @ fffff880`050acc20) 00000000`002ffb18 00000000`00000000 0x77929e6a
As you can see, the clipboard view was registered on behalf of the viewer02.exe process. Since browsing is in the chain, determining the next process in the chain will not be easy. But we can do this for those who are listening to the buffer. Take a look at our Window Station again:
kd> dt 0xfffff980`0be2af60 tagWINDOWSTATION win32k! tagWINDOWSTATION + 0x000 dwSessionId: 1 + 0x008 rpwinstaNext: (null) + 0x010 rpdeskList: 0xfffff980`0c5e2f20 tagDESKTOP + 0x018 pTerm: 0xfffff960`002f5560 tagTERMINAL + 0x020 dwWSF_Flags: 0 + 0x028 spklList: 0xfffff900`c192cf80 tagKL + 0x030 ptiClipLock: (null) + 0x038 ptiDrawingClipboard: (null) + 0x040 spwndClipOpen: (null) + 0x048 spwndClipViewer: 0xfffff900`c1a4ca70tagWND + 0x050 spwndClipOwner: 0xfffff900`c1a3ef70tagWND + 0x058 pClipBase: 0xfffff900`c5512fa0 tagCLIP + 0x060 cNumClipFormats: 4 + 0x064 iClipSerialNumber: 0x16 + 0x068 iClipSequenceNumber: 0xc1 + 0x070 spwndClipboardListener: 0xfffff900`c1a53440 tagWND + 0x078 pGlobalAtomTable: 0xfffff980`0bd56c70 Void + 0x080 luidEndSession: _LUID + 0x088 luidUser: _LUID + 0x090 psidUser: 0xfffff900`c402afe0 Void
If we start dt on spwndClipboardListener, then we will see the spwndClipboardListenerNext field with the following listening process:
kd> dt 0xfffff900`c1a53440 tagWND spwndClipboardListenerNext win32k! tagWND + 0x118 spwndClipboardListenerNext: 0xfffff900`c1a50080 tagWND
When the last process in the list of listeners for the tagWND buffer is reached, the value of its spwndClipboardListenerNext field will be zero:
kd> dt 0xfffff900`c1a50080 tagWND spwndClipboardListenerNext win32k! tagWND + 0x118 spwndClipboardListenerNext: (null)
Using the address of the window, we can use the same method to get to the name of the process. As mentioned earlier, since tagWND is a kernel structure, the OS itself stores the spwndClipboardListener / spwndClipboardListenerNext pointers, so that they cannot lead to buffer tracking problems like lookup chains.
This concludes our review of the Windows clipboard. I hope it has become informative for you. Want to know more about clipboard monitoring? Here is a good MSDN article about this.