Статья Пишем 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
Последнее редактирование модератором:
M

MAdDog719

На здоровье. Если будет интерес - напишу еще пару статей :)
Конечно интересно, очень классный материал.

А как можно получить код кейлогера без всяких ограничений? Я не кид, но в кодонаписании к сожалению понимаю пока очень, и очень мало, так что допилить под себя сам не смогу. А как написали выше, работающий кейлогер в хозяйстве сгодится.
 
Последнее редактирование модератором:
  • Нравится
Реакции: MonMJK
X

xXx

Материалу и автору зачет! Интересно
 
I

Ishikawa

Конечно интересно, очень классный материал.

А как можно получить код кейлогера без всяких ограничений? Я не кид, но в кодонаписании к сожалению понимаю пока очень, и очень мало, так что допилить под себя сам не смогу. А как написали выше, работающий кейлогер в хозяйстве сгодится.
:rolleyes:
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050);
замени на
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 0);

В этом случае система сама даст свободный порт выше 31337 :)
 
I

Ishikawa

Раз уж интересно, давайте добавим одну маленькую плюшку: скрин рабочего стола во время запуска кейлогера.
Скриншот никак не шифруется, но исправить это легко, пропустив его через функции Write(Encrypt());

для начала объявляем пространство, необходимое для работы с графикой.

upload_2016-11-22_15-17-27.png


Если вдруг ругается, что не может найти эти пространства - исправить легко. Нужно добавить референс (Project --> Add reference).

upload_2016-11-22_15-19-28.png


После этого ошибок не будет.

Поскольку задача не велика, не будем плодить всякие функции. Пишем в тело функции Main() следующий код:

upload_2016-11-22_15-20-22.png


Скриншот сохраняется туда же, где лежит дамп клавиш и сама программа. Для пытливых = есть что доработать, ибо картинка сохраняется в bmp и сохраняется только PrimaryScreen, т.е. основной монитор. Если мониторов два или больше... получите только первый :)
 

<~DarkNode~>

~^M1st3r_Bert0ni^~
Platinum
19.10.2016
722
3 099
BIT
0
Для пытливых = есть что доработать, ибо картинка сохраняется в bmp и сохраняется только PrimaryScreen, т.е. основной монитор. Если мониторов два или больше... получите только первый :)
Ну да) Тут все боги шарпа))))Ты пока второй бог шарпа которого я встретил)))
Первым был Лешка Морозов... ):)
 
M

MAdDog719

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

Эммм а клиент и дешифратор на этом форуме будут выложены? ))))
 
P

poxys

Скажи а как сделать что бы скриншот экрана всегда делался по клику мышки
 
I

Ishikawa

Скажи а как сделать что бы скриншот экрана всегда делался по клику мышки
Вообще, если используется обычное оконное приложение WinForms или WPF, Silverlight, то клик отрабатывается через обработчик:

upload_2016-11-23_9-12-15.png


Это будет работать только в пределах одного приложения. В нашем случае это будет бесполезно, т.к. приложение не имеет видимых окон. Выход - ставить хук на мышку. Если кратко, то нужно объявить константы под мышь:
upload_2016-11-23_9-16-5.png


Потом создать функцию установки хука:

upload_2016-11-23_9-17-37.png


А потом написать еще +500 строк кода, чтобы всё это работало. В общем - установка хука на мышь - отдельная статья.

p.s. галерею хочешь накликать? :)
я как-то сделал скрин каждые 2 секунды. За день сожрало 180Гб
 
  • Нравится
Реакции: <~DarkNode~>
P

poxys

Вообще, если используется обычное оконное приложение WinForms или WPF, Silverlight, то клик отрабатывается через обработчик:

Посмотреть вложение 7598

Это будет работать только в пределах одного приложения. В нашем случае это будет бесполезно, т.к. приложение не имеет видимых окон. Выход - ставить хук на мышку. Если кратко, то нужно объявить константы под мышь:
Посмотреть вложение 7599

Потом создать функцию установки хука:

Посмотреть вложение 7600

А потом написать еще +500 строк кода, чтобы всё это работало. В общем - установка хука на мышь - отдельная статья.

p.s. галерею хочешь накликать? :)
я как-то сделал скрин каждые 2 секунды. За день сожрало 180Гб

  • Как раз в том то и дело что бы не создавались каждые две минуты скриншоты всего экрана, а именно по клику мыши, юзер же кликает когда что то делает, и это не часто, плюс сделать что бы скриншот был черно-белый и сильно пожатый!
 
P

poxys

Почему сыпит ошибками? Ссылки указаны!

1.jpg

2.jpg

3.jpg
 
Последнее редактирование модератором:
I

Ishikawa

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

Ishikawa

Почему сыпит ошибками? Ссылки указаны!
Там же написано, а выше я писал: "Если вдруг ругается, что не может найти эти пространства - исправить легко. Нужно добавить референс (Project --> Add reference)."
Собственно, у тебя нет ссылок на эти пространства. Их нужно добавить самому, бро.
 
P

poxys

Там же написано, а выше я писал: "Если вдруг ругается, что не может найти эти пространства - исправить легко. Нужно добавить референс (Project --> Add reference)."
Собственно, у тебя нет ссылок на эти пространства. Их нужно добавить самому, бро.
Я добавил все ссылки которые доступны, все равно не работает! Одной ссылки на System достаточно для работы должно быть, так как в нее входит IO-Net.Sockets-Threading-Security.Cryptography.
 

Вложения

  • 4.jpg
    4.jpg
    80 КБ · Просмотры: 343
I

Ishikawa

Одной ссылки на System достаточно для работы должно быть, так как в нее входит IO-Net.Sockets-Threading-Security.Cryptography.
Нет, не будет достаточно, т.к. в system не входит все остальное. Буть так - не было бы нужды плодить адресные пространства.

Бро, если ты добавишь все без разбора, работать и не будет...
Подключать нужно только нужные пространства. Давай сделаем так:
1. Убирай все галки со всех пространств Project --> Add reference
2. В коде убери лишние using. Оставь только те, что ниже:
upload_2016-11-25_17-17-29.png

3. Идем в Project --> Add reference. Ставим только те, что я указал:
Microsoft.CSharp
System
System.Drawning
System.Windows.Forms


Должно получиться так:
upload_2016-11-25_17-24-36.png


После этого жми F5.
 

Вложения

  • upload_2016-11-25_17-18-43.png
    upload_2016-11-25_17-18-43.png
    2,7 КБ · Просмотры: 312
  • upload_2016-11-25_17-19-3.png
    upload_2016-11-25_17-19-3.png
    3,7 КБ · Просмотры: 345
  • upload_2016-11-25_17-19-39.png
    upload_2016-11-25_17-19-39.png
    2,3 КБ · Просмотры: 319
  • upload_2016-11-25_17-20-55.png
    upload_2016-11-25_17-20-55.png
    2,5 КБ · Просмотры: 295
  • upload_2016-11-25_17-22-31.png
    upload_2016-11-25_17-22-31.png
    1,7 КБ · Просмотры: 316
Последнее редактирование модератором:
Мы в соцсетях:

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