Как написать кейлоггер? Пример для ВК на Visual Studio C#

Как написать кейлоггер? Пример для ВК на Visual Studio C#

FazonX

Предисловие

Всем привет. Данная статья — дополнение к видео, вышеднее на канале FazonFix. На момент написания статьи я ещё не записал видео и не знаю в каком ключе я его сделаю, но знаю одно точно — в видосе подробного урока как написать келоггер не будет, поскольку когда я стримил как я пишу кейлоггер в Visual Studio, мой стрим в режиме онлайн забанили и офнули в течение 20 минут, типа за опасные действия или что-то вроде того. Поэтому мной было решено опубликовать исходники в Телеге, а в видосе рассказать о более теоретической части.

Справедливости ради отмечу: данная статья не призыв к действию. Моя основная цель — показать насколько легко пишутся трояны, чтобы читатель вооружился полученными знаниями и был готов в поле сражения, где главными антогонистами выступают пиратские программы, игры и всё то, что бесплатно распространяется на пиратских ресурсах и имеет в своём сердце исполняемый файл .exe.

Чуть подытожу предыдущий абзац: взломанные игры и программы не Иисус выкладывает на ЗеПайретсБэй, а такие же люди, как и мы, которые стремятся заработать. И если трояны пишутся настолько легко, то что им мешает вшить во взломанный Ред Дед Редемшен 2 свой клиент ботнета для майнинга крипты?

Логика

Есть задача: перехватывать введённые пароли на странице авторизации ВК и угонять их куда-нить на базу.

Любая задача начинается с того, что нужно сесть и подумать. Я сел и подумал. Результатом стала следующая концепция:

Надо создать .exe программу, которая будет скидываться на комп жертвы и перехватывать нажатые клавиши, но не всегда, а только тогда, когда открыта в браузере страница авторизации ВКонтакте. Раз в какой-то период она будет скидывать записанные клавиши на сервак через обычный http, передавая в get параметре при обращении к php файлу все нажатые клавиши. Сам php файл потом запишет это в базу данных на серваке. Плюс важно, чтобы сама программа была скрытной: прописывалась в автозагрузку, её не было видно ни на таскбаре, ни в диспетчере задач.

Проект

Почему именно Visual Studio и тем более C#? Не знаю. Вообще точно такой же кейлоггер я делал лет 8 назад на Pascal в среде Delphi 7. Ещё лет 5 назад я его переписал на C# в Visual Studio. У меня тогда было стремление уйти от Pascal и перейти на более прогрессивный язык, какой-нибудь C++ или C#. Вот почему-то решил написать именно на C#. А Visual Studio был выбран тупо потому что самая распространённая среда.

Заходим в Visual Studio, выбираем C# Windows Forms App (.NET Framework) и создаёт проект.

Можно и консольное приложение создать, но тогда каждый раз при запуске будет светиться чёрный экран. Возможно, это лечится, глубоко не копался, но моя задача стоит максимально быстро и просто напистать кейлоггер, а у Виндоус Формс можно поставить опасити (непрозрачность) формы на ноль и всё, проблема решена.

В пропертис первым делом надо постать KeyPreview = true, иначе любые дальнейшие операции с нажатием клавиш будут игнорироваться.

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

Перехват нажатых клавиш

Как мы будем считывать нажатые клавиши? В событиях формы можно найти прекрасные обработчики, как KeyDown и KeyUp. Есть ещё KeyPress и PreviewKeyDown, но для нас разницы особо нет даже между KeyDown и KeyUp.

Красота их в том, что они срабатывают тогда, когда нажата какая-то клавиша, и в нём же, в обработчике событий KeyDown или KeyUp, можно сразу же считать какая клавиша только что была нажата. В коде ниже в поле textBox1 будет отображаться текст "Pressed <клавиша / буква / цифра>"

Всё бы ничего, но все эти KeyDown, KeyUp и так далее не будут работать тогда, когда наша программа свёрнута или просто где-то под другими окнами. А она, между прочим, является трояном и априори должна работать на фоне.

На помощь приходит WinAPI. Это всякие системые штуки, которыми пользуется сама винда для своей работы. И в Visual Studio (как и в Delphi) можно импортировать нужную библиотеку для работы с WinAPI.

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

Импортируется это всё так:

Само использование этой фичи в двух словах не объяснишь, я и не вдовался в подробности как там всё устроено (так как моя задача максимально быстро написать код), но сперва создадим глобальную переменную keyBuffer:

А затем расспотрим следующий код:

Мы здесь видим цикл, ту самую функцию GetAsyncKeyState, Enum.GetName, Enum.GetValues и System.Int32. Как раз-таки в этих фиговинах я и не разбирался, так как весь этот код на скриншоте можно просто вынести в отдельную функцию и пусть она справляется со своей задачей молча, ведь не обязательно знать сколько литров крови качает сердце, как устроена диафрагма и зачем нам аппендицит, чтобы жить здоровой жизнью. Вот так же и в коде. Да и если ты начнёшь разбираться абсолютно в каждой строчке, то это затянется надолго и ты потеряешь кучу драгоценного времени.

Ну дак вот, вкратце что делает этот код? В момент вызова этого кода (или функции, если его поместить в фунцкию) в переменную keyBuffer записываются все нажатые клавиши в данный момент. А в tempBuf как раз-таки они записываются по отдельности.

То есть если мы одновременно нажали клавиши QWE, то в переменную keyBuffer запишется "Q W E ". А цикл foreach нужен как раз-таки для того, чтобы пройтись по списку всех нажатых клавиш в данный момент. Если нажата одна клавиша Q, окей, foreach сделает всего лишь один круг.

Теперь вопрос — а когда испольнять этот код? Ну, поскольку у нас нет обработчика событий на этот счёт, как было в случае с KeyUp, то придётся вызывать его всегда: берём таймер, делаем на нём интервал 1 (одна тысячная секунды) и кидаем этот код на обработчик событий Tick на этом таймере.

Почему именно 1/1000? Потому что если поставить даже 10/1000, то можно умудриться нажать клавишу настолько быстро, что таймер не успеет сработать за это время и это нажатие никак не считается кодом, так как таймер не успел активировать обработчик Tick вовремя.

Соответственно, наш код сейчас выглядит вот так:


Теперь понятно зачем обнулять keyBuffer в начале? Чтобы список нажатых клавиш не добавлялся каждую 1/1000 секунду в эту переменную, а обновлялся.

Плюс я добавил, чтобы keyBuffer добавлялся в textBox1. Это, так скажем, для дебага. Сделаем вид, что нажатые клавиши по итогу мы будем записать сюда, а не отправлять на сервер.

При таком коде у нас в textBox1 будут записываться даже пробелы. А зачем нам такой спам? Она нам не нужён, собсна тогда делаем простую проверку:

У нас keyBuffer обновляется только 2 раза: в начале на пустое значение и внутри цикла, когда какая-то клавиша нажата. И если ни одна клавиша не нажата, то keyBuffer остаётся пустой, поэтому такая проверка идеально подходит.

Следующая проблема. Когда пользователь нажимает на кнопку, он держит её какое-то время, скажем, 50/1000 секунды. А у нас таймер срабатывает каждые 1/1000. Тогда зачем нам записывать 50 раз одну нажатую клавишу, когда пользователь нажал по факту её один раз, но просто удерживает её долго? Правильно, незачем. Тогда делаем очередную проверку.

Но чтобы её реализовать, нужно подумать чуть логически. Пользователь нажимает клавишу Q один раз, а код записывает её 50 раз, так? Так. Почему? Потому что срабатывает каждые 1/1000 секунды и всего лишь считывает нажатые в системе в данный момент времени нажатые клавиши. Что сделать? Ну, надо тогда проверить, если в первую однотысячную секунду была нажата Q, то вторую однотысячную секунду проверяем, а сейчас опять Q нажата? Если да, то не записываем её, а иначе записываем. Собсна вот и всё. С новыми знаниями, полученными в результате логического рассуждения, обновляем код.

Во-первых, нам понадобится новая глобальная переменная — keyBufferOld. Она будет содержать инфу о том, какая была нажата предудыщая клавиша или группа клавиш. Во-вторых, обновляем то место, где у нас было условия keyBuffer != "" и получаем следующее (подчеркнул красным что мы добавили):

Мы проверяем, если предыдушая нажатая клавиша не та, что нажата сейчас, то тогда можно её записать в textBox1. А затем присваиваем значение предыдущей клавиши текущей (keyBufferOld = keyBuffer;). Успех, теперь у нас в textBox1 записываются единично нажатые клавиши. Но есть проблема: а если пользователь ввёл Q, а затем опять Q? Ну, например пароль у него типа maqqaka. В таком случае мы получим maqaka, то есть версию с одной q, ведь мы сделали проверку, чтоб не записывать такую же букву, как предыдущую.

Тут опять немного логики: ведь пользователь, чтобы напечатать вторую букву, отпускает на какое-то время клавишу на клавиатуре. А у нас обновление keyBufferOld идёт только тогда, когда какая-то нажата, да ещё и непохожая на предыдущую. Я это решил следующим образом (подчеркнул красным что изменилось):

Да, то есть я просто вынес keyBufferOld = keyBuffer; за условие, чтобы оно срабатывало всегда, даже когда никакая клавиша не нажата.

Я мог бы сразу это вам показать, но я хотел очередной раз показать пример работы логики. Возможно, кто-то из вас сразу сообразил, что keyBufferOld стоило бы обновлять всегда, а не внутри условий, в таком случае соображалка у вас работает лучше, чем у меня.

Итак, теперь у нас в textBox1 записываются нажатия клавиш последовательно, без лишний копий, а полный код внутри тика таймера у нас теперь следующий:

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

Смотрите, вот, например, пользователь вводит QWERTY. Он при написании этого слова он нажал W, но не успев отжать W, уже нажал E. В таком случае в textBox1 мы получим следующее:

То есть юзер ввёл QWERTY, но мы получаем [Q] [W] [E W] [E] [R] [T] [Y], поскльку он нажал E, не успев отпустить W. Причём третья запись у нас не [W E], а именно [E W], поскольку в буфере винды клавиши располагаются в порядке возрастания своего кода, а не в порядке какая раньше была нажата.

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

Проверка на открытое окно ВКонткте

Что ж, сам кейлоггер у нас готов. Мы с помощью нашей программы видим какие клавиши нажимает пользователь. Но у нас ведь цель написать кейлоггер для ВКонтакте, правильно? Кой чёрт нам записывать абсолютно все нажатые клавиши? Соглы, тогда что мы сделаем — мы сделаем новую проверку: если сейчас открыт барузер со страницей авторизации ВКонтакте, то только тогда записывать нажатые клавиши.

Как это решить? Ну, в первую очередь я обратил внимание на то, что все браузеры в заголовке своего окна имеют название открытой веб-страницы (красным подчеркнул):

Собственно, я решил сделать проверку: если текущее открытое окно содержит в себе < | VK> или < | ВКонтакте>, тогда записывать клавиши.

Как получить название активного окна винды? На помощь вновь приходит WinAPI. Тут опять я не вдовался в подробности того, как весь код работает, мне в первую очередь важен финальный результат (программно получить название активного окна). Итак, импортируем новые фиговины (очень важно при втором импорте поставить CharSet = CharSet.Unicode, иначе вместо кириллицы будут знаки вопроса):

Создаём функцию:

И всё. Теперь при вызове функции getActiveWindowTitle() мы получаем название открытого окна. Либо если ни одно не активно, получаем пустой результат (return null;). Теперь внедряем эту функцию в наш таймер.

Создаём глобальную переменную, куда будут записывать названия активного окна каждые 1/1000 секунд:

А в самом тике таймера в самом начале вставляем 3 новые строки кода, вот как теперь выглядит код тика:

124 строчка — в openWindowTitle вызываем нашу новую функцию, которая возвращает название активного окна.

125 строчка — если ни одно окно не активно, то дропаем всё дальнейшее исполнение кода, вызвав return;.

126 строчка — если активное окно не содержит < | VK> или < | ВКонтакте>, то вновь дропаем дальнейшее исполнение кода вызвванием return;.

Почему я не объядинил 2 условия в одно (125 и 126 строчки) — потому что когда в одном условии openWindowTitle равен null и у него же вызываешь .Contains, то приходит ошибка, типа openWindowTitle равен null, какой нафиг ещё .Contains, дядя. Поэтому пришлось сперва проверить, не пустой ли openWindowTitle (125 строчка) и только тогда уже проверять содержанимое.

Отправка данных

Фух, ну классно. Мы написали сам кейлоггер. А чё, как и куда отправлять данные? Я решил, что все записанные данные будут отправлять на мой сервер, обращаясь на определённый .php, отправляя обычный get-запрос со всеми нажатыми данными.

До создания самого php файла на серваке мы ещё дойдём, сейчас пока поясню, что данные будут отправляться в таком виде: file.php?keys=QWERTY

Итак, когда нам отправлять данные? Явно не каждые 1/1000 секунд. Для этого я создал отдельный таймер, который срабатывает каждые 30 секунд (можно и реже). Можно это реализовать и в первом таймере, но создание второго таймера было чисто моё субъективное решение.

Итак, первым делом нам нужно сделать так, чтобы нажатые клавиши записывались не в textBox1, а в переменную.

Создаём глобальную переменную totalKeys.

И в таймере меняем запись в textBox1 в переменную totalKeys. Теперь тик таймера выглядит так (красным подчеркнул изменение):

Здесь я ещё добавил разделитель в виде вертикальной палочки ("|"), чтобы в дальнейшем отличать нажатые клавиши, иначе они будут записываться в одну кучу (а ведь у меня есть ещё недочёт в виде одновременно нажатых клавишах, поэтому для меня это маст хэв).

Далее заходим в тик нашего второго таймера и пишем следующее:

Это весь код для тика. Его суть заключается в том, что если в переменной totalKeys есть какие-то данные, то выполняем функцию sendDataToServer, которая и будет отправлять get-запрос на наш сервак. totalKeys я обнуляю внутри функции, хотя правильнее было бы в самом тике при успешном выполнении функции. Но это тоже мелочи, которые не так критичны для понимания принципы работы.

Сама функция выглядит следующим образом:

159 строчка — енкодим переменную под url. То есть чтобы все пробелы и спец. символы кодировались в url-формат, чтобы не было проблем на стороне сервера.

161 и 162 строчки — асинхронно отправляем сам запрос. Если делать без async, то кейлоггер перестанет записывать нажатые клавиши, поскольку запрос на url-адрес будет выполняться в основном потоке, заставляя зависнуть всю программу, пока не будет получен ответ от сервера.

164 строчка — обнуляем totalKeys, так как мы уже все данные отправили. Тут тоже можно сделать проверку, что если возвращён от сервера успешный результат, то только тогда обнулять.

Обработка кейлогов на стороне сервера

Дальше опишу то, как сервак обрабатывает полученный запрос. Во-первых, я создал 2 таблицы.

ips:

и keylogs:

В ips записываются все айпишники, с которых пришёл запрос и время их обновления. Кейлоггер ведь может быть запущен сразу на 5 компах, поэтому стоит их разделять, для этого и записываются ip адреса. А last_update показывает когда последний раз был запрос с этого айпишника. Так можно отследить, что с этого ip больше не приходят данные и можно его теперь удалить.

В keylogs записываются все передаваемые нажатые клавиши. Они же могут передаваться постоянно, хоть каждые 30 секунд, соответственно пусть они все записываются в новую строку вместо того, что переписывать уже существующие данные. ip_id — привязка данных нажатых ключей к айпишнику из таблицы ips, а write_time — это время записи.

Сам файл обработки запросов от троянов я назвал client_handler.php и вот как он выглядит (тут, в целом, всё прокомментировано):

Проверяем, передан ли get-параметр keys, если да, то подрубаем базу данных. Дальше смотрим, если этот айпишник уже в базе данных. Если есть, то обновляем данные в таблице ips, если нет, то добавляем новую запись. А дальше в любом случае в keylogs записываем что нам пришло в get-параметре keys.

Описание функций — это просто операции с бд:

Ну, вот, собственно, и всё. Плюс я ещё создал панель управления, в которой выводятся все значения таблицы и даже есть кнопки удаления ip адреса и очистки всех нажатых клавиш с этого ip адреса:

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

Программная часть:

Часть отрисовки:

Стили:

Скрытость и автозагрузка

Мы всё сделали, осталось последнее: сделать троян трояном, а точнее — сделать его скрытым от ползователя и чтобы он прпоисывался в автозагрузку.

Чтобы прописать программу в автозагрузку, нужно создать значения в регистре, делает это стоит при загрузке форме (Form1_Load):

Здесь у меня 2 пути в регистре, как можно заметить. У меня Windows 10, вроде как они оба рабочие, но у меня просто не было времени точно проверить оба ли они рабочие или лишь какой-то из них. Тут вы тоже можете поэкспериментировать с перезагрузкой своего компа, попробовав по очереди каждый из вариантов в одиночку и компилируя проект каждый раз заново.

Тут ещё отмечу, что если вы один раз добавили троян программно в автозагрузку, а затем в диспетчере задач отключили его:

То при следующих запусках он не добавится вновь в автозагрузку до тех пор, пока вы не поменяете первое значение .setValue (на скриншоте это "bap31" и "bap32").

Как сделать, чтобы программа не была видна в приложениях диспетчера задач? Это сверх простая задача. В пропертисах формы у FormBorderStyle поставить значение FixedToolWindow:

Убрать программу из таскбара ни капли не сложнее. У ShowInTaskbar ставим false:

Ну, а делаем её невидимой ещё проще: тут же совсем рядом у Opacity ставим 0%:

Вот и всё, наш троян полносьтю готов. Я, кстати, проверял его стандартным Windows Defender и ни антивирус, ни фаервол его не блочил. Осталось только занести этот троян жертве на комп в какую-нибудь удалённую папку и запустить его, либо же вшить в exe-шник какой-нибудь уже взломанной игры и выложить её на торрент-трекеры.

Заключение

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

Повторяю зачем я это рассказываю: чтобы люди поняли насколько легко пишутся трояны и задумались над своей кибер-безопасностью.

Антивирусы — бро, но даже они не в силах распознать новоиспечённую вредоносную программу, особенно если она написана наиболее глубинней, чем мой пример.

Пиратский софт — не бро. Любой бесплатно распространенный взломанный софт сожержит в себе криптомайнеры, кейлогеры, сниферы и прочее. Взломать игру требует времени, никто этим просто так не занимается. Это своего рода доход хакеров.

Желающим воспользоваться полученными знаниями в корыстных целях советую направить свои знания и желание программировать в нужное русло.

Программисты нужны везде, хакеры — в тюрьме.

















Report Page