Click here to Skip to main content
15,885,032 members
Articles / Programming Languages / C

Forbidding the Clipboard for the Specified Process

,
Rate me:
Please Sign up or sign in to vote.
4.93/5 (49 votes)
11 Nov 2009CPOL10 min read 51.6K   1.1K   67   14
In this article we'll consider some things about the Clipboard internals by showing how you can forbid access to it.

Contents

  1. Introduction
    1. The task
    2. Who is this article for
  2. About the Windows Clipboard
    1. Clipboard overview
    2. How does it all work?
  3. Solution description
    1. Possible task solutions
    2. Project implementation
      1. How to find an unexported function?
      2. Finding the signatures
      3. Function search implementation
      4. Setting hooks
      5. Hooks implementation
    3. Building the sample
    4. Supported Windows versions
  4. Conclusion
  5. Bibliography

Introduction

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.

The Task

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.

Who is this Article for

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.

About the Windows Clipboard

Clipboard Overview

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.

C++
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 IsClipboardFormatAvailable function.

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.

C++
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 CloseClipboard.

How Does It All Work?

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, the application allocates memory using GlobalAlloc function and copies the data to that memory. Eventually, the application calls SetClipboard with a format code and a handle returned by GlobalAlloc as arguments. SetClipboardData in its turn makes a call to the kernel-mode function NtUserSetClipboardData, which performs the actual work.

forbidding-clipboard/clipboard.png

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.

Solution Description

Possible Task Solutions

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 SetClipboardData and 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 NtUserSetClipboardData and 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.

Project Implementation

How to Find an Unexported Function?

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.

Finding the Signatures

First of all, we need to find the unique signatures for two functions: NtUserSetClipboardData and NtUserGetClipboardData. Both of them are in the win32k.sys. To do it, we will use WinDbg. Let’s start with the signature for NtUserGetClipboardData.

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 .process command.

C++
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

C++
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.

forbidding-clipboard/windbg-03.PNG

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.

Function Search Implementation

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.

forbidding-clipboard/signature.png

Because signatures may have “holes” (insignificant bits), it is necessary to implement comparison with mask, to compare only significant parts. It is implemented in function PlCompareSequence. PlSearchSequence contains implementation of the forward search using comparison with mask.

C++
static int PlCompareSequence(const MASK_BUFFER* pPattern, const BUFFER* pTarget)
{
    size_t i = 0;

    if(pTarget->nSize < pPattern->nSize)
        return 0;

    for(i = 0; i < pPattern->nSize; ++i)
    {
        if ((pTarget->pStart[i] & pPattern->pMask[i]) != pPattern->pStart[i])
            return 0;
    }    
    return 1;
}

PLSTATUS PlSearchSequence(const MASK_BUFFER* pPattern, 
                          const BUFFER* pTarget, 
                          const char ** ppFirstPosition)
{
    size_t i = 0;

    if(pTarget->nSize < pPattern->nSize)
        return PL_STATUS_NOT_FOUND;

    for(i = 0; i < pTarget->nSize - pPattern->nSize; ++i)
    {
        BUFFER bufCurrentPiece = {pTarget->pStart + i, pPattern->nSize};

        if (PlCompareSequence(pPattern, &bufCurrentPiece))
        {
            *ppFirstPosition = pTarget->pStart + i;
            return PL_STATUS_SUCCESS;
        }
    }

    return PL_STATUS_NOT_FOUND;
}

Back search implementation is very similar to the forward search.

Structure MASK_BUFFER represents buffer which may have insignificant bits. It is represented by pointer to data, pointer to mask and length of both data and mask. This structure is used for storing signatures. Structure BUFFER represents plain buffer in memory, described by pointer to buffer and its length.

C++
typedef struct MASK_BUFFER_
{
    const char* pStart;
    const char* pMask;
    size_t nSize;
} MASK_BUFFER;

typedef struct BUFFER_
{
    const char* pStart;
    size_t nSize;
} BUFFER;

Function PlGetProcAddress is used for searching functions by signature. Firstly, it searches for the specified signature. If a match for the signature is found, it checks, is there are another matches for it. If there is only one match for the signature, then back search for the start bytes is performed.

C++
PLSTATUS PlSingatureDuplicates(const BUFFER* pModule, 
                               const MASK_BUFFER* pSignature, 
                               const char* pMatchedSignature)
{
    const char* pTemp=0;
    BUFFER bufTemp = {pMatchedSignature + 1, 
                      pModule->nSize - (int)(pMatchedSignature - pModule->pStart)};
    return (PlSearchSequence(pSignature, &bufTemp, &pTemp) == PL_STATUS_SUCCESS);
}

//////////////////////////////////////////////////////////////////////////

static size_t PlCalculateSearchAreaSize(const BUFFER* pModule, 
                                        const char* pSignature, 
                                        size_t nMaxSize)
{
    size_t nSignatureOffset = (size_t)(pSignature - pModule->pStart);
    
    return (nSignatureOffset < nMaxSize) ? nSignatureOffset : nMaxSize;
}

//////////////////////////////////////////////////////////////////////////

static PLSTATUS PlGetProcStart(const BUFFER* pModule, 
                               const char* pSequenceStart, 
                               size_t nMaxSignOffset, 
                               const MASK_BUFFER* pFncStart,
                               void ** ppAddress)
{
    // Define search area
    size_t nSignatureOffset = (size_t)(pSequenceStart - pModule->pStart);
    size_t nSearchArea = PlCalculateSearchAreaSize(pModule, 
                                                   pSequenceStart,
                                                   nMaxSignOffset);

    BUFFER bufFncSearchArea = {pSequenceStart, nSearchArea};

    // Search back for the function start bytes
    return PlSearchSequenceBack(pFncStart, 
                                &bufFncSearchArea, 
                                (const char**)ppAddress);
}

PLSTATUS PlGetProcAddress(const BUFFER* pModule, 
                          const MASK_BUFFER* pSignature, 
                          size_t nMaxSignOffset,
                          const MASK_BUFFER* pFncStart, 
                          void ** ppAddress)
{
    const char* pSequenceStart=0;

    // check unique key
    int status = PlSearchSequence(pSignature, pModule, &pSequenceStart);
    if(status != PL_STATUS_SUCCESS)
        return status;

    // check duplicate
    if (PlSingatureDuplicates(pModule, pSignature, pSequenceStart))
        return PL_STATUS_SIGNATURE_NOT_UNIQUE;

    // now find the beginning of the function
    return PlGetProcStart(pModule, 
                          pSequenceStart, 
                          nMaxSignOffset, 
                          pFncStart, 
                          ppAddress);
}

Setting Hooks

To hook the functions, we’ll use a very common technique, so if it is familiar to you, you can just skip this part of the article.

The technique implies replacing the first bytes of a target function with a jump to our hook function, which has the same prototype. First bytes of the target function are to be copied to somewhere to keep the possibility to use the function. Hence, when user calls the target function, he gets to the hook function.

But there is one thing to remember. You cannot divide an instruction into parts, so only whole instructions should be copied. It makes us to detect lengths of instructions we move. Also, if there are some relative addresses, they must be corrected after moving. To detect instruction length and relative addresses, we’ll use slightly modified Hacker Disassembler Engine, a small library by Veacheslav Patkov which suits well for the task.

Here are functions for moving binary code and writing jump instructions:

C++
size_t CodeWriteJump(code_t* pFrom, const code_t* pTo)
{
    const code_t cInstrCode = '\xE9';                   // JUMP
    const size_t nInstSize = 1 + sizeof(void*);

    *(code_t*)(pFrom + 0) = cInstrCode;                 // Instruction code
    *(size_t*)(pFrom + 1) = pTo - (pFrom + nInstSize);  // Offset

    return nInstSize;
}

size_t CodeDisplaceInstruction(code_t* pNew, code_t* pOld)
{
    // Parse instruction
    hde32s hs;
    hde32_disasm(pOld, &hs);

    // Copy instruction
    RtlMoveMemory(pNew, pOld, hs.len);

    // Correct relative address, if there's any
    if ((hs.flags & F_REL8) == F_REL8)
        *(uint8_t* )(pNew + hs.imm_offset) -= (pNew - pOld);

    if ((hs.flags & F_REL16) == F_REL16)
        *(uint16_t*)(pNew + hs.imm_offset) -= (pNew - pOld);

    if ((hs.flags & F_REL32) == F_REL32)
        *(uint32_t*)(pNew + hs.imm_offset) -= (pNew - pOld);

    return hs.len;
}

size_t CodeDisplace(code_t* pDestination, code_t* pCode, size_t nSize)
{
    size_t nMoved = 0;

    while (nMoved < nSize)
        nMoved += CodeDisplaceInstruction(pDestination + nMoved, 
                                          pCode + nMoved);
    CodeFreeBuffer(pCode, nMoved);

    return nMoved;
}

This functionality is used by PatchFunction function to perform hook in the way described above.

C++
void* PatchFunction(void* pFunction, void* pHook)
{
    code_t* pFncStart = NULL;
    size_t nPosition = 0;

    ULONG oldCR0 = DisableWriteProtection();

    // Move some first bytes of original function 
    pFncStart = CodeAllocate(100);
    if (pFncStart == NULL)
        return NULL;

    nPosition = CodeDisplace(pFncStart, pFunction, CodeJumpSize());
    nPosition += CodeWriteJump(pFncStart + nPosition, 
                              (code_t*)pFunction + nPosition);

    // Patch original function
    CodeWriteJump(pFunction, pHook);

    EnableWriteProtection(oldCR0);
    return pFncStart;
}

Hooks Implementation

Here is the implementation of the hook functions:

C++
typedef struct _CLIPBOARD_CONTEXT
{
    UNICODE_STRING usFrom;
    UNICODE_STRING usTo;
} 
CLIPBOARD_CONTEXT, *PCLIPBOARD_CONTEXT;

static PCLIPBOARD_CONTEXT g_pContext = NULL;

//////////////////////////////////////////////////////////////////////////

BOOLEAN IsPasteAllowed(PCLIPBOARD_CONTEXT pContext)
{
    UNICODE_STRING usWordpad, usNotepad;

    RtlInitUnicodeString(&usWordpad, L"wordpad.exe");
    RtlInitUnicodeString(&usNotepad, L"notepad.exe");

    if ((RtlCompareUnicodeString(&pContext->usFrom, &usWordpad, TRUE) == 0) &&
        (RtlCompareUnicodeString(&pContext->usTo, &usNotepad, TRUE) == 0))
    {
        DbgPrint("Copying from WORDPAD.EXE to NOTEPAD.EXE is denied\n");
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

//////////////////////////////////////////////////////////////////////////

ULONG __stdcall NtUserSetClipboardData_Hook(ULONG uFormat, HANDLE hParam, PULONG pParam)
{
    ULONG uRetVal;
    NTSTATUS status;

    uRetVal = g_pNtUserSetClipboardData(uFormat, hParam, pParam);

    // Create clipboard context to store process names
    status = CreateContext(&g_pContext);
    if (!NT_SUCCESS(status))
        goto cleanup;

    // Save name of the process, which puts data to the clipboard
    status = AllocUnicodeString(&g_pContext->usFrom, 256);
    if (!NT_SUCCESS(status))
        goto cleanup;

    status = GetCurrentProcessName(&g_pContext->usFrom);
    if (!NT_SUCCESS(status))
        goto cleanup;

cleanup:
    if (!NT_SUCCESS(status))
        FreeContext(g_pContext);

    return uRetVal;
}

//////////////////////////////////////////////////////////////////////////

HANDLE __stdcall NtUserGetClipboardData_Hook(ULONG uFormat, PVOID pParam)
{
    HANDLE hRetVal;
    NTSTATUS status;

    // Call the original function
    hRetVal = g_pNtUserGetClipboardData(uFormat, pParam);

    if (g_pContext == NULL)
        goto cleanup;

    // Save the name of the process, which gets data from the clipboard
    status = AllocUnicodeString(&g_pContext->usTo, 256);
    if (!NT_SUCCESS(status))
        goto cleanup;

    status = GetCurrentProcessName(&g_pContext->usTo);
    if (!NT_SUCCESS(status))
        goto cleanup;

    // If paste is forbidden, return NULL to the caller
    if (!IsPasteAllowed(g_pContext))
        hRetVal = NULL;

cleanup:
    return hRetVal;
}

To store names of the source and destination processes, structure CLIPBOARD_CONTEXT is used. When NtUserSetClipboardData is called, we save name of the current process in the usFrom field. When NtUserGetClipboardData is called, the name of the current process is stored in the usTo field. If both names  match the forbidding rule, we return NULL to caller, which indicates an error.

Building the Sample

To build the code sample, you need to install Windows Driver Kit. Also you have to set an environment variable BASEDIR to the path where you have installed WDK.

After that, you can just use Visual Studio to build the sample.

Supported Windows Versions

The project was tested on the following versions of Windows:

  1. Windows XP (SP2 and SP3)
  2. Windows 2003 Server
  3. Windows Vista (SP1)

Conclusion

In the article, I described how to forbid the Clipboard for the specified process from a kernel-mode driver.  Because the code sample in this article is made rather for educational purposes, it has some limitations. First of all, the sample works only on x86 operating systems. It also has never been tested on Windows 2000 and Windows 7. The sample may work incorrectly in a Remote Desktop session.

There is also one thing about forbidding copying data from the some process to itself. Some applications, like Microsoft Excel, can detect when data is copied and pasted within one process. If so, such applications may not use Clipboard API at all, therefore it makes some problems when you want to forbid it.

Read more Case Studies from our Specialists at Apriorit Case Studies Section.

Bibliography  

  • MSDN Clipboard documentation
  • Mark E. Russinovich, David A. Solomon. Microsoft Windows Internals (4th Edition): Microsoft Windows Server 2003, Windows XP, and Windows 2000
  • Sven B. Schreiber. Undocumented Windows 2000 Secrets - A Programmer's Cookbook

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Apriorit Inc.
United States United States
ApriorIT is a software research and development company specializing in cybersecurity and data management technology engineering. We work for a broad range of clients from Fortune 500 technology leaders to small innovative startups building unique solutions.

As Apriorit offers integrated research&development services for the software projects in such areas as endpoint security, network security, data security, embedded Systems, and virtualization, we have strong kernel and driver development skills, huge system programming expertise, and are reals fans of research projects.

Our specialty is reverse engineering, we apply it for security testing and security-related projects.

A separate department of Apriorit works on large-scale business SaaS solutions, handling tasks from business analysis, data architecture design, and web development to performance optimization and DevOps.

Official site: https://www.apriorit.com
Clutch profile: https://clutch.co/profile/apriorit
This is a Organisation

33 members

Written By
Software Developer (Senior) Apriorit Inc
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionwin7+ system support ? Pin
Member 1542832914-Nov-21 20:52
Member 1542832914-Nov-21 20:52 
Questionhow to unhook? Pin
Sainese1-Aug-13 8:03
Sainese1-Aug-13 8:03 
AnswerRe: how to unhook? Pin
Sainese1-Aug-13 8:55
Sainese1-Aug-13 8:55 
QuestionHow can I setup the cliphook.sys and use it after i build success??? Pin
hua_hero0722-Jan-13 22:46
hua_hero0722-Jan-13 22:46 
QuestionHmmm Pin
Michael Chourdakis1-Jun-12 8:22
mvaMichael Chourdakis1-Jun-12 8:22 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey15-Apr-12 23:43
professionalManoj Kumar Choubey15-Apr-12 23:43 
Questionvery cool Pin
BillW3313-Apr-12 5:07
professionalBillW3313-Apr-12 5:07 
QuestionHow to read the data? Pin
zeroruru14-Sep-11 23:51
zeroruru14-Sep-11 23:51 
GeneralMy vote of 5 Pin
gndnet21-Jan-11 7:51
gndnet21-Jan-11 7:51 
GeneralMy vote of 5 Pin
gndnet8-Jan-11 0:05
gndnet8-Jan-11 0:05 
Generala question Pin
Frank_chen1-Sep-10 2:56
Frank_chen1-Sep-10 2:56 
GeneralVery good article Pin
konikula17-Nov-09 4:19
konikula17-Nov-09 4:19 
GeneralRe: Very good article Pin
Volodymyr Shamray17-Nov-09 4:48
Volodymyr Shamray17-Nov-09 4:48 
GeneralRe: Very good article PinPopular
konikula17-Nov-09 5:09
konikula17-Nov-09 5:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.