Friday, August 14, 2015

How Can you Find Who is Using What Device Programmatically in Windows?

Well, that is not an easy question to answer.  The short answer is that there is no API to do what you are asking.  In fact the OS isn't really designed to allow that, so some of the information required to figure it out 100% is not preserved in the kernel's object manager.  In other words, you can't, not 100% anyway.  Even Mark Russinovich could figure out how to make a sysinternal tool to answer the question.  With that said, it is possible to figure out quite a lot about who is using what.

First off, people use devices by opening a handle to them using CreateFile.  You typically pass in a device interface path that you discovered using SetupAPI, or one of the other APIs Windows has.  The IO Manager will resolve the device directly, or it also has other branch points where it can involve drivers on the stack to resolve the device.  Ultimately the client gets a handle to the device.  I will leave it at that.  The important thing to know is that devices that are being used have handles backed by objects that the kernel's object manager tracks.

Devices being used will have open handles, so you can look at the handles; however, they don't always tell you enough information of what they are for.  With experience, you can recognize some types of handles, and even figure out what interfaces the match to.  Still, some other handles to device, will not have that information: they can be symlinked, they could just be a named object created directly by the device driver (assuming the set the right permissions, etc.), or a lot of other reasons.

Next, are we talking about user mode or kernel mode?  Drivers in KM can open handles to other devices and they will not be directly visible to a UM process.  You will not be able to see a lot of the references, unless you are discovering them in KM.  If you are talking about a UM tool, then the tool could open other processes in UM and enumerate their handles, assuming it has enough permissions.  It would probably need TCB (trusted computing base) to open them all.  The UM tool will not have visibility into the KM handles.

Do you think you could write such a tool?  It would be a great way to learn how IOMGR works, and PnP.  I could write one, but it still would not be 100% complete.

A good starting place to learn about this is another tool called KD (the Kenrel Debugger).  Dump all of the handles in the system, and go spelunking.  That will give you a lot of experience you would need to write the tool I was talking about.

-Sam

How to Capture Control Events (eg. Ctrl-C) in a C/C++ Windows Console App

I was recently working on a tool of mine that implements a RPC client for my RPC server.  I wanted to let the win32 console app run until the user hits ctrl-c or ctrl-break.  I guess you can also do something like getchar, but I didn't want to.  Also, I could just let the user hit ctrl-c and it would kill the app.  I do use RPC handle rundowns to clean up the RPC server state; however, I wanted to clean up and exit "safely" within the app.

So, without further ado, here is the code for using SetConsoleCtrlHandler in Windows for handling console control signals.

There are a few caveats you need to think about though when implementing control handlers.  For some events, there may be other handlers already registered, so your's may not get called.  This happens, for instance, when using a debugger.  Processes with a window message pump will also get some events through the pump that are not synchronized the process signal handler.

 
/*--
    User mode only.
--*/

#include "precomp.h"

HANDLE g_hControlEvent = NULL;

///// ctl event handling
BOOL WINAPI ControlHandlerCallback(
    _In_ DWORD dwCtrlType)
{
    HRESULT hr = S_OK;
    BOOL bControlEventHandled = FALSE;

    wprintf(L"got event %i\n", dwCtrlType);

    switch (dwCtrlType) {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:

        if (!SetEvent(g_hControlEvent)) {
            hr = HRESULT_FROM_WIN32(GetLastError());
            NT_ASSERT(hr == S_OK);
        }

        if (S_OK == hr) {
            bControlEventHandled = TRUE;
        }

        break;
    CTRL_CLOSE_EVENT:
    CTRL_LOGOFF_EVENT:
    CTRL_SHUTDOWN_EVENT:
    default:
        break;
    }

    return bControlEventHandled;
}  // ControlHandlerCallback

HRESULT RegisterControlHandler()
{
    HRESULT hr = S_OK;

    g_hControlEvent = NULL;
    g_hControlEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (!g_hControlEvent) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto exit;
    }

    if (!SetConsoleCtrlHandler(ControlHandlerCallback, TRUE)) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto exit;
    }

    wprintf(L"press ctrl+c to stop\n");

exit:

    return hr;
}  // RegisterControlHandler

HRESULT UnregisterControlHandler()
{
    HRESULT hr = S_OK;

    if (!SetConsoleCtrlHandler(ControlHandlerCallback, FALSE)) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto exit;
    }

    if (!CloseHandle(g_hControlEvent)) {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    g_hControlEvent = NULL;

exit:

    return hr;
}  // UnregisterControlHandler

HRESULT WaitForControlEvent()
{
    HRESULT hr = S_OK;

    if (!g_hControlEvent) {
        // called before registring
        hr = E_UNEXPECTED;
        goto exit;
    }

    if (WAIT_OBJECT_0 != WaitForSingleObject(g_hControlEvent, INFINITE)) {
        hr = E_FAIL;
        goto exit;
    }

exit:

    return hr;
}  // WaitForControlEvent