SetWindowsHookEx + WH_CBT не работает? Или, по крайней мере, не так, как мне кажется?

У меня есть диагностическая программа, которая использует SetWindowsHookEx а также WH_KEYBOARD_LL для сканирования кодов в масштабе всей системы, я хотел бы расширить его, чтобы отслеживать изменения фокуса окна, что возможно при использовании SetWindowsHookEx и компьютерный обучающий крючок CBT WH_CBT,

Для WH_KEYBOARD_LL hook, я смог включить функцию hook в мой процесс, и он работал, фиксируя нажатия клавиш практически в каждом окне приложения на моем рабочем столе. Я понимаю, что WH_CBT на самом деле должен быть в отдельной DLL, чтобы его можно было внедрить в другие процессы. Итак, я сделал это.

Я также знаю, что это накладывает требование битности - если моя dll 64-битная, я не могу внедрить ее в 32-битные процессы, и наоборот.

В любом случае, я попробовал это в отладчике VS2008, и, конечно же, я видел OutputDebugString вывод (мой обработчик вызывает OutputDebugString). Но только в Visual Studio и в DebugView - когда я переключил фокус на DebugView, DebugView показывал бы вывод строки с изменением фокуса. Когда я переключился обратно на отладчик VS, окно вывода VS показывало бы вывод строки с изменением фокуса.

Я подумал, что это может быть ужасное взаимодействие между VS и DebugView, поэтому я попытался запустить свою программу самостоятельно, без отладчика. Опять же, он будет отображать вывод в DebugView, но только при переключении на DebugView. Когда я переключил фокус на Notepad++, SourceTree и полдюжины других приложений, в DebugView ничего не было зарегистрировано.

Я стал немного подозрительным, поэтому я запустил обозреватель процессов и выполнил поиск своей инъекции DLL. Конечно же, только небольшой выбор процессов, кажется, получить DLL. Когда я собираю DLL 32-разрядный, Visual Studio, DebugView, procexp.exe Кажется, что все получают DLL, но НЕ другие 32-битные процессы, запущенные на моей машине. Когда я собираю dll 64-битный, explorer.exe а также procexp64.exe получить DLL, но не любой из других 64-битных процессов на моей машине.

Кто-нибудь может предложить что-нибудь? Любые возможные объяснения? Можно ли где-нибудь получить запись событий, что может объяснить, почему моя DLL входит в один конкретный процесс, а не в другой? SetWindowsHookEx отчеты ERROR_SUCCESS с GetLastError, Где я могу посмотреть дальше?

ОБНОВИТЬ:

Я загрузил проекты визуальной студии, которые демонстрируют это.

https://dl.dropboxusercontent.com/u/7059499/keylog.zip

Я использую cmake и, к сожалению, cmake не будет помещать 32-битные и 64-битные цели в одну и ту же sln - так что 64-битная.sln находится в _build64и 32-битный.sln находится в _build32, Просто чтобы прояснить, вам не нужен cmake, чтобы попробовать это - просто я использовал cmake для генерации этих файлов проекта изначально.

Вот мой main.cpp

#include <iostream>
#include <iomanip>
#include <sstream>
#include "stdafx.h"
#include "km_inject.h"

using namespace std;

typedef pair<DWORD, string> LastErrorMessage;

LastErrorMessage GetLastErrorMessage()
{
    DWORD code = GetLastError();
    _com_error error(code);
    LPCTSTR errorText = error.ErrorMessage();
    return LastErrorMessage( code, string(errorText) );
}

static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
  case WM_DESTROY:
      PostQuitMessage(0);
      break;
  default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}


LRESULT __stdcall CALLBACK LowLevelKeyboardProc(
  _In_  int nCode,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
)
{
    KBDLLHOOKSTRUCT * hookobj = (KBDLLHOOKSTRUCT *)lParam;
    DWORD vkCode = hookobj->vkCode;
    DWORD scanCode = hookobj->scanCode;
    DWORD flags = hookobj->flags;
    DWORD messageTime = hookobj->time;

    UINT vkCodeChar = MapVirtualKey( vkCode, MAPVK_VK_TO_CHAR );

#define BITFIELD(m) string m##_str = (flags & m)? #m : "NOT " #m
    BITFIELD(LLKHF_EXTENDED);
    BITFIELD(LLKHF_INJECTED);
    BITFIELD(LLKHF_ALTDOWN);
    BITFIELD(LLKHF_UP);
#undef BITFIELD

    string windowMessageType;

#define KEYSTRING(m) case m: windowMessageType = #m; break

    switch ( wParam )
    {
        KEYSTRING( WM_KEYDOWN );
        KEYSTRING( WM_KEYUP );
        KEYSTRING( WM_SYSKEYDOWN );
        KEYSTRING( WM_SYSKEYUP );
    default: windowMessageType = "UNKNOWN"; break;
    };
#undef KEYSTRING

    stringstream ss;
    ss << left 
       << setw(10) << messageTime << " "
       << setw(15) << windowMessageType << ": "
       << right
       << "VK=" << setw(3) << vkCode << " (0x" << hex << setw(3) << vkCode << dec << ") " << setw(2) << vkCodeChar << ", " 
       << "SC=" << setw(3) << scanCode << " (0x" << hex << setw(3) << scanCode << dec << "), " 
       << setw(20) << LLKHF_EXTENDED_str << ", " 
       << setw(20) << LLKHF_INJECTED_str << ", " 
       << setw(20) << LLKHF_ALTDOWN_str << ", " 
       << setw(15) << LLKHF_UP_str << endl;
    OutputDebugString( ss.str().c_str() );

    return CallNextHookEx( 0, nCode, wParam, lParam );
}


int WINAPI WinMain(
  __in  HINSTANCE hInstance,
  __in_opt  HINSTANCE hPrevInstance,
  __in_opt  LPSTR lpCmdLine,
  __in  int nCmdShow )
{
    OutputDebugString( "Beginning test...\n" );

    // Set up main event loop for our application.
    WNDCLASS windowClass = {};
    windowClass.lpfnWndProc = WndProc;
    char * windowClassName = "StainedGlassWindow";
    windowClass.lpszClassName = windowClassName;
    windowClass.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
    if (!RegisterClass(&windowClass)) 
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to register window class: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }
    HWND mainWindow = CreateWindow(windowClassName, // class
        "keylogger", // title
        WS_OVERLAPPEDWINDOW | WS_VISIBLE , // 'style'
        CW_USEDEFAULT, // x
        CW_USEDEFAULT, // y
        CW_USEDEFAULT, // width
        CW_USEDEFAULT, // height
        NULL, // parent hwnd - can be HWND_MESSAGE
        NULL, // menu - use class menu
        hInstance, // module handle
        NULL); // extra param for WM_CREATE

    if (!mainWindow) 
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to create main window: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }

    // Get the name of the executable
    char injectFileName[ MAX_PATH + 1 ];
    {
        int ret = GetModuleFileName( hInstance, injectFileName, MAX_PATH );
        if ( ret == 0 || ret == MAX_PATH )
        {
            LastErrorMessage fullMessage = GetLastErrorMessage();
            stringstream ss;
            ss << "GetModuleFileName failed: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
            OutputDebugString( ss.str().c_str() );
            return -1;
        }
        char * sep = strrchr( injectFileName, '\\' );
        if ( sep == NULL )
        {
            stringstream ss;
            ss << "Couldn't find path separator in " << injectFileName << endl;
            OutputDebugString( ss.str().c_str() );
            return -1;
        }
        *sep = 0;
        strcat_s( injectFileName, "\\km_inject.dll" );
    }

    // Get the module handle
    HINSTANCE inject = LoadLibrary( injectFileName );
    if ( NULL == inject )
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to load injector with LoadLibrary: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }

#ifdef _WIN64
    HOOKPROC LowLevelCBTProc = (HOOKPROC)GetProcAddress( inject, "LowLevelCBTProc" );
#else
    HOOKPROC LowLevelCBTProc = (HOOKPROC)GetProcAddress( inject, "_LowLevelCBTProc@12" );
#endif

    if ( !LowLevelCBTProc )
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to find LowLevelCBTProc function: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }

    // Install the keyboard and CBT handlers
    if ( NULL == SetWindowsHookEx( WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, 0 ) )
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to set llkey hook: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }

    if ( NULL == SetWindowsHookEx( WH_CBT, LowLevelCBTProc, inject, 0 ) )
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to set cbt hook: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }


    BOOL bRet;
    MSG msg;

    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            LastErrorMessage fullMessage = GetLastErrorMessage();
            stringstream ss;
            ss << "What on earth happened? errcode=" << fullMessage.first << ", \"" << fullMessage.second << "\"\n";
            OutputDebugString( ss.str().c_str() );
            break;
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    } 



    OutputDebugString( "Bye, bye!\n" );

    return 0;
}

Это DLL, которую я создал для этого, km_inject.cpp /.h

km_inject.h:

#ifndef INCLUDED_keyloggermini_km_inject_h
#define INCLUDED_keyloggermini_km_inject_h

#if defined(__cplusplus__)
extern "C" {
#endif

LRESULT __declspec(dllimport)__stdcall CALLBACK LowLevelCBTProc(
  _In_  int nCode,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
);

#if defined(__cplusplus__)
};
#endif


#endif

km_inject.cpp:

#include <windows.h>
#include <utility>
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <ctime>

using namespace std;

extern "C" LRESULT __declspec(dllexport)__stdcall CALLBACK LowLevelCBTProc(
  _In_  int nCode,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
)
{
#define HCBTCODE(m) case m: OutputDebugString( #m "\n" ); break;
    switch ( nCode )
    {
        HCBTCODE( HCBT_ACTIVATE );
        HCBTCODE( HCBT_CLICKSKIPPED );
        HCBTCODE( HCBT_CREATEWND );
        HCBTCODE( HCBT_DESTROYWND );
        HCBTCODE( HCBT_KEYSKIPPED );
        HCBTCODE( HCBT_MINMAX );
        HCBTCODE( HCBT_MOVESIZE );
        HCBTCODE( HCBT_QS );
        HCBTCODE( HCBT_SETFOCUS );
        HCBTCODE( HCBT_SYSCOMMAND );
    default:
        OutputDebugString( "HCBT_?\n" );
        break;
    }
    return CallNextHookEx( 0, nCode, wParam, lParam );
}

extern "C" BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        //
        break;

    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;

    case DLL_PROCESS_DETACH:
        //
        break;
    }
    return TRUE;
}

1 ответ

Я уверен, что знаю, что здесь происходит. @500-InternalServerError упомянул это, когда у него есть OutputDebugString() в его введенной DLL, кажется, зависает и не устанавливается. Я думаю, что это то, что происходит со мной тоже.

OutputDebugString() разработал очень плохую серию с Vista. В частности, Vista представила фильтр вывода отладки в HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter, Я сталкивался с этим раньше, но в контексте отладки ядра, и это может быть причиной полного молчания вашего DbgPrint / OutputDebugString / printk выход.

В соответствии с инструкциями здесь ( http://blogs.msdn.com/b/doronh/archive/2006/11/14/where-did-my-debug-output-go-in-vista.aspx) я добавил полностью разрешающий DEFAULT Отфильтруйте мой отладочный вывод, а затем перезагрузите. Теперь, когда я запускаю свой кейлоггер, каждое приложение, которое запускается после моего кейлоггера, похоже, получает внедренную dll. Оно работает! DebugView теперь видит результаты отладки практически всех приложений, которые я запускаю после кейлоггера.

Я думаю, что согласно опыту @ 500-InternalServerError, возможно, когда Windows не видит DEFAULT фильтровать Debug Print Filter и это только мое предположение, что Windows не делает OutputDebugString символ, доступный для связывания и, таким образом, вставки DLL не удастся (молча?). Приложения, которые уже ссылаются на OutputDebugString - Как и сам DebugView, Visual Studio, обозреватель процессов и, по-видимому, explorer.exe, будет в порядке - моя DLL инъекции будет связываться правильно. Во всяком случае, это мое предположение.

Спасибо всем за предложения.

ОБНОВИТЬ:

Хорошо, я больше не уверен в этом. Я вернулся и удалил DEFAULT фильтр, и я все еще могу видеть, как мой крючок DLL загружается в новые процессы. Так что здесь происходит? Я действительно не знаю. Неправильное использование Process Explorer? Если вы не запустите Process Explorer с правами администратора, он не сможет найти все процессы для определенной библиотеки DLL. Но даже тогда не должно было быть проблем с обнаружением дюжины или стандартных процессов без прав администратора, которые я начал.

Я больше не могу воспроизвести проблему. Если кому-то интересно, пример кода все еще доступен по ссылке выше. Очевидно, я не собираюсь отмечать это как ответ, потому что проблема просто "ушла". Я обновлю это, если проблема вернется.

Другие вопросы по тегам